diff options
author | Andrew Arnott <andrewarnott@gmail.com> | 2012-01-29 14:32:45 -0800 |
---|---|---|
committer | Andrew Arnott <andrewarnott@gmail.com> | 2012-01-29 14:32:45 -0800 |
commit | 5fec515095ee10b522f414a03e78f282aaf520dc (patch) | |
tree | 204c75486639c23cdda2ef38b34d7e5050a1a2e3 | |
parent | f1a4155398635a4fd9f485eec817152627682704 (diff) | |
parent | 8f4165ee515728aca3faaa26e8354a40612e85e4 (diff) | |
download | DotNetOpenAuth-5fec515095ee10b522f414a03e78f282aaf520dc.zip DotNetOpenAuth-5fec515095ee10b522f414a03e78f282aaf520dc.tar.gz DotNetOpenAuth-5fec515095ee10b522f414a03e78f282aaf520dc.tar.bz2 |
Merge branch 'splitDlls'.
DNOA now builds and (in some cases) ships as many distinct assemblies.
988 files changed, 60260 insertions, 54161 deletions
diff --git a/ILMergeInternalizeExceptions.txt b/ILMergeInternalizeExceptions.txt index e69de29..cfc4e21 100644 --- a/ILMergeInternalizeExceptions.txt +++ b/ILMergeInternalizeExceptions.txt @@ -0,0 +1 @@ +DotNetOpenAuth.*
\ No newline at end of file @@ -1,19 +1,24 @@ -<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> +<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" InitialTargets="TouchUpProjectsForDualFrameworks"> <Import Project="EnlistmentInfo.props" /> <Import Project="$(MSBuildProjectDirectory)\tools\DotNetOpenAuth.automated.props"/> <Import Project="$(ProjectRoot)tools\Translation.targets"/> <ItemGroup> + <!--Removed from NightlyProjects until it can be fixed up. samples\tools.proj;--> <NightlyProjects Include=" - samples\tools.proj; - tools\drop.proj; nuget\nuget.proj; " /> <NightlyProjects Include=" + tools\drop.proj; + "> + <BuildOnlyOnClr2>true</BuildOnlyOnClr2> + </NightlyProjects> + <NightlyProjects Include=" samples\samples.proj; doc\doc.proj; "> <Targets>DeployableArchive</Targets> + <BuildOnlyOnClr2>true</BuildOnlyOnClr2> </NightlyProjects> <ProjectsToClean Include=" @@ -36,10 +41,33 @@ $(ProjectRoot)**\*.log*; $(ProjectRoot)doc\$(ProductName).chm; " /> + + <TestProjects Include="DotNetOpenAuth_Test;DotNetOpenAuth_TestWeb" Condition=" '$(ClrVersion)' == '2' " /> + <TestProjects Include="DotNetOpenAuth_AspNet_Test" Condition=" '$(ClrVersion)' == '4' " /> + <TestAssemblies Include="$(OutputPath)$(ProductName).Test.dll" Condition=" '$(ClrVersion)' == '2' " /> + <TestAssemblies Include="$(OutputPath)$(ProductName).AspNet.Test.dll" Condition=" '$(ClrVersion)' == '4' " /> </ItemGroup> + <Target Name="TouchUpProjectsForDualFrameworks"> + <ItemGroup> + <!-- The point here is to duplicate all projects, once targeting each version of .NET that we support. --> + <NightlyProjects> + <Properties>TargetFrameworkVersion=v3.5</Properties> + </NightlyProjects> + <NightlyProjects Include="@(NightlyProjects)" Condition=" '%(NightlyProjects.BuildOnlyOnClr2)' != 'true' "> + <Properties>TargetFrameworkVersion=v4.0</Properties> + </NightlyProjects> + <ProjectsToClean> + <Properties>TargetFrameworkVersion=v3.5</Properties> + </ProjectsToClean> + <ProjectsToClean Include="@(ProjectsToClean)"> + <Properties>TargetFrameworkVersion=v4.0</Properties> + </ProjectsToClean> + </ItemGroup> + </Target> + <Target Name="BuildTests" DependsOnTargets="SkipVerification"> - <MSBuild Projects="$(SolutionPath)" Targets="DotNetOpenAuth_Test;DotNetOpenAuth_TestWeb" BuildInParallel="$(BuildInParallel)" /> + <MSBuild Projects="$(SolutionPath)" Targets="@(TestProjects)" BuildInParallel="$(BuildInParallel)" /> </Target> <Target Name="Build" DependsOnTargets="SkipVerification"> @@ -51,20 +79,20 @@ </Target> <Target Name="Test" DependsOnTargets="BuildTests" - Inputs="$(OutputPath)$(ProductName).dll;$(OutputPath)$(ProductName).Test.dll" + Inputs="$(OutputPath)$(ProductName).dll;@(TestAssemblies)" Outputs='$(OutputPath)Test-result.xml'> <PropertyGroup> <!-- Performance tests are only expected to pass in optimized builds. --> <NUnitExcludeCategories Condition=" '$(Configuration)' != 'Release' ">Performance</NUnitExcludeCategories> </PropertyGroup> - <NUnit Assemblies="$(OutputPath)$(ProductName).Test.dll" + <NUnit Assemblies="@(TestAssemblies)" ToolPath="$(NUnitToolPath)" OutputXmlFile="$(OutputPath)Test-result.xml" ExcludeCategory="$(NUnitExcludeCategories)" /> </Target> <Target Name="Nightly"> - <MSBuild Projects="@(NightlyProjects)" Targets="%(NightlyProjects.Targets)" BuildInParallel="$(BuildInParallel)" /> + <MSBuild Projects="@(NightlyProjects)" Targets="%(NightlyProjects.Targets)" BuildInParallel="$(BuildInParallel)" Properties="%(NightlyProjects.Properties)" /> </Target> <Target Name="Publish"> diff --git a/doc/doc.proj b/doc/doc.proj index 55b2cb8..c966eb8 100644 --- a/doc/doc.proj +++ b/doc/doc.proj @@ -11,8 +11,8 @@ --> <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), EnlistmentInfo.props))\EnlistmentInfo.props" Condition=" '$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), EnlistmentInfo.props))' != '' " /> <Import Project="..\tools\DotNetOpenAuth.automated.props"/> - - <Target Name="Build" DependsOnTargets="BuildProduct;Chm" Condition=" '$(NoDocumentation)' != 'true' " /> + + <Target Name="Build" DependsOnTargets="BuildUnifiedProduct;Chm" Condition=" '$(NoDocumentation)' != 'true' " /> <Target Name="Prepare"> <Error Text="The BranchName property must be set." Condition=" '$(BranchName)' == '' " /> diff --git a/lib/DotNetOpenAuth.BuildTasks.dll b/lib/DotNetOpenAuth.BuildTasks.dll Binary files differindex bb667f9..54387bb 100644 --- a/lib/DotNetOpenAuth.BuildTasks.dll +++ b/lib/DotNetOpenAuth.BuildTasks.dll diff --git a/lib/DotNetOpenAuth.BuildTasks.pdb b/lib/DotNetOpenAuth.BuildTasks.pdb Binary files differindex bcb8d51..d81cf23 100644 --- a/lib/DotNetOpenAuth.BuildTasks.pdb +++ b/lib/DotNetOpenAuth.BuildTasks.pdb diff --git a/lib/DotNetOpenAuth.BuildTasks.targets b/lib/DotNetOpenAuth.BuildTasks.targets index 27ca0ed..9ef88f6 100644 --- a/lib/DotNetOpenAuth.BuildTasks.targets +++ b/lib/DotNetOpenAuth.BuildTasks.targets @@ -30,4 +30,5 @@ <UsingTask AssemblyFile="DotNetOpenAuth.BuildTasks.dll" TaskName="PrepareOhlohRelease" /> <UsingTask AssemblyFile="DotNetOpenAuth.BuildTasks.dll" TaskName="AddFilesTo7Zip" /> <UsingTask AssemblyFile="DotNetOpenAuth.BuildTasks.dll" TaskName="NuGetPack" /> + <UsingTask AssemblyFile="DotNetOpenAuth.BuildTasks.dll" TaskName="RegexFileReplace" /> </Project> diff --git a/lib/Ionic.Zip.Reduced.dll b/lib/Ionic.Zip.Reduced.dll Binary files differnew file mode 100644 index 0000000..da07436 --- /dev/null +++ b/lib/Ionic.Zip.Reduced.dll diff --git a/lib/MSBuild.Community.Tasks.dll b/lib/MSBuild.Community.Tasks.dll Binary files differindex 4687f97..765156a 100644 --- a/lib/MSBuild.Community.Tasks.dll +++ b/lib/MSBuild.Community.Tasks.dll diff --git a/lib/MSBuild.Community.Tasks.pdb b/lib/MSBuild.Community.Tasks.pdb Binary files differindex 1dcf3ae..f7944fb 100644 --- a/lib/MSBuild.Community.Tasks.pdb +++ b/lib/MSBuild.Community.Tasks.pdb diff --git a/lib/MSBuild.Community.Tasks.xml b/lib/MSBuild.Community.Tasks.xml index 99bc198..fb481f1 100644 --- a/lib/MSBuild.Community.Tasks.xml +++ b/lib/MSBuild.Community.Tasks.xml @@ -322,6 +322,12 @@ if the locale on the computer isn't supported. Example is setting this to "en-US". </summary> </member> + <member name="P:MSBuild.Community.Tasks.AssemblyInfo.UltimateResourceFallbackLocation"> + <summary> + Gets or sets the ultimate resource fallback location. + </summary> + <value>The ultimate resource fallback location.</value> + </member> <member name="P:MSBuild.Community.Tasks.AssemblyInfo.InternalsVisibleTo"> <summary> Makes it possible to make certain assemblies able to use constructs marked as internal. @@ -330,6 +336,11 @@ tests should be able to use them. </summary> </member> + <member name="P:MSBuild.Community.Tasks.AssemblyInfo.AllowPartiallyTrustedCallers"> + <summary> + Gets or sets whether to allow strong-named assemblies to be called by partially trusted code. + </summary> + </member> <member name="P:MSBuild.Community.Tasks.AssemblyInfo.OutputFile"> <summary> Gets or sets the output file. @@ -1677,6 +1688,91 @@ Optional; the name of the file to write the response to. </summary> </member> + <member name="T:MSBuild.Community.Tasks.NuGet"> + <summary> + Creates a NuGet package based on the specified manifest (Nuspec) file. + Can set the version number in the manifest prior to building the package. + </summary> + </member> + <member name="M:MSBuild.Community.Tasks.NuGet.Execute"> + <summary> + Runs the exectuable file with the specified task parameters. + </summary> + <returns> + true if the task runs successfully; otherwise, false. + </returns> + </member> + <member name="M:MSBuild.Community.Tasks.NuGet.GenerateCommandLineCommands"> + <summary> + Returns a string value containing the command line arguments to pass directly to the executable file. + </summary> + <returns> + A string value containing the command line arguments to pass directly to the executable file. + </returns> + </member> + <member name="M:MSBuild.Community.Tasks.NuGet.GenerateFullPathToTool"> + <summary> + Returns the fully qualified path to the executable file. + </summary> + <returns> + The fully qualified path to the executable file. + </returns> + </member> + <member name="M:MSBuild.Community.Tasks.NuGet.GetWorkingDirectory"> + <summary> + Returns the directory in which to run the executable file. + </summary> + <returns> + The directory in which to run the executable file, or a null reference (Nothing in Visual Basic) if the executable file should be run in the current directory. + </returns> + </member> + <member name="P:MSBuild.Community.Tasks.NuGet.Manifest"> + <summary> + The location of the manifest (Nuspec) file to create a package. + </summary> + <value>The manifest.</value> + </member> + <member name="P:MSBuild.Community.Tasks.NuGet.OutputDirectory"> + <summary> + Specifies the directory for the created NuGet package. + </summary> + <value>The output directory.</value> + </member> + <member name="P:MSBuild.Community.Tasks.NuGet.BasePath"> + <summary> + The Base Path of the files defined in the nuspec file. + </summary> + <value>The base path.</value> + </member> + <member name="P:MSBuild.Community.Tasks.NuGet.Verbose"> + <summary> + Shows verbose output for package building. + </summary> + <value><c>true</c> if verbose; otherwise, <c>false</c>.</value> + </member> + <member name="P:MSBuild.Community.Tasks.NuGet.Version"> + <summary> + Sets the version of the package in the manifest (Nuspec) file. + The version must specify at least two places "X.X". + </summary> + <value>The version to set in the manifest.</value> + </member> + <member name="P:MSBuild.Community.Tasks.NuGet.WorkingDirectory"> + <summary> + Gets or sets the working directory. + </summary> + <value>The working directory.</value> + <returns> + The directory in which to run the executable file, or a null reference (Nothing in Visual Basic) if the executable file should be run in the current directory. + </returns> + </member> + <member name="P:MSBuild.Community.Tasks.NuGet.ToolName"> + <summary> + Gets the name of the executable file to run. + </summary> + <value></value> + <returns>The name of the executable file to run.</returns> + </member> <member name="M:MSBuild.Community.Tasks.PathUtil.RelativePathTo(System.String,System.String)"> <summary> Creates a relative path from one file @@ -1865,6 +1961,12 @@ </summary> <value>The regular expressions.</value> </member> + <member name="P:MSBuild.Community.Tasks.RegexCompiler.RegularExpressionsFile"> + <summary> + Gets or sets the file defining the regular expressions. + </summary> + <value>The regular expressions file.</value> + </member> <member name="P:MSBuild.Community.Tasks.RegexCompiler.IsPublic"> <summary> Gets or sets a value indicating whether the default value is public for regular expression instances. @@ -4000,6 +4102,16 @@ <member name="P:MSBuild.Community.Tasks.Subversion.LastCommit.Revision"> <remarks/> </member> + <member name="T:MSBuild.Community.Tasks.Subversion.SvnStatus"> + <summary> + Subversion status command. + </summary> + </member> + <member name="M:MSBuild.Community.Tasks.Subversion.SvnStatus.#ctor"> + <summary> + Creates an instance of SvnStatus. + </summary> + </member> <member name="T:MSBuild.Community.Tasks.SymbolServer.SymStoreCommands"> <summary> Commands for the SymStore tasks. @@ -4189,99 +4301,50 @@ <value></value> <returns>The <see cref="T:Microsoft.Build.Framework.MessageImportance"></see> with which to log errors.</returns> </member> - <member name="T:MSBuild.Community.Tasks.Tfs.IServer"> - <summary> - Describes the behavior of a Team Foundation Server - </summary> - <exclude /> - </member> - <member name="M:MSBuild.Community.Tasks.Tfs.IServer.GetLatestChangesetId(System.String,System.Net.ICredentials)"> - <summary> - Retrieves the latest changeset ID associated with a path - </summary> - <param name="localPath">A path on the local filesystem</param> - <param name="credentials">Credentials used to authenticate against the serer</param> - <returns></returns> - </member> - <member name="T:MSBuild.Community.Tasks.Tfs.TeamFoundationServer"> - <summary> - Handles all communication with the Team Foundation Server - </summary> - </member> - <member name="M:MSBuild.Community.Tasks.Tfs.TeamFoundationServer.#ctor(System.String)"> - <summary> - Creates an instace of the TeamFoundationServer class - </summary> - <param name="clientLocation">The local file path containing the TFS libraries. null if TFS is in the GAC.</param> - </member> - <member name="M:MSBuild.Community.Tasks.Tfs.TeamFoundationServer.GetLatestChangesetId(System.String,System.Net.ICredentials)"> - <summary> - Retrieves the latest changeset ID associated with a path - </summary> - <param name="localPath">A path on the local filesystem</param> - <param name="credentials">Credentials used to authenticate against the serer</param> - <returns></returns> - </member> - <member name="T:MSBuild.Community.Tasks.Tfs.TeamFoundationServerException"> - <summary> - Exceptions returned by the Team Foundation Server - </summary> - <exclude /> - </member> - <member name="M:MSBuild.Community.Tasks.Tfs.TeamFoundationServerException.#ctor"> - <summary> - Creates a new instance of the exception - </summary> - </member> - <member name="M:MSBuild.Community.Tasks.Tfs.TeamFoundationServerException.#ctor(System.String)"> - <summary> - Creates a new instance of the exception - </summary> - <param name="message">A description of the exception</param> - </member> - <member name="T:MSBuild.Community.Tasks.Tfs.TfsVersion"> + <member name="M:MSBuild.Community.Tasks.Tfs.TfsClient.GenerateFullPathToTool"> <summary> - Determines the changeset in a local Team Foundation Server workspace - </summary> - </member> - <member name="M:MSBuild.Community.Tasks.Tfs.TfsVersion.Execute"> - <summary> - Runs the exectuable file with the specified task parameters. + Returns the fully qualified path to the executable file. </summary> <returns> - true if the task runs successfully; otherwise, false. + The fully qualified path to the executable file. </returns> </member> - <member name="P:MSBuild.Community.Tasks.Tfs.TfsVersion.Username"> + <member name="M:MSBuild.Community.Tasks.Tfs.TfsClient.LogToolCommand(System.String)"> <summary> - The user to authenticate on the server + Logs the starting point of the run to all registered loggers. </summary> - <remarks>Leave empty to use the credentials of the current user.</remarks> + <param name="message">A descriptive message to provide loggers, usually the command line and switches.</param> </member> - <member name="P:MSBuild.Community.Tasks.Tfs.TfsVersion.Password"> + <member name="M:MSBuild.Community.Tasks.Tfs.TfsClient.GenerateCommandLineCommands"> <summary> - The password for the user to authenticate on the server + Returns a string value containing the command line arguments to pass directly to the executable file. </summary> - <remarks>Leave empty to use the credentials of the current user.</remarks> + <returns> + A string value containing the command line arguments to pass directly to the executable file. + </returns> </member> - <member name="P:MSBuild.Community.Tasks.Tfs.TfsVersion.Domain"> + <member name="M:MSBuild.Community.Tasks.Tfs.TfsClient.ValidateParameters"> <summary> - The domain of the user to authenticate on the server + Indicates whether all task paratmeters are valid. </summary> - <remarks>Leave empty to use the credentials of the current user.</remarks> - </member> - <member name="P:MSBuild.Community.Tasks.Tfs.TfsVersion.LocalPath"> - <summary>Path to local working copy.</summary> + <returns> + true if all task parameters are valid; otherwise, false. + </returns> </member> - <member name="P:MSBuild.Community.Tasks.Tfs.TfsVersion.Changeset"> + <member name="P:MSBuild.Community.Tasks.Tfs.TfsClient.StandardOutputLoggingImportance"> <summary> - The latest changeset ID in the local path + Gets the <see cref="T:Microsoft.Build.Framework.MessageImportance"></see> with which to log errors. </summary> + <value></value> + <returns>The <see cref="T:Microsoft.Build.Framework.MessageImportance"></see> with which to log errors.</returns> </member> - <member name="P:MSBuild.Community.Tasks.Tfs.TfsVersion.TfsLibraryLocation"> + <member name="P:MSBuild.Community.Tasks.Tfs.TfsClient.ToolName"> <summary> - The location of the Team Foundation Server client assemblies. Leave empty when the client is installed in the Global Assembly Cache. + Gets the name of the executable file to run. </summary> + <returns> + The name of the executable file to run. + </returns> </member> <member name="T:MSBuild.Community.Tasks.Services.IRegistry"> <summary> @@ -4927,45 +4990,42 @@ </summary> </member> <member name="T:MSBuild.Community.Tasks.ILMerge"> - <summary> - A wrapper for the ILMerge tool. - </summary> - <remarks> - <para> - The ILMerge tool itself must be installed separately. - It is available <a href="http://research.microsoft.com/~mbarnett/ILMerge.aspx">here</a>. - </para> - <para> - The command line options "/wildcards" and "/lib" of ILMerge are not supported, - because MSBuild is in charge of expanding wildcards for item groups. - </para> - </remarks> - <example> - This example merges two assemblies A.dll and B.dll into one: - <code><![CDATA[ - <PropertyGroup> - <outputFile>$(testDir)\ilmergetest.dll</outputFile> - <keyFile>$(testDir)\keypair.snk</keyFile> - <excludeFile>$(testDir)\ExcludeTypes.txt</excludeFile> - <logFile>$(testDir)\ilmergetest.log</logFile> - </PropertyGroup> - - <ItemGroup> - <inputAssemblies Include="$(testDir)\A.dll" /> - <inputAssemblies Include="$(testDir)\B.dll" /> - - <allowDuplicates Include="ClassAB" /> - </ItemGroup> - - <Target Name="merge" > - <ILMerge InputAssemblies="@(inputAssemblies)" - AllowDuplicateTypes="@(allowDuplicates)" - ExcludeFile="$(excludeFile)" - OutputFile="$(outputFile)" LogFile="$(logFile)" - DebugInfo="true" XmlDocumentation="true" - KeyFile="$(keyFile)" DelaySign="true" /> - </Target>]]></code> - </example> + <summary> + A wrapper for the ILMerge tool. + </summary> + <remarks> + <para> + The ILMerge tool itself must be installed separately. + It is available <a href="http://research.microsoft.com/~mbarnett/ILMerge.aspx">here</a>. + </para> + <para> + The command line options "/wildcards" and "/lib" of ILMerge are not supported, + because MSBuild is in charge of expanding wildcards for item groups. + </para> + </remarks> + <example> + This example merges two assemblies A.dll and B.dll into one: + <code><![CDATA[ + <PropertyGroup> + <outputFile>$(testDir)\ilmergetest.dll</outputFile> + <keyFile>$(testDir)\keypair.snk</keyFile> + <excludeFile>$(testDir)\ExcludeTypes.txt</excludeFile> + <logFile>$(testDir)\ilmergetest.log</logFile> + </PropertyGroup> + <ItemGroup> + <inputAssemblies Include="$(testDir)\A.dll" /> + <inputAssemblies Include="$(testDir)\B.dll" /> + <allowDuplicates Include="ClassAB" /> + </ItemGroup> + <Target Name="merge" > + <ILMerge InputAssemblies="@(inputAssemblies)" + AllowDuplicateTypes="@(allowDuplicates)" + ExcludeFile="$(excludeFile)" + OutputFile="$(outputFile)" LogFile="$(logFile)" + DebugInfo="true" XmlDocumentation="true" + KeyFile="$(keyFile)" DelaySign="true" /> + </Target>]]></code> + </example> </member> <member name="M:MSBuild.Community.Tasks.ILMerge.#ctor"> <summary> @@ -4984,11 +5044,11 @@ </member> <member name="M:MSBuild.Community.Tasks.ILMerge.GenerateCommandLineCommands"> <summary> - Returns a string value containing the command line arguments + Generates a string value containing the command line arguments to pass directly to the executable file. </summary> <returns> - Returns a string value containing the command line arguments + Returns a string value containing the command line arguments to pass directly to the executable file. </returns> </member> @@ -5006,7 +5066,7 @@ </member> <member name="P:MSBuild.Community.Tasks.ILMerge.AllowZeroPeKind"> <summary> - Gets or sets the flag to treat an assembly + Gets or sets a value indicating whether to treat an assembly with a zero PeKind flag (this is the value of the field listed as .corflags in the Manifest) as if it was ILonly. @@ -5035,8 +5095,8 @@ </member> <member name="P:MSBuild.Community.Tasks.ILMerge.Closed"> <summary> - Gets or sets the flag to indicate - whether to augment the list of input assemblies + Gets or sets a value indicating whether + to augment the list of input assemblies to its "transitive closure". </summary> <remarks> @@ -5053,8 +5113,8 @@ </member> <member name="P:MSBuild.Community.Tasks.ILMerge.CopyAttributes"> <summary> - Gets or sets the flag to indicate - whether to copy the assembly level attributes + Gets or sets a value indicating whether + to copy the assembly level attributes of each input assembly over into the target assembly. </summary> <remarks> @@ -5069,8 +5129,8 @@ </member> <member name="P:MSBuild.Community.Tasks.ILMerge.DebugInfo"> <summary> - Gets or sets the flag to indicate - whether to preserve any .pdb files + Gets or sets a value indicating whether + to preserve any .pdb files that are found for the input assemblies into a .pdb file for the target assembly. </summary> @@ -5081,8 +5141,8 @@ </member> <member name="P:MSBuild.Community.Tasks.ILMerge.DelaySign"> <summary> - Gets or sets the flag to indicate - whether the target assembly will be delay signed. + Gets or sets a value indicating whether + the target assembly will be delay signed. </summary> <remarks> <para>This property can be set only in conjunction with <see cref="P:MSBuild.Community.Tasks.ILMerge.KeyFile"/>.</para> @@ -5171,8 +5231,8 @@ </member> <member name="P:MSBuild.Community.Tasks.ILMerge.PublicKeyTokens"> <summary> - Gets or sets the flag to indicate - whether external assembly references in the manifest + Gets or sets a value indicating whether + external assembly references in the manifest of the target assembly will use public keys (<c>false</c>) or public key tokens (<c>true</c>). </summary> @@ -5181,6 +5241,12 @@ <para>The default value is <c>false</c>.</para> </remarks> </member> + <member name="P:MSBuild.Community.Tasks.ILMerge.SearchDirectories"> + <summary> + Gets or sets the directories to be used to search for input assemblies. + </summary> + <value>The search directories.</value> + </member> <member name="P:MSBuild.Community.Tasks.ILMerge.TargetPlatformVersion"> <summary> Gets or sets the .NET framework version for the target assembly. @@ -5224,8 +5290,8 @@ </member> <member name="P:MSBuild.Community.Tasks.ILMerge.XmlDocumentation"> <summary> - Gets or sets the flag to indicate - whether to merge XML documentation files + Gets or sets a value indicating whether + to merge XML documentation files into one for the target assembly. </summary> <remarks> @@ -5238,6 +5304,22 @@ Gets the name of the executable file to run. </summary> </member> + <member name="P:MSBuild.Community.Tasks.ILMerge.StandardOutputLoggingImportance"> + <summary> + Gets the <see cref="T:Microsoft.Build.Framework.MessageImportance"></see> with which to log errors. + </summary> + <value></value> + <returns>The <see cref="T:Microsoft.Build.Framework.MessageImportance"></see> with which to log errors.</returns> + </member> + <member name="P:MSBuild.Community.Tasks.ILMerge.StandardErrorLoggingImportance"> + <summary> + Gets the <see cref="T:Microsoft.Build.Framework.MessageImportance"/> with which to log errors. + </summary> + <value></value> + <returns> + The <see cref="T:Microsoft.Build.Framework.MessageImportance"/> with which to log errors. + </returns> + </member> <member name="T:MSBuild.Community.Tasks.Install.InstallAssembly"> <summary> Installs assemblies. @@ -6407,7 +6489,7 @@ </p> <p> Parameter <see cref="P:MSBuild.Community.Tasks.Xslt.RootAttributes"/> defaults to - one attribute with a name specified by <see cref="F:MSBuild.Community.Tasks.Xslt.CREATED_ATTRIBUTE"/>, + one attribute with a name specified by <see cref="F:MSBuild.Community.Tasks.Xslt.CreatedAttributeName"/>, and a local time stamp as value. To suppress the default value, an empty parameter <code>RootAttributes=""</code> @@ -6425,6 +6507,13 @@ as parameters. </p> <p> + If only one input file is given with <see cref="T:Microsoft.Build.Framework.ITaskItem"/> <see cref="P:MSBuild.Community.Tasks.Xslt.Inputs"/>, + metadata will also be handed to the xsl transformation. + However, the <see cref="P:MSBuild.Community.Tasks.Xslt.Xsl"/> metadata is preferred in case of equal names. + To overcome this limitation, for each input item metadatum, an additional parameter + prefixed with <see cref="F:MSBuild.Community.Tasks.Xslt.InputMetadataArgumentPrefix"/> is created. + </p> + <p> The output is written to the file specified by parameter <see cref="P:MSBuild.Community.Tasks.Xslt.Output"/>. </p> @@ -6468,7 +6557,7 @@ </code> </example> </member> - <member name="F:MSBuild.Community.Tasks.Xslt.CREATED_ATTRIBUTE"> + <member name="F:MSBuild.Community.Tasks.Xslt.CreatedAttributeName"> <summary> The name of the default attribute of the <see cref="P:MSBuild.Community.Tasks.Xslt.RootTag"/>. @@ -6476,6 +6565,12 @@ and the attribute will contain a local time stamp. </summary> </member> + <member name="F:MSBuild.Community.Tasks.Xslt.InputMetadataArgumentPrefix"> + <summary> + The prefix of XSLT parameters created from single XML input metadata. + <para>The value is <c>"input_"</c>.</para> + </summary> + </member> <member name="M:MSBuild.Community.Tasks.Xslt.Execute"> <summary> When overridden in a derived class, executes the task. @@ -6484,6 +6579,17 @@ Returns <c>true</c> if the task successfully executed; otherwise, <c>false</c>. </returns> </member> + <member name="M:MSBuild.Community.Tasks.Xslt.AddParameter(System.String,System.String,System.Xml.Xsl.XsltArgumentList)"> + <summary> + Adds a new xsl parameter with to the specified argument list. + </summary> + <param name="name">The name of the parameter.</param> + <param name="value">The value of the parameter.</param> + <param name="parameters">The parameter list.</param> + <returns>Whether the parameter was added.</returns> + <remarks>Does not add the parameter + when a parameter with the same name is already part of the <paramref name="parameters"/>.</remarks> + </member> <member name="P:MSBuild.Community.Tasks.Xslt.Inputs"> <summary> Gets or sets the xml input files. @@ -6670,6 +6776,11 @@ should be run in the current directory. </returns> </member> + <member name="P:MSBuild.Community.Tasks.FxCop.SearchGac"> + <summary> + Tells FxCop to search the GAC for assembly references. This parameter was added in FxCop 1.35 + </summary> + </member> <member name="P:MSBuild.Community.Tasks.FxCop.ApplyOutXsl"> <summary> Applies the XSL transformation specified in /outXsl to the @@ -6709,6 +6820,11 @@ included in the analysis results. </summary> </member> + <member name="P:MSBuild.Community.Tasks.FxCop.CustomDictionary"> + <summary> + Specifies the custom dictionary. + </summary> + </member> <member name="P:MSBuild.Community.Tasks.FxCop.RuleLibraries"> <summary> Specifies the filename(s) of FxCop rule assemblies @@ -8377,6 +8493,16 @@ Allows tests to be run in a new thread, allowing you to take advantage of ApartmentState and ThreadPriority settings in the config file. </summary> </member> + <member name="P:MSBuild.Community.Tasks.NUnit.Force32Bit"> + <summary> + Determines whether the tests are run in a 32bit process on a 64bit OS. + </summary> + </member> + <member name="P:MSBuild.Community.Tasks.NUnit.Framework"> + <summary> + Determines the framework to run aganist. + </summary> + </member> <member name="P:MSBuild.Community.Tasks.NUnit.ToolName"> <summary> Gets the name of the executable file to run. @@ -8923,17 +9049,17 @@ </member> <member name="P:MSBuild.Community.Tasks.Properties.Resources.XsltAddingParameter"> <summary> - Looks up a localized string similar to Adding Parameter \"{0}\": \"{1}\".. + Looks up a localized string similar to Adding Parameter "{0}": "{1}".. </summary> </member> <member name="P:MSBuild.Community.Tasks.Properties.Resources.XsltAddingRootAttribute"> <summary> - Looks up a localized string similar to Adding root attribute {0}=\"{1}\".. + Looks up a localized string similar to Adding root attribute {0}="{1}".. </summary> </member> <member name="P:MSBuild.Community.Tasks.Properties.Resources.XsltCreatingRootTag"> <summary> - Looks up a localized string similar to Creating root tag \"{0}\".. + Looks up a localized string similar to Creating root tag "{0}".. </summary> </member> <member name="P:MSBuild.Community.Tasks.Properties.Resources.XsltNoInputFiles"> @@ -9635,6 +9761,12 @@ The command to execute </summary> </member> + <member name="P:MSBuild.Community.Tasks.SqlExecute.CommandTimeout"> + <summary> + Command Timeout + </summary> + <remarks>Defaults to 30 seconds. Set to 0 for an infinite timeout period.</remarks> + </member> <member name="P:MSBuild.Community.Tasks.SqlExecute.SelectMode"> <summary> The SQL Selection Mode. Set to NonQuery, Scalar, or ScalarXml. Default is NonQuery. @@ -10333,5 +10465,20 @@ All files will be made relative from the working directory. </remarks> </member> + <member name="P:MSBuild.Community.Tasks.Zip.Password"> + <summary> + Gets or sets the password. + </summary> + <value>The password.</value> + </member> + <member name="P:MSBuild.Community.Tasks.Zip.Encryption"> + <summary> + Gets or sets the encryption algorithm. + </summary> + <value>The encryption algorithm.</value> + <remarks> + Possible values are None, PkzipWeak, WinZipAes128 and WinZipAes256 + </remarks> + </member> </members> </doc> diff --git a/nuget/DotNetOpenAuth.Core.UI.nuspec b/nuget/DotNetOpenAuth.Core.UI.nuspec new file mode 100644 index 0000000..232164a --- /dev/null +++ b/nuget/DotNetOpenAuth.Core.UI.nuspec @@ -0,0 +1,32 @@ +<?xml version="1.0"?> +<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"> + <metadata> + <id>DotNetOpenAuth.Core.UI</id> + <version>$version$</version> + <title>DotNetOpenAuth Core (ASP.NET controls)</title> + <authors>Andrew Arnott</authors> + <owners>Outercurve Foundation</owners> + <projectUrl>http://www.dotnetopenauth.net/</projectUrl> + <iconUrl>https://github.com/AArnott/dotnetopenid/raw/v3.4/doc/logo/dnoa-logo_32x32.png</iconUrl> + <licenseUrl>http://www.opensource.org/licenses/ms-pl.html</licenseUrl> + <requireLicenseAcceptance>false</requireLicenseAcceptance> + <summary>Contains core assemblies of DotNetOpenOAuth library.</summary> + <description>Shared library implementing the messaging pipeline used by auxiliary DotNetOpenAuth libraries.</description> + <language>en-US</language> + <dependencies> + <dependency id="DotNetOpenAuth.Core" version="[$version$]" /> + </dependencies> + </metadata> + <files> + <file src="$OutputPath35$signed\DotNetOpenAuth.Core.UI.dll" target="lib\net35-full\DotNetOpenAuth.Core.UI.dll" /> + <file src="$OutputPath40$signed\DotNetOpenAuth.Core.UI.dll" target="lib\net40-full\DotNetOpenAuth.Core.UI.dll" /> + + <file src="$OutputPath35$DotNetOpenAuth.Core.UI.pdb" target="lib\net35-full\DotNetOpenAuth.Core.UI.pdb" /> + <file src="$OutputPath40$DotNetOpenAuth.Core.UI.pdb" target="lib\net40-full\DotNetOpenAuth.Core.UI.pdb" /> + + <file src="$OutputPath35$DotNetOpenAuth.Core.UI.xml" target="lib\net35-full\DotNetOpenAuth.Core.UI.xml" /> + <file src="$OutputPath40$DotNetOpenAuth.Core.UI.xml" target="lib\net40-full\DotNetOpenAuth.Core.UI.xml" /> + + <file src="..\src\DotNetOpenAuth.Core.UI\**\*.cs" target="src" /> + </files> +</package>
\ No newline at end of file diff --git a/nuget/DotNetOpenAuth.Core.nuspec b/nuget/DotNetOpenAuth.Core.nuspec new file mode 100644 index 0000000..55d1b00 --- /dev/null +++ b/nuget/DotNetOpenAuth.Core.nuspec @@ -0,0 +1,37 @@ +<?xml version="1.0"?> +<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"> + <metadata> + <id>DotNetOpenAuth.Core</id> + <version>$version$</version> + <title>DotNetOpenAuth Core</title> + <authors>Andrew Arnott</authors> + <owners>Outercurve Foundation</owners> + <projectUrl>http://www.dotnetopenauth.net/</projectUrl> + <iconUrl>https://github.com/AArnott/dotnetopenid/raw/v3.4/doc/logo/dnoa-logo_32x32.png</iconUrl> + <licenseUrl>http://www.opensource.org/licenses/ms-pl.html</licenseUrl> + <requireLicenseAcceptance>false</requireLicenseAcceptance> + <summary>Contains core assemblies of DotNetOpenOAuth library.</summary> + <description>Shared library implementing the messaging pipeline used by auxiliary DotNetOpenAuth libraries.</description> + <language>en-US</language> + <frameworkAssemblies> + <frameworkAssembly assemblyName="System.Configuration" targetFramework="net40" /> + </frameworkAssemblies> + <dependencies> + <dependency id="CodeContracts.Unofficial" /> + </dependencies> + </metadata> + <files> + <file src="$OutputPath35$signed\DotNetOpenAuth.Core.dll" target="lib\net35-full\DotNetOpenAuth.Core.dll" /> + <file src="$OutputPath40$signed\DotNetOpenAuth.Core.dll" target="lib\net40-full\DotNetOpenAuth.Core.dll" /> + + <file src="$OutputPath35$DotNetOpenAuth.Core.pdb" target="lib\net35-full\DotNetOpenAuth.Core.pdb" /> + <file src="$OutputPath40$DotNetOpenAuth.Core.pdb" target="lib\net40-full\DotNetOpenAuth.Core.pdb" /> + + <file src="$OutputPath35$DotNetOpenAuth.Core.xml" target="lib\net35-full\DotNetOpenAuth.Core.xml" /> + <file src="$OutputPath40$DotNetOpenAuth.Core.xml" target="lib\net40-full\DotNetOpenAuth.Core.xml" /> + + <file src="content\Core\web.config.transform" target="content\web.config.transform" /> + + <file src="..\src\DotNetOpenAuth.Core\**\*.cs" target="src" /> + </files> +</package>
\ No newline at end of file diff --git a/nuget/DotNetOpenAuth.InfoCard.UI.nuspec b/nuget/DotNetOpenAuth.InfoCard.UI.nuspec new file mode 100644 index 0000000..8b343ec --- /dev/null +++ b/nuget/DotNetOpenAuth.InfoCard.UI.nuspec @@ -0,0 +1,33 @@ +<?xml version="1.0"?> +<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"> + <metadata> + <id>DotNetOpenAuth.InfoCard.UI</id> + <version>$version$</version> + <title>DotNetOpenAuth InfoCard (ASP.NET controls)</title> + <authors>Andrew Arnott</authors> + <owners>Outercurve Foundation</owners> + <projectUrl>http://www.dotnetopenauth.net/</projectUrl> + <iconUrl>https://github.com/AArnott/dotnetopenid/raw/v3.4/doc/logo/dnoa-logo_32x32.png</iconUrl> + <licenseUrl>http://www.opensource.org/licenses/ms-pl.html</licenseUrl> + <requireLicenseAcceptance>false</requireLicenseAcceptance> + <summary>Contains assemblies that are required to implement an InfoCard relying party.</summary> + <description>Contains assemblies that are required to implement an InfoCard relying party.</description> + <language>en-US</language> + <dependencies> + <dependency id="DotNetOpenAuth.Core.UI" version="[$version$]" /> + <dependency id="DotNetOpenAuth.InfoCard" version="[$version$]" /> + </dependencies> + </metadata> + <files> + <file src="$OutputPath35$signed\DotNetOpenAuth.InfoCard.UI.dll" target="lib\net35-full\DotNetOpenAuth.InfoCard.UI.dll" /> + <file src="$OutputPath40$signed\DotNetOpenAuth.InfoCard.UI.dll" target="lib\net40-full\DotNetOpenAuth.InfoCard.UI.dll" /> + + <file src="$OutputPath35$DotNetOpenAuth.InfoCard.UI.pdb" target="lib\net35-full\DotNetOpenAuth.InfoCard.UI.pdb" /> + <file src="$OutputPath40$DotNetOpenAuth.InfoCard.UI.pdb" target="lib\net40-full\DotNetOpenAuth.InfoCard.UI.pdb" /> + + <file src="$OutputPath35$DotNetOpenAuth.InfoCard.UI.xml" target="lib\net35-full\DotNetOpenAuth.InfoCard.UI.xml" /> + <file src="$OutputPath40$DotNetOpenAuth.InfoCard.UI.xml" target="lib\net40-full\DotNetOpenAuth.InfoCard.UI.xml" /> + + <file src="..\src\DotNetOpenAuth.InfoCard.UI\**\*.cs" target="src" /> + </files> +</package>
\ No newline at end of file diff --git a/nuget/DotNetOpenAuth.InfoCard.nuspec b/nuget/DotNetOpenAuth.InfoCard.nuspec new file mode 100644 index 0000000..166abca --- /dev/null +++ b/nuget/DotNetOpenAuth.InfoCard.nuspec @@ -0,0 +1,30 @@ +<?xml version="1.0"?> +<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"> + <metadata> + <id>DotNetOpenAuth.InfoCard</id> + <version>$version$</version> + <title>DotNetOpenAuth InfoCard</title> + <authors>Andrew Arnott</authors> + <owners>Outercurve Foundation</owners> + <projectUrl>http://www.dotnetopenauth.net/</projectUrl> + <iconUrl>https://github.com/AArnott/dotnetopenid/raw/v3.4/doc/logo/dnoa-logo_32x32.png</iconUrl> + <licenseUrl>http://www.opensource.org/licenses/ms-pl.html</licenseUrl> + <requireLicenseAcceptance>false</requireLicenseAcceptance> + <description>Contains assemblies that are required to implement an InfoCard relying party.</description> + <dependencies> + <dependency id="DotNetOpenAuth.Core" version="[$version$]" /> + </dependencies> + </metadata> + <files> + <file src="$OutputPath35$signed\DotNetOpenAuth.InfoCard.dll" target="lib\net35-full\DotNetOpenAuth.InfoCard.dll" /> + <file src="$OutputPath40$signed\DotNetOpenAuth.InfoCard.dll" target="lib\net40-full\DotNetOpenAuth.InfoCard.dll" /> + + <file src="$OutputPath35$DotNetOpenAuth.InfoCard.pdb" target="lib\net35-full\DotNetOpenAuth.InfoCard.pdb" /> + <file src="$OutputPath40$DotNetOpenAuth.InfoCard.pdb" target="lib\net40-full\DotNetOpenAuth.InfoCard.pdb" /> + + <file src="$OutputPath35$DotNetOpenAuth.InfoCard.xml" target="lib\net35-full\DotNetOpenAuth.InfoCard.xml" /> + <file src="$OutputPath40$DotNetOpenAuth.InfoCard.xml" target="lib\net40-full\DotNetOpenAuth.InfoCard.xml" /> + + <file src="..\src\DotNetOpenAuth.InfoCard\**\*.cs" target="src" /> + </files> +</package>
\ No newline at end of file diff --git a/nuget/DotNetOpenAuth.OAuth.Consumer.nuspec b/nuget/DotNetOpenAuth.OAuth.Consumer.nuspec new file mode 100644 index 0000000..3453b55 --- /dev/null +++ b/nuget/DotNetOpenAuth.OAuth.Consumer.nuspec @@ -0,0 +1,30 @@ +<?xml version="1.0"?> +<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"> + <metadata> + <id>DotNetOpenAuth.OAuth.Consumer</id> + <version>$version$</version> + <title>DotNetOpenAuth OAuth 1.0(a) Consumer</title> + <authors>Andrew Arnott</authors> + <owners>Outercurve Foundation</owners> + <projectUrl>http://www.dotnetopenauth.net/</projectUrl> + <iconUrl>https://github.com/AArnott/dotnetopenid/raw/v3.4/doc/logo/dnoa-logo_32x32.png</iconUrl> + <licenseUrl>http://www.opensource.org/licenses/ms-pl.html</licenseUrl> + <requireLicenseAcceptance>false</requireLicenseAcceptance> + <description>Contains assemblies that are required to implement an OAuth consumer.</description> + <dependencies> + <dependency id="DotNetOpenAuth.OAuth.Core" version="[$version$]" /> + </dependencies> + </metadata> + <files> + <file src="$OutputPath35$signed\DotNetOpenAuth.OAuth.Consumer.dll" target="lib\net35-full\DotNetOpenAuth.OAuth.Consumer.dll" /> + <file src="$OutputPath40$signed\DotNetOpenAuth.OAuth.Consumer.dll" target="lib\net40-full\DotNetOpenAuth.OAuth.Consumer.dll" /> + + <file src="$OutputPath35$DotNetOpenAuth.OAuth.Consumer.pdb" target="lib\net35-full\DotNetOpenAuth.OAuth.Consumer.pdb" /> + <file src="$OutputPath40$DotNetOpenAuth.OAuth.Consumer.pdb" target="lib\net40-full\DotNetOpenAuth.OAuth.Consumer.pdb" /> + + <file src="$OutputPath35$DotNetOpenAuth.OAuth.Consumer.xml" target="lib\net35-full\DotNetOpenAuth.OAuth.Consumer.xml" /> + <file src="$OutputPath40$DotNetOpenAuth.OAuth.Consumer.xml" target="lib\net40-full\DotNetOpenAuth.OAuth.Consumer.xml" /> + + <file src="..\src\DotNetOpenAuth.OAuth.Consumer\**\*.cs" target="src" /> + </files> +</package>
\ No newline at end of file diff --git a/nuget/DotNetOpenAuth.OAuth.Core.nuspec b/nuget/DotNetOpenAuth.OAuth.Core.nuspec new file mode 100644 index 0000000..f330df8 --- /dev/null +++ b/nuget/DotNetOpenAuth.OAuth.Core.nuspec @@ -0,0 +1,32 @@ +<?xml version="1.0"?> +<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"> + <metadata> + <id>DotNetOpenAuth.OAuth.Core</id> + <version>$version$</version> + <title>DotNetOpenAuth OAuth 1.0(a)</title> + <authors>Andrew Arnott</authors> + <owners>Outercurve Foundation</owners> + <projectUrl>http://www.dotnetopenauth.net/</projectUrl> + <iconUrl>https://github.com/AArnott/dotnetopenid/raw/v3.4/doc/logo/dnoa-logo_32x32.png</iconUrl> + <licenseUrl>http://www.opensource.org/licenses/ms-pl.html</licenseUrl> + <requireLicenseAcceptance>false</requireLicenseAcceptance> + <description>Contains assemblies that are required to implement an OAuth consumer or service provider.</description> + <dependencies> + <dependency id="DotNetOpenAuth.Core" version="[$version$]" /> + </dependencies> + </metadata> + <files> + <file src="$OutputPath35$signed\DotNetOpenAuth.OAuth.dll" target="lib\net35-full\DotNetOpenAuth.OAuth.dll" /> + <file src="$OutputPath40$signed\DotNetOpenAuth.OAuth.dll" target="lib\net40-full\DotNetOpenAuth.OAuth.dll" /> + + <file src="$OutputPath35$DotNetOpenAuth.OAuth.pdb" target="lib\net35-full\DotNetOpenAuth.OAuth.pdb" /> + <file src="$OutputPath40$DotNetOpenAuth.OAuth.pdb" target="lib\net40-full\DotNetOpenAuth.OAuth.pdb" /> + + <file src="$OutputPath35$DotNetOpenAuth.OAuth.xml" target="lib\net35-full\DotNetOpenAuth.OAuth.xml" /> + <file src="$OutputPath40$DotNetOpenAuth.OAuth.xml" target="lib\net40-full\DotNetOpenAuth.OAuth.xml" /> + + <file src="content\OAuth.Core\web.config.transform" target="content\web.config.transform" /> + + <file src="..\src\DotNetOpenAuth.OAuth\**\*.cs" target="src" /> + </files> +</package>
\ No newline at end of file diff --git a/nuget/DotNetOpenAuth.OAuth.ServiceProvider.nuspec b/nuget/DotNetOpenAuth.OAuth.ServiceProvider.nuspec new file mode 100644 index 0000000..9aec3f8 --- /dev/null +++ b/nuget/DotNetOpenAuth.OAuth.ServiceProvider.nuspec @@ -0,0 +1,30 @@ +<?xml version="1.0"?> +<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"> + <metadata> + <id>DotNetOpenAuth.OAuth.ServiceProvider</id> + <version>$version$</version> + <title>DotNetOpenAuth OAuth 1.0(a) Service Provider</title> + <authors>Andrew Arnott</authors> + <owners>Outercurve Foundation</owners> + <projectUrl>http://www.dotnetopenauth.net/</projectUrl> + <iconUrl>https://github.com/AArnott/dotnetopenid/raw/v3.4/doc/logo/dnoa-logo_32x32.png</iconUrl> + <licenseUrl>http://www.opensource.org/licenses/ms-pl.html</licenseUrl> + <requireLicenseAcceptance>false</requireLicenseAcceptance> + <description>Contains assemblies that are required to implement an OAuth consumer.</description> + <dependencies> + <dependency id="DotNetOpenAuth.OAuth.Core" version="[$version$]" /> + </dependencies> + </metadata> + <files> + <file src="$OutputPath35$signed\DotNetOpenAuth.OAuth.ServiceProvider.dll" target="lib\net35-full\DotNetOpenAuth.OAuth.ServiceProvider.dll" /> + <file src="$OutputPath40$signed\DotNetOpenAuth.OAuth.ServiceProvider.dll" target="lib\net40-full\DotNetOpenAuth.OAuth.ServiceProvider.dll" /> + + <file src="$OutputPath35$DotNetOpenAuth.OAuth.ServiceProvider.pdb" target="lib\net35-full\DotNetOpenAuth.OAuth.ServiceProvider.pdb" /> + <file src="$OutputPath40$DotNetOpenAuth.OAuth.ServiceProvider.pdb" target="lib\net40-full\DotNetOpenAuth.OAuth.ServiceProvider.pdb" /> + + <file src="$OutputPath35$DotNetOpenAuth.OAuth.ServiceProvider.xml" target="lib\net35-full\DotNetOpenAuth.OAuth.ServiceProvider.xml" /> + <file src="$OutputPath40$DotNetOpenAuth.OAuth.ServiceProvider.xml" target="lib\net40-full\DotNetOpenAuth.OAuth.ServiceProvider.xml" /> + + <file src="..\src\DotNetOpenAuth.OAuth.ServiceProvider\**\*.cs" target="src" /> + </files> +</package>
\ No newline at end of file diff --git a/nuget/DotNetOpenAuth.OAuth2.AuthorizationServer.nuspec b/nuget/DotNetOpenAuth.OAuth2.AuthorizationServer.nuspec new file mode 100644 index 0000000..dfbe119 --- /dev/null +++ b/nuget/DotNetOpenAuth.OAuth2.AuthorizationServer.nuspec @@ -0,0 +1,30 @@ +<?xml version="1.0"?> +<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"> + <metadata> + <id>DotNetOpenAuth.OAuth2.AuthorizationServer</id> + <version>$oauth2version$</version> + <title>DotNetOpenAuth OAuth 2.0 Authorization Server</title> + <authors>Andrew Arnott</authors> + <owners>Outercurve Foundation</owners> + <projectUrl>http://www.dotnetopenauth.net/</projectUrl> + <iconUrl>https://github.com/AArnott/dotnetopenid/raw/v3.4/doc/logo/dnoa-logo_32x32.png</iconUrl> + <licenseUrl>http://www.opensource.org/licenses/ms-pl.html</licenseUrl> + <requireLicenseAcceptance>false</requireLicenseAcceptance> + <description>Contains assemblies that are required to implement an OAuth 2.0 Authorization Server.</description> + <dependencies> + <dependency id="DotNetOpenAuth.OAuth2.Core" version="[$oauth2version$]" /> + </dependencies> + </metadata> + <files> + <file src="$OutputPath35$signed\DotNetOpenAuth.OAuth2.AuthorizationServer.dll" target="lib\net35-full" /> + <file src="$OutputPath40$signed\DotNetOpenAuth.OAuth2.AuthorizationServer.dll" target="lib\net40-full" /> + + <file src="$OutputPath35$DotNetOpenAuth.OAuth2.AuthorizationServer.pdb" target="lib\net35-full" /> + <file src="$OutputPath40$DotNetOpenAuth.OAuth2.AuthorizationServer.pdb" target="lib\net40-full" /> + + <file src="$OutputPath35$DotNetOpenAuth.OAuth2.AuthorizationServer.xml" target="lib\net35-full" /> + <file src="$OutputPath40$DotNetOpenAuth.OAuth2.AuthorizationServer.xml" target="lib\net40-full" /> + + <file src="..\src\DotNetOpenAuth.OAuth2.AuthorizationServer\**\*.cs" target="src" /> + </files> +</package>
\ No newline at end of file diff --git a/nuget/DotNetOpenAuth.OAuth2.Client.UI.nuspec b/nuget/DotNetOpenAuth.OAuth2.Client.UI.nuspec new file mode 100644 index 0000000..9075b67 --- /dev/null +++ b/nuget/DotNetOpenAuth.OAuth2.Client.UI.nuspec @@ -0,0 +1,30 @@ +<?xml version="1.0"?> +<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"> + <metadata> + <id>DotNetOpenAuth.OAuth2.Client.UI</id> + <version>$oauth2version$</version> + <title>DotNetOpenAuth OAuth 2.0 Client (ASP.NET controls)</title> + <authors>Andrew Arnott</authors> + <owners>Outercurve Foundation</owners> + <projectUrl>http://www.dotnetopenauth.net/</projectUrl> + <iconUrl>https://github.com/AArnott/dotnetopenid/raw/v3.4/doc/logo/dnoa-logo_32x32.png</iconUrl> + <licenseUrl>http://www.opensource.org/licenses/ms-pl.html</licenseUrl> + <requireLicenseAcceptance>false</requireLicenseAcceptance> + <description>Contains assemblies that are required to implement an OAuth 2.0 Client ASP.NET controls.</description> + <dependencies> + <dependency id="DotNetOpenAuth.OAuth2.Client" version="[$oauth2version$]" /> + </dependencies> + </metadata> + <files> + <file src="$OutputPath35$signed\DotNetOpenAuth.OAuth2.Client.UI.dll" target="lib\net35-full" /> + <file src="$OutputPath40$signed\DotNetOpenAuth.OAuth2.Client.UI.dll" target="lib\net40-full" /> + + <file src="$OutputPath35$DotNetOpenAuth.OAuth2.Client.UI.pdb" target="lib\net35-full" /> + <file src="$OutputPath40$DotNetOpenAuth.OAuth2.Client.UI.pdb" target="lib\net40-full" /> + + <file src="$OutputPath35$DotNetOpenAuth.OAuth2.Client.UI.xml" target="lib\net35-full" /> + <file src="$OutputPath40$DotNetOpenAuth.OAuth2.Client.UI.xml" target="lib\net40-full" /> + + <file src="..\src\DotNetOpenAuth.OAuth2.Client.UI\**\*.cs" target="src" /> + </files> +</package>
\ No newline at end of file diff --git a/nuget/DotNetOpenAuth.OAuth2.Client.nuspec b/nuget/DotNetOpenAuth.OAuth2.Client.nuspec new file mode 100644 index 0000000..67de333 --- /dev/null +++ b/nuget/DotNetOpenAuth.OAuth2.Client.nuspec @@ -0,0 +1,30 @@ +<?xml version="1.0"?> +<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"> + <metadata> + <id>DotNetOpenAuth.OAuth2.Client</id> + <version>$oauth2version$</version> + <title>DotNetOpenAuth OAuth 2.0 Client</title> + <authors>Andrew Arnott</authors> + <owners>Outercurve Foundation</owners> + <projectUrl>http://www.dotnetopenauth.net/</projectUrl> + <iconUrl>https://github.com/AArnott/dotnetopenid/raw/v3.4/doc/logo/dnoa-logo_32x32.png</iconUrl> + <licenseUrl>http://www.opensource.org/licenses/ms-pl.html</licenseUrl> + <requireLicenseAcceptance>false</requireLicenseAcceptance> + <description>Contains assemblies that are required to implement an OAuth 2.0 Client.</description> + <dependencies> + <dependency id="DotNetOpenAuth.OAuth2.Core" version="[$oauth2version$]" /> + </dependencies> + </metadata> + <files> + <file src="$OutputPath35$signed\DotNetOpenAuth.OAuth2.Client.dll" target="lib\net35-full" /> + <file src="$OutputPath40$signed\DotNetOpenAuth.OAuth2.Client.dll" target="lib\net40-full" /> + + <file src="$OutputPath35$DotNetOpenAuth.OAuth2.Client.pdb" target="lib\net35-full" /> + <file src="$OutputPath40$DotNetOpenAuth.OAuth2.Client.pdb" target="lib\net40-full" /> + + <file src="$OutputPath35$DotNetOpenAuth.OAuth2.Client.xml" target="lib\net35-full" /> + <file src="$OutputPath40$DotNetOpenAuth.OAuth2.Client.xml" target="lib\net40-full" /> + + <file src="..\src\DotNetOpenAuth.OAuth2.Client\**\*.cs" target="src" /> + </files> +</package>
\ No newline at end of file diff --git a/nuget/DotNetOpenAuth.OAuth2.Core.nuspec b/nuget/DotNetOpenAuth.OAuth2.Core.nuspec new file mode 100644 index 0000000..4193407 --- /dev/null +++ b/nuget/DotNetOpenAuth.OAuth2.Core.nuspec @@ -0,0 +1,31 @@ +<?xml version="1.0"?> +<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"> + <metadata> + <id>DotNetOpenAuth.OAuth2.Core</id> + <version>$oauth2version$</version> + <title>DotNetOpenAuth OAuth 2.0 Core</title> + <authors>Andrew Arnott</authors> + <owners>Outercurve Foundation</owners> + <projectUrl>http://www.dotnetopenauth.net/</projectUrl> + <iconUrl>https://github.com/AArnott/dotnetopenid/raw/v3.4/doc/logo/dnoa-logo_32x32.png</iconUrl> + <licenseUrl>http://www.opensource.org/licenses/ms-pl.html</licenseUrl> + <requireLicenseAcceptance>false</requireLicenseAcceptance> + <description>Contains assemblies that are required to implement an OAuth 2.0 client, authorization server or resource server.</description> + <dependencies> + <dependency id="DotNetOpenAuth.Core" version="[$version$]" /> + <dependency id="DotNetOpenAuth.OAuth.Core" version="[$version$]" /> + </dependencies> + </metadata> + <files> + <file src="$OutputPath35$signed\DotNetOpenAuth.OAuth2.dll" target="lib\net35-full" /> + <file src="$OutputPath40$signed\DotNetOpenAuth.OAuth2.dll" target="lib\net40-full" /> + + <file src="$OutputPath35$DotNetOpenAuth.OAuth2.pdb" target="lib\net35-full" /> + <file src="$OutputPath40$DotNetOpenAuth.OAuth2.pdb" target="lib\net40-full" /> + + <file src="$OutputPath35$DotNetOpenAuth.OAuth2.xml" target="lib\net35-full" /> + <file src="$OutputPath40$DotNetOpenAuth.OAuth2.xml" target="lib\net40-full" /> + + <file src="..\src\DotNetOpenAuth.OAuth2\**\*.cs" target="src" /> + </files> +</package>
\ No newline at end of file diff --git a/nuget/DotNetOpenAuth.OAuth2.ResourceServer.nuspec b/nuget/DotNetOpenAuth.OAuth2.ResourceServer.nuspec new file mode 100644 index 0000000..e31adab --- /dev/null +++ b/nuget/DotNetOpenAuth.OAuth2.ResourceServer.nuspec @@ -0,0 +1,31 @@ +<?xml version="1.0"?> +<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"> + <metadata> + <id>DotNetOpenAuth.OAuth2.ResourceServer</id> + <version>$oauth2version$</version> + <title>DotNetOpenAuth OAuth 2.0 Resource Server</title> + <authors>Andrew Arnott</authors> + <owners>Outercurve Foundation</owners> + <projectUrl>http://www.dotnetopenauth.net/</projectUrl> + <iconUrl>https://github.com/AArnott/dotnetopenid/raw/v3.4/doc/logo/dnoa-logo_32x32.png</iconUrl> + <licenseUrl>http://www.opensource.org/licenses/ms-pl.html</licenseUrl> + <requireLicenseAcceptance>false</requireLicenseAcceptance> + <description>Contains assemblies that are required to implement an OAuth 2.0 Resource Server.</description> + <dependencies> + <dependency id="DotNetOpenAuth.OAuth.ServiceProvider" version="[$version$]" /> + <dependency id="DotNetOpenAuth.OAuth2.Core" version="[$oauth2version$]" /> + </dependencies> + </metadata> + <files> + <file src="$OutputPath35$signed\DotNetOpenAuth.OAuth2.ResourceServer.dll" target="lib\net35-full" /> + <file src="$OutputPath40$signed\DotNetOpenAuth.OAuth2.ResourceServer.dll" target="lib\net40-full" /> + + <file src="$OutputPath35$DotNetOpenAuth.OAuth2.ResourceServer.pdb" target="lib\net35-full" /> + <file src="$OutputPath40$DotNetOpenAuth.OAuth2.ResourceServer.pdb" target="lib\net40-full" /> + + <file src="$OutputPath35$DotNetOpenAuth.OAuth2.ResourceServer.xml" target="lib\net35-full" /> + <file src="$OutputPath40$DotNetOpenAuth.OAuth2.ResourceServer.xml" target="lib\net40-full" /> + + <file src="..\src\DotNetOpenAuth.OAuth2.ResourceServer\**\*.cs" target="src" /> + </files> +</package>
\ No newline at end of file diff --git a/nuget/DotNetOpenAuth.OpenId.Core.UI.nuspec b/nuget/DotNetOpenAuth.OpenId.Core.UI.nuspec new file mode 100644 index 0000000..b70762a --- /dev/null +++ b/nuget/DotNetOpenAuth.OpenId.Core.UI.nuspec @@ -0,0 +1,30 @@ +<?xml version="1.0"?> +<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"> + <metadata> + <id>DotNetOpenAuth.OpenId.Core.UI</id> + <version>$version$</version> + <title>DotNetOpenAuth OpenID Core (ASP.NET controls)</title> + <authors>Andrew Arnott</authors> + <owners>Outercurve Foundation</owners> + <projectUrl>http://www.dotnetopenauth.net/</projectUrl> + <iconUrl>https://github.com/AArnott/dotnetopenid/raw/v3.4/doc/logo/dnoa-logo_32x32.png</iconUrl> + <licenseUrl>http://www.opensource.org/licenses/ms-pl.html</licenseUrl> + <requireLicenseAcceptance>false</requireLicenseAcceptance> + <description>Contains assemblies that are required to implement an OpenID Relying Party or Provider.</description> + <dependencies> + <dependency id="DotNetOpenAuth.Core.UI" version="[$version$]" /> + </dependencies> + </metadata> + <files> + <file src="$OutputPath35$signed\DotNetOpenAuth.OpenId.UI.dll" target="lib\net35-full\DotNetOpenAuth.OpenId.UI.dll" /> + <file src="$OutputPath40$signed\DotNetOpenAuth.OpenId.UI.dll" target="lib\net40-full\DotNetOpenAuth.OpenId.UI.dll" /> + + <file src="$OutputPath35$DotNetOpenAuth.OpenId.UI.pdb" target="lib\net35-full\DotNetOpenAuth.OpenId.UI.pdb" /> + <file src="$OutputPath40$DotNetOpenAuth.OpenId.UI.pdb" target="lib\net40-full\DotNetOpenAuth.OpenId.UI.pdb" /> + + <file src="$OutputPath35$DotNetOpenAuth.OpenId.UI.xml" target="lib\net35-full\DotNetOpenAuth.OpenId.UI.xml" /> + <file src="$OutputPath40$DotNetOpenAuth.OpenId.UI.xml" target="lib\net40-full\DotNetOpenAuth.OpenId.UI.xml" /> + + <file src="..\src\DotNetOpenAuth.OpenId.UI\**\*.cs" target="src" /> + </files> +</package>
\ No newline at end of file diff --git a/nuget/DotNetOpenAuth.OpenId.Core.nuspec b/nuget/DotNetOpenAuth.OpenId.Core.nuspec new file mode 100644 index 0000000..24a9f8e --- /dev/null +++ b/nuget/DotNetOpenAuth.OpenId.Core.nuspec @@ -0,0 +1,50 @@ +<?xml version="1.0"?> +<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"> + <metadata> + <id>DotNetOpenAuth.OpenId.Core</id> + <version>$version$</version> + <title>DotNetOpenAuth OpenID Core</title> + <authors>Andrew Arnott</authors> + <owners>Outercurve Foundation</owners> + <projectUrl>http://www.dotnetopenauth.net/</projectUrl> + <iconUrl>https://github.com/AArnott/dotnetopenid/raw/v3.4/doc/logo/dnoa-logo_32x32.png</iconUrl> + <licenseUrl>http://www.opensource.org/licenses/ms-pl.html</licenseUrl> + <requireLicenseAcceptance>false</requireLicenseAcceptance> + <description>Contains assemblies that are required to implement an OpenID Relying Party or Provider.</description> + <dependencies> + <dependency id="DotNetOpenAuth.Core" version="[$version$]" /> + </dependencies> + <references> + <!-- Explicitly specify references so that Mono.Math other implementation detail assemblies are not referenced. --> + <reference file="DotNetOpenAuth.OpenId.dll" /> + </references> + </metadata> + <files> + <file src="$OutputPath35$signed\DotNetOpenAuth.OpenId.dll" target="lib\net35-full\DotNetOpenAuth.OpenId.dll" /> + <file src="$OutputPath40$signed\DotNetOpenAuth.OpenId.dll" target="lib\net40-full\DotNetOpenAuth.OpenId.dll" /> + <file src="$OutputPath35$DotNetOpenAuth.OpenId.pdb" target="lib\net35-full\DotNetOpenAuth.OpenId.pdb" /> + <file src="$OutputPath40$DotNetOpenAuth.OpenId.pdb" target="lib\net40-full\DotNetOpenAuth.OpenId.pdb" /> + <file src="$OutputPath35$DotNetOpenAuth.OpenId.xml" target="lib\net35-full\DotNetOpenAuth.OpenId.xml" /> + <file src="$OutputPath40$DotNetOpenAuth.OpenId.xml" target="lib\net40-full\DotNetOpenAuth.OpenId.xml" /> + + <file src="$OutputPath35$signed\Org.Mentalis.Security.Cryptography.dll" target="lib\net35-full\Org.Mentalis.Security.Cryptography.dll" /> + <file src="$OutputPath40$signed\Org.Mentalis.Security.Cryptography.dll" target="lib\net40-full\Org.Mentalis.Security.Cryptography.dll" /> + <file src="$OutputPath35$Org.Mentalis.Security.Cryptography.pdb" target="lib\net35-full\Org.Mentalis.Security.Cryptography.pdb" /> + <file src="$OutputPath40$Org.Mentalis.Security.Cryptography.pdb" target="lib\net40-full\Org.Mentalis.Security.Cryptography.pdb" /> + <file src="$OutputPath35$Org.Mentalis.Security.Cryptography.xml" target="lib\net35-full\Org.Mentalis.Security.Cryptography.xml" /> + <file src="$OutputPath40$Org.Mentalis.Security.Cryptography.xml" target="lib\net40-full\Org.Mentalis.Security.Cryptography.xml" /> + + <file src="$OutputPath35$signed\Mono.Math.dll" target="lib\net35-full\Mono.Math.dll" /> + <file src="$OutputPath40$signed\Mono.Math.dll" target="lib\net40-full\Mono.Math.dll" /> + <file src="$OutputPath35$Mono.Math.pdb" target="lib\net35-full\Mono.Math.pdb" /> + <file src="$OutputPath40$Mono.Math.pdb" target="lib\net40-full\Mono.Math.pdb" /> + <file src="$OutputPath35$Mono.Math.xml" target="lib\net35-full\Mono.Math.xml" /> + <file src="$OutputPath40$Mono.Math.xml" target="lib\net40-full\Mono.Math.xml" /> + + <file src="content\OpenId.Core\web.config.transform" target="content\web.config.transform" /> + + <file src="..\src\DotNetOpenAuth.OpenId\**\*.cs" target="src\DotNetOpenAuth.OpenId" /> + <file src="..\src\Org.Mentalis.Security.Cryptography\**\*.cs" target="src\Org.Mentalis.Security.Cryptography" /> + <file src="..\src\Mono.Math\**\*.cs" target="src\Mono.Math" /> + </files> +</package>
\ No newline at end of file diff --git a/nuget/DotNetOpenAuth.OpenId.Provider.UI.nuspec b/nuget/DotNetOpenAuth.OpenId.Provider.UI.nuspec new file mode 100644 index 0000000..83dd484 --- /dev/null +++ b/nuget/DotNetOpenAuth.OpenId.Provider.UI.nuspec @@ -0,0 +1,31 @@ +<?xml version="1.0"?> +<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"> + <metadata> + <id>DotNetOpenAuth.OpenId.Provider.UI</id> + <version>$version$</version> + <title>DotNetOpenAuth OpenID Provider (ASP.NET controls)</title> + <authors>Andrew Arnott</authors> + <owners>Outercurve Foundation</owners> + <projectUrl>http://www.dotnetopenauth.net/</projectUrl> + <iconUrl>https://github.com/AArnott/dotnetopenid/raw/v3.4/doc/logo/dnoa-logo_32x32.png</iconUrl> + <licenseUrl>http://www.opensource.org/licenses/ms-pl.html</licenseUrl> + <requireLicenseAcceptance>false</requireLicenseAcceptance> + <description>Contains assemblies that are required to implement an OpenID Provider.</description> + <dependencies> + <dependency id="DotNetOpenAuth.OpenId.Core.UI" version="[$version$]" /> + <dependency id="DotNetOpenAuth.OpenId.Provider" version="[$version$]" /> + </dependencies> + </metadata> + <files> + <file src="$OutputPath35$signed\DotNetOpenAuth.OpenId.Provider.UI.dll" target="lib\net35-full\DotNetOpenAuth.OpenId.Provider.UI.dll" /> + <file src="$OutputPath40$signed\DotNetOpenAuth.OpenId.Provider.UI.dll" target="lib\net40-full\DotNetOpenAuth.OpenId.Provider.UI.dll" /> + + <file src="$OutputPath35$DotNetOpenAuth.OpenId.Provider.UI.pdb" target="lib\net35-full\DotNetOpenAuth.OpenId.Provider.UI.pdb" /> + <file src="$OutputPath40$DotNetOpenAuth.OpenId.Provider.UI.pdb" target="lib\net40-full\DotNetOpenAuth.OpenId.Provider.UI.pdb" /> + + <file src="$OutputPath35$DotNetOpenAuth.OpenId.Provider.UI.xml" target="lib\net35-full\DotNetOpenAuth.OpenId.Provider.UI.xml" /> + <file src="$OutputPath40$DotNetOpenAuth.OpenId.Provider.UI.xml" target="lib\net40-full\DotNetOpenAuth.OpenId.Provider.UI.xml" /> + + <file src="..\src\DotNetOpenAuth.OpenId.Provider.UI\**\*.cs" target="src" /> + </files> +</package>
\ No newline at end of file diff --git a/nuget/DotNetOpenAuth.OpenId.Provider.nuspec b/nuget/DotNetOpenAuth.OpenId.Provider.nuspec new file mode 100644 index 0000000..3c46153 --- /dev/null +++ b/nuget/DotNetOpenAuth.OpenId.Provider.nuspec @@ -0,0 +1,32 @@ +<?xml version="1.0"?> +<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"> + <metadata> + <id>DotNetOpenAuth.OpenId.Provider</id> + <version>$version$</version> + <title>DotNetOpenAuth OpenID Provider</title> + <authors>Andrew Arnott</authors> + <owners>Outercurve Foundation</owners> + <projectUrl>http://www.dotnetopenauth.net/</projectUrl> + <iconUrl>https://github.com/AArnott/dotnetopenid/raw/v3.4/doc/logo/dnoa-logo_32x32.png</iconUrl> + <licenseUrl>http://www.opensource.org/licenses/ms-pl.html</licenseUrl> + <requireLicenseAcceptance>false</requireLicenseAcceptance> + <description>Contains assemblies that are required to implement an OpenID Provider.</description> + <dependencies> + <dependency id="DotNetOpenAuth.OpenId.Core" version="[$version$]" /> + </dependencies> + </metadata> + <files> + <file src="$OutputPath35$signed\DotNetOpenAuth.OpenId.Provider.dll" target="lib\net35-full\DotNetOpenAuth.OpenId.Provider.dll" /> + <file src="$OutputPath40$signed\DotNetOpenAuth.OpenId.Provider.dll" target="lib\net40-full\DotNetOpenAuth.OpenId.Provider.dll" /> + + <file src="$OutputPath35$DotNetOpenAuth.OpenId.Provider.pdb" target="lib\net35-full\DotNetOpenAuth.OpenId.Provider.pdb" /> + <file src="$OutputPath40$DotNetOpenAuth.OpenId.Provider.pdb" target="lib\net40-full\DotNetOpenAuth.OpenId.Provider.pdb" /> + + <file src="$OutputPath35$DotNetOpenAuth.OpenId.Provider.xml" target="lib\net35-full\DotNetOpenAuth.OpenId.Provider.xml" /> + <file src="$OutputPath40$DotNetOpenAuth.OpenId.Provider.xml" target="lib\net40-full\DotNetOpenAuth.OpenId.Provider.xml" /> + + <file src="content\OpenId.Provider\web.config.transform" target="content\web.config.transform" /> + + <file src="..\src\DotNetOpenAuth.OpenId.Provider\**\*.cs" target="src" /> + </files> +</package>
\ No newline at end of file diff --git a/nuget/DotNetOpenAuth.OpenId.RelyingParty.UI.nuspec b/nuget/DotNetOpenAuth.OpenId.RelyingParty.UI.nuspec new file mode 100644 index 0000000..d868e4f --- /dev/null +++ b/nuget/DotNetOpenAuth.OpenId.RelyingParty.UI.nuspec @@ -0,0 +1,31 @@ +<?xml version="1.0"?> +<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"> + <metadata> + <id>DotNetOpenAuth.OpenId.RelyingParty.UI</id> + <version>$version$</version> + <title>DotNetOpenAuth OpenID Relying Party (ASP.NET controls)</title> + <authors>Andrew Arnott</authors> + <owners>Outercurve Foundation</owners> + <projectUrl>http://www.dotnetopenauth.net/</projectUrl> + <iconUrl>https://github.com/AArnott/dotnetopenid/raw/v3.4/doc/logo/dnoa-logo_32x32.png</iconUrl> + <licenseUrl>http://www.opensource.org/licenses/ms-pl.html</licenseUrl> + <requireLicenseAcceptance>false</requireLicenseAcceptance> + <description>Contains assemblies that are required to implement an OpenID Relying Party.</description> + <dependencies> + <dependency id="DotNetOpenAuth.OpenId.Core.UI" version="[$version$]" /> + <dependency id="DotNetOpenAuth.OpenId.RelyingParty" version="[$version$]" /> + </dependencies> + </metadata> + <files> + <file src="$OutputPath35$signed\DotNetOpenAuth.OpenId.RelyingParty.UI.dll" target="lib\net35-full\DotNetOpenAuth.OpenId.RelyingParty.UI.dll" /> + <file src="$OutputPath40$signed\DotNetOpenAuth.OpenId.RelyingParty.UI.dll" target="lib\net40-full\DotNetOpenAuth.OpenId.RelyingParty.UI.dll" /> + + <file src="$OutputPath35$DotNetOpenAuth.OpenId.RelyingParty.UI.pdb" target="lib\net35-full\DotNetOpenAuth.OpenId.RelyingParty.UI.pdb" /> + <file src="$OutputPath40$DotNetOpenAuth.OpenId.RelyingParty.UI.pdb" target="lib\net40-full\DotNetOpenAuth.OpenId.RelyingParty.UI.pdb" /> + + <file src="$OutputPath35$DotNetOpenAuth.OpenId.RelyingParty.UI.xml" target="lib\net35-full\DotNetOpenAuth.OpenId.RelyingParty.UI.xml" /> + <file src="$OutputPath40$DotNetOpenAuth.OpenId.RelyingParty.UI.xml" target="lib\net40-full\DotNetOpenAuth.OpenId.RelyingParty.UI.xml" /> + + <file src="..\src\DotNetOpenAuth.OpenId.RelyingParty.UI\**\*.cs" target="src" /> + </files> +</package>
\ No newline at end of file diff --git a/nuget/DotNetOpenAuth.OpenId.RelyingParty.nuspec b/nuget/DotNetOpenAuth.OpenId.RelyingParty.nuspec new file mode 100644 index 0000000..fa6a765 --- /dev/null +++ b/nuget/DotNetOpenAuth.OpenId.RelyingParty.nuspec @@ -0,0 +1,32 @@ +<?xml version="1.0"?> +<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"> + <metadata> + <id>DotNetOpenAuth.OpenId.RelyingParty</id> + <version>$version$</version> + <title>DotNetOpenAuth OpenID Relying Party</title> + <authors>Andrew Arnott</authors> + <owners>Outercurve Foundation</owners> + <projectUrl>http://www.dotnetopenauth.net/</projectUrl> + <iconUrl>https://github.com/AArnott/dotnetopenid/raw/v3.4/doc/logo/dnoa-logo_32x32.png</iconUrl> + <licenseUrl>http://www.opensource.org/licenses/ms-pl.html</licenseUrl> + <requireLicenseAcceptance>false</requireLicenseAcceptance> + <description>Contains assemblies that are required to implement an OpenID Relying Party.</description> + <dependencies> + <dependency id="DotNetOpenAuth.OpenId.Core" version="[$version$]" /> + </dependencies> + </metadata> + <files> + <file src="$OutputPath35$signed\DotNetOpenAuth.OpenId.RelyingParty.dll" target="lib\net35-full\DotNetOpenAuth.OpenId.RelyingParty.dll" /> + <file src="$OutputPath40$signed\DotNetOpenAuth.OpenId.RelyingParty.dll" target="lib\net40-full\DotNetOpenAuth.OpenId.RelyingParty.dll" /> + + <file src="$OutputPath35$DotNetOpenAuth.OpenId.RelyingParty.pdb" target="lib\net35-full\DotNetOpenAuth.OpenId.RelyingParty.pdb" /> + <file src="$OutputPath40$DotNetOpenAuth.OpenId.RelyingParty.pdb" target="lib\net40-full\DotNetOpenAuth.OpenId.RelyingParty.pdb" /> + + <file src="$OutputPath35$DotNetOpenAuth.OpenId.RelyingParty.xml" target="lib\net35-full\DotNetOpenAuth.OpenId.RelyingParty.xml" /> + <file src="$OutputPath40$DotNetOpenAuth.OpenId.RelyingParty.xml" target="lib\net40-full\DotNetOpenAuth.OpenId.RelyingParty.xml" /> + + <file src="content\OpenId.RelyingParty\web.config.transform" target="content\web.config.transform" /> + + <file src="..\src\DotNetOpenAuth.OpenId.RelyingParty\**\*.cs" target="src" /> + </files> +</package>
\ No newline at end of file diff --git a/nuget/DotNetOpenAuth.OpenIdInfoCard.UI.nuspec b/nuget/DotNetOpenAuth.OpenIdInfoCard.UI.nuspec new file mode 100644 index 0000000..3c54275 --- /dev/null +++ b/nuget/DotNetOpenAuth.OpenIdInfoCard.UI.nuspec @@ -0,0 +1,31 @@ +<?xml version="1.0"?> +<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"> + <metadata> + <id>DotNetOpenAuth.OpenIdInfoCard.UI</id> + <version>$version$</version> + <title>DotNetOpenAuth OpenID InfoCard Relying Party ASP.NET control</title> + <authors>Andrew Arnott</authors> + <owners>Outercurve Foundation</owners> + <projectUrl>http://www.dotnetopenauth.net/</projectUrl> + <iconUrl>https://github.com/AArnott/dotnetopenid/raw/v3.4/doc/logo/dnoa-logo_32x32.png</iconUrl> + <licenseUrl>http://www.opensource.org/licenses/ms-pl.html</licenseUrl> + <requireLicenseAcceptance>false</requireLicenseAcceptance> + <description>Contains assemblies that are required to implement an InfoCard relying party.</description> + <dependencies> + <dependency id="DotNetOpenAuth.OpenId.RelyingParty.UI" version="[$version$]" /> + <dependency id="DotNetOpenAuth.InfoCard.UI" version="[$version$]" /> + </dependencies> + </metadata> + <files> + <file src="$OutputPath35$signed\DotNetOpenAuth.OpenIdInfoCard.UI.dll" target="lib\net35-full\DotNetOpenAuth.OpenIdInfoCard.UI.dll" /> + <file src="$OutputPath40$signed\DotNetOpenAuth.OpenIdInfoCard.UI.dll" target="lib\net40-full\DotNetOpenAuth.OpenIdInfoCard.UI.dll" /> + + <file src="$OutputPath35$DotNetOpenAuth.OpenIdInfoCard.UI.pdb" target="lib\net35-full\DotNetOpenAuth.OpenIdInfoCard.UI.pdb" /> + <file src="$OutputPath40$DotNetOpenAuth.OpenIdInfoCard.UI.pdb" target="lib\net40-full\DotNetOpenAuth.OpenIdInfoCard.UI.pdb" /> + + <file src="$OutputPath35$DotNetOpenAuth.OpenIdInfoCard.UI.xml" target="lib\net35-full\DotNetOpenAuth.OpenIdInfoCard.UI.xml" /> + <file src="$OutputPath40$DotNetOpenAuth.OpenIdInfoCard.UI.xml" target="lib\net40-full\DotNetOpenAuth.OpenIdInfoCard.UI.xml" /> + + <file src="..\src\DotNetOpenAuth.OpenIdInfoCard.UI\**\*.cs" target="src" /> + </files> +</package>
\ No newline at end of file diff --git a/nuget/DotNetOpenAuth.Ultimate.nuspec b/nuget/DotNetOpenAuth.Ultimate.nuspec new file mode 100644 index 0000000..40ff627 --- /dev/null +++ b/nuget/DotNetOpenAuth.Ultimate.nuspec @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="utf-8"?> +<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"> + <metadata> + <id>DotNetOpenAuth.Ultimate</id> + <version>$version$</version> + <title>DotNetOpenAuth Ultimate</title> + <authors>Andrew Arnott</authors> + <owners>Outercurve Foundation</owners> + <projectUrl>http://www.dotnetopenauth.net/</projectUrl> + <iconUrl>https://github.com/AArnott/dotnetopenid/raw/v3.4/doc/logo/dnoa-logo_32x32.png</iconUrl> + <licenseUrl>http://www.opensource.org/licenses/ms-pl.html</licenseUrl> + <requireLicenseAcceptance>false</requireLicenseAcceptance> + <summary>OpenID, OAuth, & InfoCard library for web sites/services and apps.</summary> + <description>A C# library that adds OpenID 2.0 Provider and Relying Party, OAuth Consumer and Service Provider, and InfoCard Selector support to your web site both programmatically and through convenient drop-in ASP.NET controls.</description> + <language>en-US</language> + <dependencies> + <dependency id="DotNetOpenAuth.OpenId.RelyingParty.UI" version="[$version$]" /> + <dependency id="DotNetOpenAuth.OpenId.Provider.UI" version="[$version$]" /> + <dependency id="DotNetOpenAuth.OAuth.Consumer" version="[$version$]" /> + <dependency id="DotNetOpenAuth.OAuth.ServiceProvider" version="[$version$]" /> + <dependency id="DotNetOpenAuth.OpenIdInfoCard.UI" version="[$version$]" /> + </dependencies> + </metadata> + <files> + <!-- this area intentionally left blank (so that nuget pack doesn't include all files and folders under this one in the package. --> + </files> +</package>
\ No newline at end of file diff --git a/nuget/DotNetOpenAuth.nuspec b/nuget/DotNetOpenAuth.nuspec index fcf0465..4befe6c 100644 --- a/nuget/DotNetOpenAuth.nuspec +++ b/nuget/DotNetOpenAuth.nuspec @@ -1,10 +1,11 @@ <?xml version="1.0" encoding="utf-8"?> -<package> +<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"> <metadata> <id>DotNetOpenAuth</id> <version>$version$</version> + <title>DotNetOpenAuth Ultimate (unified)</title> <authors>Andrew Arnott</authors> - <owners>Andrew Arnott</owners> + <owners>Outercurve Foundation</owners> <projectUrl>http://www.dotnetopenauth.net/</projectUrl> <iconUrl>https://github.com/AArnott/dotnetopenid/raw/v3.4/doc/logo/dnoa-logo_32x32.png</iconUrl> <licenseUrl>http://www.opensource.org/licenses/ms-pl.html</licenseUrl> @@ -12,5 +13,22 @@ <summary>OpenID, OAuth, & InfoCard library for web sites/services and apps.</summary> <description>A C# library that adds OpenID 2.0 Provider and Relying Party, OAuth Consumer and Service Provider, and InfoCard Selector support to your web site both programmatically and through convenient drop-in ASP.NET controls.</description> <language>en-US</language> + <frameworkAssemblies> + <frameworkAssembly assemblyName="System.Configuration" targetFramework="net40" /> + </frameworkAssemblies> </metadata> + <files> + <file src="$OutputPath35$unified\signed\DotNetOpenAuth.dll" target="lib\net35-full\DotNetOpenAuth.dll" /> + <file src="$OutputPath40$unified\signed\DotNetOpenAuth.dll" target="lib\net40-full\DotNetOpenAuth.dll" /> + + <file src="$OutputPath35$unified\DotNetOpenAuth.pdb" target="lib\net35-full\DotNetOpenAuth.pdb" /> + <file src="$OutputPath40$unified\DotNetOpenAuth.pdb" target="lib\net40-full\DotNetOpenAuth.pdb" /> + + <file src="$OutputPath35$unified\DotNetOpenAuth.xml" target="lib\net35-full\DotNetOpenAuth.xml" /> + <file src="$OutputPath40$unified\DotNetOpenAuth.xml" target="lib\net40-full\DotNetOpenAuth.xml" /> + + <file src="content\Ultimate\web.config.transform" target="content\web.config.transform" /> + + <file src="..\src\**\*.cs" target="src" /> + </files> </package>
\ No newline at end of file diff --git a/nuget/content/Core/web.config.transform b/nuget/content/Core/web.config.transform new file mode 100644 index 0000000..57a4692 --- /dev/null +++ b/nuget/content/Core/web.config.transform @@ -0,0 +1,36 @@ +<configuration> + <configSections> + <sectionGroup name="dotNetOpenAuth" type="DotNetOpenAuth.Configuration.DotNetOpenAuthSection, DotNetOpenAuth.Core"> + <section name="messaging" type="DotNetOpenAuth.Configuration.MessagingElement, DotNetOpenAuth.Core" requirePermission="false" allowLocation="true" /> + <section name="reporting" type="DotNetOpenAuth.Configuration.ReportingElement, DotNetOpenAuth.Core" requirePermission="false" allowLocation="true" /> + </sectionGroup> + </configSections> + + <system.net> + <defaultProxy enabled="true" /> + <settings> + <!-- This setting causes .NET to check certificate revocation lists (CRL) + before trusting HTTPS certificates. But this setting tends to not + be allowed in shared hosting environments. --> + <!--<servicePointManager checkCertificateRevocationList="true"/>--> + </settings> + </system.net> + + <runtime> + <!-- This prevents the Windows Event Log from frequently logging that HMAC1 is being used (when the other party needs it). --> + <legacyHMACWarning enabled="0" /> + </runtime> + + <dotNetOpenAuth> + <messaging> + <untrustedWebRequest> + <whitelistHosts> + <!-- Uncomment to enable communication with localhost (should generally not activate in production!) --> + <!--<add name="localhost" />--> + </whitelistHosts> + </untrustedWebRequest> + </messaging> + <!-- Allow DotNetOpenAuth to publish usage statistics to library authors to improve the library. --> + <reporting enabled="true" /> + </dotNetOpenAuth> +</configuration>
\ No newline at end of file diff --git a/nuget/content/OAuth.Core/web.config.transform b/nuget/content/OAuth.Core/web.config.transform new file mode 100644 index 0000000..d9b5d92 --- /dev/null +++ b/nuget/content/OAuth.Core/web.config.transform @@ -0,0 +1,7 @@ +<configuration> + <configSections> + <sectionGroup name="dotNetOpenAuth" type="DotNetOpenAuth.Configuration.DotNetOpenAuthSection, DotNetOpenAuth.Core"> + <section name="oauth" type="DotNetOpenAuth.Configuration.OAuthElement, DotNetOpenAuth.OAuth" requirePermission="false" allowLocation="true" /> + </sectionGroup> + </configSections> +</configuration>
\ No newline at end of file diff --git a/nuget/content/OpenId.Core/web.config.transform b/nuget/content/OpenId.Core/web.config.transform new file mode 100644 index 0000000..0771d2c --- /dev/null +++ b/nuget/content/OpenId.Core/web.config.transform @@ -0,0 +1,35 @@ +<configuration> + <configSections> + <section name="uri" type="System.Configuration.UriSection, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" /> + <sectionGroup name="dotNetOpenAuth" type="DotNetOpenAuth.Configuration.DotNetOpenAuthSection, DotNetOpenAuth.Core"> + <section name="openid" type="DotNetOpenAuth.Configuration.OpenIdElement, DotNetOpenAuth.OpenId" requirePermission="false" allowLocation="true" /> + </sectionGroup> + </configSections> + + <uri> + <!-- The uri section is necessary to turn on .NET 3.5 support for IDN (international domain names), + which is necessary for OpenID urls with unicode characters in the domain/host name. + It is also required to put the Uri class into RFC 3986 escaping mode, which OpenID and OAuth require. --> + <idn enabled="All"/> + <iriParsing enabled="true"/> + </uri> + + <runtime> + <!-- When targeting ASP.NET MVC 2, this assemblyBinding makes MVC 1 references relink + to MVC 2 so libraries such as DotNetOpenAuth that compile against MVC 1 will work with it. --> + <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1"> + <dependentAssembly> + <assemblyIdentity name="System.Web.Mvc" publicKeyToken="31bf3856ad364e35" /> + <bindingRedirect oldVersion="1.0.0.0" newVersion="2.0.0.0" /> + </dependentAssembly> + </assemblyBinding> + </runtime> + + <dotNetOpenAuth> + <!-- This is an optional configuration section where aspects of dotnetopenauth can be customized. --> + <!-- For a complete set of configuration options see http://www.dotnetopenauth.net/developers/code-snippets/configuration-options/ --> + <!-- You may also refer to README.Bin.html for instructions on enabling Intellisense for this section. --> + <openid> + </openid> + </dotNetOpenAuth> +</configuration>
\ No newline at end of file diff --git a/nuget/content/OpenId.Provider/web.config.transform b/nuget/content/OpenId.Provider/web.config.transform new file mode 100644 index 0000000..8365a3a --- /dev/null +++ b/nuget/content/OpenId.Provider/web.config.transform @@ -0,0 +1,8 @@ +<configuration> + <dotNetOpenAuth> + <openid> + <provider> + </provider> + </openid> + </dotNetOpenAuth> +</configuration>
\ No newline at end of file diff --git a/nuget/content/OpenId.RelyingParty/web.config.transform b/nuget/content/OpenId.RelyingParty/web.config.transform new file mode 100644 index 0000000..c9adf3f --- /dev/null +++ b/nuget/content/OpenId.RelyingParty/web.config.transform @@ -0,0 +1,19 @@ +<configuration> + <dotNetOpenAuth> + <openid> + <relyingParty> + <security requireSsl="false"> + <!-- Uncomment the trustedProviders tag if your relying party should only accept positive assertions from a closed set of OpenID Providers. --> + <!--<trustedProviders rejectAssertionsFromUntrustedProviders="true"> + <add endpoint="https://www.google.com/accounts/o8/ud" /> + </trustedProviders>--> + </security> + <behaviors> + <!-- The following OPTIONAL behavior allows RPs to use SREG only, but be compatible + with OPs that use Attribute Exchange (in various formats). --> + <add type="DotNetOpenAuth.OpenId.RelyingParty.Behaviors.AXFetchAsSregTransform, DotNetOpenAuth.OpenId.RelyingParty" /> + </behaviors> + </relyingParty> + </openid> + </dotNetOpenAuth> +</configuration>
\ No newline at end of file diff --git a/nuget/content/Ultimate/web.config.transform b/nuget/content/Ultimate/web.config.transform new file mode 100644 index 0000000..38544f3 --- /dev/null +++ b/nuget/content/Ultimate/web.config.transform @@ -0,0 +1,74 @@ +<configuration> + <configSections> + <section name="uri" type="System.Configuration.UriSection, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" /> + <sectionGroup name="dotNetOpenAuth" type="DotNetOpenAuth.Configuration.DotNetOpenAuthSection, DotNetOpenAuth"> + <section name="openid" type="DotNetOpenAuth.Configuration.OpenIdElement, DotNetOpenAuth" requirePermission="false" allowLocation="true" /> + <section name="oauth" type="DotNetOpenAuth.Configuration.OAuthElement, DotNetOpenAuth" requirePermission="false" allowLocation="true" /> + <section name="messaging" type="DotNetOpenAuth.Configuration.MessagingElement, DotNetOpenAuth" requirePermission="false" allowLocation="true" /> + <section name="reporting" type="DotNetOpenAuth.Configuration.ReportingElement, DotNetOpenAuth" requirePermission="false" allowLocation="true" /> + </sectionGroup> + </configSections> + + <uri> + <!-- The uri section is necessary to turn on .NET 3.5 support for IDN (international domain names), + which is necessary for OpenID urls with unicode characters in the domain/host name. + It is also required to put the Uri class into RFC 3986 escaping mode, which OpenID and OAuth require. --> + <idn enabled="All"/> + <iriParsing enabled="true"/> + </uri> + + <system.net> + <defaultProxy enabled="true" /> + <settings> + <!-- This setting causes .NET to check certificate revocation lists (CRL) + before trusting HTTPS certificates. But this setting tends to not + be allowed in shared hosting environments. --> + <!--<servicePointManager checkCertificateRevocationList="true"/>--> + </settings> + </system.net> + + <runtime> + <!-- This prevents the Windows Event Log from frequently logging that HMAC1 is being used (when the other party needs it). --> + <legacyHMACWarning enabled="0" /> + + <!-- When targeting ASP.NET MVC 2, this assemblyBinding makes MVC 1 references relink + to MVC 2 so libraries such as DotNetOpenAuth that compile against MVC 1 will work with it. --> + <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1"> + <dependentAssembly> + <assemblyIdentity name="System.Web.Mvc" publicKeyToken="31bf3856ad364e35" /> + <bindingRedirect oldVersion="1.0.0.0" newVersion="2.0.0.0" /> + </dependentAssembly> + </assemblyBinding> + </runtime> + + <dotNetOpenAuth> + <!-- This is an optional configuration section where aspects of dotnetopenauth can be customized. --> + <!-- For a complete set of configuration options see http://www.dotnetopenauth.net/developers/code-snippets/configuration-options/ --> + <!-- You may also refer to README.Bin.html for instructions on enabling Intellisense for this section. --> + <openid> + <relyingParty> + <security requireSsl="false"> + <!-- Uncomment the trustedProviders tag if your relying party should only accept positive assertions from a closed set of OpenID Providers. --> + <!--<trustedProviders rejectAssertionsFromUntrustedProviders="true"> + <add endpoint="https://www.google.com/accounts/o8/ud" /> + </trustedProviders>--> + </security> + <behaviors> + <!-- The following OPTIONAL behavior allows RPs to use SREG only, but be compatible + with OPs that use Attribute Exchange (in various formats). --> + <add type="DotNetOpenAuth.OpenId.RelyingParty.Behaviors.AXFetchAsSregTransform, DotNetOpenAuth" /> + </behaviors> + </relyingParty> + </openid> + <messaging> + <untrustedWebRequest> + <whitelistHosts> + <!-- Uncomment to enable communication with localhost (should generally not activate in production!) --> + <!--<add name="localhost" />--> + </whitelistHosts> + </untrustedWebRequest> + </messaging> + <!-- Allow DotNetOpenAuth to publish usage statistics to library authors to improve the library. --> + <reporting enabled="true" /> + </dotNetOpenAuth> +</configuration>
\ No newline at end of file diff --git a/nuget/content/web.config.transform b/nuget/content/web.config.transform deleted file mode 100644 index 1958981..0000000 --- a/nuget/content/web.config.transform +++ /dev/null @@ -1,69 +0,0 @@ -<configuration> - <configSections> - <section name="uri" type="System.Configuration.UriSection, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" /> - <section name="dotNetOpenAuth" type="DotNetOpenAuth.Configuration.DotNetOpenAuthSection" requirePermission="false" allowLocation="true"/> - </configSections> - - <uri> - <!-- The uri section is necessary to turn on .NET 3.5 support for IDN (international domain names), - which is necessary for OpenID urls with unicode characters in the domain/host name. - It is also required to put the Uri class into RFC 3986 escaping mode, which OpenID and OAuth require. --> - <idn enabled="All"/> - <iriParsing enabled="true"/> - </uri> - - <system.net> - <defaultProxy enabled="true" /> - <settings> - <!-- This setting causes .NET to check certificate revocation lists (CRL) - before trusting HTTPS certificates. But this setting tends to not - be allowed in shared hosting environments. --> - <!--<servicePointManager checkCertificateRevocationList="true"/>--> - </settings> - </system.net> - - <runtime> - <!-- This prevents the Windows Event Log from frequently logging that HMAC1 is being used (when the other party needs it). --> - <legacyHMACWarning enabled="0" /> - - <!-- When targeting ASP.NET MVC 2, this assemblyBinding makes MVC 1 references relink - to MVC 2 so libraries such as DotNetOpenAuth that compile against MVC 1 will work with it. --> - <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1"> - <dependentAssembly> - <assemblyIdentity name="System.Web.Mvc" publicKeyToken="31bf3856ad364e35" /> - <bindingRedirect oldVersion="1.0.0.0" newVersion="2.0.0.0" /> - </dependentAssembly> - </assemblyBinding> - </runtime> - - <dotNetOpenAuth> - <!-- This is an optional configuration section where aspects of dotnetopenauth can be customized. --> - <!-- For a complete set of configuration options see http://www.dotnetopenauth.net/developers/code-snippets/configuration-options/ --> - <!-- You may also refer to README.Bin.html for instructions on enabling Intellisense for this section. --> - <openid> - <relyingParty> - <security requireSsl="false"> - <!-- Uncomment the trustedProviders tag if your relying party should only accept positive assertions from a closed set of OpenID Providers. --> - <!--<trustedProviders rejectAssertionsFromUntrustedProviders="true"> - <add endpoint="https://www.google.com/accounts/o8/ud" /> - </trustedProviders>--> - </security> - <behaviors> - <!-- The following OPTIONAL behavior allows RPs to use SREG only, but be compatible - with OPs that use Attribute Exchange (in various formats). --> - <add type="DotNetOpenAuth.OpenId.Behaviors.AXFetchAsSregTransform, DotNetOpenAuth" /> - </behaviors> - </relyingParty> - </openid> - <messaging> - <untrustedWebRequest> - <whitelistHosts> - <!-- Uncomment to enable communication with localhost (should generally not activate in production!) --> - <!--<add name="localhost" />--> - </whitelistHosts> - </untrustedWebRequest> - </messaging> - <!-- Allow DotNetOpenAuth to publish usage statistics to library authors to improve the library. --> - <reporting enabled="true" /> - </dotNetOpenAuth> -</configuration>
\ No newline at end of file diff --git a/nuget/nuget.proj b/nuget/nuget.proj index f8b8e93..efa8845 100644 --- a/nuget/nuget.proj +++ b/nuget/nuget.proj @@ -3,60 +3,60 @@ <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), EnlistmentInfo.props))\EnlistmentInfo.props" Condition=" '$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), EnlistmentInfo.props))' != '' " /> <Import Project="$(MSBuildProjectDirectory)\..\tools\DotNetOpenAuth.automated.props"/> - <Target Name="Layout" DependsOnTargets="BuildUnifiedProduct;ReSignDelaySignedAssemblies"> - <PropertyGroup> - <NuGetLayoutPath>$(DropsRoot)NuGet\$(BuildVersion)\</NuGetLayoutPath> - </PropertyGroup> + <Target Name="BuildIntermediates"> + <ItemGroup> + <ProductTargets Include="BuildUnifiedProduct;ReSignDelaySignedAssemblies" Condition=" '$(SkipNugetDependenciesBuild)' != 'true' " /> + <ProductTargets Include="GetOutputPath" /> + </ItemGroup> - <MSBuild Projects="$(ProjectRoot)src\DotNetOpenAuth\DotNetOpenAuth.csproj" Targets="DocumentationProjectOutputGroup" BuildInParallel="$(BuildInParallel)"> - <Output TaskParameter="TargetOutputs" ItemName="NuGetSource"/> + <!-- We build the entire unified, signed product targeting both CLRs, since NuGet supports packages that contain both, + and by building against CLR 4, several assembly references fall away from dotnetopenauth.dll, which makes some folks happy. --> + <MSBuild + Projects="$(ProjectRoot)src\DotNetOpenAuth\DotNetOpenAuth.proj" + Targets="@(ProductTargets)" + Properties="TargetFrameworkVersion=v3.5" + BuildInParallel="$(BuildInParallel)"> + <Output TaskParameter="TargetOutputs" ItemName="TargetOutputs35"/> + </MSBuild> + <MSBuild + Projects="$(ProjectRoot)src\DotNetOpenAuth\DotNetOpenAuth.proj" + Targets="@(ProductTargets)" + Properties="TargetFrameworkVersion=v4.0" + BuildInParallel="$(BuildInParallel)"> + <Output TaskParameter="TargetOutputs" ItemName="TargetOutputs40"/> </MSBuild> - <!-- IMPORTANT: These must appear as separate ItemGroups or else batching screws it up. --> <ItemGroup> - <NuGetSource Include="%(ResignedAssembliesOutputs.Identity)" Condition=" '%(FileName)%(Extension)' == 'DotNetOpenAuth.dll' "/> - <NuGetSource> - <TargetPath>$(NuGetLayoutPath)lib\%(FileName)%(Extension)</TargetPath> - </NuGetSource> + <ResignedAssembliesOutputs Include="@(TargetOutputs35)" Condition=" '%(MSBuildSourceTargetName)' == 'Sign' "> + <TargetFramework>v3.5</TargetFramework> + </ResignedAssembliesOutputs> + <ResignedAssembliesOutputs Include="@(TargetOutputs40)" Condition=" '%(MSBuildSourceTargetName)' == 'Sign' "> + <TargetFramework>v4.0</TargetFramework> + </ResignedAssembliesOutputs> </ItemGroup> - <ItemGroup> - <NuGetContentSource Include="$(ProjectRoot)NuGet\content\**"/> - </ItemGroup> - <ItemGroup> - <NuGetSource Include="@(NuGetContentSource)"> - <TargetPath>$(NuGetLayoutPath)content\%(RecursiveDir)%(FileName)%(Extension)</TargetPath> - </NuGetSource> - - <NuSpecSource Include="DotNetOpenAuth.nuspec"> - <LayoutPath>$(NuGetLayoutPath)</LayoutPath> - <BeforeTokens>$version$</BeforeTokens> - <AfterTokens>$(BuildVersion)</AfterTokens> - </NuSpecSource> + <PropertyGroup> + <OutputPath35 Condition=" '%(MSBuildSourceTargetName)' == 'GetOutputPath' ">@(TargetOutputs35)</OutputPath35> + <OutputPath40 Condition=" '%(MSBuildSourceTargetName)' == 'GetOutputPath' ">@(TargetOutputs40)</OutputPath40> + </PropertyGroup> + </Target> - <NuSpecTarget Include="@(NuSpecSource->'$(NuGetLayoutPath)%(FileName)%(Extension)')" /> - </ItemGroup> + <Target Name="Build" DependsOnTargets="BuildIntermediates"> <ItemGroup> - <NuGetContentsTarget Include="%(NuGetSource.TargetPath)" /> - </ItemGroup> + <NuGetPackages Include="*.nuspec" Exclude="DotNetOpenAuth.Ultimate.nuspec"> + <Symbols>true</Symbols> + </NuGetPackages> + <NuGetPackages Include="DotNetOpenAuth.Ultimate.nuspec" /> - <CopyWithTokenSubstitution - SourceFiles="@(NuSpecSource)" - DestinationFiles="@(NuSpecTarget)" - /> - - <Copy - SourceFiles="@(NuGetSource)" - DestinationFiles="@(NuGetContentsTarget)" - SkipUnchangedFiles="true" /> - - <Purge Directories="$(NuGetLayoutPath)" IntendedFiles="@(NuGetContentsTarget);@(NuSpecTarget)" /> - </Target> - - <Target Name="Build" DependsOnTargets="Layout"> + <NuGetProperties Include="version=$(NuGetPackageVersion)" /> + <NuGetProperties Include="oauth2version=0.11.0-draft" /> + <NuGetProperties Include="OutputPath35=$(OutputPath35)" /> + <NuGetProperties Include="OutputPath40=$(OutputPath40)" /> + </ItemGroup> <NuGetPack - NuSpec="%(NuSpecTarget.Identity)" - BaseDirectory="%(NuSpecTarget.LayoutPath)" + NuSpec="%(NuGetPackages.Identity)" OutputPackageDirectory="$(DropsRoot)" + Properties="@(NuGetProperties)" + Symbols="%(NuGetPackages.Symbols)" ToolPath="$(NuGetToolPath)" /> </Target> diff --git a/projecttemplates/MvcRelyingParty/MvcRelyingParty.csproj b/projecttemplates/MvcRelyingParty/MvcRelyingParty.csproj index e08465f..40e96b8 100644 --- a/projecttemplates/MvcRelyingParty/MvcRelyingParty.csproj +++ b/projecttemplates/MvcRelyingParty/MvcRelyingParty.csproj @@ -17,6 +17,7 @@ </FileUpgradeFlags> <OldToolsVersion>4.0</OldToolsVersion> <UpgradeBackupLocation /> + <UseIISExpress>false</UseIISExpress> </PropertyGroup> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> <DebugSymbols>true</DebugSymbols> @@ -165,9 +166,49 @@ <Content Include="Views\Web.config" /> </ItemGroup> <ItemGroup> - <ProjectReference Include="..\..\src\DotNetOpenAuth\DotNetOpenAuth.csproj"> - <Project>{3191B653-F76D-4C1A-9A5A-347BC3AAAAB7}</Project> - <Name>DotNetOpenAuth</Name> + <ProjectReference Include="..\..\src\DotNetOpenAuth.InfoCard.UI\DotNetOpenAuth.InfoCard.UI.csproj"> + <Project>{E040EB58-B4D2-457B-A023-AE6EF3BD34DE}</Project> + <Name>DotNetOpenAuth.InfoCard.UI</Name> + </ProjectReference> + <ProjectReference Include="..\..\src\DotNetOpenAuth.InfoCard\DotNetOpenAuth.InfoCard.csproj"> + <Project>{408D10B8-34BA-4CBD-B7AA-FEB1907ABA4C}</Project> + <Name>DotNetOpenAuth.InfoCard</Name> + </ProjectReference> + <ProjectReference Include="..\..\src\DotNetOpenAuth.Core\DotNetOpenAuth.Core.csproj"> + <Project>{60426312-6AE5-4835-8667-37EDEA670222}</Project> + <Name>DotNetOpenAuth.Core</Name> + </ProjectReference> + <ProjectReference Include="..\..\src\DotNetOpenAuth.OAuth2.AuthorizationServer\DotNetOpenAuth.OAuth2.AuthorizationServer.csproj"> + <Project>{99BB7543-EA16-43EE-A7BC-D7A25A3B22F6}</Project> + <Name>DotNetOpenAuth.OAuth2.AuthorizationServer</Name> + </ProjectReference> + <ProjectReference Include="..\..\src\DotNetOpenAuth.OAuth2.ResourceServer\DotNetOpenAuth.OAuth2.ResourceServer.csproj"> + <Project>{A1A3150A-7B0E-4A34-8E35-045296CD3C76}</Project> + <Name>DotNetOpenAuth.OAuth2.ResourceServer</Name> + </ProjectReference> + <ProjectReference Include="..\..\src\DotNetOpenAuth.OAuth2\DotNetOpenAuth.OAuth2.csproj"> + <Project>{56459A6C-6BA2-4BAC-A9C0-27E3BD961FA6}</Project> + <Name>DotNetOpenAuth.OAuth2</Name> + </ProjectReference> + <ProjectReference Include="..\..\src\DotNetOpenAuth.OAuth\DotNetOpenAuth.OAuth.csproj"> + <Project>{A288FCC8-6FCF-46DA-A45E-5F9281556361}</Project> + <Name>DotNetOpenAuth.OAuth</Name> + </ProjectReference> + <ProjectReference Include="..\..\src\DotNetOpenAuth.OpenId.RelyingParty.UI\DotNetOpenAuth.OpenId.RelyingParty.UI.csproj"> + <Project>{1ED8D424-F8AB-4050-ACEB-F27F4F909484}</Project> + <Name>DotNetOpenAuth.OpenId.RelyingParty.UI</Name> + </ProjectReference> + <ProjectReference Include="..\..\src\DotNetOpenAuth.OpenId.RelyingParty\DotNetOpenAuth.OpenId.RelyingParty.csproj"> + <Project>{F458AB60-BA1C-43D9-8CEF-EC01B50BE87B}</Project> + <Name>DotNetOpenAuth.OpenId.RelyingParty</Name> + </ProjectReference> + <ProjectReference Include="..\..\src\DotNetOpenAuth.OpenId.UI\DotNetOpenAuth.OpenId.UI.csproj"> + <Project>{75E13AAE-7D51-4421-ABFD-3F3DC91F576E}</Project> + <Name>DotNetOpenAuth.OpenId.UI</Name> + </ProjectReference> + <ProjectReference Include="..\..\src\DotNetOpenAuth.OpenId\DotNetOpenAuth.OpenId.csproj"> + <Project>{3896A32A-E876-4C23-B9B8-78E17D134CD3}</Project> + <Name>DotNetOpenAuth.OpenId</Name> </ProjectReference> <ProjectReference Include="..\RelyingPartyLogic\RelyingPartyLogic.csproj"> <Project>{17932639-1F50-48AF-B0A5-E2BF832F82CC}</Project> diff --git a/projecttemplates/MvcRelyingParty/Web.config b/projecttemplates/MvcRelyingParty/Web.config index fc14598..1884dd8 100644 --- a/projecttemplates/MvcRelyingParty/Web.config +++ b/projecttemplates/MvcRelyingParty/Web.config @@ -11,7 +11,12 @@ <configSections> <section name="uri" type="System.Configuration.UriSection, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"/> <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler" requirePermission="false"/> - <section name="dotNetOpenAuth" type="DotNetOpenAuth.Configuration.DotNetOpenAuthSection" requirePermission="false" allowLocation="true"/> + <sectionGroup name="dotNetOpenAuth" type="DotNetOpenAuth.Configuration.DotNetOpenAuthSection, DotNetOpenAuth.Core"> + <section name="openid" type="DotNetOpenAuth.Configuration.OpenIdElement, DotNetOpenAuth.OpenId" requirePermission="false" allowLocation="true" /> + <section name="oauth" type="DotNetOpenAuth.Configuration.OAuthElement, DotNetOpenAuth.OAuth" requirePermission="false" allowLocation="true" /> + <section name="messaging" type="DotNetOpenAuth.Configuration.MessagingElement, DotNetOpenAuth.Core" requirePermission="false" allowLocation="true" /> + <section name="reporting" type="DotNetOpenAuth.Configuration.ReportingElement, DotNetOpenAuth.Core" requirePermission="false" allowLocation="true" /> + </sectionGroup> <sectionGroup name="system.web.extensions" type="System.Web.Configuration.SystemWebExtensionsSectionGroup, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"> <sectionGroup name="scripting" type="System.Web.Configuration.ScriptingSectionGroup, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"> <section name="scriptResourceHandler" type="System.Web.Configuration.ScriptingScriptResourceHandlerSection, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" allowDefinition="MachineToApplication"/> @@ -60,7 +65,7 @@ <behaviors> <!-- The following OPTIONAL behavior allows RPs to use SREG only, but be compatible with OPs that use Attribute Exchange (in various formats). --> - <add type="DotNetOpenAuth.OpenId.Behaviors.AXFetchAsSregTransform, DotNetOpenAuth"/> + <add type="DotNetOpenAuth.OpenId.RelyingParty.Behaviors.AXFetchAsSregTransform, DotNetOpenAuth.OpenId.RelyingParty"/> </behaviors> <store type="RelyingPartyLogic.RelyingPartyApplicationDbStore, RelyingPartyLogic"/> </relyingParty> diff --git a/projecttemplates/RelyingPartyLogic/NonceDbStore.cs b/projecttemplates/RelyingPartyLogic/NonceDbStore.cs index 951bf0f..3aa8cf2 100644 --- a/projecttemplates/RelyingPartyLogic/NonceDbStore.cs +++ b/projecttemplates/RelyingPartyLogic/NonceDbStore.cs @@ -54,7 +54,7 @@ namespace RelyingPartyLogic { /// be processed within before being discarded as an expired message. /// This maximum message age can be looked up via the /// <see cref="DotNetOpenAuth.Configuration.MessagingElement.MaximumMessageLifetime"/> - /// property, accessible via the <see cref="DotNetOpenAuth.Configuration.DotNetOpenAuthSection.Configuration"/> + /// property, accessible via the <see cref="DotNetOpenAuth.Configuration.MessagingElement.Configuration"/> /// property. /// </remarks> public bool StoreNonce(string context, string nonce, DateTime timestampUtc) { @@ -64,7 +64,7 @@ namespace RelyingPartyLogic { Context = context, Code = nonce, IssuedUtc = timestampUtc, - ExpiresUtc = timestampUtc + DotNetOpenAuthSection.Configuration.Messaging.MaximumMessageLifetime, + ExpiresUtc = timestampUtc + DotNetOpenAuthSection.Messaging.MaximumMessageLifetime, }; // The database columns [context] and [code] MUST be using diff --git a/projecttemplates/RelyingPartyLogic/OAuthServiceProvider.cs b/projecttemplates/RelyingPartyLogic/OAuthServiceProvider.cs index d5509aa..940ab85 100644 --- a/projecttemplates/RelyingPartyLogic/OAuthServiceProvider.cs +++ b/projecttemplates/RelyingPartyLogic/OAuthServiceProvider.cs @@ -1,20 +1,20 @@ -//----------------------------------------------------------------------- -// <copyright file="OAuthServiceProvider.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace RelyingPartyLogic { - using System; - using System.Collections.Generic; - using System.Linq; - using System.Web; - using DotNetOpenAuth.Messaging; - using DotNetOpenAuth.OAuth2; - using DotNetOpenAuth.OAuth2.ChannelElements; - using DotNetOpenAuth.OAuth2.Messages; - - public class OAuthServiceProvider { +//-----------------------------------------------------------------------
+// <copyright file="OAuthServiceProvider.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace RelyingPartyLogic {
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Web;
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.OAuth2;
+ using DotNetOpenAuth.OAuth2.ChannelElements;
+ using DotNetOpenAuth.OAuth2.Messages;
+
+ public class OAuthServiceProvider {
private const string PendingAuthorizationRequestSessionKey = "PendingAuthorizationRequest";
/// <summary>
@@ -22,56 +22,56 @@ namespace RelyingPartyLogic { /// </summary>
private static readonly object InitializerLock = new object();
- /// <summary> - /// The shared service description for this web site. - /// </summary> - private static AuthorizationServerDescription authorizationServerDescription; - - /// <summary> - /// The shared authorization server. - /// </summary> - private static AuthorizationServer authorizationServer; - - /// <summary> - /// Gets the service provider. - /// </summary> - /// <value>The service provider.</value> - public static AuthorizationServer AuthorizationServer { - get { - EnsureInitialized(); - return authorizationServer; - } - } - - /// <summary> - /// Gets the service description. - /// </summary> - /// <value>The service description.</value> - public static AuthorizationServerDescription AuthorizationServerDescription { - get { - EnsureInitialized(); - return authorizationServerDescription; - } - } - - /// <summary> - /// Initializes the <see cref="authorizationServer"/> field if it has not yet been initialized. - /// </summary> - private static void EnsureInitialized() { - if (authorizationServer == null) { - lock (InitializerLock) { - if (authorizationServerDescription == null) { - authorizationServerDescription = new AuthorizationServerDescription { - AuthorizationEndpoint = new Uri(Utilities.ApplicationRoot, "OAuth.ashx"), - TokenEndpoint = new Uri(Utilities.ApplicationRoot, "OAuth.ashx"), - }; - } - - if (authorizationServer == null) { - authorizationServer = new AuthorizationServer(new OAuthAuthorizationServer()); - } - } - } - } - } -} + /// <summary>
+ /// The shared service description for this web site.
+ /// </summary>
+ private static AuthorizationServerDescription authorizationServerDescription;
+
+ /// <summary>
+ /// The shared authorization server.
+ /// </summary>
+ private static AuthorizationServer authorizationServer;
+
+ /// <summary>
+ /// Gets the service provider.
+ /// </summary>
+ /// <value>The service provider.</value>
+ public static AuthorizationServer AuthorizationServer {
+ get {
+ EnsureInitialized();
+ return authorizationServer;
+ }
+ }
+
+ /// <summary>
+ /// Gets the service description.
+ /// </summary>
+ /// <value>The service description.</value>
+ public static AuthorizationServerDescription AuthorizationServerDescription {
+ get {
+ EnsureInitialized();
+ return authorizationServerDescription;
+ }
+ }
+
+ /// <summary>
+ /// Initializes the <see cref="authorizationServer"/> field if it has not yet been initialized.
+ /// </summary>
+ private static void EnsureInitialized() {
+ if (authorizationServer == null) {
+ lock (InitializerLock) {
+ if (authorizationServerDescription == null) {
+ authorizationServerDescription = new AuthorizationServerDescription {
+ AuthorizationEndpoint = new Uri(Utilities.ApplicationRoot, "OAuth.ashx"),
+ TokenEndpoint = new Uri(Utilities.ApplicationRoot, "OAuth.ashx"),
+ };
+ }
+
+ if (authorizationServer == null) {
+ authorizationServer = new AuthorizationServer(new OAuthAuthorizationServer());
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/projecttemplates/RelyingPartyLogic/RelyingPartyLogic.csproj b/projecttemplates/RelyingPartyLogic/RelyingPartyLogic.csproj index 129b278..6bbb9a3 100644 --- a/projecttemplates/RelyingPartyLogic/RelyingPartyLogic.csproj +++ b/projecttemplates/RelyingPartyLogic/RelyingPartyLogic.csproj @@ -138,9 +138,49 @@ </EntityDeploy> </ItemGroup> <ItemGroup> - <ProjectReference Include="..\..\src\DotNetOpenAuth\DotNetOpenAuth.csproj"> - <Project>{3191B653-F76D-4C1A-9A5A-347BC3AAAAB7}</Project> - <Name>DotNetOpenAuth</Name> + <ProjectReference Include="..\..\src\DotNetOpenAuth.InfoCard\DotNetOpenAuth.InfoCard.csproj"> + <Project>{408D10B8-34BA-4CBD-B7AA-FEB1907ABA4C}</Project> + <Name>DotNetOpenAuth.InfoCard</Name> + </ProjectReference> + <ProjectReference Include="..\..\src\DotNetOpenAuth.Core\DotNetOpenAuth.Core.csproj"> + <Project>{60426312-6AE5-4835-8667-37EDEA670222}</Project> + <Name>DotNetOpenAuth.Core</Name> + </ProjectReference> + <ProjectReference Include="..\..\src\DotNetOpenAuth.OAuth.ServiceProvider\DotNetOpenAuth.OAuth.ServiceProvider.csproj"> + <Project>{FED1923A-6D70-49B5-A37A-FB744FEC1C86}</Project> + <Name>DotNetOpenAuth.OAuth.ServiceProvider</Name> + </ProjectReference> + <ProjectReference Include="..\..\src\DotNetOpenAuth.OAuth2.AuthorizationServer\DotNetOpenAuth.OAuth2.AuthorizationServer.csproj"> + <Project>{99BB7543-EA16-43EE-A7BC-D7A25A3B22F6}</Project> + <Name>DotNetOpenAuth.OAuth2.AuthorizationServer</Name> + </ProjectReference> + <ProjectReference Include="..\..\src\DotNetOpenAuth.OAuth2.ResourceServer\DotNetOpenAuth.OAuth2.ResourceServer.csproj"> + <Project>{A1A3150A-7B0E-4A34-8E35-045296CD3C76}</Project> + <Name>DotNetOpenAuth.OAuth2.ResourceServer</Name> + </ProjectReference> + <ProjectReference Include="..\..\src\DotNetOpenAuth.OAuth2\DotNetOpenAuth.OAuth2.csproj"> + <Project>{56459A6C-6BA2-4BAC-A9C0-27E3BD961FA6}</Project> + <Name>DotNetOpenAuth.OAuth2</Name> + </ProjectReference> + <ProjectReference Include="..\..\src\DotNetOpenAuth.OAuth\DotNetOpenAuth.OAuth.csproj"> + <Project>{A288FCC8-6FCF-46DA-A45E-5F9281556361}</Project> + <Name>DotNetOpenAuth.OAuth</Name> + </ProjectReference> + <ProjectReference Include="..\..\src\DotNetOpenAuth.OpenId.RelyingParty.UI\DotNetOpenAuth.OpenId.RelyingParty.UI.csproj"> + <Project>{1ED8D424-F8AB-4050-ACEB-F27F4F909484}</Project> + <Name>DotNetOpenAuth.OpenId.RelyingParty.UI</Name> + </ProjectReference> + <ProjectReference Include="..\..\src\DotNetOpenAuth.OpenId.RelyingParty\DotNetOpenAuth.OpenId.RelyingParty.csproj"> + <Project>{F458AB60-BA1C-43D9-8CEF-EC01B50BE87B}</Project> + <Name>DotNetOpenAuth.OpenId.RelyingParty</Name> + </ProjectReference> + <ProjectReference Include="..\..\src\DotNetOpenAuth.OpenId.UI\DotNetOpenAuth.OpenId.UI.csproj"> + <Project>{75E13AAE-7D51-4421-ABFD-3F3DC91F576E}</Project> + <Name>DotNetOpenAuth.OpenId.UI</Name> + </ProjectReference> + <ProjectReference Include="..\..\src\DotNetOpenAuth.OpenId\DotNetOpenAuth.OpenId.csproj"> + <Project>{3896A32A-E876-4C23-B9B8-78E17D134CD3}</Project> + <Name>DotNetOpenAuth.OpenId</Name> </ProjectReference> <ProjectReference Include="..\RelyingPartyDatabase\RelyingPartyDatabase.dbproj"> <Name>RelyingPartyDatabase</Name> diff --git a/projecttemplates/WebFormsRelyingParty/Web.config b/projecttemplates/WebFormsRelyingParty/Web.config index cd26e26..d60edc3 100644 --- a/projecttemplates/WebFormsRelyingParty/Web.config +++ b/projecttemplates/WebFormsRelyingParty/Web.config @@ -11,7 +11,12 @@ <configSections> <section name="uri" type="System.Configuration.UriSection, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" /> <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler" requirePermission="false" /> - <section name="dotNetOpenAuth" type="DotNetOpenAuth.Configuration.DotNetOpenAuthSection" requirePermission="false" allowLocation="true" /> + <sectionGroup name="dotNetOpenAuth" type="DotNetOpenAuth.Configuration.DotNetOpenAuthSection, DotNetOpenAuth.Core"> + <section name="openid" type="DotNetOpenAuth.Configuration.OpenIdElement, DotNetOpenAuth.OpenId" requirePermission="false" allowLocation="true" /> + <section name="oauth" type="DotNetOpenAuth.Configuration.OAuthElement, DotNetOpenAuth.OAuth" requirePermission="false" allowLocation="true" /> + <section name="messaging" type="DotNetOpenAuth.Configuration.MessagingElement, DotNetOpenAuth.Core" requirePermission="false" allowLocation="true" /> + <section name="reporting" type="DotNetOpenAuth.Configuration.ReportingElement, DotNetOpenAuth.Core" requirePermission="false" allowLocation="true" /> + </sectionGroup> <sectionGroup name="system.web.extensions" type="System.Web.Configuration.SystemWebExtensionsSectionGroup, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"> <sectionGroup name="scripting" type="System.Web.Configuration.ScriptingSectionGroup, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"> <section name="scriptResourceHandler" type="System.Web.Configuration.ScriptingScriptResourceHandlerSection, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" allowDefinition="MachineToApplication" /> @@ -63,7 +68,7 @@ <behaviors> <!-- The following OPTIONAL behavior allows RPs to use SREG only, but be compatible with OPs that use Attribute Exchange (in various formats). --> - <add type="DotNetOpenAuth.OpenId.Behaviors.AXFetchAsSregTransform, DotNetOpenAuth" /> + <add type="DotNetOpenAuth.OpenId.RelyingParty.Behaviors.AXFetchAsSregTransform, DotNetOpenAuth.OpenId.RelyingParty" /> </behaviors> <store type="RelyingPartyLogic.RelyingPartyApplicationDbStore, RelyingPartyLogic"/> </relyingParty> diff --git a/projecttemplates/WebFormsRelyingParty/WebFormsRelyingParty.csproj b/projecttemplates/WebFormsRelyingParty/WebFormsRelyingParty.csproj index 04c85b7..f1001ef 100644 --- a/projecttemplates/WebFormsRelyingParty/WebFormsRelyingParty.csproj +++ b/projecttemplates/WebFormsRelyingParty/WebFormsRelyingParty.csproj @@ -18,6 +18,7 @@ <OldToolsVersion>3.5</OldToolsVersion> <UpgradeBackupLocation /> <TargetFrameworkProfile /> + <UseIISExpress>false</UseIISExpress> </PropertyGroup> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> <DebugSymbols>true</DebugSymbols> @@ -240,9 +241,49 @@ <Content Include="PrivacyPolicy.aspx" /> </ItemGroup> <ItemGroup> - <ProjectReference Include="..\..\src\DotNetOpenAuth\DotNetOpenAuth.csproj"> - <Project>{3191B653-F76D-4C1A-9A5A-347BC3AAAAB7}</Project> - <Name>DotNetOpenAuth</Name> + <ProjectReference Include="..\..\src\DotNetOpenAuth.InfoCard.UI\DotNetOpenAuth.InfoCard.UI.csproj"> + <Project>{E040EB58-B4D2-457B-A023-AE6EF3BD34DE}</Project> + <Name>DotNetOpenAuth.InfoCard.UI</Name> + </ProjectReference> + <ProjectReference Include="..\..\src\DotNetOpenAuth.InfoCard\DotNetOpenAuth.InfoCard.csproj"> + <Project>{408D10B8-34BA-4CBD-B7AA-FEB1907ABA4C}</Project> + <Name>DotNetOpenAuth.InfoCard</Name> + </ProjectReference> + <ProjectReference Include="..\..\src\DotNetOpenAuth.Core.UI\DotNetOpenAuth.Core.UI.csproj"> + <Project>{173E7B8D-E751-46E2-A133-F72297C0D2F4}</Project> + <Name>DotNetOpenAuth.Core.UI</Name> + </ProjectReference> + <ProjectReference Include="..\..\src\DotNetOpenAuth.Core\DotNetOpenAuth.Core.csproj"> + <Project>{60426312-6AE5-4835-8667-37EDEA670222}</Project> + <Name>DotNetOpenAuth.Core</Name> + </ProjectReference> + <ProjectReference Include="..\..\src\DotNetOpenAuth.OAuth2.AuthorizationServer\DotNetOpenAuth.OAuth2.AuthorizationServer.csproj"> + <Project>{99BB7543-EA16-43EE-A7BC-D7A25A3B22F6}</Project> + <Name>DotNetOpenAuth.OAuth2.AuthorizationServer</Name> + </ProjectReference> + <ProjectReference Include="..\..\src\DotNetOpenAuth.OAuth2\DotNetOpenAuth.OAuth2.csproj"> + <Project>{56459A6C-6BA2-4BAC-A9C0-27E3BD961FA6}</Project> + <Name>DotNetOpenAuth.OAuth2</Name> + </ProjectReference> + <ProjectReference Include="..\..\src\DotNetOpenAuth.OAuth\DotNetOpenAuth.OAuth.csproj"> + <Project>{A288FCC8-6FCF-46DA-A45E-5F9281556361}</Project> + <Name>DotNetOpenAuth.OAuth</Name> + </ProjectReference> + <ProjectReference Include="..\..\src\DotNetOpenAuth.OpenId.RelyingParty.UI\DotNetOpenAuth.OpenId.RelyingParty.UI.csproj"> + <Project>{1ED8D424-F8AB-4050-ACEB-F27F4F909484}</Project> + <Name>DotNetOpenAuth.OpenId.RelyingParty.UI</Name> + </ProjectReference> + <ProjectReference Include="..\..\src\DotNetOpenAuth.OpenId.RelyingParty\DotNetOpenAuth.OpenId.RelyingParty.csproj"> + <Project>{F458AB60-BA1C-43D9-8CEF-EC01B50BE87B}</Project> + <Name>DotNetOpenAuth.OpenId.RelyingParty</Name> + </ProjectReference> + <ProjectReference Include="..\..\src\DotNetOpenAuth.OpenId.UI\DotNetOpenAuth.OpenId.UI.csproj"> + <Project>{75E13AAE-7D51-4421-ABFD-3F3DC91F576E}</Project> + <Name>DotNetOpenAuth.OpenId.UI</Name> + </ProjectReference> + <ProjectReference Include="..\..\src\DotNetOpenAuth.OpenId\DotNetOpenAuth.OpenId.csproj"> + <Project>{3896A32A-E876-4C23-B9B8-78E17D134CD3}</Project> + <Name>DotNetOpenAuth.OpenId</Name> </ProjectReference> <ProjectReference Include="..\RelyingPartyLogic\RelyingPartyLogic.csproj"> <Project>{17932639-1F50-48AF-B0A5-E2BF832F82CC}</Project> diff --git a/projecttemplates/projecttemplates.proj b/projecttemplates/projecttemplates.proj index 6b38dda..3046553 100644 --- a/projecttemplates/projecttemplates.proj +++ b/projecttemplates/projecttemplates.proj @@ -20,9 +20,12 @@ $(LayoutDependsOn); </LayoutDependsOn> </PropertyGroup> - + <ItemGroup> <ProjectTemplates Include="**\*.*proj" Exclude="$(MSBuildThisFile)" /> + + <ProjectReferencesToRemove Include="..\RelyingPartyDatabase\RelyingPartyDatabase.dbproj"/> + <AssemblyReferencesToReplaceWith Include="REMOVE" /> </ItemGroup> <Target Name="Validate"> @@ -35,7 +38,7 @@ </Target> <Target Name="LayoutProjects"> - <MSBuild Projects="..\src\$(ProductName)\$(ProductName).csproj" Targets="Sign" BuildInParallel="$(BuildInParallel)"> + <MSBuild Projects="..\src\$(ProductName)\$(ProductName).proj" Targets="Sign" BuildInParallel="$(BuildInParallel)"> <Output TaskParameter="TargetOutputs" ItemName="SignedProductAssemblies" /> </MSBuild> <ItemGroup> @@ -85,9 +88,9 @@ </CopyWithTokenSubstitution> <ChangeProjectReferenceToAssemblyReference Projects="@(CopiedProjectFiles)" - Condition=" '%(Extension)' == '.csproj' " - ProjectReferences="..\..\src\$(ProductName)\$(ProductName).csproj;..\RelyingPartyDatabase\RelyingPartyDatabase.dbproj" - References="Lib\$(ProductName).dll;REMOVE" /> + Condition=" '%(CopiedProjectFiles.Extension)' == '.csproj' " + ProjectReferences="@(ProjectReferencesToRemove)" + References="@(AssemblyReferencesToReplaceWith)" /> <FixupReferenceHintPaths Projects="@(CopiedProjectFiles)" References="@(FixupReferenceAssemblies)" @@ -133,12 +136,12 @@ <VS2010ProjectTemplateZipFiles Include="@(TopLevelVS2010ProjectTemplates->'%(RootDir)%(Directory)%(FileName).zip')" /> </ItemGroup> - <Copy - SourceFiles="@(ProjectTemplatesSource)" - DestinationFiles="@(ProjectTemplatesLayout)" + <Copy + SourceFiles="@(ProjectTemplatesSource)" + DestinationFiles="@(ProjectTemplatesLayout)" SkipUnchangedFiles="true" /> - <CopyWithTokenSubstitution - SourceFiles="@(TemplateProjectItemsForTransformSource)" + <CopyWithTokenSubstitution + SourceFiles="@(TemplateProjectItemsForTransformSource)" DestinationFiles="@(TemplateProjectItemsForTransformLayout)" /> <ItemGroup> @@ -190,7 +193,7 @@ ZipLevel="$(ZipLevel)" /> </Target> - + <Target Name="Layout2008" DependsOnTargets="Layout"> <ItemGroup> <ProjectTemplates2008Source Include="$(ProjectTemplatesLayoutPath)**" Exclude="$(ProjectTemplatesLayoutPath)*.zip" /> @@ -214,7 +217,7 @@ InPlaceDowngrade="true" /> - <Purge Directories="$(ProjectTemplates2008LayoutPath)" + <Purge Directories="$(ProjectTemplates2008LayoutPath)" IntendedFiles="@(ProjectTemplates2008Layout);@(VS2008ProjectTemplateZipFiles)" /> </Target> diff --git a/samples/DotNetOpenAuth.ApplicationBlock/DotNetOpenAuth.ApplicationBlock.csproj b/samples/DotNetOpenAuth.ApplicationBlock/DotNetOpenAuth.ApplicationBlock.csproj index f56251f..aac67d4 100644 --- a/samples/DotNetOpenAuth.ApplicationBlock/DotNetOpenAuth.ApplicationBlock.csproj +++ b/samples/DotNetOpenAuth.ApplicationBlock/DotNetOpenAuth.ApplicationBlock.csproj @@ -105,12 +105,6 @@ <Compile Include="YubikeyRelyingParty.cs" /> </ItemGroup> <ItemGroup> - <ProjectReference Include="..\..\src\DotNetOpenAuth\DotNetOpenAuth.csproj"> - <Project>{3191B653-F76D-4C1A-9A5A-347BC3AAAAB7}</Project> - <Name>DotNetOpenAuth</Name> - </ProjectReference> - </ItemGroup> - <ItemGroup> <BootstrapperPackage Include="Microsoft.Net.Client.3.5"> <Visible>False</Visible> <ProductName>.NET Framework 3.5 SP1 Client Profile</ProductName> @@ -127,6 +121,48 @@ <Install>true</Install> </BootstrapperPackage> </ItemGroup> + <ItemGroup> + <ProjectReference Include="..\..\src\DotNetOpenAuth.InfoCard\DotNetOpenAuth.InfoCard.csproj"> + <Project>{408D10B8-34BA-4CBD-B7AA-FEB1907ABA4C}</Project> + <Name>DotNetOpenAuth.InfoCard</Name> + </ProjectReference> + <ProjectReference Include="..\..\src\DotNetOpenAuth.Core\DotNetOpenAuth.Core.csproj"> + <Project>{60426312-6AE5-4835-8667-37EDEA670222}</Project> + <Name>DotNetOpenAuth.Core</Name> + </ProjectReference> + <ProjectReference Include="..\..\src\DotNetOpenAuth.OAuth.Consumer\DotNetOpenAuth.OAuth.Consumer.csproj"> + <Project>{B202E40D-4663-4A2B-ACDA-865F88FF7CAA}</Project> + <Name>DotNetOpenAuth.OAuth.Consumer</Name> + </ProjectReference> + <ProjectReference Include="..\..\src\DotNetOpenAuth.OAuth2.Client.UI\DotNetOpenAuth.OAuth2.Client.UI.csproj"> + <Project>{ADC2CC8C-541E-4F86-ACB1-DD504A36FA4B}</Project> + <Name>DotNetOpenAuth.OAuth2.Client.UI</Name> + </ProjectReference> + <ProjectReference Include="..\..\src\DotNetOpenAuth.OAuth2.Client\DotNetOpenAuth.OAuth2.Client.csproj"> + <Project>{CDEDD439-7F35-4E6E-8605-4E70BDC4CC99}</Project> + <Name>DotNetOpenAuth.OAuth2.Client</Name> + </ProjectReference> + <ProjectReference Include="..\..\src\DotNetOpenAuth.OAuth2\DotNetOpenAuth.OAuth2.csproj"> + <Project>{56459A6C-6BA2-4BAC-A9C0-27E3BD961FA6}</Project> + <Name>DotNetOpenAuth.OAuth2</Name> + </ProjectReference> + <ProjectReference Include="..\..\src\DotNetOpenAuth.OAuth\DotNetOpenAuth.OAuth.csproj"> + <Project>{A288FCC8-6FCF-46DA-A45E-5F9281556361}</Project> + <Name>DotNetOpenAuth.OAuth</Name> + </ProjectReference> + <ProjectReference Include="..\..\src\DotNetOpenAuth.OpenId.Provider\DotNetOpenAuth.OpenId.Provider.csproj"> + <Project>{F8284738-3B5D-4733-A511-38C23F4A763F}</Project> + <Name>DotNetOpenAuth.OpenId.Provider</Name> + </ProjectReference> + <ProjectReference Include="..\..\src\DotNetOpenAuth.OpenId.RelyingParty\DotNetOpenAuth.OpenId.RelyingParty.csproj"> + <Project>{F458AB60-BA1C-43D9-8CEF-EC01B50BE87B}</Project> + <Name>DotNetOpenAuth.OpenId.RelyingParty</Name> + </ProjectReference> + <ProjectReference Include="..\..\src\DotNetOpenAuth.OpenId\DotNetOpenAuth.OpenId.csproj"> + <Project>{3896A32A-E876-4C23-B9B8-78E17D134CD3}</Project> + <Name>DotNetOpenAuth.OpenId</Name> + </ProjectReference> + </ItemGroup> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), EnlistmentInfo.targets))\EnlistmentInfo.targets" Condition=" '$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), EnlistmentInfo.targets))' != '' " /> -</Project> +</Project>
\ No newline at end of file diff --git a/samples/DotNetOpenAuth.ApplicationBlock/GoogleConsumer.cs b/samples/DotNetOpenAuth.ApplicationBlock/GoogleConsumer.cs index 474a569..7be629e 100644 --- a/samples/DotNetOpenAuth.ApplicationBlock/GoogleConsumer.cs +++ b/samples/DotNetOpenAuth.ApplicationBlock/GoogleConsumer.cs @@ -163,7 +163,7 @@ namespace DotNetOpenAuth.ApplicationBlock { RequestTokenEndpoint = new MessageReceivingEndpoint("https://www.google.com/accounts/OAuthGetRequestToken", HttpDeliveryMethods.AuthorizationHeaderRequest | HttpDeliveryMethods.GetRequest), UserAuthorizationEndpoint = new MessageReceivingEndpoint("https://www.google.com/accounts/OAuthAuthorizeToken", HttpDeliveryMethods.AuthorizationHeaderRequest | HttpDeliveryMethods.GetRequest), AccessTokenEndpoint = new MessageReceivingEndpoint("https://www.google.com/accounts/OAuthGetAccessToken", HttpDeliveryMethods.AuthorizationHeaderRequest | HttpDeliveryMethods.GetRequest), - TamperProtectionElements = new ITamperProtectionChannelBindingElement[] { new RsaSha1SigningBindingElement(signingCertificate) }, + TamperProtectionElements = new ITamperProtectionChannelBindingElement[] { new RsaSha1ConsumerSigningBindingElement(signingCertificate) }, }; } diff --git a/samples/InfoCardRelyingParty/Login.aspx b/samples/InfoCardRelyingParty/Login.aspx index dfb7499..b4b7f2f 100644 --- a/samples/InfoCardRelyingParty/Login.aspx +++ b/samples/InfoCardRelyingParty/Login.aspx @@ -15,7 +15,7 @@ End Sub </script> -<%@ Register Assembly="DotNetOpenAuth" Namespace="DotNetOpenAuth.InfoCard" TagPrefix="ic" %> +<%@ Register Assembly="DotNetOpenAuth.InfoCard.UI" Namespace="DotNetOpenAuth.InfoCard" TagPrefix="ic" %> <asp:Content ID="Content2" ContentPlaceHolderID="Main" runat="Server"> <p>This login page demonstrates logging in using the InfoCard selector. Click the InfoCard image below to login. </p> diff --git a/samples/InfoCardRelyingParty/web.config b/samples/InfoCardRelyingParty/web.config index 7599561..578ea26 100644 --- a/samples/InfoCardRelyingParty/web.config +++ b/samples/InfoCardRelyingParty/web.config @@ -2,7 +2,12 @@ <configuration> <configSections> <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler" requirePermission="false" /> - <section name="dotNetOpenAuth" type="DotNetOpenAuth.Configuration.DotNetOpenAuthSection" requirePermission="false" allowLocation="true"/> + <sectionGroup name="dotNetOpenAuth" type="DotNetOpenAuth.Configuration.DotNetOpenAuthSection, DotNetOpenAuth.Core"> + <section name="openid" type="DotNetOpenAuth.Configuration.OpenIdElement, DotNetOpenAuth.OpenId" requirePermission="false" allowLocation="true" /> + <section name="oauth" type="DotNetOpenAuth.Configuration.OAuthElement, DotNetOpenAuth.OAuth" requirePermission="false" allowLocation="true" /> + <section name="messaging" type="DotNetOpenAuth.Configuration.MessagingElement, DotNetOpenAuth.Core" requirePermission="false" allowLocation="true" /> + <section name="reporting" type="DotNetOpenAuth.Configuration.ReportingElement, DotNetOpenAuth.Core" requirePermission="false" allowLocation="true" /> + </sectionGroup> <sectionGroup name="system.web.extensions" type="System.Web.Configuration.SystemWebExtensionsSectionGroup, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"> <sectionGroup name="scripting" type="System.Web.Configuration.ScriptingSectionGroup, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"> <section name="scriptResourceHandler" type="System.Web.Configuration.ScriptingScriptResourceHandlerSection, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" allowDefinition="MachineToApplication"/> diff --git a/samples/OAuthAuthorizationServer/OAuthAuthorizationServer.csproj b/samples/OAuthAuthorizationServer/OAuthAuthorizationServer.csproj index 07b55b4..8dff7d5 100644 --- a/samples/OAuthAuthorizationServer/OAuthAuthorizationServer.csproj +++ b/samples/OAuthAuthorizationServer/OAuthAuthorizationServer.csproj @@ -15,6 +15,7 @@ <AssemblyName>OAuthAuthorizationServer</AssemblyName> <TargetFrameworkVersion>v4.0</TargetFrameworkVersion> <MvcBuildViews>false</MvcBuildViews> + <UseIISExpress>false</UseIISExpress> </PropertyGroup> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> <DebugSymbols>true</DebugSymbols> @@ -125,12 +126,6 @@ <Folder Include="App_Data\" /> </ItemGroup> <ItemGroup> - <ProjectReference Include="..\..\src\DotNetOpenAuth\DotNetOpenAuth.csproj"> - <Project>{3191B653-F76D-4C1A-9A5A-347BC3AAAAB7}</Project> - <Name>DotNetOpenAuth</Name> - </ProjectReference> - </ItemGroup> - <ItemGroup> <None Include="Code\DataClasses.dbml"> <Generator>MSLinqToSQLGenerator</Generator> <LastGenOutput>DataClasses.designer.cs</LastGenOutput> @@ -145,6 +140,28 @@ <ItemGroup> <Service Include="{3259AA49-8AA1-44D3-9025-A0B520596A8C}" /> </ItemGroup> + <ItemGroup> + <ProjectReference Include="..\..\src\DotNetOpenAuth.Core\DotNetOpenAuth.Core.csproj"> + <Project>{60426312-6AE5-4835-8667-37EDEA670222}</Project> + <Name>DotNetOpenAuth.Core</Name> + </ProjectReference> + <ProjectReference Include="..\..\src\DotNetOpenAuth.OAuth2.AuthorizationServer\DotNetOpenAuth.OAuth2.AuthorizationServer.csproj"> + <Project>{99BB7543-EA16-43EE-A7BC-D7A25A3B22F6}</Project> + <Name>DotNetOpenAuth.OAuth2.AuthorizationServer</Name> + </ProjectReference> + <ProjectReference Include="..\..\src\DotNetOpenAuth.OAuth2\DotNetOpenAuth.OAuth2.csproj"> + <Project>{56459A6C-6BA2-4BAC-A9C0-27E3BD961FA6}</Project> + <Name>DotNetOpenAuth.OAuth2</Name> + </ProjectReference> + <ProjectReference Include="..\..\src\DotNetOpenAuth.OpenId.RelyingParty\DotNetOpenAuth.OpenId.RelyingParty.csproj"> + <Project>{F458AB60-BA1C-43D9-8CEF-EC01B50BE87B}</Project> + <Name>DotNetOpenAuth.OpenId.RelyingParty</Name> + </ProjectReference> + <ProjectReference Include="..\..\src\DotNetOpenAuth.OpenId\DotNetOpenAuth.OpenId.csproj"> + <Project>{3896A32A-E876-4C23-B9B8-78E17D134CD3}</Project> + <Name>DotNetOpenAuth.OpenId</Name> + </ProjectReference> + </ItemGroup> <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v10.0\WebApplications\Microsoft.WebApplication.targets" /> <!-- To modify your build process, add your task inside one of the targets below and uncomment it. diff --git a/samples/OAuthAuthorizationServer/Web.config b/samples/OAuthAuthorizationServer/Web.config index 9f0ad2e..3d13e5a 100644 --- a/samples/OAuthAuthorizationServer/Web.config +++ b/samples/OAuthAuthorizationServer/Web.config @@ -9,7 +9,12 @@ <configSections> <section name="uri" type="System.Configuration.UriSection, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"/> <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler" requirePermission="false"/> - <section name="dotNetOpenAuth" type="DotNetOpenAuth.Configuration.DotNetOpenAuthSection" requirePermission="false" allowLocation="true"/> + <sectionGroup name="dotNetOpenAuth" type="DotNetOpenAuth.Configuration.DotNetOpenAuthSection, DotNetOpenAuth.Core"> + <section name="openid" type="DotNetOpenAuth.Configuration.OpenIdElement, DotNetOpenAuth.OpenId" requirePermission="false" allowLocation="true" /> + <section name="oauth" type="DotNetOpenAuth.Configuration.OAuthElement, DotNetOpenAuth.OAuth" requirePermission="false" allowLocation="true" /> + <section name="messaging" type="DotNetOpenAuth.Configuration.MessagingElement, DotNetOpenAuth.Core" requirePermission="false" allowLocation="true" /> + <section name="reporting" type="DotNetOpenAuth.Configuration.ReportingElement, DotNetOpenAuth.Core" requirePermission="false" allowLocation="true" /> + </sectionGroup> </configSections> <!-- The uri section is necessary to turn on .NET 3.5 support for IDN (international domain names), diff --git a/samples/OAuthClient/OAuthClient.csproj b/samples/OAuthClient/OAuthClient.csproj index e857351..1690126 100644 --- a/samples/OAuthClient/OAuthClient.csproj +++ b/samples/OAuthClient/OAuthClient.csproj @@ -173,9 +173,29 @@ <None Include="Settings.StyleCop" /> </ItemGroup> <ItemGroup> - <ProjectReference Include="..\..\src\DotNetOpenAuth\DotNetOpenAuth.csproj"> - <Project>{3191B653-F76D-4C1A-9A5A-347BC3AAAAB7}</Project> - <Name>DotNetOpenAuth</Name> + <ProjectReference Include="..\..\src\DotNetOpenAuth.Core\DotNetOpenAuth.Core.csproj"> + <Project>{60426312-6AE5-4835-8667-37EDEA670222}</Project> + <Name>DotNetOpenAuth.Core</Name> + </ProjectReference> + <ProjectReference Include="..\..\src\DotNetOpenAuth.OAuth.Consumer\DotNetOpenAuth.OAuth.Consumer.csproj"> + <Project>{B202E40D-4663-4A2B-ACDA-865F88FF7CAA}</Project> + <Name>DotNetOpenAuth.OAuth.Consumer</Name> + </ProjectReference> + <ProjectReference Include="..\..\src\DotNetOpenAuth.OAuth2.Client\DotNetOpenAuth.OAuth2.Client.csproj"> + <Project>{CDEDD439-7F35-4E6E-8605-4E70BDC4CC99}</Project> + <Name>DotNetOpenAuth.OAuth2.Client</Name> + </ProjectReference> + <ProjectReference Include="..\..\src\DotNetOpenAuth.OAuth2\DotNetOpenAuth.OAuth2.csproj"> + <Project>{56459A6C-6BA2-4BAC-A9C0-27E3BD961FA6}</Project> + <Name>DotNetOpenAuth.OAuth2</Name> + </ProjectReference> + <ProjectReference Include="..\..\src\DotNetOpenAuth.OAuth\DotNetOpenAuth.OAuth.csproj"> + <Project>{A288FCC8-6FCF-46DA-A45E-5F9281556361}</Project> + <Name>DotNetOpenAuth.OAuth</Name> + </ProjectReference> + <ProjectReference Include="..\..\src\DotNetOpenAuth.OpenId\DotNetOpenAuth.OpenId.csproj"> + <Project>{3896A32A-E876-4C23-B9B8-78E17D134CD3}</Project> + <Name>DotNetOpenAuth.OpenId</Name> </ProjectReference> <ProjectReference Include="..\DotNetOpenAuth.ApplicationBlock\DotNetOpenAuth.ApplicationBlock.csproj"> <Project>{AA78D112-D889-414B-A7D4-467B34C7B663}</Project> diff --git a/samples/OAuthClient/Web.config b/samples/OAuthClient/Web.config index 9ae29da..9fa30d9 100644 --- a/samples/OAuthClient/Web.config +++ b/samples/OAuthClient/Web.config @@ -3,7 +3,12 @@ <configSections> <section name="uri" type="System.Configuration.UriSection, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"/> <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler" requirePermission="false" /> - <section name="dotNetOpenAuth" type="DotNetOpenAuth.Configuration.DotNetOpenAuthSection" requirePermission="false" allowLocation="true"/> + <sectionGroup name="dotNetOpenAuth" type="DotNetOpenAuth.Configuration.DotNetOpenAuthSection, DotNetOpenAuth.Core"> + <section name="openid" type="DotNetOpenAuth.Configuration.OpenIdElement, DotNetOpenAuth.OpenId" requirePermission="false" allowLocation="true" /> + <section name="oauth" type="DotNetOpenAuth.Configuration.OAuthElement, DotNetOpenAuth.OAuth" requirePermission="false" allowLocation="true" /> + <section name="messaging" type="DotNetOpenAuth.Configuration.MessagingElement, DotNetOpenAuth.Core" requirePermission="false" allowLocation="true" /> + <section name="reporting" type="DotNetOpenAuth.Configuration.ReportingElement, DotNetOpenAuth.Core" requirePermission="false" allowLocation="true" /> + </sectionGroup> <sectionGroup name="system.web.extensions" type="System.Web.Configuration.SystemWebExtensionsSectionGroup, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"> <sectionGroup name="scripting" type="System.Web.Configuration.ScriptingSectionGroup, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"> <section name="scriptResourceHandler" type="System.Web.Configuration.ScriptingScriptResourceHandlerSection, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" allowDefinition="MachineToApplication"/> diff --git a/samples/OAuthConsumerWpf/App.config b/samples/OAuthConsumerWpf/App.config index fd7cc66..64d2032 100644 --- a/samples/OAuthConsumerWpf/App.config +++ b/samples/OAuthConsumerWpf/App.config @@ -3,7 +3,12 @@ <configSections> <section name="uri" type="System.Configuration.UriSection, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"/> <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler" requirePermission="false" /> - <section name="dotNetOpenAuth" type="DotNetOpenAuth.Configuration.DotNetOpenAuthSection, DotNetOpenAuth" requirePermission="false" allowLocation="true"/> + <sectionGroup name="dotNetOpenAuth" type="DotNetOpenAuth.Configuration.DotNetOpenAuthSection, DotNetOpenAuth.Core"> + <section name="openid" type="DotNetOpenAuth.Configuration.OpenIdElement, DotNetOpenAuth.OpenId" requirePermission="false" allowLocation="true" /> + <section name="oauth" type="DotNetOpenAuth.Configuration.OAuthElement, DotNetOpenAuth.OAuth" requirePermission="false" allowLocation="true" /> + <section name="messaging" type="DotNetOpenAuth.Configuration.MessagingElement, DotNetOpenAuth.Core" requirePermission="false" allowLocation="true" /> + <section name="reporting" type="DotNetOpenAuth.Configuration.ReportingElement, DotNetOpenAuth.Core" requirePermission="false" allowLocation="true" /> + </sectionGroup> </configSections> <!-- The uri section is necessary to turn on .NET 3.5 support for IDN (international domain names), diff --git a/samples/OAuthConsumerWpf/Authorize2.xaml b/samples/OAuthConsumerWpf/Authorize2.xaml index b477488..b4bb9d8 100644 --- a/samples/OAuthConsumerWpf/Authorize2.xaml +++ b/samples/OAuthConsumerWpf/Authorize2.xaml @@ -1,6 +1,6 @@ <Window x:Class="DotNetOpenAuth.Samples.OAuthConsumerWpf.Authorize2" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" - xmlns:oauth2="clr-namespace:DotNetOpenAuth.OAuth2;assembly=DotNetOpenAuth" + xmlns:oauth2="clr-namespace:DotNetOpenAuth.OAuth2;assembly=DotNetOpenAuth.OAuth2.Client.UI" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="Authorize" Height="500" Width="500"> <DockPanel LastChildFill="True"> diff --git a/samples/OAuthConsumerWpf/OAuthConsumerWpf.csproj b/samples/OAuthConsumerWpf/OAuthConsumerWpf.csproj index bf15c0b..f1c03db 100644 --- a/samples/OAuthConsumerWpf/OAuthConsumerWpf.csproj +++ b/samples/OAuthConsumerWpf/OAuthConsumerWpf.csproj @@ -71,7 +71,7 @@ <SpecificVersion>False</SpecificVersion> <HintPath>..\..\lib\log4net.dll</HintPath> </Reference> - <Reference Include="Microsoft.Contracts, Version=1.0.0.0, Culture=neutral, PublicKeyToken=736440c9b414ea16, processorArchitecture=MSIL"> + <Reference Include="Microsoft.Contracts, Version=1.0.0.0, Culture=neutral, PublicKeyToken=736440c9b414ea16, processorArchitecture=MSIL" Condition=" '$(TargetFrameworkVersion)' == 'v3.5' "> <SpecificVersion>False</SpecificVersion> <HintPath>..\..\lib\Microsoft.Contracts.dll</HintPath> </Reference> @@ -186,9 +186,29 @@ <AppDesigner Include="Properties\" /> </ItemGroup> <ItemGroup> - <ProjectReference Include="..\..\src\DotNetOpenAuth\DotNetOpenAuth.csproj"> - <Project>{3191B653-F76D-4C1A-9A5A-347BC3AAAAB7}</Project> - <Name>DotNetOpenAuth</Name> + <ProjectReference Include="..\..\src\DotNetOpenAuth.Core\DotNetOpenAuth.Core.csproj"> + <Project>{60426312-6AE5-4835-8667-37EDEA670222}</Project> + <Name>DotNetOpenAuth.Core</Name> + </ProjectReference> + <ProjectReference Include="..\..\src\DotNetOpenAuth.OAuth.Consumer\DotNetOpenAuth.OAuth.Consumer.csproj"> + <Project>{B202E40D-4663-4A2B-ACDA-865F88FF7CAA}</Project> + <Name>DotNetOpenAuth.OAuth.Consumer</Name> + </ProjectReference> + <ProjectReference Include="..\..\src\DotNetOpenAuth.OAuth2.Client.UI\DotNetOpenAuth.OAuth2.Client.UI.csproj"> + <Project>{ADC2CC8C-541E-4F86-ACB1-DD504A36FA4B}</Project> + <Name>DotNetOpenAuth.OAuth2.Client.UI</Name> + </ProjectReference> + <ProjectReference Include="..\..\src\DotNetOpenAuth.OAuth2.Client\DotNetOpenAuth.OAuth2.Client.csproj"> + <Project>{CDEDD439-7F35-4E6E-8605-4E70BDC4CC99}</Project> + <Name>DotNetOpenAuth.OAuth2.Client</Name> + </ProjectReference> + <ProjectReference Include="..\..\src\DotNetOpenAuth.OAuth2\DotNetOpenAuth.OAuth2.csproj"> + <Project>{56459A6C-6BA2-4BAC-A9C0-27E3BD961FA6}</Project> + <Name>DotNetOpenAuth.OAuth2</Name> + </ProjectReference> + <ProjectReference Include="..\..\src\DotNetOpenAuth.OAuth\DotNetOpenAuth.OAuth.csproj"> + <Project>{A288FCC8-6FCF-46DA-A45E-5F9281556361}</Project> + <Name>DotNetOpenAuth.OAuth</Name> </ProjectReference> <ProjectReference Include="..\DotNetOpenAuth.ApplicationBlock\DotNetOpenAuth.ApplicationBlock.csproj"> <Project>{AA78D112-D889-414B-A7D4-467B34C7B663}</Project> @@ -243,4 +263,4 @@ </Target> --> <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), EnlistmentInfo.targets))\EnlistmentInfo.targets" Condition=" '$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), EnlistmentInfo.targets))' != '' " /> -</Project> +</Project>
\ No newline at end of file diff --git a/samples/OAuthResourceServer/Login.aspx b/samples/OAuthResourceServer/Login.aspx index fd858c8..9593faa 100644 --- a/samples/OAuthResourceServer/Login.aspx +++ b/samples/OAuthResourceServer/Login.aspx @@ -1,6 +1,6 @@ <%@ Page Title="Login" Language="C#" MasterPageFile="~/MasterPage.master" %> -<%@ Register Assembly="DotNetOpenAuth" Namespace="DotNetOpenAuth.OpenId.RelyingParty" TagPrefix="rp" %> +<%@ Register Assembly="DotNetOpenAuth.OpenId.RelyingParty.UI" Namespace="DotNetOpenAuth.OpenId.RelyingParty" TagPrefix="rp" %> <script runat="server"> protected void Page_Load(object sender, EventArgs e) { OpenIdLogin1.Focus(); diff --git a/samples/OAuthResourceServer/OAuthResourceServer.csproj b/samples/OAuthResourceServer/OAuthResourceServer.csproj index e2cb954..1d81d85 100644 --- a/samples/OAuthResourceServer/OAuthResourceServer.csproj +++ b/samples/OAuthResourceServer/OAuthResourceServer.csproj @@ -14,6 +14,7 @@ <RootNamespace>OAuthResourceServer</RootNamespace> <AssemblyName>OAuthResourceServer</AssemblyName> <TargetFrameworkVersion>v3.5</TargetFrameworkVersion> + <UseIISExpress>false</UseIISExpress> </PropertyGroup> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> <DebugSymbols>true</DebugSymbols> @@ -102,9 +103,17 @@ <Service Include="{3259AA49-8AA1-44D3-9025-A0B520596A8C}" /> </ItemGroup> <ItemGroup> - <ProjectReference Include="..\..\src\DotNetOpenAuth\DotNetOpenAuth.csproj"> - <Project>{3191B653-F76D-4C1A-9A5A-347BC3AAAAB7}</Project> - <Name>DotNetOpenAuth</Name> + <ProjectReference Include="..\..\src\DotNetOpenAuth.Core\DotNetOpenAuth.Core.csproj"> + <Project>{60426312-6AE5-4835-8667-37EDEA670222}</Project> + <Name>DotNetOpenAuth.Core</Name> + </ProjectReference> + <ProjectReference Include="..\..\src\DotNetOpenAuth.OAuth2.ResourceServer\DotNetOpenAuth.OAuth2.ResourceServer.csproj"> + <Project>{A1A3150A-7B0E-4A34-8E35-045296CD3C76}</Project> + <Name>DotNetOpenAuth.OAuth2.ResourceServer</Name> + </ProjectReference> + <ProjectReference Include="..\..\src\DotNetOpenAuth.OAuth2\DotNetOpenAuth.OAuth2.csproj"> + <Project>{56459A6C-6BA2-4BAC-A9C0-27E3BD961FA6}</Project> + <Name>DotNetOpenAuth.OAuth2</Name> </ProjectReference> </ItemGroup> <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" /> diff --git a/samples/OAuthResourceServer/Web.config b/samples/OAuthResourceServer/Web.config index 5d7d30c..827fb07 100644 --- a/samples/OAuthResourceServer/Web.config +++ b/samples/OAuthResourceServer/Web.config @@ -3,7 +3,12 @@ <configSections> <section name="uri" type="System.Configuration.UriSection, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"/> <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler" requirePermission="false"/> - <section name="dotNetOpenAuth" type="DotNetOpenAuth.Configuration.DotNetOpenAuthSection" requirePermission="false" allowLocation="true"/> + <sectionGroup name="dotNetOpenAuth" type="DotNetOpenAuth.Configuration.DotNetOpenAuthSection, DotNetOpenAuth.Core"> + <section name="openid" type="DotNetOpenAuth.Configuration.OpenIdElement, DotNetOpenAuth.OpenId" requirePermission="false" allowLocation="true" /> + <section name="oauth" type="DotNetOpenAuth.Configuration.OAuthElement, DotNetOpenAuth.OAuth" requirePermission="false" allowLocation="true" /> + <section name="messaging" type="DotNetOpenAuth.Configuration.MessagingElement, DotNetOpenAuth.Core" requirePermission="false" allowLocation="true" /> + <section name="reporting" type="DotNetOpenAuth.Configuration.ReportingElement, DotNetOpenAuth.Core" requirePermission="false" allowLocation="true" /> + </sectionGroup> <sectionGroup name="system.web.extensions" type="System.Web.Configuration.SystemWebExtensionsSectionGroup, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"> <sectionGroup name="scripting" type="System.Web.Configuration.ScriptingSectionGroup, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"> <section name="scriptResourceHandler" type="System.Web.Configuration.ScriptingScriptResourceHandlerSection, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" allowDefinition="MachineToApplication"/> diff --git a/samples/OpenIdOfflineProvider/App.config b/samples/OpenIdOfflineProvider/App.config index 7263338..7bd5b37 100644 --- a/samples/OpenIdOfflineProvider/App.config +++ b/samples/OpenIdOfflineProvider/App.config @@ -2,7 +2,12 @@ <configuration> <configSections> <section name="uri" type="System.Configuration.UriSection, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"/> - <section name="dotNetOpenAuth" type="DotNetOpenAuth.Configuration.DotNetOpenAuthSection, DotNetOpenAuth" requirePermission="false" allowLocation="true"/> + <sectionGroup name="dotNetOpenAuth" type="DotNetOpenAuth.Configuration.DotNetOpenAuthSection, DotNetOpenAuth.Core"> + <section name="openid" type="DotNetOpenAuth.Configuration.OpenIdElement, DotNetOpenAuth.OpenId" requirePermission="false" allowLocation="true" /> + <section name="oauth" type="DotNetOpenAuth.Configuration.OAuthElement, DotNetOpenAuth.OAuth" requirePermission="false" allowLocation="true" /> + <section name="messaging" type="DotNetOpenAuth.Configuration.MessagingElement, DotNetOpenAuth.Core" requirePermission="false" allowLocation="true" /> + <section name="reporting" type="DotNetOpenAuth.Configuration.ReportingElement, DotNetOpenAuth.Core" requirePermission="false" allowLocation="true" /> + </sectionGroup> <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net" requirePermission="false"/> </configSections> diff --git a/samples/OpenIdOfflineProvider/CheckIdWindow.xaml.cs b/samples/OpenIdOfflineProvider/CheckIdWindow.xaml.cs index c80264d..89a581c 100644 --- a/samples/OpenIdOfflineProvider/CheckIdWindow.xaml.cs +++ b/samples/OpenIdOfflineProvider/CheckIdWindow.xaml.cs @@ -40,7 +40,7 @@ namespace DotNetOpenAuth.OpenIdOfflineProvider { this.immediateModeLabel.Visibility = request.Immediate ? Visibility.Visible : Visibility.Collapsed; this.setupModeLabel.Visibility = request.Immediate ? Visibility.Collapsed : Visibility.Visible; - bool isRPDiscoverable = request.IsReturnUrlDiscoverable(provider.Provider) == RelyingPartyDiscoveryResult.Success; + bool isRPDiscoverable = request.IsReturnUrlDiscoverable(provider.Provider.Channel.WebRequestHandler) == RelyingPartyDiscoveryResult.Success; this.discoverableYesLabel.Visibility = isRPDiscoverable ? Visibility.Visible : Visibility.Collapsed; this.discoverableNoLabel.Visibility = isRPDiscoverable ? Visibility.Collapsed : Visibility.Visible; diff --git a/samples/OpenIdOfflineProvider/OpenIdOfflineProvider.csproj b/samples/OpenIdOfflineProvider/OpenIdOfflineProvider.csproj index beb332d..6971026 100644 --- a/samples/OpenIdOfflineProvider/OpenIdOfflineProvider.csproj +++ b/samples/OpenIdOfflineProvider/OpenIdOfflineProvider.csproj @@ -99,6 +99,7 @@ <RequiredTargetFramework>3.5</RequiredTargetFramework> </Reference> <Reference Include="System.Web" /> + <Reference Include="System.Web.Abstractions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL" Condition=" '$(ClrVersion)' == '2' " /> <Reference Include="System.Xml.Linq"> <RequiredTargetFramework>3.5</RequiredTargetFramework> </Reference> @@ -184,12 +185,6 @@ <AppDesigner Include="Properties\" /> </ItemGroup> <ItemGroup> - <ProjectReference Include="..\..\src\DotNetOpenAuth\DotNetOpenAuth.csproj"> - <Project>{3191B653-F76D-4C1A-9A5A-347BC3AAAAB7}</Project> - <Name>DotNetOpenAuth</Name> - </ProjectReference> - </ItemGroup> - <ItemGroup> <Resource Include="openid.ico" /> </ItemGroup> <ItemGroup> @@ -224,15 +219,37 @@ <DelaySignedAssemblies Include="$(ILMergeProjectOutputAssembly)"> <Visible>false</Visible> </DelaySignedAssemblies> - <ILMergeProjectInputAssemblies Include="$(TargetPath); - $(ProjectRoot)lib\Microsoft.Contracts.dll; "> + <ILMergeProjectInputAssemblies Include="$(TargetPath);
 $(ProjectRoot)lib\Microsoft.Contracts.dll; "> <Visible>false</Visible> </ILMergeProjectInputAssemblies> </ItemGroup> + <ItemGroup> + <ProjectReference Include="..\..\src\DotNetOpenAuth.Core\DotNetOpenAuth.Core.csproj"> + <Project>{60426312-6AE5-4835-8667-37EDEA670222}</Project> + <Name>DotNetOpenAuth.Core</Name> + </ProjectReference> + <ProjectReference Include="..\..\src\DotNetOpenAuth.OpenId.Provider\DotNetOpenAuth.OpenId.Provider.csproj"> + <Project>{F8284738-3B5D-4733-A511-38C23F4A763F}</Project> + <Name>DotNetOpenAuth.OpenId.Provider</Name> + </ProjectReference> + <ProjectReference Include="..\..\src\DotNetOpenAuth.OpenId\DotNetOpenAuth.OpenId.csproj"> + <Project>{3896A32A-E876-4C23-B9B8-78E17D134CD3}</Project> + <Name>DotNetOpenAuth.OpenId</Name> + </ProjectReference> + </ItemGroup> <Target Name="BuildUnified" DependsOnTargets="Build" Inputs="@(ILMergeProjectInputAssemblies)" Outputs="$(ILMergeProjectOutputAssembly)"> <MakeDir Directories="$(ILMergeOutputAssemblyDirectory)" /> - <ILMerge ExcludeFile="$(ProjectRoot)ILMergeInternalizeExceptions.txt" InputAssemblies="@(ILMergeProjectInputAssemblies)" OutputFile="$(ILMergeProjectOutputAssembly)" KeyFile="$(PublicKeyFile)" DelaySign="true" /> + <ILMerge + ExcludeFile="$(ProjectRoot)ILMergeInternalizeExceptions.txt" + InputAssemblies="@(ILMergeProjectInputAssemblies)" + OutputFile="$(ILMergeProjectOutputAssembly)" + SearchDirectories="$(OutputPath);@(ILMergeSearchDirectories)" + ToolPath="$(ProjectRoot)tools\ILMerge" + KeyFile="$(PublicKeyFile)" + DelaySign="true" + TargetPlatformVersion="$(ClrVersion).0" + TargetPlatformDirectory="$(ILMergeTargetPlatformDirectory)" /> </Target> <Import Project="$(ProjectRoot)tools\DotNetOpenAuth.targets" /> <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), EnlistmentInfo.targets))\EnlistmentInfo.targets" Condition=" '$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), EnlistmentInfo.targets))' != '' " /> -</Project> +</Project>
\ No newline at end of file diff --git a/samples/OpenIdProviderMvc/Controllers/OpenIdController.cs b/samples/OpenIdProviderMvc/Controllers/OpenIdController.cs index 5445875..198c434 100644 --- a/samples/OpenIdProviderMvc/Controllers/OpenIdController.cs +++ b/samples/OpenIdProviderMvc/Controllers/OpenIdController.cs @@ -11,6 +11,7 @@ namespace OpenIdProviderMvc.Controllers { using DotNetOpenAuth.OpenId.Extensions.ProviderAuthenticationPolicy; using DotNetOpenAuth.OpenId.Extensions.SimpleRegistration; using DotNetOpenAuth.OpenId.Provider; + using DotNetOpenAuth.OpenId.Provider.Behaviors; using OpenIdProviderMvc.Code; public class OpenIdController : Controller { @@ -144,7 +145,7 @@ namespace OpenIdProviderMvc.Controllers { /// <returns>A value indicating whether an automatic response is possible.</returns> private bool AutoRespondIfPossible(out ActionResult response) { // If the odds are good we can respond to this one immediately (without prompting the user)... - if (ProviderEndpoint.PendingRequest.IsReturnUrlDiscoverable(OpenIdProvider) == RelyingPartyDiscoveryResult.Success + if (ProviderEndpoint.PendingRequest.IsReturnUrlDiscoverable(OpenIdProvider.Channel.WebRequestHandler) == RelyingPartyDiscoveryResult.Success && User.Identity.IsAuthenticated && this.HasUserAuthorizedAutoLogin(ProviderEndpoint.PendingRequest)) { // Is this is an identity authentication request? (as opposed to an anonymous request)... diff --git a/samples/OpenIdProviderMvc/Global.asax.cs b/samples/OpenIdProviderMvc/Global.asax.cs index 8390c46..08b962a 100644 --- a/samples/OpenIdProviderMvc/Global.asax.cs +++ b/samples/OpenIdProviderMvc/Global.asax.cs @@ -42,11 +42,11 @@ } private static void InitializeBehaviors() { - if (DotNetOpenAuth.OpenId.Behaviors.PpidGeneration.PpidIdentifierProvider == null) { + if (DotNetOpenAuth.OpenId.Provider.Behaviors.PpidGeneration.PpidIdentifierProvider == null) { lock (behaviorInitializationSyncObject) { - if (DotNetOpenAuth.OpenId.Behaviors.PpidGeneration.PpidIdentifierProvider == null) { - DotNetOpenAuth.OpenId.Behaviors.PpidGeneration.PpidIdentifierProvider = new Code.AnonymousIdentifierProvider(); - DotNetOpenAuth.OpenId.Behaviors.GsaIcamProfile.PpidIdentifierProvider = new Code.AnonymousIdentifierProvider(); + if (DotNetOpenAuth.OpenId.Provider.Behaviors.PpidGeneration.PpidIdentifierProvider == null) { + DotNetOpenAuth.OpenId.Provider.Behaviors.PpidGeneration.PpidIdentifierProvider = new Code.AnonymousIdentifierProvider(); + DotNetOpenAuth.OpenId.Provider.Behaviors.GsaIcamProfile.PpidIdentifierProvider = new Code.AnonymousIdentifierProvider(); } } } diff --git a/samples/OpenIdProviderMvc/OpenIdProviderMvc.csproj b/samples/OpenIdProviderMvc/OpenIdProviderMvc.csproj index d609a29..2607c47 100644 --- a/samples/OpenIdProviderMvc/OpenIdProviderMvc.csproj +++ b/samples/OpenIdProviderMvc/OpenIdProviderMvc.csproj @@ -14,6 +14,7 @@ <TargetFrameworkVersion>v3.5</TargetFrameworkVersion> <MvcBuildViews>false</MvcBuildViews> <TargetFrameworkProfile /> + <UseIISExpress>false</UseIISExpress> </PropertyGroup> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> <DebugSymbols>true</DebugSymbols> @@ -115,9 +116,21 @@ <Content Include="Views\Web.config" /> </ItemGroup> <ItemGroup> - <ProjectReference Include="..\..\src\DotNetOpenAuth\DotNetOpenAuth.csproj"> - <Project>{3191B653-F76D-4C1A-9A5A-347BC3AAAAB7}</Project> - <Name>DotNetOpenAuth</Name> + <ProjectReference Include="..\..\src\DotNetOpenAuth.Core\DotNetOpenAuth.Core.csproj"> + <Project>{60426312-6AE5-4835-8667-37EDEA670222}</Project> + <Name>DotNetOpenAuth.Core</Name> + </ProjectReference> + <ProjectReference Include="..\..\src\DotNetOpenAuth.OpenId.Provider.UI\DotNetOpenAuth.OpenId.Provider.UI.csproj"> + <Project>{9D0F8866-2131-4C2A-BC0E-16FEA5B50828}</Project> + <Name>DotNetOpenAuth.OpenId.Provider.UI</Name> + </ProjectReference> + <ProjectReference Include="..\..\src\DotNetOpenAuth.OpenId.Provider\DotNetOpenAuth.OpenId.Provider.csproj"> + <Project>{F8284738-3B5D-4733-A511-38C23F4A763F}</Project> + <Name>DotNetOpenAuth.OpenId.Provider</Name> + </ProjectReference> + <ProjectReference Include="..\..\src\DotNetOpenAuth.OpenId\DotNetOpenAuth.OpenId.csproj"> + <Project>{3896A32A-E876-4C23-B9B8-78E17D134CD3}</Project> + <Name>DotNetOpenAuth.OpenId</Name> </ProjectReference> <ProjectReference Include="..\DotNetOpenAuth.ApplicationBlock\DotNetOpenAuth.ApplicationBlock.csproj"> <Project>{AA78D112-D889-414B-A7D4-467B34C7B663}</Project> @@ -153,4 +166,4 @@ </VisualStudio> </ProjectExtensions> <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), EnlistmentInfo.targets))\EnlistmentInfo.targets" Condition=" '$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), EnlistmentInfo.targets))' != '' " /> -</Project> +</Project>
\ No newline at end of file diff --git a/samples/OpenIdProviderMvc/Views/Home/Index.aspx b/samples/OpenIdProviderMvc/Views/Home/Index.aspx index 365a7c6..17cb6c4 100644 --- a/samples/OpenIdProviderMvc/Views/Home/Index.aspx +++ b/samples/OpenIdProviderMvc/Views/Home/Index.aspx @@ -1,6 +1,6 @@ <%@ Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage" %> -<%@ Register Assembly="DotNetOpenAuth" Namespace="DotNetOpenAuth" TagPrefix="openauth" %> +<%@ Register Assembly="DotNetOpenAuth.OpenId.UI" Namespace="DotNetOpenAuth" TagPrefix="openauth" %> <asp:Content ID="indexTitle" ContentPlaceHolderID="TitleContent" runat="server"> Home Page </asp:Content> diff --git a/samples/OpenIdProviderMvc/Views/User/Identity.aspx b/samples/OpenIdProviderMvc/Views/User/Identity.aspx index 51233a3..31d9c0a 100644 --- a/samples/OpenIdProviderMvc/Views/User/Identity.aspx +++ b/samples/OpenIdProviderMvc/Views/User/Identity.aspx @@ -1,6 +1,6 @@ <%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage" %> -<%@ Register Assembly="DotNetOpenAuth" Namespace="DotNetOpenAuth.OpenId.Provider" +<%@ Register Assembly="DotNetOpenAuth.OpenId.Provider.UI" Namespace="DotNetOpenAuth.OpenId.Provider" TagPrefix="op" %> <asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server"> <%=Html.Encode(ViewData["username"] ?? string.Empty)%> diff --git a/samples/OpenIdProviderMvc/Web.config b/samples/OpenIdProviderMvc/Web.config index 9b1eb90..6c5b5b6 100644 --- a/samples/OpenIdProviderMvc/Web.config +++ b/samples/OpenIdProviderMvc/Web.config @@ -3,7 +3,12 @@ <configSections> <section name="uri" type="System.Configuration.UriSection, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"/> <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler" requirePermission="false"/> - <section name="dotNetOpenAuth" type="DotNetOpenAuth.Configuration.DotNetOpenAuthSection" requirePermission="false" allowLocation="true"/> + <sectionGroup name="dotNetOpenAuth" type="DotNetOpenAuth.Configuration.DotNetOpenAuthSection, DotNetOpenAuth.Core"> + <section name="openid" type="DotNetOpenAuth.Configuration.OpenIdElement, DotNetOpenAuth.OpenId" requirePermission="false" allowLocation="true" /> + <section name="oauth" type="DotNetOpenAuth.Configuration.OAuthElement, DotNetOpenAuth.OAuth" requirePermission="false" allowLocation="true" /> + <section name="messaging" type="DotNetOpenAuth.Configuration.MessagingElement, DotNetOpenAuth.Core" requirePermission="false" allowLocation="true" /> + <section name="reporting" type="DotNetOpenAuth.Configuration.ReportingElement, DotNetOpenAuth.Core" requirePermission="false" allowLocation="true" /> + </sectionGroup> <sectionGroup name="system.web.extensions" type="System.Web.Configuration.SystemWebExtensionsSectionGroup, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"> <sectionGroup name="scripting" type="System.Web.Configuration.ScriptingSectionGroup, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"> <section name="scriptResourceHandler" type="System.Web.Configuration.ScriptingScriptResourceHandlerSection, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" allowDefinition="MachineToApplication"/> @@ -44,9 +49,9 @@ <!-- Behaviors activate themselves automatically for individual matching requests. The first one in this list to match an incoming request "owns" the request. If no profile matches, the default behavior is assumed. --> - <!--<add type="DotNetOpenAuth.OpenId.Behaviors.GsaIcamProfile, DotNetOpenAuth" />--> - <add type="DotNetOpenAuth.OpenId.Behaviors.PpidGeneration, DotNetOpenAuth" /> - <add type="DotNetOpenAuth.OpenId.Behaviors.AXFetchAsSregTransform, DotNetOpenAuth" /> + <!--<add type="DotNetOpenAuth.OpenId.Provider.Behaviors.GsaIcamProfile, DotNetOpenAuth.OpenId.Provider" />--> + <add type="DotNetOpenAuth.OpenId.Provider.Behaviors.PpidGeneration, DotNetOpenAuth.OpenId.Provider" /> + <add type="DotNetOpenAuth.OpenId.Provider.Behaviors.AXFetchAsSregTransform, DotNetOpenAuth.OpenId.Provider" /> </behaviors> <!-- Uncomment the following to activate the sample custom store. --> <!--<store type="RelyingPartyWebForms.CustomStore, RelyingPartyWebForms" />--> diff --git a/samples/OpenIdProviderWebForms/Code/CustomStore.cs b/samples/OpenIdProviderWebForms/Code/CustomStore.cs index b2316a4..6688e27 100644 --- a/samples/OpenIdProviderWebForms/Code/CustomStore.cs +++ b/samples/OpenIdProviderWebForms/Code/CustomStore.cs @@ -67,7 +67,7 @@ namespace OpenIdProviderWebForms.Code { return false; } - TimeSpan maxMessageAge = DotNetOpenAuthSection.Configuration.Messaging.MaximumMessageLifetime; + TimeSpan maxMessageAge = DotNetOpenAuthSection.Messaging.MaximumMessageLifetime; dataSet.Nonce.AddNonceRow(context, nonce, timestampUtc, timestampUtc + maxMessageAge); return true; } diff --git a/samples/OpenIdProviderWebForms/Default.aspx b/samples/OpenIdProviderWebForms/Default.aspx index c1c948e..4f9e4bc 100644 --- a/samples/OpenIdProviderWebForms/Default.aspx +++ b/samples/OpenIdProviderWebForms/Default.aspx @@ -2,8 +2,8 @@ Inherits="OpenIdProviderWebForms._default" %> <%@ Import Namespace="OpenIdProviderWebForms.Code" %> -<%@ Register Assembly="DotNetOpenAuth" Namespace="DotNetOpenAuth.OpenId" TagPrefix="openid" %> -<%@ Register Assembly="DotNetOpenAuth" Namespace="DotNetOpenAuth" TagPrefix="openauth" %> +<%@ Register Assembly="DotNetOpenAuth.OpenId.UI" Namespace="DotNetOpenAuth.OpenId" TagPrefix="openid" %> +<%@ Register Assembly="DotNetOpenAuth.OpenId.UI" Namespace="DotNetOpenAuth" TagPrefix="openauth" %> <asp:Content runat="server" ContentPlaceHolderID="head"> <openauth:XrdsPublisher runat="server" XrdsUrl="~/op_xrds.aspx" /> diff --git a/samples/OpenIdProviderWebForms/Default.aspx.designer.cs b/samples/OpenIdProviderWebForms/Default.aspx.designer.cs index 74f2647..70872c7 100644 --- a/samples/OpenIdProviderWebForms/Default.aspx.designer.cs +++ b/samples/OpenIdProviderWebForms/Default.aspx.designer.cs @@ -1,10 +1,9 @@ //------------------------------------------------------------------------------ // <auto-generated> // This code was generated by a tool. -// Runtime Version:2.0.50727.4912 // // Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. +// the code is regenerated. // </auto-generated> //------------------------------------------------------------------------------ diff --git a/samples/OpenIdProviderWebForms/OpenIdProviderWebForms.csproj b/samples/OpenIdProviderWebForms/OpenIdProviderWebForms.csproj index 861cdb7..f1da9b6 100644 --- a/samples/OpenIdProviderWebForms/OpenIdProviderWebForms.csproj +++ b/samples/OpenIdProviderWebForms/OpenIdProviderWebForms.csproj @@ -18,6 +18,7 @@ <OldToolsVersion>3.5</OldToolsVersion> <UpgradeBackupLocation /> <TargetFrameworkProfile /> + <UseIISExpress>false</UseIISExpress> </PropertyGroup> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> <DebugSymbols>true</DebugSymbols> @@ -183,9 +184,33 @@ <Content Include="Provider.ashx" /> </ItemGroup> <ItemGroup> - <ProjectReference Include="..\..\src\DotNetOpenAuth\DotNetOpenAuth.csproj"> - <Project>{3191B653-F76D-4C1A-9A5A-347BC3AAAAB7}</Project> - <Name>DotNetOpenAuth</Name> + <ProjectReference Include="..\..\src\DotNetOpenAuth.Core\DotNetOpenAuth.Core.csproj"> + <Project>{60426312-6AE5-4835-8667-37EDEA670222}</Project> + <Name>DotNetOpenAuth.Core</Name> + </ProjectReference> + <ProjectReference Include="..\..\src\DotNetOpenAuth.OAuth.ServiceProvider\DotNetOpenAuth.OAuth.ServiceProvider.csproj"> + <Project>{FED1923A-6D70-49B5-A37A-FB744FEC1C86}</Project> + <Name>DotNetOpenAuth.OAuth.ServiceProvider</Name> + </ProjectReference> + <ProjectReference Include="..\..\src\DotNetOpenAuth.OAuth\DotNetOpenAuth.OAuth.csproj"> + <Project>{A288FCC8-6FCF-46DA-A45E-5F9281556361}</Project> + <Name>DotNetOpenAuth.OAuth</Name> + </ProjectReference> + <ProjectReference Include="..\..\src\DotNetOpenAuth.OpenId.Provider.UI\DotNetOpenAuth.OpenId.Provider.UI.csproj"> + <Project>{9D0F8866-2131-4C2A-BC0E-16FEA5B50828}</Project> + <Name>DotNetOpenAuth.OpenId.Provider.UI</Name> + </ProjectReference> + <ProjectReference Include="..\..\src\DotNetOpenAuth.OpenId.Provider\DotNetOpenAuth.OpenId.Provider.csproj"> + <Project>{F8284738-3B5D-4733-A511-38C23F4A763F}</Project> + <Name>DotNetOpenAuth.OpenId.Provider</Name> + </ProjectReference> + <ProjectReference Include="..\..\src\DotNetOpenAuth.OpenId.UI\DotNetOpenAuth.OpenId.UI.csproj"> + <Project>{75E13AAE-7D51-4421-ABFD-3F3DC91F576E}</Project> + <Name>DotNetOpenAuth.OpenId.UI</Name> + </ProjectReference> + <ProjectReference Include="..\..\src\DotNetOpenAuth.OpenId\DotNetOpenAuth.OpenId.csproj"> + <Project>{3896A32A-E876-4C23-B9B8-78E17D134CD3}</Project> + <Name>DotNetOpenAuth.OpenId</Name> </ProjectReference> <ProjectReference Include="..\DotNetOpenAuth.ApplicationBlock\DotNetOpenAuth.ApplicationBlock.csproj"> <Project>{AA78D112-D889-414B-A7D4-467B34C7B663}</Project> @@ -221,4 +246,4 @@ </VisualStudio> </ProjectExtensions> <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), EnlistmentInfo.targets))\EnlistmentInfo.targets" Condition=" '$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), EnlistmentInfo.targets))' != '' " /> -</Project> +</Project>
\ No newline at end of file diff --git a/samples/OpenIdProviderWebForms/Web.config b/samples/OpenIdProviderWebForms/Web.config index de92621..0c56bfe 100644 --- a/samples/OpenIdProviderWebForms/Web.config +++ b/samples/OpenIdProviderWebForms/Web.config @@ -3,7 +3,12 @@ <configSections> <section name="uri" type="System.Configuration.UriSection, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"/> <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler" requirePermission="false"/> - <section name="dotNetOpenAuth" type="DotNetOpenAuth.Configuration.DotNetOpenAuthSection" requirePermission="false" allowLocation="true"/> + <sectionGroup name="dotNetOpenAuth" type="DotNetOpenAuth.Configuration.DotNetOpenAuthSection, DotNetOpenAuth.Core"> + <section name="openid" type="DotNetOpenAuth.Configuration.OpenIdElement, DotNetOpenAuth.OpenId" requirePermission="false" allowLocation="true" /> + <section name="oauth" type="DotNetOpenAuth.Configuration.OAuthElement, DotNetOpenAuth.OAuth" requirePermission="false" allowLocation="true" /> + <section name="messaging" type="DotNetOpenAuth.Configuration.MessagingElement, DotNetOpenAuth.Core" requirePermission="false" allowLocation="true" /> + <section name="reporting" type="DotNetOpenAuth.Configuration.ReportingElement, DotNetOpenAuth.Core" requirePermission="false" allowLocation="true" /> + </sectionGroup> <sectionGroup name="system.web.extensions" type="System.Web.Configuration.SystemWebExtensionsSectionGroup, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"> <sectionGroup name="scripting" type="System.Web.Configuration.ScriptingSectionGroup, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"> <section name="scriptResourceHandler" type="System.Web.Configuration.ScriptingScriptResourceHandlerSection, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" allowDefinition="MachineToApplication"/> @@ -44,7 +49,7 @@ <!-- Behaviors activate themselves automatically for individual matching requests. The first one in this list to match an incoming request "owns" the request. If no profile matches, the default behavior is assumed. --> - <!--<add type="DotNetOpenAuth.OpenId.Behaviors.PpidGeneration, DotNetOpenAuth" />--> + <!--<add type="DotNetOpenAuth.OpenId.Provider.Behaviors.PpidGeneration, DotNetOpenAuth.OpenId.Provider" />--> </behaviors> <!-- Uncomment the following to activate the sample custom store. --> <!--<store type="OpenIdProviderWebForms.Code.CustomStore, OpenIdProviderWebForms" />--> diff --git a/samples/OpenIdProviderWebForms/decide.aspx.cs b/samples/OpenIdProviderWebForms/decide.aspx.cs index 40c17c0..8c8f927 100644 --- a/samples/OpenIdProviderWebForms/decide.aspx.cs +++ b/samples/OpenIdProviderWebForms/decide.aspx.cs @@ -18,7 +18,7 @@ namespace OpenIdProviderWebForms { } this.relyingPartyVerificationResultLabel.Text = - ProviderEndpoint.PendingRequest.IsReturnUrlDiscoverable(ProviderEndpoint.Provider) == RelyingPartyDiscoveryResult.Success ? "passed" : "failed"; + ProviderEndpoint.PendingRequest.IsReturnUrlDiscoverable(ProviderEndpoint.Provider.Channel.WebRequestHandler) == RelyingPartyDiscoveryResult.Success ? "passed" : "failed"; this.realmLabel.Text = ProviderEndpoint.PendingRequest.Realm.ToString(); diff --git a/samples/OpenIdProviderWebForms/server.aspx b/samples/OpenIdProviderWebForms/server.aspx index a874ccd..101aeee 100644 --- a/samples/OpenIdProviderWebForms/server.aspx +++ b/samples/OpenIdProviderWebForms/server.aspx @@ -1,6 +1,6 @@ <%@ Page Language="C#" AutoEventWireup="true" Inherits="OpenIdProviderWebForms.server" CodeBehind="server.aspx.cs" ValidateRequest="false" %> -<%@ Register Assembly="DotNetOpenAuth" Namespace="DotNetOpenAuth.OpenId.Provider" TagPrefix="openid" %> +<%@ Register Assembly="DotNetOpenAuth.OpenId.Provider.UI" Namespace="DotNetOpenAuth.OpenId.Provider" TagPrefix="openid" %> <html> <head> <title>This is an OpenID server</title> diff --git a/samples/OpenIdProviderWebForms/server.aspx.designer.cs b/samples/OpenIdProviderWebForms/server.aspx.designer.cs index 89c8c8a..562e06e 100644 --- a/samples/OpenIdProviderWebForms/server.aspx.designer.cs +++ b/samples/OpenIdProviderWebForms/server.aspx.designer.cs @@ -1,10 +1,9 @@ //------------------------------------------------------------------------------ // <auto-generated> // This code was generated by a tool. -// Runtime Version:2.0.50727.3521 // // Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. +// the code is regenerated. // </auto-generated> //------------------------------------------------------------------------------ diff --git a/samples/OpenIdProviderWebForms/user.aspx b/samples/OpenIdProviderWebForms/user.aspx index 7306e79..455434e 100644 --- a/samples/OpenIdProviderWebForms/user.aspx +++ b/samples/OpenIdProviderWebForms/user.aspx @@ -1,6 +1,6 @@ <%@ Page Language="C#" AutoEventWireup="true" Inherits="OpenIdProviderWebForms.user" CodeBehind="user.aspx.cs" MasterPageFile="~/Site.Master" %> -<%@ Register Assembly="DotNetOpenAuth" Namespace="DotNetOpenAuth.OpenId.Provider" TagPrefix="openid" %> +<%@ Register Assembly="DotNetOpenAuth.OpenId.Provider.UI" Namespace="DotNetOpenAuth.OpenId.Provider" TagPrefix="openid" %> <asp:Content ID="Content2" runat="server" ContentPlaceHolderID="head"> <openid:IdentityEndpoint ID="IdentityEndpoint20" runat="server" ProviderEndpointUrl="~/Server.aspx" XrdsUrl="~/user_xrds.aspx" ProviderVersion="V20" diff --git a/samples/OpenIdProviderWebForms/user.aspx.designer.cs b/samples/OpenIdProviderWebForms/user.aspx.designer.cs index ab7b4a1..4509a6e 100644 --- a/samples/OpenIdProviderWebForms/user.aspx.designer.cs +++ b/samples/OpenIdProviderWebForms/user.aspx.designer.cs @@ -1,10 +1,9 @@ //------------------------------------------------------------------------------ // <auto-generated> // This code was generated by a tool. -// Runtime Version:2.0.50727.3521 // // Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. +// the code is regenerated. // </auto-generated> //------------------------------------------------------------------------------ diff --git a/samples/OpenIdRelyingPartyMvc/OpenIdRelyingPartyMvc.csproj b/samples/OpenIdRelyingPartyMvc/OpenIdRelyingPartyMvc.csproj index f8df49d..0d65632 100644 --- a/samples/OpenIdRelyingPartyMvc/OpenIdRelyingPartyMvc.csproj +++ b/samples/OpenIdRelyingPartyMvc/OpenIdRelyingPartyMvc.csproj @@ -14,6 +14,7 @@ <TargetFrameworkVersion>v3.5</TargetFrameworkVersion> <MvcBuildViews>false</MvcBuildViews> <TargetFrameworkProfile /> + <UseIISExpress>false</UseIISExpress> </PropertyGroup> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> <DebugSymbols>true</DebugSymbols> @@ -116,7 +117,9 @@ <Content Include="Global.asax" /> <Content Include="Views\User\Index.aspx" /> <Content Include="Views\User\Login.aspx" /> - <Content Include="Web.config" /> + <Content Include="Web.config"> + <SubType>Designer</SubType> + </Content> <Content Include="Content\Site.css" /> <Content Include="Views\Home\Index.aspx" /> <Content Include="Views\Shared\Site.Master" /> @@ -124,15 +127,27 @@ <Content Include="Views\Home\xrds.aspx" /> </ItemGroup> <ItemGroup> - <ProjectReference Include="..\..\src\DotNetOpenAuth\DotNetOpenAuth.csproj"> - <Project>{3191B653-F76D-4C1A-9A5A-347BC3AAAAB7}</Project> - <Name>DotNetOpenAuth</Name> - </ProjectReference> - </ItemGroup> - <ItemGroup> <Folder Include="App_Data\" /> <Folder Include="Models\" /> </ItemGroup> + <ItemGroup> + <ProjectReference Include="..\..\src\DotNetOpenAuth.Core\DotNetOpenAuth.Core.csproj"> + <Project>{60426312-6AE5-4835-8667-37EDEA670222}</Project> + <Name>DotNetOpenAuth.Core</Name> + </ProjectReference> + <ProjectReference Include="..\..\src\DotNetOpenAuth.OpenId.RelyingParty.UI\DotNetOpenAuth.OpenId.RelyingParty.UI.csproj"> + <Project>{1ED8D424-F8AB-4050-ACEB-F27F4F909484}</Project> + <Name>DotNetOpenAuth.OpenId.RelyingParty.UI</Name> + </ProjectReference> + <ProjectReference Include="..\..\src\DotNetOpenAuth.OpenId.RelyingParty\DotNetOpenAuth.OpenId.RelyingParty.csproj"> + <Project>{F458AB60-BA1C-43D9-8CEF-EC01B50BE87B}</Project> + <Name>DotNetOpenAuth.OpenId.RelyingParty</Name> + </ProjectReference> + <ProjectReference Include="..\..\src\DotNetOpenAuth.OpenId\DotNetOpenAuth.OpenId.csproj"> + <Project>{3896A32A-E876-4C23-B9B8-78E17D134CD3}</Project> + <Name>DotNetOpenAuth.OpenId</Name> + </ProjectReference> + </ItemGroup> <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v10.0\WebApplications\Microsoft.WebApplication.targets" /> <!-- To modify your build process, add your task inside one of the targets below and uncomment it. diff --git a/samples/OpenIdRelyingPartyMvc/Web.config b/samples/OpenIdRelyingPartyMvc/Web.config index 9561a28..5442e0a 100644 --- a/samples/OpenIdRelyingPartyMvc/Web.config +++ b/samples/OpenIdRelyingPartyMvc/Web.config @@ -2,7 +2,12 @@ <configuration> <configSections> <section name="uri" type="System.Configuration.UriSection, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" /> - <section name="dotNetOpenAuth" type="DotNetOpenAuth.Configuration.DotNetOpenAuthSection" requirePermission="false" allowLocation="true"/> + <sectionGroup name="dotNetOpenAuth" type="DotNetOpenAuth.Configuration.DotNetOpenAuthSection, DotNetOpenAuth.Core"> + <section name="openid" type="DotNetOpenAuth.Configuration.OpenIdElement, DotNetOpenAuth.OpenId" requirePermission="false" allowLocation="true" /> + <section name="oauth" type="DotNetOpenAuth.Configuration.OAuthElement, DotNetOpenAuth.OAuth" requirePermission="false" allowLocation="true" /> + <section name="messaging" type="DotNetOpenAuth.Configuration.MessagingElement, DotNetOpenAuth.Core" requirePermission="false" allowLocation="true" /> + <section name="reporting" type="DotNetOpenAuth.Configuration.ReportingElement, DotNetOpenAuth.Core" requirePermission="false" allowLocation="true" /> + </sectionGroup> <sectionGroup name="system.web.extensions" type="System.Web.Configuration.SystemWebExtensionsSectionGroup, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"> <sectionGroup name="scripting" type="System.Web.Configuration.ScriptingSectionGroup, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"> <section name="scriptResourceHandler" type="System.Web.Configuration.ScriptingScriptResourceHandlerSection, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" allowDefinition="MachineToApplication"/> @@ -47,7 +52,7 @@ <behaviors> <!-- The following OPTIONAL behavior allows RPs to use SREG only, but be compatible with OPs that use Attribute Exchange (in various formats). --> - <add type="DotNetOpenAuth.OpenId.Behaviors.AXFetchAsSregTransform, DotNetOpenAuth" /> + <add type="DotNetOpenAuth.OpenId.RelyingParty.Behaviors.AXFetchAsSregTransform, DotNetOpenAuth.OpenId.RelyingParty" /> </behaviors> </relyingParty> </openid> diff --git a/samples/OpenIdRelyingPartyWebForms/Code/CustomStore.cs b/samples/OpenIdRelyingPartyWebForms/Code/CustomStore.cs index 3ab6292..3f52015 100644 --- a/samples/OpenIdRelyingPartyWebForms/Code/CustomStore.cs +++ b/samples/OpenIdRelyingPartyWebForms/Code/CustomStore.cs @@ -68,7 +68,7 @@ namespace OpenIdRelyingPartyWebForms.Code { return false; } - TimeSpan maxMessageAge = DotNetOpenAuthSection.Configuration.Messaging.MaximumMessageLifetime; + TimeSpan maxMessageAge = DotNetOpenAuthSection.Messaging.MaximumMessageLifetime; dataSet.Nonce.AddNonceRow(context, nonce, timestampUtc, timestampUtc + maxMessageAge); return true; } diff --git a/samples/OpenIdRelyingPartyWebForms/Default.aspx b/samples/OpenIdRelyingPartyWebForms/Default.aspx index 602d5ed..99f9885 100644 --- a/samples/OpenIdRelyingPartyWebForms/Default.aspx +++ b/samples/OpenIdRelyingPartyWebForms/Default.aspx @@ -1,6 +1,6 @@ <%@ Page Language="C#" AutoEventWireup="true" MasterPageFile="~/Site.Master" %> -<%@ Register Assembly="DotNetOpenAuth" Namespace="DotNetOpenAuth" TagPrefix="openid" %> +<%@ Register Assembly="DotNetOpenAuth.OpenId.UI" Namespace="DotNetOpenAuth" TagPrefix="openid" %> <asp:Content runat="server" ContentPlaceHolderID="head"> <openid:XrdsPublisher ID="XrdsPublisher1" runat="server" XrdsUrl="~/xrds.aspx" /> </asp:Content> diff --git a/samples/OpenIdRelyingPartyWebForms/DetectGoogleSession.aspx b/samples/OpenIdRelyingPartyWebForms/DetectGoogleSession.aspx index 403858f..8c99efe 100644 --- a/samples/OpenIdRelyingPartyWebForms/DetectGoogleSession.aspx +++ b/samples/OpenIdRelyingPartyWebForms/DetectGoogleSession.aspx @@ -2,9 +2,9 @@ Inherits="OpenIdRelyingPartyWebForms.DetectGoogleSession" ValidateRequest="false" MasterPageFile="~/Site.Master" %> -<%@ Register Assembly="DotNetOpenAuth" Namespace="DotNetOpenAuth.OpenId.RelyingParty" +<%@ Register Assembly="DotNetOpenAuth.OpenId.RelyingParty.UI" Namespace="DotNetOpenAuth.OpenId.RelyingParty" TagPrefix="rp" %> -<%@ Register Assembly="DotNetOpenAuth" Namespace="DotNetOpenAuth.OpenId.Extensions.SimpleRegistration" +<%@ Register Assembly="DotNetOpenAuth.OpenId.RelyingParty.UI" Namespace="DotNetOpenAuth.OpenId.Extensions.SimpleRegistration" TagPrefix="sreg" %> <asp:Content ID="Content1" runat="server" ContentPlaceHolderID="Main"> <asp:Label Text="We've detected that you're logged into Google!" runat="server" Visible="false" diff --git a/samples/OpenIdRelyingPartyWebForms/DetectGoogleSession.aspx.cs b/samples/OpenIdRelyingPartyWebForms/DetectGoogleSession.aspx.cs index 98fe745..d6cf2ac 100644 --- a/samples/OpenIdRelyingPartyWebForms/DetectGoogleSession.aspx.cs +++ b/samples/OpenIdRelyingPartyWebForms/DetectGoogleSession.aspx.cs @@ -1,6 +1,7 @@ namespace OpenIdRelyingPartyWebForms { using System; using DotNetOpenAuth.ApplicationBlock.CustomExtensions; + using DotNetOpenAuth.OpenId; using DotNetOpenAuth.OpenId.Extensions.UI; using DotNetOpenAuth.OpenId.RelyingParty; diff --git a/samples/OpenIdRelyingPartyWebForms/OpenIdRelyingPartyWebForms.csproj b/samples/OpenIdRelyingPartyWebForms/OpenIdRelyingPartyWebForms.csproj index cbcd758..9e82b2e 100644 --- a/samples/OpenIdRelyingPartyWebForms/OpenIdRelyingPartyWebForms.csproj +++ b/samples/OpenIdRelyingPartyWebForms/OpenIdRelyingPartyWebForms.csproj @@ -18,6 +18,7 @@ <OldToolsVersion>3.5</OldToolsVersion> <UpgradeBackupLocation /> <TargetFrameworkProfile /> + <UseIISExpress>false</UseIISExpress> </PropertyGroup> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> <DebugSymbols>true</DebugSymbols> @@ -219,9 +220,37 @@ </None> </ItemGroup> <ItemGroup> - <ProjectReference Include="..\..\src\DotNetOpenAuth\DotNetOpenAuth.csproj"> - <Project>{3191B653-F76D-4C1A-9A5A-347BC3AAAAB7}</Project> - <Name>DotNetOpenAuth</Name> + <ProjectReference Include="..\..\src\DotNetOpenAuth.Core.UI\DotNetOpenAuth.Core.UI.csproj"> + <Project>{173E7B8D-E751-46E2-A133-F72297C0D2F4}</Project> + <Name>DotNetOpenAuth.Core.UI</Name> + </ProjectReference> + <ProjectReference Include="..\..\src\DotNetOpenAuth.Core\DotNetOpenAuth.Core.csproj"> + <Project>{60426312-6AE5-4835-8667-37EDEA670222}</Project> + <Name>DotNetOpenAuth.Core</Name> + </ProjectReference> + <ProjectReference Include="..\..\src\DotNetOpenAuth.OAuth.Consumer\DotNetOpenAuth.OAuth.Consumer.csproj"> + <Project>{B202E40D-4663-4A2B-ACDA-865F88FF7CAA}</Project> + <Name>DotNetOpenAuth.OAuth.Consumer</Name> + </ProjectReference> + <ProjectReference Include="..\..\src\DotNetOpenAuth.OAuth\DotNetOpenAuth.OAuth.csproj"> + <Project>{A288FCC8-6FCF-46DA-A45E-5F9281556361}</Project> + <Name>DotNetOpenAuth.OAuth</Name> + </ProjectReference> + <ProjectReference Include="..\..\src\DotNetOpenAuth.OpenId.RelyingParty.UI\DotNetOpenAuth.OpenId.RelyingParty.UI.csproj"> + <Project>{1ED8D424-F8AB-4050-ACEB-F27F4F909484}</Project> + <Name>DotNetOpenAuth.OpenId.RelyingParty.UI</Name> + </ProjectReference> + <ProjectReference Include="..\..\src\DotNetOpenAuth.OpenId.RelyingParty\DotNetOpenAuth.OpenId.RelyingParty.csproj"> + <Project>{F458AB60-BA1C-43D9-8CEF-EC01B50BE87B}</Project> + <Name>DotNetOpenAuth.OpenId.RelyingParty</Name> + </ProjectReference> + <ProjectReference Include="..\..\src\DotNetOpenAuth.OpenId.UI\DotNetOpenAuth.OpenId.UI.csproj"> + <Project>{75E13AAE-7D51-4421-ABFD-3F3DC91F576E}</Project> + <Name>DotNetOpenAuth.OpenId.UI</Name> + </ProjectReference> + <ProjectReference Include="..\..\src\DotNetOpenAuth.OpenId\DotNetOpenAuth.OpenId.csproj"> + <Project>{3896A32A-E876-4C23-B9B8-78E17D134CD3}</Project> + <Name>DotNetOpenAuth.OpenId</Name> </ProjectReference> <ProjectReference Include="..\DotNetOpenAuth.ApplicationBlock\DotNetOpenAuth.ApplicationBlock.csproj"> <Project>{AA78D112-D889-414B-A7D4-467B34C7B663}</Project> diff --git a/samples/OpenIdRelyingPartyWebForms/Web.config b/samples/OpenIdRelyingPartyWebForms/Web.config index 485f8dc..3a667c2 100644 --- a/samples/OpenIdRelyingPartyWebForms/Web.config +++ b/samples/OpenIdRelyingPartyWebForms/Web.config @@ -3,7 +3,12 @@ <configSections> <section name="uri" type="System.Configuration.UriSection, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" /> <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler" requirePermission="false" /> - <section name="dotNetOpenAuth" type="DotNetOpenAuth.Configuration.DotNetOpenAuthSection" requirePermission="false" allowLocation="true"/> + <sectionGroup name="dotNetOpenAuth" type="DotNetOpenAuth.Configuration.DotNetOpenAuthSection, DotNetOpenAuth.Core"> + <section name="openid" type="DotNetOpenAuth.Configuration.OpenIdElement, DotNetOpenAuth.OpenId" requirePermission="false" allowLocation="true" /> + <section name="oauth" type="DotNetOpenAuth.Configuration.OAuthElement, DotNetOpenAuth.OAuth" requirePermission="false" allowLocation="true" /> + <section name="messaging" type="DotNetOpenAuth.Configuration.MessagingElement, DotNetOpenAuth.Core" requirePermission="false" allowLocation="true" /> + <section name="reporting" type="DotNetOpenAuth.Configuration.ReportingElement, DotNetOpenAuth.Core" requirePermission="false" allowLocation="true" /> + </sectionGroup> </configSections> <!-- The uri section is necessary to turn on .NET 3.5 support for IDN (international domain names), @@ -37,8 +42,8 @@ <behaviors> <!-- The following OPTIONAL behavior allows RPs to use SREG only, but be compatible with OPs that use Attribute Exchange (in various formats). --> - <add type="DotNetOpenAuth.OpenId.Behaviors.AXFetchAsSregTransform, DotNetOpenAuth" /> - <!--<add type="DotNetOpenAuth.OpenId.Behaviors.GsaIcamProfile, DotNetOpenAuth" />--> + <add type="DotNetOpenAuth.OpenId.RelyingParty.Behaviors.AXFetchAsSregTransform, DotNetOpenAuth.OpenId.RelyingParty" /> + <!--<add type="DotNetOpenAuth.OpenId.RelyingParty.Behaviors.GsaIcamProfile, DotNetOpenAuth.OpenId.RelyingParty" />--> </behaviors> <!-- Uncomment the following to activate the sample custom store. --> <!--<store type="OpenIdRelyingPartyWebForms.Code.CustomStore, OpenIdRelyingPartyWebForms" />--> diff --git a/samples/OpenIdRelyingPartyWebForms/ajaxlogin.aspx b/samples/OpenIdRelyingPartyWebForms/ajaxlogin.aspx index d542834..d2b3255 100644 --- a/samples/OpenIdRelyingPartyWebForms/ajaxlogin.aspx +++ b/samples/OpenIdRelyingPartyWebForms/ajaxlogin.aspx @@ -1,7 +1,7 @@ <%@ Page Language="C#" AutoEventWireup="true" CodeBehind="ajaxlogin.aspx.cs" Inherits="OpenIdRelyingPartyWebForms.ajaxlogin" ValidateRequest="false" MasterPageFile="~/Site.Master" %> -<%@ Register Assembly="DotNetOpenAuth" Namespace="DotNetOpenAuth.OpenId.RelyingParty" TagPrefix="openid" %> +<%@ Register Assembly="DotNetOpenAuth.OpenId.RelyingParty.UI" Namespace="DotNetOpenAuth.OpenId.RelyingParty" TagPrefix="openid" %> <asp:Content runat="server" ContentPlaceHolderID="head"> <script> // window.openid_visible_iframe = true; // causes the hidden iframe to show up diff --git a/samples/OpenIdRelyingPartyWebForms/login.aspx b/samples/OpenIdRelyingPartyWebForms/login.aspx index e35a649..98eee7a 100644 --- a/samples/OpenIdRelyingPartyWebForms/login.aspx +++ b/samples/OpenIdRelyingPartyWebForms/login.aspx @@ -1,8 +1,8 @@ <%@ Page Language="C#" AutoEventWireup="True" CodeBehind="login.aspx.cs" Inherits="OpenIdRelyingPartyWebForms.login" ValidateRequest="false" MasterPageFile="~/Site.Master" %> -<%@ Register Assembly="DotNetOpenAuth" Namespace="DotNetOpenAuth.OpenId.RelyingParty" TagPrefix="rp" %> -<%@ Register Assembly="DotNetOpenAuth" Namespace="DotNetOpenAuth.OpenId.Extensions.SimpleRegistration" TagPrefix="sreg" %> +<%@ Register Assembly="DotNetOpenAuth.OpenId.RelyingParty.UI" Namespace="DotNetOpenAuth.OpenId.RelyingParty" TagPrefix="rp" %> +<%@ Register Assembly="DotNetOpenAuth.OpenId" Namespace="DotNetOpenAuth.OpenId.Extensions.SimpleRegistration" TagPrefix="sreg" %> <asp:Content runat="server" ContentPlaceHolderID="Main"> <h2>Login Page </h2> <rp:OpenIdLogin ID="OpenIdLogin1" runat="server" CssClass="openid_login" RequestCountry="Request" diff --git a/samples/OpenIdRelyingPartyWebForms/loginGoogleApps.aspx b/samples/OpenIdRelyingPartyWebForms/loginGoogleApps.aspx index 3f3860e..cde9966 100644 --- a/samples/OpenIdRelyingPartyWebForms/loginGoogleApps.aspx +++ b/samples/OpenIdRelyingPartyWebForms/loginGoogleApps.aspx @@ -2,7 +2,7 @@ Inherits="OpenIdRelyingPartyWebForms.loginGoogleApps" ValidateRequest="false" MasterPageFile="~/Site.Master" %> -<%@ Register Assembly="DotNetOpenAuth" Namespace="DotNetOpenAuth.OpenId.RelyingParty" +<%@ Register Assembly="DotNetOpenAuth.OpenId.RelyingParty.UI" Namespace="DotNetOpenAuth.OpenId.RelyingParty" TagPrefix="rp" %> <asp:Content ID="Content1" runat="server" ContentPlaceHolderID="Main"> <rp:OpenIdLogin ID="OpenIdLogin1" runat="server" ExampleUrl="yourname@yourdomain.com" diff --git a/samples/OpenIdRelyingPartyWebForms/loginGoogleApps.aspx.designer.cs b/samples/OpenIdRelyingPartyWebForms/loginGoogleApps.aspx.designer.cs index e927f65..54223bb 100644 --- a/samples/OpenIdRelyingPartyWebForms/loginGoogleApps.aspx.designer.cs +++ b/samples/OpenIdRelyingPartyWebForms/loginGoogleApps.aspx.designer.cs @@ -1,10 +1,9 @@ //------------------------------------------------------------------------------ // <auto-generated> // This code was generated by a tool. -// Runtime Version:2.0.50727.4927 // // Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. +// the code is regenerated. // </auto-generated> //------------------------------------------------------------------------------ diff --git a/samples/OpenIdRelyingPartyWebForms/loginPlusOAuth.aspx b/samples/OpenIdRelyingPartyWebForms/loginPlusOAuth.aspx index 57bca52..31c48fa 100644 --- a/samples/OpenIdRelyingPartyWebForms/loginPlusOAuth.aspx +++ b/samples/OpenIdRelyingPartyWebForms/loginPlusOAuth.aspx @@ -2,7 +2,7 @@ Inherits="OpenIdRelyingPartyWebForms.loginPlusOAuth" ValidateRequest="false" MasterPageFile="~/Site.Master" %> -<%@ Register Assembly="DotNetOpenAuth" Namespace="DotNetOpenAuth.OpenId.RelyingParty" +<%@ Register Assembly="DotNetOpenAuth.OpenId.RelyingParty.UI" Namespace="DotNetOpenAuth.OpenId.RelyingParty" TagPrefix="rp" %> <asp:Content ID="Content1" runat="server" ContentPlaceHolderID="Main"> <h2>Login Page </h2> diff --git a/samples/OpenIdRelyingPartyWebForms/loginPlusOAuth.aspx.designer.cs b/samples/OpenIdRelyingPartyWebForms/loginPlusOAuth.aspx.designer.cs index b9c836d..a9624fe 100644 --- a/samples/OpenIdRelyingPartyWebForms/loginPlusOAuth.aspx.designer.cs +++ b/samples/OpenIdRelyingPartyWebForms/loginPlusOAuth.aspx.designer.cs @@ -1,10 +1,9 @@ //------------------------------------------------------------------------------ // <auto-generated> // This code was generated by a tool. -// Runtime Version:2.0.50727.4918 // // Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. +// the code is regenerated. // </auto-generated> //------------------------------------------------------------------------------ diff --git a/samples/OpenIdRelyingPartyWebForms/loginPlusOAuthSampleOP.aspx b/samples/OpenIdRelyingPartyWebForms/loginPlusOAuthSampleOP.aspx index 863f335..13ef590 100644 --- a/samples/OpenIdRelyingPartyWebForms/loginPlusOAuthSampleOP.aspx +++ b/samples/OpenIdRelyingPartyWebForms/loginPlusOAuthSampleOP.aspx @@ -2,7 +2,7 @@ Inherits="OpenIdRelyingPartyWebForms.loginPlusOAuthSampleOP" ValidateRequest="false" MasterPageFile="~/Site.Master" %> -<%@ Register Assembly="DotNetOpenAuth" Namespace="DotNetOpenAuth.OpenId.RelyingParty" +<%@ Register Assembly="DotNetOpenAuth.OpenId.RelyingParty.UI" Namespace="DotNetOpenAuth.OpenId.RelyingParty" TagPrefix="rp" %> <asp:Content ID="Content1" runat="server" ContentPlaceHolderID="Main"> <h2>Login Page </h2> diff --git a/samples/OpenIdRelyingPartyWebForms/loginPlusOAuthSampleOP.aspx.designer.cs b/samples/OpenIdRelyingPartyWebForms/loginPlusOAuthSampleOP.aspx.designer.cs index 9bf29b9..a81b441 100644 --- a/samples/OpenIdRelyingPartyWebForms/loginPlusOAuthSampleOP.aspx.designer.cs +++ b/samples/OpenIdRelyingPartyWebForms/loginPlusOAuthSampleOP.aspx.designer.cs @@ -1,10 +1,9 @@ //------------------------------------------------------------------------------ // <auto-generated> // This code was generated by a tool. -// Runtime Version:2.0.50727.4918 // // Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. +// the code is regenerated. // </auto-generated> //------------------------------------------------------------------------------ diff --git a/samples/OpenIdRelyingPartyWebFormsVB/Default.aspx b/samples/OpenIdRelyingPartyWebFormsVB/Default.aspx index 53f6dfc..12ccfd0 100644 --- a/samples/OpenIdRelyingPartyWebFormsVB/Default.aspx +++ b/samples/OpenIdRelyingPartyWebFormsVB/Default.aspx @@ -1,7 +1,7 @@ <%@ Page Language="vb" AutoEventWireup="false" CodeBehind="Default.aspx.vb" Inherits="OpenIdRelyingPartyWebFormsVB._Default" MasterPageFile="~/Site.Master" %> -<%@ Register Assembly="DotNetOpenAuth" Namespace="DotNetOpenAuth" TagPrefix="openid" %> +<%@ Register Assembly="DotNetOpenAuth.OpenId.UI" Namespace="DotNetOpenAuth" TagPrefix="openid" %> <asp:Content runat="server" ContentPlaceHolderID="head"> <openid:XrdsPublisher ID="XrdsPublisher1" runat="server" XrdsUrl="~/xrds.aspx" /> </asp:Content> diff --git a/samples/OpenIdRelyingPartyWebFormsVB/Global.asax.vb b/samples/OpenIdRelyingPartyWebFormsVB/Global.asax.vb index 60ab0cc..257e11a 100644 --- a/samples/OpenIdRelyingPartyWebFormsVB/Global.asax.vb +++ b/samples/OpenIdRelyingPartyWebFormsVB/Global.asax.vb @@ -5,7 +5,6 @@ Imports System.IO Imports System.Text Imports System.Web Imports DotNetOpenAuth.ApplicationBlock -Imports DotNetOpenAuth.OAuth Imports OpenIdRelyingPartyWebFormsVB Public Class Global_asax diff --git a/samples/OpenIdRelyingPartyWebFormsVB/Login.aspx b/samples/OpenIdRelyingPartyWebFormsVB/Login.aspx index d0e978b..c28611e 100644 --- a/samples/OpenIdRelyingPartyWebFormsVB/Login.aspx +++ b/samples/OpenIdRelyingPartyWebFormsVB/Login.aspx @@ -1,7 +1,7 @@ <%@ Page Language="vb" AutoEventWireup="false" CodeBehind="Login.aspx.vb" Inherits="OpenIdRelyingPartyWebFormsVB.Login" ValidateRequest="false" MasterPageFile="~/Site.Master" %> -<%@ Register Assembly="DotNetOpenAuth" Namespace="DotNetOpenAuth.OpenId.RelyingParty" TagPrefix="rp" %> +<%@ Register Assembly="DotNetOpenAuth.OpenId.RelyingParty.UI" Namespace="DotNetOpenAuth.OpenId.RelyingParty" TagPrefix="rp" %> <asp:Content ID="Content1" runat="server" ContentPlaceHolderID="Main"> <h2>Login Page </h2> <rp:OpenIdLogin ID="OpenIdLogin1" runat="server" CssClass="openid_login" RequestCountry="Request" diff --git a/samples/OpenIdRelyingPartyWebFormsVB/OpenIdRelyingPartyWebFormsVB.vbproj b/samples/OpenIdRelyingPartyWebFormsVB/OpenIdRelyingPartyWebFormsVB.vbproj index decfc20..85e1fb3 100644 --- a/samples/OpenIdRelyingPartyWebFormsVB/OpenIdRelyingPartyWebFormsVB.vbproj +++ b/samples/OpenIdRelyingPartyWebFormsVB/OpenIdRelyingPartyWebFormsVB.vbproj @@ -21,6 +21,7 @@ </FileUpgradeFlags> <OldToolsVersion>3.5</OldToolsVersion> <UpgradeBackupLocation /> + <UseIISExpress>false</UseIISExpress> </PropertyGroup> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> <DebugSymbols>true</DebugSymbols> @@ -188,9 +189,21 @@ <Content Include="xrds.aspx" /> </ItemGroup> <ItemGroup> - <ProjectReference Include="..\..\src\DotNetOpenAuth\DotNetOpenAuth.csproj"> - <Project>{3191B653-F76D-4C1A-9A5A-347BC3AAAAB7}</Project> - <Name>DotNetOpenAuth</Name> + <ProjectReference Include="..\..\src\DotNetOpenAuth.Core\DotNetOpenAuth.Core.csproj"> + <Project>{60426312-6AE5-4835-8667-37EDEA670222}</Project> + <Name>DotNetOpenAuth.Core</Name> + </ProjectReference> + <ProjectReference Include="..\..\src\DotNetOpenAuth.OpenId.RelyingParty.UI\DotNetOpenAuth.OpenId.RelyingParty.UI.csproj"> + <Project>{1ED8D424-F8AB-4050-ACEB-F27F4F909484}</Project> + <Name>DotNetOpenAuth.OpenId.RelyingParty.UI</Name> + </ProjectReference> + <ProjectReference Include="..\..\src\DotNetOpenAuth.OpenId.RelyingParty\DotNetOpenAuth.OpenId.RelyingParty.csproj"> + <Project>{F458AB60-BA1C-43D9-8CEF-EC01B50BE87B}</Project> + <Name>DotNetOpenAuth.OpenId.RelyingParty</Name> + </ProjectReference> + <ProjectReference Include="..\..\src\DotNetOpenAuth.OpenId\DotNetOpenAuth.OpenId.csproj"> + <Project>{3896A32A-E876-4C23-B9B8-78E17D134CD3}</Project> + <Name>DotNetOpenAuth.OpenId</Name> </ProjectReference> <ProjectReference Include="..\DotNetOpenAuth.ApplicationBlock\DotNetOpenAuth.ApplicationBlock.csproj"> <Project>{AA78D112-D889-414B-A7D4-467B34C7B663}</Project> diff --git a/samples/OpenIdRelyingPartyWebFormsVB/Web.config b/samples/OpenIdRelyingPartyWebFormsVB/Web.config index 51b3d26..9c65b28 100644 --- a/samples/OpenIdRelyingPartyWebFormsVB/Web.config +++ b/samples/OpenIdRelyingPartyWebFormsVB/Web.config @@ -3,7 +3,12 @@ <configSections> <section name="uri" type="System.Configuration.UriSection, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" /> <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler" requirePermission="false" /> - <section name="dotNetOpenAuth" type="DotNetOpenAuth.Configuration.DotNetOpenAuthSection" requirePermission="false" allowLocation="true"/> + <sectionGroup name="dotNetOpenAuth" type="DotNetOpenAuth.Configuration.DotNetOpenAuthSection, DotNetOpenAuth.Core"> + <section name="openid" type="DotNetOpenAuth.Configuration.OpenIdElement, DotNetOpenAuth.OpenId" requirePermission="false" allowLocation="true" /> + <section name="oauth" type="DotNetOpenAuth.Configuration.OAuthElement, DotNetOpenAuth.OAuth" requirePermission="false" allowLocation="true" /> + <section name="messaging" type="DotNetOpenAuth.Configuration.MessagingElement, DotNetOpenAuth.Core" requirePermission="false" allowLocation="true" /> + <section name="reporting" type="DotNetOpenAuth.Configuration.ReportingElement, DotNetOpenAuth.Core" requirePermission="false" allowLocation="true" /> + </sectionGroup> </configSections> <!-- The uri section is necessary to turn on .NET 3.5 support for IDN (international domain names), @@ -37,8 +42,8 @@ <behaviors> <!-- The following OPTIONAL behavior allows RPs to use SREG only, but be compatible with OPs that use Attribute Exchange (in various formats). --> - <add type="DotNetOpenAuth.OpenId.Behaviors.AXFetchAsSregTransform, DotNetOpenAuth" /> - <!--<add type="DotNetOpenAuth.OpenId.Behaviors.GsaIcamProfile, DotNetOpenAuth" />--> + <add type="DotNetOpenAuth.OpenId.RelyingParty.Behaviors.AXFetchAsSregTransform, DotNetOpenAuth.OpenId.RelyingParty" /> + <!--<add type="DotNetOpenAuth.OpenId.RelyingParty.Behaviors.GsaIcamProfile, DotNetOpenAuth.OpenId.RelyingParty" />--> </behaviors> <!-- Uncomment the following to activate the sample custom store. --> <!--<store type="OpenIdRelyingPartyWebFormsVB.CustomStore, OpenIdRelyingPartyWebFormsVB" />--> diff --git a/samples/OpenIdWebRingSsoProvider/Code/Util.cs b/samples/OpenIdWebRingSsoProvider/Code/Util.cs index 5a3a2fc..c9cb581 100644 --- a/samples/OpenIdWebRingSsoProvider/Code/Util.cs +++ b/samples/OpenIdWebRingSsoProvider/Code/Util.cs @@ -52,7 +52,7 @@ namespace OpenIdWebRingSsoProvider.Code { internal static void ProcessAuthenticationChallenge(IAuthenticationRequest idrequest) { // Verify that RP discovery is successful. - if (idrequest.IsReturnUrlDiscoverable(ProviderEndpoint.Provider) != RelyingPartyDiscoveryResult.Success) { + if (idrequest.IsReturnUrlDiscoverable(ProviderEndpoint.Provider.Channel.WebRequestHandler) != RelyingPartyDiscoveryResult.Success) { idrequest.IsAuthenticated = false; return; } diff --git a/samples/OpenIdWebRingSsoProvider/Default.aspx b/samples/OpenIdWebRingSsoProvider/Default.aspx index 9bddc98..5e392c5 100644 --- a/samples/OpenIdWebRingSsoProvider/Default.aspx +++ b/samples/OpenIdWebRingSsoProvider/Default.aspx @@ -1,6 +1,6 @@ <%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="OpenIdWebRingSsoProvider._Default" %> -<%@ Register Assembly="DotNetOpenAuth" Namespace="DotNetOpenAuth" TagPrefix="openid" %> +<%@ Register Assembly="DotNetOpenAuth.OpenId.UI" Namespace="DotNetOpenAuth" TagPrefix="openid" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head runat="server"> diff --git a/samples/OpenIdWebRingSsoProvider/Default.aspx.designer.cs b/samples/OpenIdWebRingSsoProvider/Default.aspx.designer.cs index b2f84f7..64b98eb 100644 --- a/samples/OpenIdWebRingSsoProvider/Default.aspx.designer.cs +++ b/samples/OpenIdWebRingSsoProvider/Default.aspx.designer.cs @@ -1,10 +1,9 @@ //------------------------------------------------------------------------------ // <auto-generated> // This code was generated by a tool. -// Runtime Version:2.0.50727.4927 // // Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. +// the code is regenerated. // </auto-generated> //------------------------------------------------------------------------------ diff --git a/samples/OpenIdWebRingSsoProvider/OpenIdWebRingSsoProvider.csproj b/samples/OpenIdWebRingSsoProvider/OpenIdWebRingSsoProvider.csproj index 3120f1e..870a22c 100644 --- a/samples/OpenIdWebRingSsoProvider/OpenIdWebRingSsoProvider.csproj +++ b/samples/OpenIdWebRingSsoProvider/OpenIdWebRingSsoProvider.csproj @@ -18,6 +18,7 @@ <OldToolsVersion>3.5</OldToolsVersion> <UpgradeBackupLocation /> <TargetFrameworkProfile /> + <UseIISExpress>false</UseIISExpress> </PropertyGroup> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> <DebugSymbols>true</DebugSymbols> @@ -103,12 +104,31 @@ </Compile> </ItemGroup> <ItemGroup> - <ProjectReference Include="..\..\src\DotNetOpenAuth\DotNetOpenAuth.csproj"> - <Project>{3191B653-F76D-4C1A-9A5A-347BC3AAAAB7}</Project> - <Name>DotNetOpenAuth</Name> + <ProjectReference Include="..\..\src\DotNetOpenAuth.Core.UI\DotNetOpenAuth.Core.UI.csproj"> + <Project>{173E7B8D-E751-46E2-A133-F72297C0D2F4}</Project> + <Name>DotNetOpenAuth.Core.UI</Name> + </ProjectReference> + <ProjectReference Include="..\..\src\DotNetOpenAuth.Core\DotNetOpenAuth.Core.csproj"> + <Project>{60426312-6AE5-4835-8667-37EDEA670222}</Project> + <Name>DotNetOpenAuth.Core</Name> + </ProjectReference> + <ProjectReference Include="..\..\src\DotNetOpenAuth.OpenId.Provider.UI\DotNetOpenAuth.OpenId.Provider.UI.csproj"> + <Project>{9D0F8866-2131-4C2A-BC0E-16FEA5B50828}</Project> + <Name>DotNetOpenAuth.OpenId.Provider.UI</Name> + </ProjectReference> + <ProjectReference Include="..\..\src\DotNetOpenAuth.OpenId.Provider\DotNetOpenAuth.OpenId.Provider.csproj"> + <Project>{F8284738-3B5D-4733-A511-38C23F4A763F}</Project> + <Name>DotNetOpenAuth.OpenId.Provider</Name> + </ProjectReference> + <ProjectReference Include="..\..\src\DotNetOpenAuth.OpenId.UI\DotNetOpenAuth.OpenId.UI.csproj"> + <Project>{75E13AAE-7D51-4421-ABFD-3F3DC91F576E}</Project> + <Name>DotNetOpenAuth.OpenId.UI</Name> + </ProjectReference> + <ProjectReference Include="..\..\src\DotNetOpenAuth.OpenId\DotNetOpenAuth.OpenId.csproj"> + <Project>{3896A32A-E876-4C23-B9B8-78E17D134CD3}</Project> + <Name>DotNetOpenAuth.OpenId</Name> </ProjectReference> </ItemGroup> - <ItemGroup /> <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v10.0\WebApplications\Microsoft.WebApplication.targets" /> <!-- To modify your build process, add your task inside one of the targets below and uncomment it. @@ -138,4 +158,4 @@ </VisualStudio> </ProjectExtensions> <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), EnlistmentInfo.targets))\EnlistmentInfo.targets" Condition=" '$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), EnlistmentInfo.targets))' != '' " /> -</Project> +</Project>
\ No newline at end of file diff --git a/samples/OpenIdWebRingSsoProvider/Server.aspx b/samples/OpenIdWebRingSsoProvider/Server.aspx index b6fa69d..31373be 100644 --- a/samples/OpenIdWebRingSsoProvider/Server.aspx +++ b/samples/OpenIdWebRingSsoProvider/Server.aspx @@ -1,6 +1,6 @@ <%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Server.aspx.cs" Inherits="OpenIdWebRingSsoProvider.Server" ValidateRequest="false" %> -<%@ Register Assembly="DotNetOpenAuth" Namespace="DotNetOpenAuth.OpenId.Provider" +<%@ Register Assembly="DotNetOpenAuth.OpenId.Provider.UI" Namespace="DotNetOpenAuth.OpenId.Provider" TagPrefix="openid" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> diff --git a/samples/OpenIdWebRingSsoProvider/Web.config b/samples/OpenIdWebRingSsoProvider/Web.config index 87fde10..656ea44 100644 --- a/samples/OpenIdWebRingSsoProvider/Web.config +++ b/samples/OpenIdWebRingSsoProvider/Web.config @@ -3,7 +3,12 @@ <configSections> <section name="uri" type="System.Configuration.UriSection, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"/> <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler" requirePermission="false"/> - <section name="dotNetOpenAuth" type="DotNetOpenAuth.Configuration.DotNetOpenAuthSection" requirePermission="false" allowLocation="true"/> + <sectionGroup name="dotNetOpenAuth" type="DotNetOpenAuth.Configuration.DotNetOpenAuthSection, DotNetOpenAuth.Core"> + <section name="openid" type="DotNetOpenAuth.Configuration.OpenIdElement, DotNetOpenAuth.OpenId" requirePermission="false" allowLocation="true" /> + <section name="oauth" type="DotNetOpenAuth.Configuration.OAuthElement, DotNetOpenAuth.OAuth" requirePermission="false" allowLocation="true" /> + <section name="messaging" type="DotNetOpenAuth.Configuration.MessagingElement, DotNetOpenAuth.Core" requirePermission="false" allowLocation="true" /> + <section name="reporting" type="DotNetOpenAuth.Configuration.ReportingElement, DotNetOpenAuth.Core" requirePermission="false" allowLocation="true" /> + </sectionGroup> <sectionGroup name="system.web.extensions" type="System.Web.Configuration.SystemWebExtensionsSectionGroup, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"> <sectionGroup name="scripting" type="System.Web.Configuration.ScriptingSectionGroup, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"> <section name="scriptResourceHandler" type="System.Web.Configuration.ScriptingScriptResourceHandlerSection, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" allowDefinition="MachineToApplication"/> @@ -44,7 +49,7 @@ <!-- Behaviors activate themselves automatically for individual matching requests. The first one in this list to match an incoming request "owns" the request. If no profile matches, the default behavior is assumed. --> - <!--<add type="DotNetOpenAuth.OpenId.Behaviors.PpidGeneration, DotNetOpenAuth" />--> + <!--<add type="DotNetOpenAuth.OpenId.Provider.Behaviors.PpidGeneration, DotNetOpenAuth.OpenId.Provider" />--> </behaviors> </provider> </openid> diff --git a/samples/OpenIdWebRingSsoProvider/user.aspx b/samples/OpenIdWebRingSsoProvider/user.aspx index 0cef559..44ef3e2 100644 --- a/samples/OpenIdWebRingSsoProvider/user.aspx +++ b/samples/OpenIdWebRingSsoProvider/user.aspx @@ -1,7 +1,7 @@ <%@ Page Language="C#" AutoEventWireup="true" Inherits="OpenIdWebRingSsoProvider.User" CodeBehind="user.aspx.cs" %> -<%@ Register Assembly="DotNetOpenAuth" Namespace="DotNetOpenAuth.OpenId.Provider" +<%@ Register Assembly="DotNetOpenAuth.OpenId.Provider.UI" Namespace="DotNetOpenAuth.OpenId.Provider" TagPrefix="openid" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> diff --git a/samples/OpenIdWebRingSsoProvider/user.aspx.designer.cs b/samples/OpenIdWebRingSsoProvider/user.aspx.designer.cs index 171c898..d001dad 100644 --- a/samples/OpenIdWebRingSsoProvider/user.aspx.designer.cs +++ b/samples/OpenIdWebRingSsoProvider/user.aspx.designer.cs @@ -1,10 +1,9 @@ //------------------------------------------------------------------------------ // <auto-generated> // This code was generated by a tool. -// Runtime Version:2.0.50727.4927 // // Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. +// the code is regenerated. // </auto-generated> //------------------------------------------------------------------------------ diff --git a/samples/OpenIdWebRingSsoRelyingParty/AuthTicketRoles.cs b/samples/OpenIdWebRingSsoRelyingParty/AuthTicketRoles.cs index 06783bd..48fd47e 100644 --- a/samples/OpenIdWebRingSsoRelyingParty/AuthTicketRoles.cs +++ b/samples/OpenIdWebRingSsoRelyingParty/AuthTicketRoles.cs @@ -12,9 +12,6 @@ namespace OpenIdWebRingSsoRelyingParty { using System.Web; using System.Web.Security; using DotNetOpenAuth.Messaging; - using DotNetOpenAuth.OAuth; - using DotNetOpenAuth.OAuth.ChannelElements; - using DotNetOpenAuth.OAuth.Messages; /// <summary> /// An authentication module that utilizes the forms auth ticket cookie diff --git a/samples/OpenIdWebRingSsoRelyingParty/OpenIdWebRingSsoRelyingParty.csproj b/samples/OpenIdWebRingSsoRelyingParty/OpenIdWebRingSsoRelyingParty.csproj index 36d6d14..a3b16f7 100644 --- a/samples/OpenIdWebRingSsoRelyingParty/OpenIdWebRingSsoRelyingParty.csproj +++ b/samples/OpenIdWebRingSsoRelyingParty/OpenIdWebRingSsoRelyingParty.csproj @@ -18,6 +18,7 @@ <OldToolsVersion>3.5</OldToolsVersion> <UpgradeBackupLocation /> <TargetFrameworkProfile /> + <UseIISExpress>false</UseIISExpress> </PropertyGroup> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> <DebugSymbols>true</DebugSymbols> @@ -86,18 +87,30 @@ <Compile Include="Properties\AssemblyInfo.cs" /> </ItemGroup> <ItemGroup> - <ProjectReference Include="..\..\src\DotNetOpenAuth\DotNetOpenAuth.csproj"> - <Project>{3191B653-F76D-4C1A-9A5A-347BC3AAAAB7}</Project> - <Name>DotNetOpenAuth</Name> - </ProjectReference> - </ItemGroup> - <ItemGroup> <Content Include="Admin\Default.aspx" /> <Content Include="Admin\Web.config" /> </ItemGroup> <ItemGroup> <Folder Include="App_Data\" /> </ItemGroup> + <ItemGroup> + <ProjectReference Include="..\..\src\DotNetOpenAuth.Core\DotNetOpenAuth.Core.csproj"> + <Project>{60426312-6AE5-4835-8667-37EDEA670222}</Project> + <Name>DotNetOpenAuth.Core</Name> + </ProjectReference> + <ProjectReference Include="..\..\src\DotNetOpenAuth.OpenId.RelyingParty.UI\DotNetOpenAuth.OpenId.RelyingParty.UI.csproj"> + <Project>{1ED8D424-F8AB-4050-ACEB-F27F4F909484}</Project> + <Name>DotNetOpenAuth.OpenId.RelyingParty.UI</Name> + </ProjectReference> + <ProjectReference Include="..\..\src\DotNetOpenAuth.OpenId.RelyingParty\DotNetOpenAuth.OpenId.RelyingParty.csproj"> + <Project>{F458AB60-BA1C-43D9-8CEF-EC01B50BE87B}</Project> + <Name>DotNetOpenAuth.OpenId.RelyingParty</Name> + </ProjectReference> + <ProjectReference Include="..\..\src\DotNetOpenAuth.OpenId\DotNetOpenAuth.OpenId.csproj"> + <Project>{3896A32A-E876-4C23-B9B8-78E17D134CD3}</Project> + <Name>DotNetOpenAuth.OpenId</Name> + </ProjectReference> + </ItemGroup> <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v10.0\WebApplications\Microsoft.WebApplication.targets" /> <!-- To modify your build process, add your task inside one of the targets below and uncomment it. diff --git a/samples/OpenIdWebRingSsoRelyingParty/Web.config b/samples/OpenIdWebRingSsoRelyingParty/Web.config index 98e5c3f..5c5965d 100644 --- a/samples/OpenIdWebRingSsoRelyingParty/Web.config +++ b/samples/OpenIdWebRingSsoRelyingParty/Web.config @@ -4,7 +4,12 @@ <configSections> <section name="uri" type="System.Configuration.UriSection, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" /> <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler" requirePermission="false" /> - <section name="dotNetOpenAuth" type="DotNetOpenAuth.Configuration.DotNetOpenAuthSection" requirePermission="false" allowLocation="true"/> + <sectionGroup name="dotNetOpenAuth" type="DotNetOpenAuth.Configuration.DotNetOpenAuthSection, DotNetOpenAuth.Core"> + <section name="openid" type="DotNetOpenAuth.Configuration.OpenIdElement, DotNetOpenAuth.OpenId" requirePermission="false" allowLocation="true" /> + <section name="oauth" type="DotNetOpenAuth.Configuration.OAuthElement, DotNetOpenAuth.OAuth" requirePermission="false" allowLocation="true" /> + <section name="messaging" type="DotNetOpenAuth.Configuration.MessagingElement, DotNetOpenAuth.Core" requirePermission="false" allowLocation="true" /> + <section name="reporting" type="DotNetOpenAuth.Configuration.ReportingElement, DotNetOpenAuth.Core" requirePermission="false" allowLocation="true" /> + </sectionGroup> <sectionGroup name="system.web.extensions" type="System.Web.Configuration.SystemWebExtensionsSectionGroup, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"> <sectionGroup name="scripting" type="System.Web.Configuration.ScriptingSectionGroup, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"> <section name="scriptResourceHandler" type="System.Web.Configuration.ScriptingScriptResourceHandlerSection, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" allowDefinition="MachineToApplication"/> @@ -49,8 +54,8 @@ <behaviors> <!-- The following OPTIONAL behavior allows RPs to use SREG only, but be compatible with OPs that use Attribute Exchange (in various formats). --> - <add type="DotNetOpenAuth.OpenId.Behaviors.AXFetchAsSregTransform, DotNetOpenAuth" /> - <!--<add type="DotNetOpenAuth.OpenId.Behaviors.GsaIcamProfile, DotNetOpenAuth" />--> + <add type="DotNetOpenAuth.OpenId.RelyingParty.Behaviors.AXFetchAsSregTransform, DotNetOpenAuth.OpenId.RelyingParty" /> + <!--<add type="DotNetOpenAuth.OpenId.RelyingParty.Behaviors.GsaIcamProfile, DotNetOpenAuth.OpenId.RelyingParty" />--> </behaviors> <!-- Uncomment the following to activate the sample custom store. --> <!--<store type="OpenIdRelyingPartyWebForms.CustomStore, OpenIdRelyingPartyWebForms" />--> diff --git a/src/.gitignore b/src/.gitignore index 22d54e5..ad0e465 100644 --- a/src/.gitignore +++ b/src/.gitignore @@ -1,3 +1,6 @@ +Backup* +_UpgradeReport_Files +UpgradeLog.XML PrecompiledWeb _ReSharper.* *.suo diff --git a/src/DotNetOpenAuth.BuildTasks/ChangeProjectReferenceToAssemblyReference.cs b/src/DotNetOpenAuth.BuildTasks/ChangeProjectReferenceToAssemblyReference.cs index fb42ade..a25701d 100644 --- a/src/DotNetOpenAuth.BuildTasks/ChangeProjectReferenceToAssemblyReference.cs +++ b/src/DotNetOpenAuth.BuildTasks/ChangeProjectReferenceToAssemblyReference.cs @@ -26,7 +26,7 @@ public ITaskItem[] ProjectReferences { get; set; } /// <summary> - /// The assembly references to add. + /// The assembly references to replace removed project references with. /// </summary> [Required] public ITaskItem[] References { get; set; } @@ -34,6 +34,9 @@ public override bool Execute() { if (this.ProjectReferences.Length != this.References.Length) { this.Log.LogError("ProjectReferences and References arrays do not have matching lengths."); + this.Log.LogError("ProjectReferences contents ({0} elements): {1}", this.ProjectReferences.Length, String.Join<ITaskItem>(";", this.ProjectReferences)); + this.Log.LogError("References contents ({0} elements): {1}", this.References.Length, String.Join<ITaskItem>(";", this.References)); + return false; } foreach (var project in Projects) { @@ -50,8 +53,12 @@ doc.RemoveItem(matchingReference.Remove); if (matchingReference.Add.ItemSpec != "REMOVE") { this.Log.LogMessage("Adding assembly reference to \"{0}\" to \"{1}\".", matchingReference.Add.ItemSpec, project.ItemSpec); - var newReference = doc.AddNewItem("Reference", Path.GetFileNameWithoutExtension(matchingReference.Add.ItemSpec), true); - newReference.SetMetadata("HintPath", matchingReference.Add.ItemSpec); + + string newItemSpec = Path.GetFileNameWithoutExtension(matchingReference.Add.ItemSpec); + if (!doc.GetEvaluatedItemsByName("Reference").OfType<BuildItem>().Any(bi => String.Equals(bi.Include, newItemSpec, StringComparison.OrdinalIgnoreCase))) { + var newReference = doc.AddNewItem("Reference", newItemSpec, true); + newReference.SetMetadata("HintPath", matchingReference.Add.ItemSpec); + } } } diff --git a/src/DotNetOpenAuth.BuildTasks/DotNetOpenAuth.BuildTasks.csproj b/src/DotNetOpenAuth.BuildTasks/DotNetOpenAuth.BuildTasks.csproj index 310ee9d..f7d7256 100644 --- a/src/DotNetOpenAuth.BuildTasks/DotNetOpenAuth.BuildTasks.csproj +++ b/src/DotNetOpenAuth.BuildTasks/DotNetOpenAuth.BuildTasks.csproj @@ -103,6 +103,7 @@ <Compile Include="ChangeProjectReferenceToAssemblyReference.cs" /> <Compile Include="CompareFiles.cs" /> <Compile Include="ChangeAssemblyReference.cs" /> + <Compile Include="RegexFileReplace.cs" /> <Compile Include="CopyWithTokenSubstitution.cs" /> <Compile Include="CreateWebApplication.cs" /> <Compile Include="DeleteWebApplication.cs" /> diff --git a/src/DotNetOpenAuth.BuildTasks/DotNetOpenAuth.BuildTasks.sln b/src/DotNetOpenAuth.BuildTasks/DotNetOpenAuth.BuildTasks.sln index 5c2ffbb..ddf80bd 100644 --- a/src/DotNetOpenAuth.BuildTasks/DotNetOpenAuth.BuildTasks.sln +++ b/src/DotNetOpenAuth.BuildTasks/DotNetOpenAuth.BuildTasks.sln @@ -10,13 +10,13 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution ..\..\lib\DotNetOpenAuth.BuildTasks.targets = ..\..\lib\DotNetOpenAuth.BuildTasks.targets ..\..\tools\DotNetOpenAuth.Common.Settings.targets = ..\..\tools\DotNetOpenAuth.Common.Settings.targets ..\..\nuget\DotNetOpenAuth.nuspec = ..\..\nuget\DotNetOpenAuth.nuspec + ..\DotNetOpenAuth\DotNetOpenAuth.proj = ..\DotNetOpenAuth\DotNetOpenAuth.proj ..\..\tools\DotNetOpenAuth.props = ..\..\tools\DotNetOpenAuth.props ..\..\tools\DotNetOpenAuth.targets = ..\..\tools\DotNetOpenAuth.targets ..\..\tools\DotNetOpenAuth.Versioning.targets = ..\..\tools\DotNetOpenAuth.Versioning.targets ..\..\tools\drop.proj = ..\..\tools\drop.proj ..\..\EnlistmentInfo.props = ..\..\EnlistmentInfo.props ..\..\EnlistmentInfo.targets = ..\..\EnlistmentInfo.targets - ..\..\nuget\nuget.proj = ..\..\nuget\nuget.proj ..\..\tools\ohloh.proj = ..\..\tools\ohloh.proj ..\..\projecttemplates\projecttemplates.proj = ..\..\projecttemplates\projecttemplates.proj ..\..\samples\Samples.proj = ..\..\samples\Samples.proj @@ -29,14 +29,16 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotNetOpenAuth.BuildTasks", EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "NuGet", "NuGet", "{D49E2011-0E1C-4AB5-9887-BD1D42266503}" ProjectSection(SolutionItems) = preProject - ..\..\nuget\DotNetOpenAuth.nuspec = ..\..\nuget\DotNetOpenAuth.nuspec + ..\..\nuget\DotNetOpenAuth.Core.nuspec = ..\..\nuget\DotNetOpenAuth.Core.nuspec + ..\..\nuget\DotNetOpenAuth.OAuth.Consumer.nuspec = ..\..\nuget\DotNetOpenAuth.OAuth.Consumer.nuspec + ..\..\nuget\DotNetOpenAuth.OAuth.Core.nuspec = ..\..\nuget\DotNetOpenAuth.OAuth.Core.nuspec + ..\..\nuget\DotNetOpenAuth.OpenId.Core.nuspec = ..\..\nuget\DotNetOpenAuth.OpenId.Core.nuspec + ..\..\nuget\DotNetOpenAuth.OpenId.RelyingParty.nuspec = ..\..\nuget\DotNetOpenAuth.OpenId.RelyingParty.nuspec + ..\..\nuget\DotNetOpenAuth.Ultimate.nuspec = ..\..\nuget\DotNetOpenAuth.Ultimate.nuspec ..\..\nuget\nuget.proj = ..\..\nuget\nuget.proj EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "content", "content", "{BF3868D6-3BBA-4E40-B180-213370C15494}" - ProjectSection(SolutionItems) = preProject - ..\..\nuget\content\web.config.transform = ..\..\nuget\content\web.config.transform - EndProjectSection EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/src/DotNetOpenAuth.BuildTasks/DowngradeProjects.cs b/src/DotNetOpenAuth.BuildTasks/DowngradeProjects.cs index 2f09583..380bd17 100644 --- a/src/DotNetOpenAuth.BuildTasks/DowngradeProjects.cs +++ b/src/DotNetOpenAuth.BuildTasks/DowngradeProjects.cs @@ -68,7 +68,7 @@ namespace DotNetOpenAuth.BuildTasks { case ProjectClassification.VS2010Project: this.Log.LogMessage(MessageImportance.Low, "Downgrading project \"{0}\".", taskItem.ItemSpec); var project = new Project(); - project.Load(taskItem.ItemSpec); + project.Load(taskItem.ItemSpec, ProjectLoadSettings.IgnoreMissingImports); project.DefaultToolsVersion = "3.5"; if (this.DowngradeMvc2ToMvc1) { diff --git a/src/DotNetOpenAuth.BuildTasks/GetBuildVersion.cs b/src/DotNetOpenAuth.BuildTasks/GetBuildVersion.cs index 9818885..50bc89f 100644 --- a/src/DotNetOpenAuth.BuildTasks/GetBuildVersion.cs +++ b/src/DotNetOpenAuth.BuildTasks/GetBuildVersion.cs @@ -35,6 +35,12 @@ namespace DotNetOpenAuth.BuildTasks { public string GitCommitId { get; private set; } /// <summary> + /// Gets the build number (JDate) for this version. + /// </summary> + [Output] + public int BuildNumber { get; private set; } + + /// <summary> /// The file that contains the version base (Major.Minor.Build) to use. /// </summary> [Required] @@ -48,11 +54,12 @@ namespace DotNetOpenAuth.BuildTasks { public override bool Execute() { try { Version typedVersion = ReadVersionFromFile(); - SimpleVersion = typedVersion.ToString(); + this.SimpleVersion = typedVersion.ToString(); + this.BuildNumber = this.CalculateJDate(DateTime.Now); + + var fullVersion = new Version(typedVersion.Major, typedVersion.Minor, typedVersion.Build, this.BuildNumber); + this.Version = fullVersion.ToString(); - var fullVersion = new Version(typedVersion.Major, typedVersion.Minor, typedVersion.Build, CalculateJDate(DateTime.Now)); - Version = fullVersion.ToString(); - this.GitCommitId = GetGitHeadCommitId(); } catch (ArgumentOutOfRangeException ex) { Log.LogErrorFromException(ex); diff --git a/src/DotNetOpenAuth.BuildTasks/NuGetPack.cs b/src/DotNetOpenAuth.BuildTasks/NuGetPack.cs index 356c51f..91365e5 100644 --- a/src/DotNetOpenAuth.BuildTasks/NuGetPack.cs +++ b/src/DotNetOpenAuth.BuildTasks/NuGetPack.cs @@ -35,6 +35,16 @@ namespace DotNetOpenAuth.BuildTasks { public ITaskItem OutputPackageDirectory { get; set; } /// <summary> + /// Gets or sets the semicolon delimited list of properties to supply to the NuGet Pack command to perform token substitution. + /// </summary> + public string Properties { get; set; } + + /// <summary> + /// Gets or sets a value indicating whether to generate a symbols nuget package. + /// </summary> + public bool Symbols { get; set; } + + /// <summary> /// Returns the fully qualified path to the executable file. /// </summary> /// <returns> @@ -63,15 +73,7 @@ namespace DotNetOpenAuth.BuildTasks { Directory.CreateDirectory(Path.GetDirectoryName(this.OutputPackageDirectory.ItemSpec)); } - string fullPackagePath = this.DeriveFullPackagePath(); - this.Log.LogMessage("Creating NuGet package '{0}'.", fullPackagePath); - bool result = base.Execute(); - - if (result) { - this.Log.LogMessage(MessageImportance.High, "Successfully created package '{0}'.", fullPackagePath); - } - return result; } @@ -86,23 +88,14 @@ namespace DotNetOpenAuth.BuildTasks { args.AppendSwitch("pack"); args.AppendFileNameIfNotNull(this.NuSpec); - args.AppendSwitchIfNotNull("-b ", this.BaseDirectory); - args.AppendSwitchIfNotNull("-o ", this.OutputPackageDirectory); + args.AppendSwitchIfNotNull("-BasePath ", this.BaseDirectory); + args.AppendSwitchIfNotNull("-OutputDirectory ", this.OutputPackageDirectory); + args.AppendSwitchIfNotNull("-Properties ", this.Properties); + if (this.Symbols) { + args.AppendSwitch("-Symbols"); + } return args.ToString(); } - - /// <summary> - /// Derives the path to the generated .nupkg file. - /// </summary> - /// <returns>A relative path.</returns> - private string DeriveFullPackagePath() { - var spec = XDocument.Load(this.NuSpec.ItemSpec); - var metadata = spec.Element("package").Element("metadata"); - string id = metadata.Element("id").Value; - string version = metadata.Element("version").Value; - string baseDirectory = this.OutputPackageDirectory != null ? this.OutputPackageDirectory.ItemSpec : String.Empty; - return Path.Combine(baseDirectory, String.Format("{0}.{1}.nupkg", id, version)); - } } } diff --git a/src/DotNetOpenAuth.BuildTasks/PathSegment.cs b/src/DotNetOpenAuth.BuildTasks/PathSegment.cs index 1f17b5e..9179c82 100644 --- a/src/DotNetOpenAuth.BuildTasks/PathSegment.cs +++ b/src/DotNetOpenAuth.BuildTasks/PathSegment.cs @@ -292,12 +292,9 @@ namespace DotNetOpenAuth.BuildTasks { if (match == null) { match = new PathSegment(segments[segmentIndex], this); this.Children.Add(match); - if (segments.Length == segmentIndex + 1) { - return match; - } } - return match.Add(segments, segmentIndex + 1); + return segments.Length == segmentIndex + 1 ? match : match.Add(segments, segmentIndex + 1); } private PathSegment FindByOriginalPath(string[] segments, int segmentIndex) { diff --git a/src/DotNetOpenAuth.BuildTasks/RegexFileReplace.cs b/src/DotNetOpenAuth.BuildTasks/RegexFileReplace.cs new file mode 100644 index 0000000..294a859 --- /dev/null +++ b/src/DotNetOpenAuth.BuildTasks/RegexFileReplace.cs @@ -0,0 +1,55 @@ +//----------------------------------------------------------------------- +// <copyright file="RegexFileReplace.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.BuildTasks { + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Reflection; + using System.Text; + using Microsoft.Build.BuildEngine; + using Microsoft.Build.Framework; + using Microsoft.Build.Utilities; + using System.Text.RegularExpressions; + + public class RegexFileReplace : Task { + /// <summary> + /// Gets or sets the set of files to search. + /// </summary> + [Required] + public ITaskItem[] Files { get; set; } + + /// <summary> + /// Gets or sets the files to save the changed files to. This may be the same as the input files to make the change in place. + /// </summary> + public ITaskItem[] OutputFiles { get; set; } + + public string Pattern { get; set; } + + public string Replacement { get; set; } + + /// <summary> + /// Executes this instance. + /// </summary> + public override bool Execute() { + if (this.OutputFiles == null || this.OutputFiles.Length == 0) { + this.OutputFiles = this.Files; + } + + foreach (var file in this.Files) { + string[] lines = File.ReadAllLines(file.ItemSpec); + for (int i = 0; i < lines.Length; i++) { + lines[i] = Regex.Replace(lines[i], this.Pattern, this.Replacement); + } + + File.WriteAllLines(file.ItemSpec, lines); + } + + return !this.Log.HasLoggedErrors; + } + } +} diff --git a/src/DotNetOpenAuth/ComponentModel/ClaimTypeSuggestions.cs b/src/DotNetOpenAuth.Core.UI/ComponentModel/ClaimTypeSuggestions.cs index c9f4d96..c9f4d96 100644 --- a/src/DotNetOpenAuth/ComponentModel/ClaimTypeSuggestions.cs +++ b/src/DotNetOpenAuth.Core.UI/ComponentModel/ClaimTypeSuggestions.cs diff --git a/src/DotNetOpenAuth.Core.UI/ComponentModel/ConverterBase.cs b/src/DotNetOpenAuth.Core.UI/ComponentModel/ConverterBase.cs new file mode 100644 index 0000000..404d375 --- /dev/null +++ b/src/DotNetOpenAuth.Core.UI/ComponentModel/ConverterBase.cs @@ -0,0 +1,223 @@ +//----------------------------------------------------------------------- +// <copyright file="ConverterBase.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.ComponentModel { + using System; + using System.Collections; + using System.ComponentModel; + using System.ComponentModel.Design.Serialization; + using System.Diagnostics.CodeAnalysis; + using System.Diagnostics.Contracts; + using System.Globalization; + using System.Reflection; + using System.Security; + using System.Security.Permissions; + + /// <summary> + /// A design-time helper to allow Intellisense to aid typing + /// ClaimType URIs. + /// </summary> + /// <typeparam name="T">The strong-type of the property this class is affixed to.</typeparam> + public abstract class ConverterBase<T> : TypeConverter { + /// <summary> + /// A cache of the standard claim types known to the application. + /// </summary> + private StandardValuesCollection standardValues; + + /// <summary> + /// Initializes a new instance of the ConverterBase class. + /// </summary> + protected ConverterBase() { + } + + /// <summary> + /// Gets a cache of the standard values to suggest. + /// </summary> + private StandardValuesCollection StandardValueCache { + get { + if (this.standardValues == null) { + this.standardValues = new StandardValuesCollection(this.GetStandardValuesForCache()); + } + + return this.standardValues; + } + } + + /// <summary> + /// Returns whether this object supports a standard set of values that can be picked from a list, using the specified context. + /// </summary> + /// <param name="context">An <see cref="T:System.ComponentModel.ITypeDescriptorContext"/> that provides a format context.</param> + /// <returns> + /// true if <see cref="M:System.ComponentModel.TypeConverter.GetStandardValues"/> should be called to find a common set of values the object supports; otherwise, false. + /// </returns> + public override bool GetStandardValuesSupported(ITypeDescriptorContext context) { + return this.StandardValueCache.Count > 0; + } + + /// <summary> + /// Returns a collection of standard values for the data type this type converter is designed for when provided with a format context. + /// </summary> + /// <param name="context">An <see cref="T:System.ComponentModel.ITypeDescriptorContext"/> that provides a format context that can be used to extract additional information about the environment from which this converter is invoked. This parameter or properties of this parameter can be null.</param> + /// <returns> + /// A <see cref="T:System.ComponentModel.TypeConverter.StandardValuesCollection"/> that holds a standard set of valid values, or null if the data type does not support a standard set of values. + /// </returns> + public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext context) { + return this.StandardValueCache; + } + + /// <summary> + /// Returns whether the collection of standard values returned from <see cref="M:System.ComponentModel.TypeConverter.GetStandardValues"/> is an exclusive list of possible values, using the specified context. + /// </summary> + /// <param name="context">An <see cref="T:System.ComponentModel.ITypeDescriptorContext"/> that provides a format context.</param> + /// <returns> + /// true if the <see cref="T:System.ComponentModel.TypeConverter.StandardValuesCollection"/> returned from <see cref="M:System.ComponentModel.TypeConverter.GetStandardValues"/> is an exhaustive list of possible values; false if other values are possible. + /// </returns> + public override bool GetStandardValuesExclusive(ITypeDescriptorContext context) { + return false; + } + + /// <summary> + /// Returns whether this converter can convert an object of the given type to the type of this converter, using the specified context. + /// </summary> + /// <param name="context">An <see cref="T:System.ComponentModel.ITypeDescriptorContext"/> that provides a format context.</param> + /// <param name="sourceType">A <see cref="T:System.Type"/> that represents the type you want to convert from.</param> + /// <returns> + /// true if this converter can perform the conversion; otherwise, false. + /// </returns> + public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) { + return sourceType == typeof(string) + || base.CanConvertFrom(context, sourceType); + } + + /// <summary> + /// Returns whether this converter can convert the object to the specified type, using the specified context. + /// </summary> + /// <param name="context">An <see cref="T:System.ComponentModel.ITypeDescriptorContext"/> that provides a format context.</param> + /// <param name="destinationType">A <see cref="T:System.Type"/> that represents the type you want to convert to.</param> + /// <returns> + /// true if this converter can perform the conversion; otherwise, false. + /// </returns> + public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) { + return destinationType == typeof(string) + || destinationType == typeof(InstanceDescriptor) + || base.CanConvertTo(context, destinationType); + } + + /// <summary> + /// Converts the given object to the type of this converter, using the specified context and culture information. + /// </summary> + /// <param name="context">An <see cref="T:System.ComponentModel.ITypeDescriptorContext"/> that provides a format context.</param> + /// <param name="culture">The <see cref="T:System.Globalization.CultureInfo"/> to use as the current culture.</param> + /// <param name="value">The <see cref="T:System.Object"/> to convert.</param> + /// <returns> + /// An <see cref="T:System.Object"/> that represents the converted value. + /// </returns> + /// <exception cref="T:System.NotSupportedException"> + /// The conversion cannot be performed. + /// </exception> + public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) { + string stringValue = value as string; + if (stringValue != null) { + return this.ConvertFrom(stringValue); + } else { + return base.ConvertFrom(context, culture, value); + } + } + + /// <summary> + /// Converts the given value object to the specified type, using the specified context and culture information. + /// </summary> + /// <param name="context">An <see cref="T:System.ComponentModel.ITypeDescriptorContext"/> that provides a format context.</param> + /// <param name="culture">A <see cref="T:System.Globalization.CultureInfo"/>. If null is passed, the current culture is assumed.</param> + /// <param name="value">The <see cref="T:System.Object"/> to convert.</param> + /// <param name="destinationType">The <see cref="T:System.Type"/> to convert the <paramref name="value"/> parameter to.</param> + /// <returns> + /// An <see cref="T:System.Object"/> that represents the converted value. + /// </returns> + /// <exception cref="T:System.ArgumentNullException"> + /// The <paramref name="destinationType"/> parameter is null. + /// </exception> + /// <exception cref="T:System.NotSupportedException"> + /// The conversion cannot be performed. + /// </exception> + [SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", MessageId = "System.Diagnostics.Contracts.__ContractsRuntime.Assume(System.Boolean,System.String,System.String)", Justification = "No localization required.")] + public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType) { + Contract.Assume(destinationType != null, "Missing contract."); + if (destinationType.IsInstanceOfType(value)) { + return value; + } + + T typedValue = (T)value; + if (destinationType == typeof(string)) { + return this.ConvertToString(typedValue); + } else if (destinationType == typeof(InstanceDescriptor)) { + return this.CreateFrom(typedValue); + } else { + return base.ConvertTo(context, culture, value, destinationType); + } + } + + /// <summary> + /// Creates an <see cref="InstanceDescriptor"/> instance, protecting against the LinkDemand. + /// </summary> + /// <param name="memberInfo">The member info.</param> + /// <param name="arguments">The arguments.</param> + /// <returns>A <see cref="InstanceDescriptor"/>, or <c>null</c> if sufficient permissions are unavailable.</returns> + protected static InstanceDescriptor CreateInstanceDescriptor(MemberInfo memberInfo, ICollection arguments) { + try { + return CreateInstanceDescriptorPrivate(memberInfo, arguments); + } catch (SecurityException) { + return null; + } + } + + /// <summary> + /// Gets the standard values to suggest with Intellisense in the designer. + /// </summary> + /// <returns>A collection of the standard values.</returns> + [Pure] + [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "Potentially expensive call.")] + protected virtual ICollection GetStandardValuesForCache() { + Contract.Ensures(Contract.Result<ICollection>() != null); + return new T[0]; + } + + /// <summary> + /// Converts a value from its string representation to its strongly-typed object. + /// </summary> + /// <param name="value">The value.</param> + /// <returns>The strongly-typed object.</returns> + [Pure] + protected abstract T ConvertFrom(string value); + + /// <summary> + /// Creates the reflection instructions for recreating an instance later. + /// </summary> + /// <param name="value">The value to recreate later.</param> + /// <returns>The description of how to recreate an instance.</returns> + [Pure] + protected abstract InstanceDescriptor CreateFrom(T value); + + /// <summary> + /// Converts the strongly-typed value to a string. + /// </summary> + /// <param name="value">The value to convert.</param> + /// <returns>The string representation of the object.</returns> + [Pure] + protected abstract string ConvertToString(T value); + + /// <summary> + /// Creates an <see cref="InstanceDescriptor"/> instance, protecting against the LinkDemand. + /// </summary> + /// <param name="memberInfo">The member info.</param> + /// <param name="arguments">The arguments.</param> + /// <returns>A <see cref="InstanceDescriptor"/>.</returns> + [PermissionSet(SecurityAction.Demand, Name = "FullTrust")] + private static InstanceDescriptor CreateInstanceDescriptorPrivate(MemberInfo memberInfo, ICollection arguments) { + return new InstanceDescriptor(memberInfo, arguments); + } + } +} diff --git a/src/DotNetOpenAuth.Core.UI/ComponentModel/SuggestedStringsConverter.cs b/src/DotNetOpenAuth.Core.UI/ComponentModel/SuggestedStringsConverter.cs new file mode 100644 index 0000000..62a4b13 --- /dev/null +++ b/src/DotNetOpenAuth.Core.UI/ComponentModel/SuggestedStringsConverter.cs @@ -0,0 +1,91 @@ +//----------------------------------------------------------------------- +// <copyright file="SuggestedStringsConverter.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.ComponentModel { + using System; + using System.Collections; + using System.ComponentModel.Design.Serialization; + using System.Diagnostics.Contracts; + using System.Linq; + using System.Reflection; + + /// <summary> + /// A type that generates suggested strings for Intellisense, + /// but doesn't actually convert between strings and other types. + /// </summary> + [ContractClass(typeof(SuggestedStringsConverterContract))] + public abstract class SuggestedStringsConverter : ConverterBase<string> { + /// <summary> + /// Initializes a new instance of the <see cref="SuggestedStringsConverter"/> class. + /// </summary> + protected SuggestedStringsConverter() { + } + + /// <summary> + /// Gets the type to reflect over for the well known values. + /// </summary> + [Pure] + protected abstract Type WellKnownValuesType { get; } + + /// <summary> + /// Gets the values of public static fields and properties on a given type. + /// </summary> + /// <param name="type">The type to reflect over.</param> + /// <returns>A collection of values.</returns> + internal static ICollection GetStandardValuesForCacheShared(Type type) { + Requires.NotNull(type, "type"); + Contract.Ensures(Contract.Result<ICollection>() != null); + + var fields = from field in type.GetFields(BindingFlags.Static | BindingFlags.Public) + select field.GetValue(null); + var properties = from prop in type.GetProperties(BindingFlags.Static | BindingFlags.Public) + select prop.GetValue(null, null); + return fields.Concat(properties).ToArray(); + } + + /// <summary> + /// Converts a value from its string representation to its strongly-typed object. + /// </summary> + /// <param name="value">The value.</param> + /// <returns>The strongly-typed object.</returns> + [Pure] + protected override string ConvertFrom(string value) { + return value; + } + + /// <summary> + /// Creates the reflection instructions for recreating an instance later. + /// </summary> + /// <param name="value">The value to recreate later.</param> + /// <returns> + /// The description of how to recreate an instance. + /// </returns> + [Pure] + protected override InstanceDescriptor CreateFrom(string value) { + // No implementation necessary since we're only dealing with strings. + throw new NotImplementedException(); + } + + /// <summary> + /// Converts the strongly-typed value to a string. + /// </summary> + /// <param name="value">The value to convert.</param> + /// <returns>The string representation of the object.</returns> + [Pure] + protected override string ConvertToString(string value) { + return value; + } + + /// <summary> + /// Gets the standard values to suggest with Intellisense in the designer. + /// </summary> + /// <returns>A collection of the standard values.</returns> + [Pure] + protected override ICollection GetStandardValuesForCache() { + return GetStandardValuesForCacheShared(this.WellKnownValuesType); + } + } +} diff --git a/src/DotNetOpenAuth/ComponentModel/SuggestedStringsConverterContract.cs b/src/DotNetOpenAuth.Core.UI/ComponentModel/SuggestedStringsConverterContract.cs index 1573208..1573208 100644 --- a/src/DotNetOpenAuth/ComponentModel/SuggestedStringsConverterContract.cs +++ b/src/DotNetOpenAuth.Core.UI/ComponentModel/SuggestedStringsConverterContract.cs diff --git a/src/DotNetOpenAuth.Core.UI/DotNetOpenAuth.Core.UI.csproj b/src/DotNetOpenAuth.Core.UI/DotNetOpenAuth.Core.UI.csproj new file mode 100644 index 0000000..eba7fbe --- /dev/null +++ b/src/DotNetOpenAuth.Core.UI/DotNetOpenAuth.Core.UI.csproj @@ -0,0 +1,38 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), EnlistmentInfo.props))\EnlistmentInfo.props" Condition=" '$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), EnlistmentInfo.props))' != '' " /> + <PropertyGroup> + <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> + <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> + </PropertyGroup> + <Import Project="$(ProjectRoot)tools\DotNetOpenAuth.props" /> + <PropertyGroup> + <SchemaVersion>2.0</SchemaVersion> + <ProjectGuid>{173E7B8D-E751-46E2-A133-F72297C0D2F4}</ProjectGuid> + <OutputType>Library</OutputType> + <AppDesignerFolder>Properties</AppDesignerFolder> + <RootNamespace>DotNetOpenAuth</RootNamespace> + <AssemblyName>DotNetOpenAuth.Core.UI</AssemblyName> + </PropertyGroup> + <Import Project="$(ProjectRoot)tools\DotNetOpenAuth.Product.props" /> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> + </PropertyGroup> + <ItemGroup> + <Compile Include="ComponentModel\ClaimTypeSuggestions.cs" /> + <Compile Include="ComponentModel\ConverterBase.cs" /> + <Compile Include="ComponentModel\SuggestedStringsConverter.cs" /> + <Compile Include="ComponentModel\SuggestedStringsConverterContract.cs" /> + <Compile Include="Properties\AssemblyInfo.cs" /> + </ItemGroup> + <ItemGroup> + <ProjectReference Include="..\DotNetOpenAuth.Core\DotNetOpenAuth.Core.csproj"> + <Project>{60426312-6AE5-4835-8667-37EDEA670222}</Project> + <Name>DotNetOpenAuth.Core</Name> + </ProjectReference> + </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))' != '' " /> +</Project>
\ No newline at end of file diff --git a/src/DotNetOpenAuth.Core.UI/Properties/AssemblyInfo.cs b/src/DotNetOpenAuth.Core.UI/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..3a94681 --- /dev/null +++ b/src/DotNetOpenAuth.Core.UI/Properties/AssemblyInfo.cs @@ -0,0 +1,74 @@ +//----------------------------------------------------------------------- +// <copyright file="AssemblyInfo.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +// We DON'T put an AssemblyVersionAttribute in here because it is generated in the build. + +using System; +using System.Diagnostics.Contracts; +using System.Net; +using System.Reflection; +using System.Resources; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Security; +using System.Security.Permissions; +using System.Web.UI; + +[assembly: TagPrefix("DotNetOpenAuth", "dnoa")] + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("DotNetOpenAuth")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("DotNetOpenAuth")] +[assembly: AssemblyCopyright("Copyright © 2008")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: NeutralResourcesLanguage("en-US")] +[assembly: CLSCompliant(true)] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("7d73990c-47c0-4256-9f20-a893add9e289")] + +[assembly: ContractVerification(true)] + +#if StrongNameSigned +// See comment at top of this file. We need this so that strong-naming doesn't +// keep this assembly from being useful to shared host (medium trust) web sites. +[assembly: AllowPartiallyTrustedCallers] + +[assembly: InternalsVisibleTo("DotNetOpenAuth.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100AD093C3765257C89A7010E853F2C7C741FF92FA8ACE06D7B8254702CAD5CF99104447F63AB05F8BB6F51CE0D81C8C93D2FCE8C20AAFF7042E721CBA16EAAE98778611DED11C0ABC8900DC5667F99B50A9DADEC24DBD8F2C91E3E8AD300EF64F1B4B9536CEB16FB440AF939F57624A9B486F867807C649AE4830EAB88C6C03998")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.InfoCard, PublicKey=0024000004800000940000000602000000240000525341310004000001000100AD093C3765257C89A7010E853F2C7C741FF92FA8ACE06D7B8254702CAD5CF99104447F63AB05F8BB6F51CE0D81C8C93D2FCE8C20AAFF7042E721CBA16EAAE98778611DED11C0ABC8900DC5667F99B50A9DADEC24DBD8F2C91E3E8AD300EF64F1B4B9536CEB16FB440AF939F57624A9B486F867807C649AE4830EAB88C6C03998")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.OpenId, PublicKey=0024000004800000940000000602000000240000525341310004000001000100AD093C3765257C89A7010E853F2C7C741FF92FA8ACE06D7B8254702CAD5CF99104447F63AB05F8BB6F51CE0D81C8C93D2FCE8C20AAFF7042E721CBA16EAAE98778611DED11C0ABC8900DC5667F99B50A9DADEC24DBD8F2C91E3E8AD300EF64F1B4B9536CEB16FB440AF939F57624A9B486F867807C649AE4830EAB88C6C03998")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.OpenId.UI, PublicKey=0024000004800000940000000602000000240000525341310004000001000100AD093C3765257C89A7010E853F2C7C741FF92FA8ACE06D7B8254702CAD5CF99104447F63AB05F8BB6F51CE0D81C8C93D2FCE8C20AAFF7042E721CBA16EAAE98778611DED11C0ABC8900DC5667F99B50A9DADEC24DBD8F2C91E3E8AD300EF64F1B4B9536CEB16FB440AF939F57624A9B486F867807C649AE4830EAB88C6C03998")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.OpenId.RelyingParty, PublicKey=0024000004800000940000000602000000240000525341310004000001000100AD093C3765257C89A7010E853F2C7C741FF92FA8ACE06D7B8254702CAD5CF99104447F63AB05F8BB6F51CE0D81C8C93D2FCE8C20AAFF7042E721CBA16EAAE98778611DED11C0ABC8900DC5667F99B50A9DADEC24DBD8F2C91E3E8AD300EF64F1B4B9536CEB16FB440AF939F57624A9B486F867807C649AE4830EAB88C6C03998")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.OpenId.RelyingParty.UI, PublicKey=0024000004800000940000000602000000240000525341310004000001000100AD093C3765257C89A7010E853F2C7C741FF92FA8ACE06D7B8254702CAD5CF99104447F63AB05F8BB6F51CE0D81C8C93D2FCE8C20AAFF7042E721CBA16EAAE98778611DED11C0ABC8900DC5667F99B50A9DADEC24DBD8F2C91E3E8AD300EF64F1B4B9536CEB16FB440AF939F57624A9B486F867807C649AE4830EAB88C6C03998")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.OpenId.Provider, PublicKey=0024000004800000940000000602000000240000525341310004000001000100AD093C3765257C89A7010E853F2C7C741FF92FA8ACE06D7B8254702CAD5CF99104447F63AB05F8BB6F51CE0D81C8C93D2FCE8C20AAFF7042E721CBA16EAAE98778611DED11C0ABC8900DC5667F99B50A9DADEC24DBD8F2C91E3E8AD300EF64F1B4B9536CEB16FB440AF939F57624A9B486F867807C649AE4830EAB88C6C03998")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.OpenId.Provider.UI, PublicKey=0024000004800000940000000602000000240000525341310004000001000100AD093C3765257C89A7010E853F2C7C741FF92FA8ACE06D7B8254702CAD5CF99104447F63AB05F8BB6F51CE0D81C8C93D2FCE8C20AAFF7042E721CBA16EAAE98778611DED11C0ABC8900DC5667F99B50A9DADEC24DBD8F2C91E3E8AD300EF64F1B4B9536CEB16FB440AF939F57624A9B486F867807C649AE4830EAB88C6C03998")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.OAuth, PublicKey=0024000004800000940000000602000000240000525341310004000001000100AD093C3765257C89A7010E853F2C7C741FF92FA8ACE06D7B8254702CAD5CF99104447F63AB05F8BB6F51CE0D81C8C93D2FCE8C20AAFF7042E721CBA16EAAE98778611DED11C0ABC8900DC5667F99B50A9DADEC24DBD8F2C91E3E8AD300EF64F1B4B9536CEB16FB440AF939F57624A9B486F867807C649AE4830EAB88C6C03998")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.OAuth2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100AD093C3765257C89A7010E853F2C7C741FF92FA8ACE06D7B8254702CAD5CF99104447F63AB05F8BB6F51CE0D81C8C93D2FCE8C20AAFF7042E721CBA16EAAE98778611DED11C0ABC8900DC5667F99B50A9DADEC24DBD8F2C91E3E8AD300EF64F1B4B9536CEB16FB440AF939F57624A9B486F867807C649AE4830EAB88C6C03998")] +[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")] +#else +[assembly: InternalsVisibleTo("DotNetOpenAuth.Test")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.InfoCard")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.OpenId")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.OpenId.UI")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.OpenId.RelyingParty")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.OpenId.RelyingParty.UI")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.OpenId.Provider")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.OpenId.Provider.UI")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.OAuth")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.OAuth2")] +[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] +#endif diff --git a/src/DotNetOpenAuth.Core/Assumes.cs b/src/DotNetOpenAuth.Core/Assumes.cs new file mode 100644 index 0000000..67205a2 --- /dev/null +++ b/src/DotNetOpenAuth.Core/Assumes.cs @@ -0,0 +1,97 @@ +//----------------------------------------------------------------------- +// <copyright file="Assumes.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth { + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Diagnostics.Contracts; + using System.Linq; + using System.Text; + + /// <summary> + /// Internal state consistency checks that throw an internal error exception when they fail. + /// </summary> + internal static class Assumes { + /// <summary> + /// Validates some expression describing the acceptable condition evaluates to true. + /// </summary> + /// <param name="condition">The expression that must evaluate to true to avoid an internal error exception.</param> + /// <param name="message">The message to include with the exception.</param> + [Pure, DebuggerStepThrough] + internal static void True(bool condition, string message = null) { + if (!condition) { + Fail(message); + } + } + + /// <summary> + /// Validates some expression describing the acceptable condition evaluates to true. + /// </summary> + /// <param name="condition">The expression that must evaluate to true to avoid an internal error exception.</param> + /// <param name="unformattedMessage">The unformatted message.</param> + /// <param name="args">Formatting arguments.</param> + [Pure, DebuggerStepThrough] + internal static void True(bool condition, string unformattedMessage, params object[] args) { + if (!condition) { + Fail(String.Format(unformattedMessage, args)); + } + } + + /// <summary> + /// Throws an internal error exception. + /// </summary> + /// <param name="message">The message.</param> + [Pure, DebuggerStepThrough] + internal static void Fail(string message = null) { + if (message != null) { + throw new InternalErrorException(message); + } else { + throw new InternalErrorException(); + } + } + + /// <summary> + /// An internal error exception that should never be caught. + /// </summary> + [Serializable] + private class InternalErrorException : Exception { + /// <summary> + /// Initializes a new instance of the <see cref="InternalErrorException"/> class. + /// </summary> + internal InternalErrorException() { + } + + /// <summary> + /// Initializes a new instance of the <see cref="InternalErrorException"/> class. + /// </summary> + /// <param name="message">The message.</param> + internal InternalErrorException(string message) : base(message) { + } + + /// <summary> + /// Initializes a new instance of the <see cref="InternalErrorException"/> class. + /// </summary> + /// <param name="message">The message.</param> + /// <param name="inner">The inner exception.</param> + internal InternalErrorException(string message, Exception inner) : base(message, inner) { + } + + /// <summary> + /// Initializes a new instance of the <see cref="InternalErrorException"/> class. + /// </summary> + /// <param name="info">The <see cref="T:System.Runtime.Serialization.SerializationInfo"/> that holds the serialized object data about the exception being thrown.</param> + /// <param name="context">The <see cref="T:System.Runtime.Serialization.StreamingContext"/> that contains contextual information about the source or destination.</param> + /// <exception cref="T:System.ArgumentNullException">The <paramref name="info"/> parameter is null. </exception> + /// <exception cref="T:System.Runtime.Serialization.SerializationException">The class name is null or <see cref="P:System.Exception.HResult"/> is zero (0). </exception> + protected InternalErrorException( + System.Runtime.Serialization.SerializationInfo info, + System.Runtime.Serialization.StreamingContext context) + : base(info, context) { + } + } + } +} diff --git a/src/DotNetOpenAuth/CodeAnalysisDictionary.xml b/src/DotNetOpenAuth.Core/CodeAnalysisDictionary.xml index 8c90df3..8c90df3 100644 --- a/src/DotNetOpenAuth/CodeAnalysisDictionary.xml +++ b/src/DotNetOpenAuth.Core/CodeAnalysisDictionary.xml diff --git a/src/DotNetOpenAuth.Core/Configuration/DotNetOpenAuth.xsd b/src/DotNetOpenAuth.Core/Configuration/DotNetOpenAuth.xsd new file mode 100644 index 0000000..d193776 --- /dev/null +++ b/src/DotNetOpenAuth.Core/Configuration/DotNetOpenAuth.xsd @@ -0,0 +1,968 @@ +<?xml version="1.0" encoding="utf-8"?> +<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" + xmlns:vs="http://schemas.microsoft.com/Visual-Studio-Intellisense" + elementFormDefault="qualified" + attributeFormDefault="unqualified"> + <xs:element name="dotNetOpenAuth"> + <xs:annotation> + <xs:documentation> + Customizations and configuration of DotNetOpenAuth behavior. + </xs:documentation> + </xs:annotation> + <xs:complexType> + <xs:choice minOccurs="0" maxOccurs="unbounded"> + <xs:element name="messaging"> + <xs:annotation> + <xs:documentation> + Options for general messaging protocols, such as whitelist/blacklist hosts and maximum message age. + </xs:documentation> + </xs:annotation> + <xs:complexType> + <xs:choice minOccurs="0" maxOccurs="unbounded"> + <xs:element name="untrustedWebRequest"> + <xs:annotation> + <xs:documentation> + Restrictions and settings to apply to outgoing HTTP requests to hosts that are not + trusted by this web site. Useful for OpenID-supporting hosts because HTTP connections + are initiated based on user input to arbitrary servers. + </xs:documentation> + </xs:annotation> + <xs:complexType> + <xs:choice minOccurs="0" maxOccurs="unbounded"> + <xs:element name="whitelistHosts"> + <xs:annotation> + <xs:documentation> + A set of host names (including domain names) to allow outgoing connections to + that would otherwise not be allowed based on security restrictions. + </xs:documentation> + </xs:annotation> + <xs:complexType> + <xs:choice minOccurs="0" maxOccurs="unbounded"> + <xs:element name="add"> + <xs:complexType> + <xs:attribute name="name" type="xs:string" use="required"> + <xs:annotation> + <xs:documentation> + The host name to trust. For example: "localhost" or "www.mypartners.com". + </xs:documentation> + </xs:annotation> + </xs:attribute> + </xs:complexType> + </xs:element> + <xs:element name="remove"> + <xs:complexType> + <xs:attribute name="name" type="xs:string" use="required"> + <xs:annotation> + <xs:documentation> + The host name to NOT trust. For example: "localhost" or "www.mypartners.com". + </xs:documentation> + </xs:annotation> + </xs:attribute> + </xs:complexType> + </xs:element> + <xs:element name="clear"> + <xs:annotation> + <xs:documentation> + Clears all hosts from the whitelist. + </xs:documentation> + </xs:annotation> + <xs:complexType> + <!--tag is empty--> + </xs:complexType> + </xs:element> + </xs:choice> + </xs:complexType> + </xs:element> + <xs:element name="whitelistHostsRegex"> + <xs:complexType> + <xs:choice minOccurs="0" maxOccurs="unbounded"> + <xs:element name="add"> + <xs:complexType> + <xs:attribute name="name" type="xs:string" use="required" /> + </xs:complexType> + </xs:element> + <xs:element name="remove"> + <xs:complexType> + <xs:attribute name="name" type="xs:string" use="required" /> + </xs:complexType> + </xs:element> + <xs:element name="clear"> + <xs:complexType> + <!--tag is empty--> + </xs:complexType> + </xs:element> + </xs:choice> + </xs:complexType> + </xs:element> + <xs:element name="blacklistHosts"> + <xs:annotation> + <xs:documentation> + A set of host names (including domain names) to disallow outgoing connections to + that would otherwise be allowed based on security restrictions. + </xs:documentation> + </xs:annotation> + <xs:complexType> + <xs:choice minOccurs="0" maxOccurs="unbounded"> + <xs:element name="add"> + <xs:complexType> + <xs:attribute name="name" type="xs:string" use="required"> + <xs:annotation> + <xs:documentation> + The host name known to add to the blacklist. For example: "localhost" or "www.mypartners.com". + </xs:documentation> + </xs:annotation> + </xs:attribute> + </xs:complexType> + </xs:element> + <xs:element name="remove"> + <xs:complexType> + <xs:attribute name="name" type="xs:string" use="required"> + <xs:annotation> + <xs:documentation> + The host name known to remove to the blacklist. For example: "localhost" or "www.mypartners.com". + </xs:documentation> + </xs:annotation> + </xs:attribute> + </xs:complexType> + </xs:element> + <xs:element name="clear"> + <xs:annotation> + <xs:documentation> + Clears all hosts from the blacklist. + </xs:documentation> + </xs:annotation> + <xs:complexType> + <!--tag is empty--> + </xs:complexType> + </xs:element> + </xs:choice> + </xs:complexType> + </xs:element> + <xs:element name="blacklistHostsRegex"> + <xs:complexType> + <xs:choice minOccurs="0" maxOccurs="unbounded"> + <xs:element name="add"> + <xs:complexType> + <xs:attribute name="name" type="xs:string" use="required" /> + </xs:complexType> + </xs:element> + <xs:element name="remove"> + <xs:complexType> + <xs:attribute name="name" type="xs:string" use="required" /> + </xs:complexType> + </xs:element> + <xs:element name="clear"> + <xs:complexType> + <!--tag is empty--> + </xs:complexType> + </xs:element> + </xs:choice> + </xs:complexType> + </xs:element> + </xs:choice> + <xs:attribute name="timeout" type="xs:string"> + <xs:annotation> + <xs:documentation> + The maximum time to allow for an outgoing HTTP request to complete before giving up. + </xs:documentation> + </xs:annotation> + </xs:attribute> + <xs:attribute name="readWriteTimeout" type="xs:string"> + <xs:annotation> + <xs:documentation> + The maximum time to allow for an outgoing HTTP request to either send or receive data before giving up. + </xs:documentation> + </xs:annotation> + </xs:attribute> + <xs:attribute name="maximumBytesToRead" type="xs:int"> + <xs:annotation> + <xs:documentation> + The maximum bytes to read from an untrusted server during an outgoing HTTP request before cutting off the response. + </xs:documentation> + </xs:annotation> + </xs:attribute> + <xs:attribute name="maximumRedirections" type="xs:int"> + <xs:annotation> + <xs:documentation> + The maximum redirection instructions to follow before giving up. + </xs:documentation> + </xs:annotation> + </xs:attribute> + </xs:complexType> + </xs:element> + <xs:element name="webResourceUrlProvider"> + <xs:annotation> + <xs:documentation> + The type that implements the DotNetOpenAuth.IEmbeddedResourceRetrieval interface + to instantiate for obtaining URLs that fetch embedded resource streams. + Primarily useful when the System.Web.UI.Page class is not used in the ASP.NET pipeline. + </xs:documentation> + </xs:annotation> + <xs:complexType> + <xs:attribute name="type" type="xs:string" use="optional"> + <xs:annotation> + <xs:documentation> + The fully-qualified name of the type that implements the IEmbeddedResourceRetrieval interface. + </xs:documentation> + </xs:annotation> + </xs:attribute> + <xs:attribute name="xaml" type="xs:string" use="optional" /> + </xs:complexType> + </xs:element> + </xs:choice> + <xs:attribute name="lifetime" type="xs:string"> + <xs:annotation> + <xs:documentation> + The maximum time allowed between a message being sent to when it is received before + it is considered expired. + </xs:documentation> + </xs:annotation> + </xs:attribute> + <xs:attribute name="clockSkew" type="xs:string"> + <xs:annotation> + <xs:documentation> + The maximum time to consider a safe difference in server clocks. + </xs:documentation> + </xs:annotation> + </xs:attribute> + <xs:attribute name="strict" type="xs:boolean" default="true"> + <xs:annotation> + <xs:documentation> + Whether remote parties will be held strictly to the protocol specifications. + Strict will require that remote parties adhere strictly to the specifications, + even when a loose interpretation would not compromise security. + true is a good default because it shakes out interoperability bugs in remote services + so they can be identified and corrected. But some web sites want things to Just Work + more than they want to file bugs against others, so false is the setting for them. + </xs:documentation> + </xs:annotation> + </xs:attribute> + <xs:attribute name="relaxSslRequirements" type="xs:boolean" default="false"> + <xs:annotation> + <xs:documentation> + Whether SSL requirements within the library are disabled/relaxed. + Use for TESTING ONLY. + </xs:documentation> + </xs:annotation> + </xs:attribute> + <xs:attribute name="maximumIndirectMessageUrlLength" type="xs:int" default="2048"> + <xs:annotation> + <xs:documentation> + The maximum allowable size for a 301 Redirect response before we send + a 200 OK response with a scripted form POST with the parameters instead + in order to ensure successfully sending a large payload to another server + that might have a maximum allowable size restriction on its GET request. + </xs:documentation> + </xs:annotation> + </xs:attribute> + <xs:attribute name="privateSecretMaximumAge" type="xs:string" default="28.00:00:00"> + <xs:annotation> + <xs:documentation> + The maximum age of a secret used for private signing or encryption before it is renewed. + </xs:documentation> + </xs:annotation> + </xs:attribute> + </xs:complexType> + </xs:element> + <xs:element name="openid"> + <xs:annotation> + <xs:documentation> + Configuration for OpenID authentication (relying parties and providers). + </xs:documentation> + </xs:annotation> + <xs:complexType> + <xs:choice minOccurs="0" maxOccurs="unbounded"> + <xs:element name="relyingParty"> + <xs:annotation> + <xs:documentation> + Configuration specific for OpenID relying parties. + </xs:documentation> + </xs:annotation> + <xs:complexType> + <xs:choice minOccurs="0" maxOccurs="unbounded"> + <xs:element name="security"> + <xs:annotation> + <xs:documentation> + Security settings that apply to OpenID relying parties. + </xs:documentation> + </xs:annotation> + <xs:complexType> + <xs:choice minOccurs="0" maxOccurs="unbounded"> + <xs:element name="trustedProviders"> + <xs:complexType> + <xs:choice minOccurs="0" maxOccurs="unbounded"> + <xs:element name="add"> + <xs:complexType> + <xs:attribute name="endpoint" type="xs:string" use="required"> + <xs:annotation> + <xs:documentation> + The OpenID Provider Endpoint (aka "OP Endpoint") that this relying party trusts. + </xs:documentation> + </xs:annotation> + </xs:attribute> + </xs:complexType> + </xs:element> + <xs:element name="remove"> + <xs:complexType> + <xs:attribute name="endpoint" type="xs:string" use="required" /> + </xs:complexType> + </xs:element> + <xs:element name="clear"> + <xs:complexType> + <!--tag is empty--> + </xs:complexType> + </xs:element> + </xs:choice> + <xs:attribute name="rejectAssertionsFromUntrustedProviders" type="xs:boolean" default="false"> + <xs:annotation> + <xs:documentation> + A value indicating whether any login attempt coming from an OpenID Provider Endpoint that is not on this + whitelist of trusted OP Endpoints will be rejected. If the trusted providers list is empty and this value + is true, all assertions are rejected. + </xs:documentation> + </xs:annotation> + </xs:attribute> + </xs:complexType> + </xs:element> + </xs:choice> + <xs:attribute name="requireSsl" type="xs:boolean" default="false"> + <xs:annotation> + <xs:documentation> + Restricts OpenID logins to identifiers that use HTTPS throughout the discovery process, + and only uses HTTPS OpenID Provider endpoints. + </xs:documentation> + </xs:annotation> + </xs:attribute> + <xs:attribute name="minimumRequiredOpenIdVersion"> + <xs:annotation> + <xs:documentation> + Optionally restricts interoperability with remote parties that + implement older versions of OpenID. + </xs:documentation> + </xs:annotation> + <xs:simpleType> + <xs:restriction base="xs:NMTOKEN"> + <xs:enumeration value="V10" /> + <xs:enumeration value="V11" /> + <xs:enumeration value="V20" /> + </xs:restriction> + </xs:simpleType> + </xs:attribute> + <xs:attribute name="minimumHashBitLength" type="xs:int"> + <xs:annotation> + <xs:documentation> + Shared associations with OpenID Providers will only be formed or used if they + are willing to form associations equal to or greater than a given level of protection. + </xs:documentation> + </xs:annotation> + </xs:attribute> + <xs:attribute name="maximumHashBitLength" type="xs:int"> + <xs:annotation> + <xs:documentation> + Shared associaitons with OpenID Providers will only be formed or used if they + are willing to form associations equal to or less than a given level of protection. + </xs:documentation> + </xs:annotation> + </xs:attribute> + <xs:attribute name="requireDirectedIdentity" type="xs:boolean"> + <xs:annotation> + <xs:documentation> + Requires that OpenID identifiers upon which authentication requests are created + are to be OP Identifiers. Claimed Identifiers are not allowed. + </xs:documentation> + </xs:annotation> + </xs:attribute> + <xs:attribute name="requireAssociation" type="xs:boolean"> + <xs:annotation> + <xs:documentation> + Requires that the relying party can form a shared association with an + OpenID Provider before creating an authentication request for it. + Note that this does not require that the Provider actually use a + shared association in its response. + </xs:documentation> + </xs:annotation> + </xs:attribute> + <xs:attribute name="rejectUnsolicitedAssertions" type="xs:boolean"> + <xs:annotation> + <xs:documentation> + Requires that users begin their login experience at the relying party + rather than at a Provider or using other forms of unsolicited assertions. + </xs:documentation> + </xs:annotation> + </xs:attribute> + <xs:attribute name="rejectDelegatingIdentifiers" type="xs:boolean"> + <xs:annotation> + <xs:documentation> + Requires that the claimed identifiers used to log into the relying party + be the same ones that are originally issued by the Provider. + </xs:documentation> + </xs:annotation> + </xs:attribute> + <xs:attribute name="ignoreUnsignedExtensions" type="xs:boolean"> + <xs:annotation> + <xs:documentation> + Makes it impossible for the relying party to read authentication response + extensions that are not signed by the Provider. + </xs:documentation> + </xs:annotation> + </xs:attribute> + <xs:attribute name="allowDualPurposeIdentifiers" type="xs:boolean"> + <xs:annotation> + <xs:documentation> + Controls whether identifiers that are both OP Identifiers and Claimed Identifiers + should ever be recognized as claimed identifiers. + </xs:documentation> + </xs:annotation> + </xs:attribute> + <xs:attribute name="allowApproximateIdentifierDiscovery" type="xs:boolean"> + <xs:annotation> + <xs:documentation> + Controls whether certain Claimed Identifiers that exploit + features that .NET does not have the ability to send exact HTTP requests for will + still be allowed by using an approximate HTTP request. + Only impacts hosts running under partial trust. + </xs:documentation> + </xs:annotation> + </xs:attribute> + <xs:attribute name="protectDownlevelReplayAttacks" type="xs:boolean"> + <xs:annotation> + <xs:documentation> + Controls whether the relying party should take special care + to protect users against replay attacks when interoperating with OpenID 1.1 Providers. + </xs:documentation> + </xs:annotation> + </xs:attribute> + </xs:complexType> + </xs:element> + <xs:element name="behaviors"> + <xs:annotation> + <xs:documentation> + Manipulates the set of custom behaviors that are automatically applied + to incoming and outgoing OpenID messages. + </xs:documentation> + </xs:annotation> + <xs:complexType> + <xs:choice minOccurs="0" maxOccurs="unbounded"> + <xs:element name="add"> + <xs:complexType> + <xs:attribute name="type" type="xs:string" use="optional"> + <xs:annotation> + <xs:documentation> + The fully-qualified name of the type that implements the IRelyingPartyBehavior interface. + </xs:documentation> + </xs:annotation> + </xs:attribute> + <xs:attribute name="xaml" type="xs:string" use="optional" /> + </xs:complexType> + </xs:element> + <xs:element name="remove"> + <xs:complexType> + <xs:attribute name="type" type="xs:string" use="required"> + <xs:annotation> + <xs:documentation> + The fully-qualified name of the type that implements the IRelyingPartyBehavior interface. + </xs:documentation> + </xs:annotation> + </xs:attribute> + </xs:complexType> + </xs:element> + <xs:element name="clear"> + <xs:complexType> + <!--tag is empty--> + </xs:complexType> + </xs:element> + </xs:choice> + </xs:complexType> + </xs:element> + <xs:element name="discoveryServices"> + <xs:complexType> + <xs:choice minOccurs="0" maxOccurs="unbounded"> + <xs:element name="add"> + <xs:complexType> + <xs:attribute name="name" type="xs:string" use="required" /> + </xs:complexType> + </xs:element> + <xs:element name="remove"> + <xs:complexType> + <xs:attribute name="name" type="xs:string" use="required" /> + </xs:complexType> + </xs:element> + <xs:element name="clear"> + <xs:complexType> + <!--tag is empty--> + </xs:complexType> + </xs:element> + </xs:choice> + </xs:complexType> + </xs:element> + <xs:element name="store"> + <xs:annotation> + <xs:documentation> + A custom implementation of IRelyingPartyApplicationStore to use by default for new + instances of OpenIdRelyingParty. + </xs:documentation> + </xs:annotation> + <xs:complexType> + <xs:attribute name="type" type="xs:string"> + <xs:annotation> + <xs:documentation> + A fully-qualified type name of the custom implementation of IRelyingPartyApplicationStore. + </xs:documentation> + </xs:annotation> + </xs:attribute> + </xs:complexType> + </xs:element> + </xs:choice> + <xs:attribute name="preserveUserSuppliedIdentifier" type="xs:boolean" default="true"> + <xs:annotation> + <xs:documentation> + Whether "dnoa.userSuppliedIdentifier" is tacked onto the openid.return_to URL in order to preserve what the user typed into the OpenID box. + </xs:documentation> + </xs:annotation> + </xs:attribute> + </xs:complexType> + </xs:element> + <xs:element name="provider"> + <xs:annotation> + <xs:documentation> + Configuration specific for OpenID providers. + </xs:documentation> + </xs:annotation> + <xs:complexType> + <xs:choice minOccurs="0" maxOccurs="unbounded"> + <xs:element name="security"> + <xs:annotation> + <xs:documentation> + Security settings that apply to OpenID providers. + </xs:documentation> + </xs:annotation> + <xs:complexType> + <xs:choice minOccurs="0" maxOccurs="unbounded"> + <xs:element name="associations"> + <xs:annotation> + <xs:documentation> + Sets maximum ages for shared associations of various strengths. + </xs:documentation> + </xs:annotation> + <xs:complexType> + <xs:choice minOccurs="0" maxOccurs="unbounded"> + <xs:element name="add"> + <xs:complexType> + <xs:attribute name="type" type="xs:string" use="required"> + <xs:annotation> + <xs:documentation> + The OpenID association type (i.e. HMAC-SHA1 or HMAC-SHA256) + </xs:documentation> + </xs:annotation> + </xs:attribute> + <xs:attribute name="lifetime" type="xs:string" use="required"> + <xs:annotation> + <xs:documentation> + The lifetime a shared association of this type will be used for. + </xs:documentation> + </xs:annotation> + </xs:attribute> + </xs:complexType> + </xs:element> + <xs:element name="remove"> + <xs:complexType> + <xs:attribute name="type" type="xs:string" use="required"> + <xs:annotation> + <xs:documentation> + The OpenID association type (i.e. HMAC-SHA1 or HMAC-SHA256) + </xs:documentation> + </xs:annotation> + </xs:attribute> + </xs:complexType> + </xs:element> + <xs:element name="clear"> + <xs:complexType> + <!--tag is empty--> + </xs:complexType> + </xs:element> + </xs:choice> + </xs:complexType> + </xs:element> + </xs:choice> + <xs:attribute name="requireSsl" type="xs:boolean" default="false"> + <xs:annotation> + <xs:documentation> + Requires that relying parties' realm URLs be protected by HTTPS, + ensuring that the RP discovery step is not vulnerable to DNS poisoning attacks. + </xs:documentation> + </xs:annotation> + </xs:attribute> + <xs:attribute name="protectDownlevelReplayAttacks" type="xs:boolean"> + <xs:annotation> + <xs:documentation> + Provides automatic security protections to OpenID 1.x relying parties + so security is comparable to OpenID 2.0 relying parties. + </xs:documentation> + </xs:annotation> + </xs:attribute> + <xs:attribute name="encodeAssociationSecretsInHandles" type="xs:boolean" default="true"> + <xs:annotation> + <xs:documentation> + Whether the Provider should ease the burden of storing associations + by encoding their secrets (in signed, encrypted form) into the association handles themselves, storing only + a few rotating, private symmetric keys in the Provider's store instead. + </xs:documentation> + </xs:annotation> + </xs:attribute> + <xs:attribute name="unsolicitedAssertionVerification"> + <xs:annotation> + <xs:documentation> + The level of verification done on a claimed identifier before an unsolicited + assertion for that identifier is issued by this Provider. + </xs:documentation> + </xs:annotation> + <xs:simpleType> + <xs:restriction base="xs:NMTOKEN"> + <xs:enumeration value="RequireSuccess"> + <xs:annotation> + <xs:documentation> + The claimed identifier being asserted must delegate to this Provider + and this must be verifiable by the Provider to send the assertion. + </xs:documentation> + </xs:annotation> + </xs:enumeration> + <xs:enumeration value="LogWarningOnFailure"> + <xs:annotation> + <xs:documentation> + The claimed identifier being asserted is checked for delegation to this Provider + and an warning is logged, but the assertion is allowed to go through. + </xs:documentation> + </xs:annotation> + </xs:enumeration> + <xs:enumeration value="NeverVerify"> + <xs:annotation> + <xs:documentation> + The claimed identifier being asserted is not checked to see that this Provider + has authority to assert its identity. + </xs:documentation> + </xs:annotation> + </xs:enumeration> + </xs:restriction> + </xs:simpleType> + </xs:attribute> + <xs:attribute name="minimumHashBitLength" type="xs:int"> + <xs:annotation> + <xs:documentation> + The minimum shared association strength to form with relying parties. + </xs:documentation> + </xs:annotation> + </xs:attribute> + <xs:attribute name="maximumHashBitLength" type="xs:int"> + <xs:annotation> + <xs:documentation> + The maximum shared association strength to form with relying parties. + </xs:documentation> + </xs:annotation> + </xs:attribute> + </xs:complexType> + </xs:element> + <xs:element name="behaviors"> + <xs:annotation> + <xs:documentation> + Manipulates the set of custom behaviors that are automatically applied + to incoming and outgoing OpenID messages. + </xs:documentation> + </xs:annotation> + <xs:complexType> + <xs:choice minOccurs="0" maxOccurs="unbounded"> + <xs:element name="add"> + <xs:complexType> + <xs:attribute name="type" type="xs:string" use="optional"> + <xs:annotation> + <xs:documentation> + The fully-qualified name of the type that implements the IRelyingPartyBehavior interface. + </xs:documentation> + </xs:annotation> + </xs:attribute> + <xs:attribute name="xaml" type="xs:string" use="optional" /> + </xs:complexType> + </xs:element> + <xs:element name="remove"> + <xs:complexType> + <xs:attribute name="type" type="xs:string" use="required" /> + </xs:complexType> + </xs:element> + <xs:element name="clear"> + <xs:complexType> + <!--tag is empty--> + </xs:complexType> + </xs:element> + </xs:choice> + </xs:complexType> + </xs:element> + <xs:element name="store"> + <xs:annotation> + <xs:documentation> + A custom implementation of IProviderApplicationStore to use by default for new + instances of OpenIdRelyingParty. + </xs:documentation> + </xs:annotation> + <xs:complexType> + <xs:attribute name="type" type="xs:string"> + <xs:annotation> + <xs:documentation> + A fully-qualified type name of the custom implementation of IProviderApplicationStore. + </xs:documentation> + </xs:annotation> + </xs:attribute> + </xs:complexType> + </xs:element> + </xs:choice> + </xs:complexType> + </xs:element> + <xs:element name="extensionFactories"> + <xs:annotation> + <xs:documentation> + Adjusts the list of known OpenID extensions via the registration of extension factories. + </xs:documentation> + </xs:annotation> + <xs:complexType> + <xs:choice minOccurs="0" maxOccurs="unbounded"> + <xs:element name="add"> + <xs:complexType> + <xs:attribute name="type" type="xs:string" use="optional"> + <xs:annotation> + <xs:documentation> + The fully-qualified name of the type that implements IOpenIdExtensionFactory. + </xs:documentation> + </xs:annotation> + </xs:attribute> + <xs:attribute name="xaml" type="xs:string" use="optional" /> + </xs:complexType> + </xs:element> + <xs:element name="remove"> + <xs:complexType> + <xs:attribute name="type" type="xs:string" use="required"> + <xs:annotation> + <xs:documentation> + The fully-qualified name of the type that implements IOpenIdExtensionFactory. + </xs:documentation> + </xs:annotation> + </xs:attribute> + </xs:complexType> + </xs:element> + <xs:element name="clear"> + <xs:complexType> + <!--tag is empty--> + </xs:complexType> + </xs:element> + </xs:choice> + </xs:complexType> + </xs:element> + <xs:element name="xriResolver"> + <xs:annotation> + <xs:documentation> + Controls XRI resolution to XRDS documents. + </xs:documentation> + </xs:annotation> + <xs:complexType> + <xs:attribute name="enabled" type="xs:boolean"> + <xs:annotation> + <xs:documentation> + Controls whether XRI identifiers are allowed at all. + </xs:documentation> + </xs:annotation> + </xs:attribute> + <xs:attribute name="proxy" type="xs:string"> + <xs:annotation> + <xs:documentation> + The XRI proxy resolver to use for obtaining XRDS documents from an XRI. + </xs:documentation> + </xs:annotation> + </xs:attribute> + </xs:complexType> + </xs:element> + </xs:choice> + <xs:attribute name="maxAuthenticationTime" type="xs:string"> + <xs:annotation> + <xs:documentation> + The maximum time a user can take at the Provider while logging in before a relying party considers + the authentication lost. + </xs:documentation> + </xs:annotation> + </xs:attribute> + <xs:attribute name="cacheDiscovery" type="xs:boolean"> + <xs:annotation> + <xs:documentation> + Whether the results of identifier discovery should be cached for a short time to improve performance + on subsequent requests, at the potential risk of reading stale data. + </xs:documentation> + </xs:annotation> + </xs:attribute> + </xs:complexType> + </xs:element> + <xs:element name="oauth"> + <xs:annotation> + <xs:documentation> + Settings for OAuth consumers and service providers. + </xs:documentation> + </xs:annotation> + <xs:complexType> + <xs:choice minOccurs="0" maxOccurs="unbounded"> + <xs:element name="consumer"> + <xs:annotation> + <xs:documentation> + Settings applicable to OAuth Consumers. + </xs:documentation> + </xs:annotation> + <xs:complexType> + <xs:choice minOccurs="0" maxOccurs="unbounded"> + <xs:element name="security"> + <xs:annotation> + <xs:documentation> + Security settings applicable to OAuth Consumers. + </xs:documentation> + </xs:annotation> + <xs:complexType> + + </xs:complexType> + </xs:element> + </xs:choice> + </xs:complexType> + </xs:element> + <xs:element name="serviceProvider"> + <xs:annotation> + <xs:documentation> + Settings applicable to OAuth Service Providers. + </xs:documentation> + </xs:annotation> + <xs:complexType> + <xs:choice minOccurs="0" maxOccurs="unbounded"> + <xs:element name="security"> + <xs:annotation> + <xs:documentation> + Security settings applicable to OAuth Service Providers. + </xs:documentation> + </xs:annotation> + <xs:complexType> + <xs:attribute name="minimumRequiredOAuthVersion" default="V10"> + <xs:annotation> + <xs:documentation> + Optionally restricts interoperability with OAuth consumers that implement + older versions of OAuth. + </xs:documentation> + </xs:annotation> + <xs:simpleType> + <xs:restriction base="xs:NMTOKEN"> + <xs:enumeration value="V10"> + <xs:annotation> + <xs:documentation> + The initial version of OAuth, now known to be vulnerable to certain social engineering attacks. + </xs:documentation> + </xs:annotation> + </xs:enumeration> + <xs:enumeration value="V10a"> + <xs:annotation> + <xs:documentation> + The OAuth version that protects against social engineering attacks by introducing + the oauth_verifier parameter. + </xs:documentation> + </xs:annotation> + </xs:enumeration> + </xs:restriction> + </xs:simpleType> + </xs:attribute> + <xs:attribute name="maxAuthorizationTime" type="xs:string" default="0:05"> + <xs:annotation> + <xs:documentation> + The maximum time allowed for users to authorize a consumer before request tokens expire. + </xs:documentation> + </xs:annotation> + </xs:attribute> + </xs:complexType> + </xs:element> + <xs:element name="store"> + <xs:annotation> + <xs:documentation> + Sets the custom type that implements the INonceStore interface to use for nonce checking. + </xs:documentation> + </xs:annotation> + <xs:complexType> + <xs:attribute name="type" type="xs:string"> + <xs:annotation> + <xs:documentation> + A fully-qualified type name of the custom implementation of INonceStore. + </xs:documentation> + </xs:annotation> + </xs:attribute> + </xs:complexType> + </xs:element> + </xs:choice> + </xs:complexType> + </xs:element> + </xs:choice> + </xs:complexType> + </xs:element> + <xs:element name="reporting"> + <xs:annotation> + <xs:documentation> + Adjusts statistical reports DotNetOpenAuth may send to the library authors to + assist with future development of the library. + </xs:documentation> + </xs:annotation> + <xs:complexType> + <xs:attribute name="enabled" type="xs:boolean"> + <xs:annotation> + <xs:documentation> + Controls whether reporting is active at all or entirely inactive. + Note that even if active, the reports may be more or less empty based + on other settings. + </xs:documentation> + </xs:annotation> + </xs:attribute> + <xs:attribute name="minimumReportingInterval" type="xs:string"> + <xs:annotation> + <xs:documentation> + Controls how frequently reports are collected and transmitted. + </xs:documentation> + </xs:annotation> + </xs:attribute> + <xs:attribute name="minimumFlushInterval" type="xs:string"> + <xs:annotation> + <xs:documentation> + Controls how frequently the statistics that are collected in memory are persisted to disk. + </xs:documentation> + </xs:annotation> + </xs:attribute> + <xs:attribute name="includeFeatureUsage" type="xs:boolean" default="true"> + <xs:annotation> + <xs:documentation> + Whether a list of features in DotNetOpenAuth that are actually used by this host + are included in the report. + </xs:documentation> + </xs:annotation> + </xs:attribute> + <xs:attribute name="includeEventStatistics" type="xs:boolean" default="true"> + <xs:annotation> + <xs:documentation> + Whether a set of counters that track how often certain events (such as an + successful or failed authentication) is included in the report. + </xs:documentation> + </xs:annotation> + </xs:attribute> + <xs:attribute name="includeLocalRequestUris" type="xs:boolean" default="true"> + <xs:annotation> + <xs:documentation> + Whether to include a few of this host's URLs that contain DotNetOpenAuth components. + </xs:documentation> + </xs:annotation> + </xs:attribute> + <xs:attribute name="includeCultures" type="xs:boolean" default="true"> + <xs:annotation> + <xs:documentation> + Whether to include the cultures as set on the user agents of incoming requests to pages + that contain DotNetOpenAuth components. + </xs:documentation> + </xs:annotation> + </xs:attribute> + </xs:complexType> + </xs:element> + </xs:choice> + </xs:complexType> + </xs:element> +</xs:schema> diff --git a/src/DotNetOpenAuth.Core/Configuration/DotNetOpenAuthSection.cs b/src/DotNetOpenAuth.Core/Configuration/DotNetOpenAuthSection.cs new file mode 100644 index 0000000..e0c7fc4 --- /dev/null +++ b/src/DotNetOpenAuth.Core/Configuration/DotNetOpenAuthSection.cs @@ -0,0 +1,70 @@ +//----------------------------------------------------------------------- +// <copyright file="DotNetOpenAuthSection.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Configuration { + using System; + using System.Configuration; + using System.Diagnostics.Contracts; + using System.Web; + using System.Web.Configuration; + + /// <summary> + /// Represents the section in the host's .config file that configures + /// this library's settings. + /// </summary> + [ContractVerification(true)] + public class DotNetOpenAuthSection : ConfigurationSectionGroup { + /// <summary> + /// The name of the section under which this library's settings must be found. + /// </summary> + internal const string SectionName = "dotNetOpenAuth"; + + /// <summary> + /// The name of the <openid> sub-element. + /// </summary> + private const string OpenIdElementName = "openid"; + + /// <summary> + /// The name of the <oauth> sub-element. + /// </summary> + private const string OAuthElementName = "oauth"; + + /// <summary> + /// Initializes a new instance of the <see cref="DotNetOpenAuthSection"/> class. + /// </summary> + internal DotNetOpenAuthSection() { + } + + /// <summary> + /// Gets the messaging configuration element. + /// </summary> + public static MessagingElement Messaging { + get { return MessagingElement.Configuration; } + } + + /// <summary> + /// Gets the reporting configuration element. + /// </summary> + internal static ReportingElement Reporting { + get { return ReportingElement.Configuration; } + } + + /// <summary> + /// Gets a named section in this section group, or <c>null</c> if no such section is defined. + /// </summary> + /// <param name="name">The name of the section to obtain.</param> + /// <returns>The desired section, or null if it could not be obtained.</returns> + internal static ConfigurationSection GetNamedSection(string name) { + string fullyQualifiedSectionName = SectionName + "/" + name; + if (HttpContext.Current != null) { + return (ConfigurationSection)WebConfigurationManager.GetSection(fullyQualifiedSectionName); + } else { + var configuration = ConfigurationManager.OpenExeConfiguration(null); + return configuration != null ? configuration.GetSection(fullyQualifiedSectionName) : null; + } + } + } +} diff --git a/src/DotNetOpenAuth/Configuration/HostNameElement.cs b/src/DotNetOpenAuth.Core/Configuration/HostNameElement.cs index 9df218e..9df218e 100644 --- a/src/DotNetOpenAuth/Configuration/HostNameElement.cs +++ b/src/DotNetOpenAuth.Core/Configuration/HostNameElement.cs diff --git a/src/DotNetOpenAuth/Configuration/HostNameOrRegexCollection.cs b/src/DotNetOpenAuth.Core/Configuration/HostNameOrRegexCollection.cs index c7d963b..c7d963b 100644 --- a/src/DotNetOpenAuth/Configuration/HostNameOrRegexCollection.cs +++ b/src/DotNetOpenAuth.Core/Configuration/HostNameOrRegexCollection.cs diff --git a/src/DotNetOpenAuth.Core/Configuration/MessagingElement.cs b/src/DotNetOpenAuth.Core/Configuration/MessagingElement.cs new file mode 100644 index 0000000..7c3e242 --- /dev/null +++ b/src/DotNetOpenAuth.Core/Configuration/MessagingElement.cs @@ -0,0 +1,209 @@ +//----------------------------------------------------------------------- +// <copyright file="MessagingElement.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Configuration { + using System; + using System.Configuration; + using System.Diagnostics.Contracts; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.Messaging.Bindings; + + /// <summary> + /// Represents the <messaging> element in the host's .config file. + /// </summary> + [ContractVerification(true)] + public class MessagingElement : ConfigurationSection { + /// <summary> + /// The name of the <webResourceUrlProvider> sub-element. + /// </summary> + private const string WebResourceUrlProviderName = "webResourceUrlProvider"; + + /// <summary> + /// The name of the <untrustedWebRequest> sub-element. + /// </summary> + private const string UntrustedWebRequestElementName = "untrustedWebRequest"; + + /// <summary> + /// The name of the attribute that stores the association's maximum lifetime. + /// </summary> + private const string MaximumMessageLifetimeConfigName = "lifetime"; + + /// <summary> + /// The name of the attribute that stores the maximum allowable clock skew. + /// </summary> + private const string MaximumClockSkewConfigName = "clockSkew"; + + /// <summary> + /// The name of the attribute that indicates whether to disable SSL requirements across the library. + /// </summary> + private const string RelaxSslRequirementsConfigName = "relaxSslRequirements"; + + /// <summary> + /// The name of the attribute that controls whether messaging rules are strictly followed. + /// </summary> + private const string StrictConfigName = "strict"; + + /// <summary> + /// The default value for the <see cref="MaximumIndirectMessageUrlLength"/> property. + /// </summary> + /// <value> + /// 2KB, recommended by OpenID group + /// </value> + private const int DefaultMaximumIndirectMessageUrlLength = 2 * 1024; + + /// <summary> + /// The name of the attribute that controls the maximum length of a URL before it is converted + /// to a POST payload. + /// </summary> + private const string MaximumIndirectMessageUrlLengthConfigName = "maximumIndirectMessageUrlLength"; + + /// <summary> + /// Gets the name of the @privateSecretMaximumAge attribute. + /// </summary> + private const string PrivateSecretMaximumAgeConfigName = "privateSecretMaximumAge"; + + /// <summary> + /// The name of the <messaging> sub-element. + /// </summary> + private const string MessagingElementName = DotNetOpenAuthSection.SectionName + "/messaging"; + + /// <summary> + /// Gets the configuration section from the .config file. + /// </summary> + public static MessagingElement Configuration { + get { + Contract.Ensures(Contract.Result<MessagingElement>() != null); + return (MessagingElement)ConfigurationManager.GetSection(MessagingElementName) ?? new MessagingElement(); + } + } + + /// <summary> + /// Gets the actual maximum message lifetime that a program should allow. + /// </summary> + /// <value>The sum of the <see cref="MaximumMessageLifetime"/> and + /// <see cref="MaximumClockSkew"/> property values.</value> + public TimeSpan MaximumMessageLifetime { + get { return this.MaximumMessageLifetimeNoSkew + this.MaximumClockSkew; } + } + + /// <summary> + /// Gets or sets the maximum lifetime of a private symmetric secret, + /// that may be used for signing or encryption. + /// </summary> + /// <value>The default value is 28 days (twice the age of the longest association).</value> + [ConfigurationProperty(PrivateSecretMaximumAgeConfigName, DefaultValue = "28.00:00:00")] + public TimeSpan PrivateSecretMaximumAge { + get { return (TimeSpan)this[PrivateSecretMaximumAgeConfigName]; } + set { this[PrivateSecretMaximumAgeConfigName] = value; } + } + + /// <summary> + /// Gets or sets the time between a message's creation and its receipt + /// before it is considered expired. + /// </summary> + /// <value> + /// The default value value is 3 minutes. + /// </value> + /// <remarks> + /// <para>Smaller timespans mean lower tolerance for delays in message delivery. + /// Larger timespans mean more nonces must be stored to provide replay protection.</para> + /// <para>The maximum age a message implementing the + /// <see cref="IExpiringProtocolMessage"/> interface can be before + /// being discarded as too old.</para> + /// <para>This time limit should NOT take into account expected + /// time skew for servers across the Internet. Time skew is added to + /// this value and is controlled by the <see cref="MaximumClockSkew"/> property.</para> + /// </remarks> + [ConfigurationProperty(MaximumMessageLifetimeConfigName, DefaultValue = "00:03:00")] + internal TimeSpan MaximumMessageLifetimeNoSkew { + get { return (TimeSpan)this[MaximumMessageLifetimeConfigName]; } + set { this[MaximumMessageLifetimeConfigName] = value; } + } + + /// <summary> + /// Gets or sets the maximum clock skew. + /// </summary> + /// <value>The default value is 10 minutes.</value> + /// <remarks> + /// <para>Smaller timespans mean lower tolerance for + /// time variance due to server clocks not being synchronized. + /// Larger timespans mean greater chance for replay attacks and + /// larger nonce caches.</para> + /// <para>For example, if a server could conceivably have its + /// clock d = 5 minutes off UTC time, then any two servers could have + /// their clocks disagree by as much as 2*d = 10 minutes. </para> + /// </remarks> + [ConfigurationProperty(MaximumClockSkewConfigName, DefaultValue = "00:10:00")] + internal TimeSpan MaximumClockSkew { + get { return (TimeSpan)this[MaximumClockSkewConfigName]; } + set { this[MaximumClockSkewConfigName] = value; } + } + + /// <summary> + /// Gets or sets a value indicating whether SSL requirements within the library are disabled/relaxed. + /// Use for TESTING ONLY. + /// </summary> + [ConfigurationProperty(RelaxSslRequirementsConfigName, DefaultValue = false)] + internal bool RelaxSslRequirements { + get { return (bool)this[RelaxSslRequirementsConfigName]; } + set { this[RelaxSslRequirementsConfigName] = value; } + } + + /// <summary> + /// Gets or sets a value indicating whether messaging rules are strictly + /// adhered to. + /// </summary> + /// <value><c>true</c> by default.</value> + /// <remarks> + /// Strict will require that remote parties adhere strictly to the specifications, + /// even when a loose interpretation would not compromise security. + /// <c>true</c> is a good default because it shakes out interoperability bugs in remote services + /// so they can be identified and corrected. But some web sites want things to Just Work + /// more than they want to file bugs against others, so <c>false</c> is the setting for them. + /// </remarks> + [ConfigurationProperty(StrictConfigName, DefaultValue = true)] + internal bool Strict { + get { return (bool)this[StrictConfigName]; } + set { this[StrictConfigName] = value; } + } + + /// <summary> + /// Gets or sets the configuration for the <see cref="UntrustedWebRequestHandler"/> class. + /// </summary> + /// <value>The untrusted web request.</value> + [ConfigurationProperty(UntrustedWebRequestElementName)] + internal UntrustedWebRequestElement UntrustedWebRequest { + get { return (UntrustedWebRequestElement)this[UntrustedWebRequestElementName] ?? new UntrustedWebRequestElement(); } + set { this[UntrustedWebRequestElementName] = value; } + } + + /// <summary> + /// Gets or sets the maximum allowable size for a 301 Redirect response before we send + /// a 200 OK response with a scripted form POST with the parameters instead + /// in order to ensure successfully sending a large payload to another server + /// that might have a maximum allowable size restriction on its GET request. + /// </summary> + /// <value>The default value is 2048.</value> + [ConfigurationProperty(MaximumIndirectMessageUrlLengthConfigName, DefaultValue = DefaultMaximumIndirectMessageUrlLength)] + [IntegerValidator(MinValue = 500, MaxValue = 4096)] + internal int MaximumIndirectMessageUrlLength { + get { return (int)this[MaximumIndirectMessageUrlLengthConfigName]; } + set { this[MaximumIndirectMessageUrlLengthConfigName] = value; } + } + + /// <summary> + /// Gets or sets the embedded resource retrieval provider. + /// </summary> + /// <value> + /// The embedded resource retrieval provider. + /// </value> + [ConfigurationProperty(WebResourceUrlProviderName)] + internal TypeConfigurationElement<IEmbeddedResourceRetrieval> EmbeddedResourceRetrievalProvider { + get { return (TypeConfigurationElement<IEmbeddedResourceRetrieval>)this[WebResourceUrlProviderName] ?? new TypeConfigurationElement<IEmbeddedResourceRetrieval>(); } + set { this[WebResourceUrlProviderName] = value; } + } + } +} diff --git a/src/DotNetOpenAuth.Core/Configuration/ReportingElement.cs b/src/DotNetOpenAuth.Core/Configuration/ReportingElement.cs new file mode 100644 index 0000000..a8eb7d3 --- /dev/null +++ b/src/DotNetOpenAuth.Core/Configuration/ReportingElement.cs @@ -0,0 +1,155 @@ +//----------------------------------------------------------------------- +// <copyright file="ReportingElement.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Configuration { + using System; + using System.Collections.Generic; + using System.Configuration; + using System.Diagnostics.Contracts; + using System.Linq; + using System.Text; + + /// <summary> + /// Represents the <reporting> element in the host's .config file. + /// </summary> + internal class ReportingElement : ConfigurationSection { + /// <summary> + /// The name of the @enabled attribute. + /// </summary> + private const string EnabledAttributeName = "enabled"; + + /// <summary> + /// The name of the @minimumReportingInterval attribute. + /// </summary> + private const string MinimumReportingIntervalAttributeName = "minimumReportingInterval"; + + /// <summary> + /// The name of the @minimumFlushInterval attribute. + /// </summary> + private const string MinimumFlushIntervalAttributeName = "minimumFlushInterval"; + + /// <summary> + /// The name of the @includeFeatureUsage attribute. + /// </summary> + private const string IncludeFeatureUsageAttributeName = "includeFeatureUsage"; + + /// <summary> + /// The name of the @includeEventStatistics attribute. + /// </summary> + private const string IncludeEventStatisticsAttributeName = "includeEventStatistics"; + + /// <summary> + /// The name of the @includeLocalRequestUris attribute. + /// </summary> + private const string IncludeLocalRequestUrisAttributeName = "includeLocalRequestUris"; + + /// <summary> + /// The name of the @includeCultures attribute. + /// </summary> + private const string IncludeCulturesAttributeName = "includeCultures"; + + /// <summary> + /// The name of the <reporting> sub-element. + /// </summary> + private const string ReportingElementName = DotNetOpenAuthSection.SectionName + "/reporting"; + + /// <summary> + /// The default value for the @minimumFlushInterval attribute. + /// </summary> +#if DEBUG + private const string MinimumFlushIntervalDefault = "0"; +#else + private const string MinimumFlushIntervalDefault = "0:15"; +#endif + + /// <summary> + /// Initializes a new instance of the <see cref="ReportingElement"/> class. + /// </summary> + internal ReportingElement() { + } + + /// <summary> + /// Gets the configuration section from the .config file. + /// </summary> + public static ReportingElement Configuration { + get { + Contract.Ensures(Contract.Result<ReportingElement>() != null); + return (ReportingElement)ConfigurationManager.GetSection(ReportingElementName) ?? new ReportingElement(); + } + } + + /// <summary> + /// Gets or sets a value indicating whether this reporting is enabled. + /// </summary> + /// <value><c>true</c> if enabled; otherwise, <c>false</c>.</value> + [ConfigurationProperty(EnabledAttributeName, DefaultValue = true)] + internal bool Enabled { + get { return (bool)this[EnabledAttributeName]; } + set { this[EnabledAttributeName] = value; } + } + + /// <summary> + /// Gets or sets the maximum frequency that reports will be published. + /// </summary> + [ConfigurationProperty(MinimumReportingIntervalAttributeName, DefaultValue = "1")] // 1 day default + internal TimeSpan MinimumReportingInterval { + get { return (TimeSpan)this[MinimumReportingIntervalAttributeName]; } + set { this[MinimumReportingIntervalAttributeName] = value; } + } + + /// <summary> + /// Gets or sets the maximum frequency the set can be flushed to disk. + /// </summary> + [ConfigurationProperty(MinimumFlushIntervalAttributeName, DefaultValue = MinimumFlushIntervalDefault)] + internal TimeSpan MinimumFlushInterval { + get { return (TimeSpan)this[MinimumFlushIntervalAttributeName]; } + set { this[MinimumFlushIntervalAttributeName] = value; } + } + + /// <summary> + /// Gets or sets a value indicating whether to include a list of library features used in the report. + /// </summary> + /// <value><c>true</c> to include a report of features used; otherwise, <c>false</c>.</value> + [ConfigurationProperty(IncludeFeatureUsageAttributeName, DefaultValue = true)] + internal bool IncludeFeatureUsage { + get { return (bool)this[IncludeFeatureUsageAttributeName]; } + set { this[IncludeFeatureUsageAttributeName] = value; } + } + + /// <summary> + /// Gets or sets a value indicating whether to include statistics of certain events such as + /// authentication success and failure counting, and can include remote endpoint URIs. + /// </summary> + /// <value> + /// <c>true</c> to include event counters in the report; otherwise, <c>false</c>. + /// </value> + [ConfigurationProperty(IncludeEventStatisticsAttributeName, DefaultValue = true)] + internal bool IncludeEventStatistics { + get { return (bool)this[IncludeEventStatisticsAttributeName]; } + set { this[IncludeEventStatisticsAttributeName] = value; } + } + + /// <summary> + /// Gets or sets a value indicating whether to include a few URLs to pages on the hosting + /// web site that host DotNetOpenAuth components. + /// </summary> + [ConfigurationProperty(IncludeLocalRequestUrisAttributeName, DefaultValue = true)] + internal bool IncludeLocalRequestUris { + get { return (bool)this[IncludeLocalRequestUrisAttributeName]; } + set { this[IncludeLocalRequestUrisAttributeName] = value; } + } + + /// <summary> + /// Gets or sets a value indicating whether to include the cultures requested by the user agent + /// on pages that host DotNetOpenAuth components. + /// </summary> + [ConfigurationProperty(IncludeCulturesAttributeName, DefaultValue = true)] + internal bool IncludeCultures { + get { return (bool)this[IncludeCulturesAttributeName]; } + set { this[IncludeCulturesAttributeName] = value; } + } + } +} diff --git a/src/DotNetOpenAuth.Core/Configuration/TrustedProviderConfigurationCollection.cs b/src/DotNetOpenAuth.Core/Configuration/TrustedProviderConfigurationCollection.cs new file mode 100644 index 0000000..1a287fd --- /dev/null +++ b/src/DotNetOpenAuth.Core/Configuration/TrustedProviderConfigurationCollection.cs @@ -0,0 +1,74 @@ +//----------------------------------------------------------------------- +// <copyright file="TrustedProviderConfigurationCollection.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Configuration { + using System; + using System.Collections.Generic; + using System.Configuration; + using System.Diagnostics.CodeAnalysis; + using System.Diagnostics.Contracts; + + /// <summary> + /// A configuration collection of trusted OP Endpoints. + /// </summary> + internal class TrustedProviderConfigurationCollection : ConfigurationElementCollection { + /// <summary> + /// The name of the "rejectAssertionsFromUntrustedProviders" element. + /// </summary> + private const string RejectAssertionsFromUntrustedProvidersConfigName = "rejectAssertionsFromUntrustedProviders"; + + /// <summary> + /// Initializes a new instance of the <see cref="TrustedProviderConfigurationCollection"/> class. + /// </summary> + internal TrustedProviderConfigurationCollection() { + } + + /// <summary> + /// Initializes a new instance of the <see cref="TrustedProviderConfigurationCollection"/> class. + /// </summary> + /// <param name="elements">The elements to initialize the collection with.</param> + [SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors", Justification = "Seems unavoidable")] + internal TrustedProviderConfigurationCollection(IEnumerable<TrustedProviderEndpointConfigurationElement> elements) { + Requires.NotNull(elements, "elements"); + + foreach (TrustedProviderEndpointConfigurationElement element in elements) { + this.BaseAdd(element); + } + } + + /// <summary> + /// Gets or sets a value indicating whether any login attempt coming from an OpenID Provider Endpoint that is not on this + /// whitelist of trusted OP Endpoints will be rejected. If the trusted providers list is empty and this value + /// is true, all assertions are rejected. + /// </summary> + [ConfigurationProperty(RejectAssertionsFromUntrustedProvidersConfigName, DefaultValue = false)] + internal bool RejectAssertionsFromUntrustedProviders { + get { return (bool)this[RejectAssertionsFromUntrustedProvidersConfigName]; } + set { this[RejectAssertionsFromUntrustedProvidersConfigName] = value; } + } + + /// <summary> + /// When overridden in a derived class, creates a new <see cref="T:System.Configuration.ConfigurationElement"/>. + /// </summary> + /// <returns> + /// A new <see cref="T:System.Configuration.ConfigurationElement"/>. + /// </returns> + protected override ConfigurationElement CreateNewElement() { + return new TrustedProviderEndpointConfigurationElement(); + } + + /// <summary> + /// Gets the element key for a specified configuration element when overridden in a derived class. + /// </summary> + /// <param name="element">The <see cref="T:System.Configuration.ConfigurationElement"/> to return the key for.</param> + /// <returns> + /// An <see cref="T:System.Object"/> that acts as the key for the specified <see cref="T:System.Configuration.ConfigurationElement"/>. + /// </returns> + protected override object GetElementKey(ConfigurationElement element) { + return ((TrustedProviderEndpointConfigurationElement)element).ProviderEndpoint; + } + } +} diff --git a/src/DotNetOpenAuth/Configuration/TrustedProviderEndpointConfigurationElement.cs b/src/DotNetOpenAuth.Core/Configuration/TrustedProviderEndpointConfigurationElement.cs index 2576eb0..2576eb0 100644 --- a/src/DotNetOpenAuth/Configuration/TrustedProviderEndpointConfigurationElement.cs +++ b/src/DotNetOpenAuth.Core/Configuration/TrustedProviderEndpointConfigurationElement.cs diff --git a/src/DotNetOpenAuth.Core/Configuration/TypeConfigurationCollection.cs b/src/DotNetOpenAuth.Core/Configuration/TypeConfigurationCollection.cs new file mode 100644 index 0000000..95b9c50 --- /dev/null +++ b/src/DotNetOpenAuth.Core/Configuration/TypeConfigurationCollection.cs @@ -0,0 +1,76 @@ +//----------------------------------------------------------------------- +// <copyright file="TypeConfigurationCollection.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Configuration { + using System; + using System.Collections.Generic; + using System.Configuration; + using System.Diagnostics.Contracts; + using System.Linq; + using System.Text; + using DotNetOpenAuth.Messaging; + + /// <summary> + /// A collection of <see cref="TypeConfigurationElement<T>"/>. + /// </summary> + /// <typeparam name="T">The type that all types specified in the elements must derive from.</typeparam> + [ContractVerification(true)] + internal class TypeConfigurationCollection<T> : ConfigurationElementCollection + where T : class { + /// <summary> + /// Initializes a new instance of the TypeConfigurationCollection class. + /// </summary> + internal TypeConfigurationCollection() { + } + + /// <summary> + /// Initializes a new instance of the TypeConfigurationCollection class. + /// </summary> + /// <param name="elements">The elements that should be added to the collection initially.</param> + internal TypeConfigurationCollection(IEnumerable<Type> elements) { + Requires.NotNull(elements, "elements"); + + foreach (Type element in elements) { + this.BaseAdd(new TypeConfigurationElement<T> { TypeName = element.AssemblyQualifiedName }); + } + } + + /// <summary> + /// Creates instances of all the types listed in the collection. + /// </summary> + /// <param name="allowInternals">if set to <c>true</c> then internal types may be instantiated.</param> + /// <returns>A sequence of instances generated from types in this collection. May be empty, but never null.</returns> + internal IEnumerable<T> CreateInstances(bool allowInternals) { + Contract.Ensures(Contract.Result<IEnumerable<T>>() != null); + return from element in this.Cast<TypeConfigurationElement<T>>() + where !element.IsEmpty + select element.CreateInstance(default(T), allowInternals); + } + + /// <summary> + /// When overridden in a derived class, creates a new <see cref="T:System.Configuration.ConfigurationElement"/>. + /// </summary> + /// <returns> + /// A new <see cref="T:System.Configuration.ConfigurationElement"/>. + /// </returns> + protected override ConfigurationElement CreateNewElement() { + return new TypeConfigurationElement<T>(); + } + + /// <summary> + /// Gets the element key for a specified configuration element when overridden in a derived class. + /// </summary> + /// <param name="element">The <see cref="T:System.Configuration.ConfigurationElement"/> to return the key for.</param> + /// <returns> + /// An <see cref="T:System.Object"/> that acts as the key for the specified <see cref="T:System.Configuration.ConfigurationElement"/>. + /// </returns> + protected override object GetElementKey(ConfigurationElement element) { + Contract.Assume(element != null); // this should be Contract.Requires in base class. + TypeConfigurationElement<T> typedElement = (TypeConfigurationElement<T>)element; + return (!string.IsNullOrEmpty(typedElement.TypeName) ? typedElement.TypeName : typedElement.XamlSource) ?? string.Empty; + } + } +} diff --git a/src/DotNetOpenAuth.Core/Configuration/TypeConfigurationElement.cs b/src/DotNetOpenAuth.Core/Configuration/TypeConfigurationElement.cs new file mode 100644 index 0000000..fb1dee0 --- /dev/null +++ b/src/DotNetOpenAuth.Core/Configuration/TypeConfigurationElement.cs @@ -0,0 +1,141 @@ +//----------------------------------------------------------------------- +// <copyright file="TypeConfigurationElement.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Configuration { + using System; + using System.Configuration; + using System.Diagnostics.Contracts; + using System.IO; + using System.Reflection; + using System.Web; +#if CLR4 + using System.Xaml; +#else + using System.Windows.Markup; +#endif + using DotNetOpenAuth.Messaging; + + /// <summary> + /// Represents an element in a .config file that allows the user to provide a @type attribute specifying + /// the full type that provides some service used by this library. + /// </summary> + /// <typeparam name="T">A constraint on the type the user may provide.</typeparam> + internal class TypeConfigurationElement<T> : ConfigurationElement + where T : class { + /// <summary> + /// The name of the attribute whose value is the full name of the type the user is specifying. + /// </summary> + private const string CustomTypeConfigName = "type"; + + /// <summary> + /// The name of the attribute whose value is the path to the XAML file to deserialize to obtain the type. + /// </summary> + private const string XamlReaderSourceConfigName = "xaml"; + + /// <summary> + /// Initializes a new instance of the TypeConfigurationElement class. + /// </summary> + public TypeConfigurationElement() { + } + + /// <summary> + /// Gets or sets the full name of the type. + /// </summary> + /// <value>The full name of the type, such as: "ConsumerPortal.Code.CustomStore, ConsumerPortal".</value> + [ConfigurationProperty(CustomTypeConfigName)] + ////[SubclassTypeValidator(typeof(T))] // this attribute is broken in .NET, I think. + public string TypeName { + get { return (string)this[CustomTypeConfigName]; } + set { this[CustomTypeConfigName] = value; } + } + + /// <summary> + /// Gets or sets the path to the XAML file to deserialize to obtain the instance. + /// </summary> + [ConfigurationProperty(XamlReaderSourceConfigName)] + public string XamlSource { + get { return (string)this[XamlReaderSourceConfigName]; } + set { this[XamlReaderSourceConfigName] = value; } + } + + /// <summary> + /// Gets the type described in the .config file. + /// </summary> + public Type CustomType { + get { return string.IsNullOrEmpty(this.TypeName) ? null : Type.GetType(this.TypeName); } + } + + /// <summary> + /// Gets a value indicating whether this type has no meaningful type to instantiate. + /// </summary> + public bool IsEmpty { + get { return this.CustomType == null && string.IsNullOrEmpty(this.XamlSource); } + } + + /// <summary> + /// Creates an instance of the type described in the .config file. + /// </summary> + /// <param name="defaultValue">The value to return if no type is given in the .config file.</param> + /// <returns>The newly instantiated type.</returns> + public T CreateInstance(T defaultValue) { + Contract.Ensures(Contract.Result<T>() != null || Contract.Result<T>() == defaultValue); + + return this.CreateInstance(defaultValue, false); + } + + /// <summary> + /// Creates an instance of the type described in the .config file. + /// </summary> + /// <param name="defaultValue">The value to return if no type is given in the .config file.</param> + /// <param name="allowInternals">if set to <c>true</c> then internal types may be instantiated.</param> + /// <returns>The newly instantiated type.</returns> + public T CreateInstance(T defaultValue, bool allowInternals) { + Contract.Ensures(Contract.Result<T>() != null || Contract.Result<T>() == defaultValue); + + if (this.CustomType != null) { + if (!allowInternals) { + // Although .NET will usually prevent our instantiating non-public types, + // it will allow our instantiation of internal types within this same assembly. + // But we don't want the host site to be able to do this, so we check ourselves. + ErrorUtilities.VerifyArgument((this.CustomType.Attributes & TypeAttributes.Public) != 0, Strings.ConfigurationTypeMustBePublic, this.CustomType.FullName); + } + return (T)Activator.CreateInstance(this.CustomType); + } else if (!string.IsNullOrEmpty(this.XamlSource)) { + string source = this.XamlSource; + if (source.StartsWith("~/", StringComparison.Ordinal)) { + ErrorUtilities.VerifyHost(HttpContext.Current != null, Strings.ConfigurationXamlReferenceRequiresHttpContext, this.XamlSource); + source = HttpContext.Current.Server.MapPath(source); + } + using (Stream xamlFile = File.OpenRead(source)) { + return CreateInstanceFromXaml(xamlFile); + } + } else { + return defaultValue; + } + } + + /// <summary> + /// Creates the instance from xaml. + /// </summary> + /// <param name="xaml">The stream of xaml to deserialize.</param> + /// <returns>The deserialized object.</returns> + /// <remarks> + /// This exists as its own method to prevent the CLR's JIT compiler from failing + /// to compile the CreateInstance method just because the PresentationFramework.dll + /// may be missing (which it is on some shared web hosts). This way, if the + /// XamlSource attribute is never used, the PresentationFramework.dll never need + /// be present. + /// </remarks> + private static T CreateInstanceFromXaml(Stream xaml) { + Contract.Ensures(Contract.Result<T>() != null); +#if CLR4 + return (T)XamlServices.Load(xaml); +#else + return (T)XamlReader.Load(xaml); +#endif + } + } +} diff --git a/src/DotNetOpenAuth.Core/Configuration/UntrustedWebRequestElement.cs b/src/DotNetOpenAuth.Core/Configuration/UntrustedWebRequestElement.cs new file mode 100644 index 0000000..43e41d9 --- /dev/null +++ b/src/DotNetOpenAuth.Core/Configuration/UntrustedWebRequestElement.cs @@ -0,0 +1,141 @@ +//----------------------------------------------------------------------- +// <copyright file="UntrustedWebRequestElement.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Configuration { + using System; + using System.Configuration; + using System.Diagnostics.Contracts; + + /// <summary> + /// Represents the section of a .config file where security policies regarding web requests + /// to user-provided, untrusted servers is controlled. + /// </summary> + internal class UntrustedWebRequestElement : ConfigurationElement { + #region Attribute names + + /// <summary> + /// Gets the name of the @timeout attribute. + /// </summary> + private const string TimeoutConfigName = "timeout"; + + /// <summary> + /// Gets the name of the @readWriteTimeout attribute. + /// </summary> + private const string ReadWriteTimeoutConfigName = "readWriteTimeout"; + + /// <summary> + /// Gets the name of the @maximumBytesToRead attribute. + /// </summary> + private const string MaximumBytesToReadConfigName = "maximumBytesToRead"; + + /// <summary> + /// Gets the name of the @maximumRedirections attribute. + /// </summary> + private const string MaximumRedirectionsConfigName = "maximumRedirections"; + + /// <summary> + /// Gets the name of the @whitelistHosts attribute. + /// </summary> + private const string WhitelistHostsConfigName = "whitelistHosts"; + + /// <summary> + /// Gets the name of the @whitelistHostsRegex attribute. + /// </summary> + private const string WhitelistHostsRegexConfigName = "whitelistHostsRegex"; + + /// <summary> + /// Gets the name of the @blacklistHosts attribute. + /// </summary> + private const string BlacklistHostsConfigName = "blacklistHosts"; + + /// <summary> + /// Gets the name of the @blacklistHostsRegex attribute. + /// </summary> + private const string BlacklistHostsRegexConfigName = "blacklistHostsRegex"; + + #endregion + + /// <summary> + /// Gets or sets the read/write timeout after which an HTTP request will fail. + /// </summary> + [ConfigurationProperty(ReadWriteTimeoutConfigName, DefaultValue = "00:00:01.500")] + [PositiveTimeSpanValidator] + public TimeSpan ReadWriteTimeout { + get { return (TimeSpan)this[ReadWriteTimeoutConfigName]; } + set { this[ReadWriteTimeoutConfigName] = value; } + } + + /// <summary> + /// Gets or sets the timeout after which an HTTP request will fail. + /// </summary> + [ConfigurationProperty(TimeoutConfigName, DefaultValue = "00:00:10")] + [PositiveTimeSpanValidator] + public TimeSpan Timeout { + get { return (TimeSpan)this[TimeoutConfigName]; } + set { this[TimeoutConfigName] = value; } + } + + /// <summary> + /// Gets or sets the maximum bytes to read from an untrusted web server. + /// </summary> + [ConfigurationProperty(MaximumBytesToReadConfigName, DefaultValue = 1024 * 1024)] + [IntegerValidator(MinValue = 2048)] + public int MaximumBytesToRead { + get { return (int)this[MaximumBytesToReadConfigName]; } + set { this[MaximumBytesToReadConfigName] = value; } + } + + /// <summary> + /// Gets or sets the maximum redirections that will be followed before an HTTP request fails. + /// </summary> + [ConfigurationProperty(MaximumRedirectionsConfigName, DefaultValue = 10)] + [IntegerValidator(MinValue = 0)] + public int MaximumRedirections { + get { return (int)this[MaximumRedirectionsConfigName]; } + set { this[MaximumRedirectionsConfigName] = value; } + } + + /// <summary> + /// Gets or sets the collection of hosts on the whitelist. + /// </summary> + [ConfigurationProperty(WhitelistHostsConfigName, IsDefaultCollection = false)] + [ConfigurationCollection(typeof(HostNameOrRegexCollection))] + public HostNameOrRegexCollection WhitelistHosts { + get { return (HostNameOrRegexCollection)this[WhitelistHostsConfigName] ?? new HostNameOrRegexCollection(); } + set { this[WhitelistHostsConfigName] = value; } + } + + /// <summary> + /// Gets or sets the collection of hosts on the blacklist. + /// </summary> + [ConfigurationProperty(BlacklistHostsConfigName, IsDefaultCollection = false)] + [ConfigurationCollection(typeof(HostNameOrRegexCollection))] + public HostNameOrRegexCollection BlacklistHosts { + get { return (HostNameOrRegexCollection)this[BlacklistHostsConfigName] ?? new HostNameOrRegexCollection(); } + set { this[BlacklistHostsConfigName] = value; } + } + + /// <summary> + /// Gets or sets the collection of regular expressions that describe hosts on the whitelist. + /// </summary> + [ConfigurationProperty(WhitelistHostsRegexConfigName, IsDefaultCollection = false)] + [ConfigurationCollection(typeof(HostNameOrRegexCollection))] + public HostNameOrRegexCollection WhitelistHostsRegex { + get { return (HostNameOrRegexCollection)this[WhitelistHostsRegexConfigName] ?? new HostNameOrRegexCollection(); } + set { this[WhitelistHostsRegexConfigName] = value; } + } + + /// <summary> + /// Gets or sets the collection of regular expressions that describe hosts on the blacklist. + /// </summary> + [ConfigurationProperty(BlacklistHostsRegexConfigName, IsDefaultCollection = false)] + [ConfigurationCollection(typeof(HostNameOrRegexCollection))] + public HostNameOrRegexCollection BlacklistHostsRegex { + get { return (HostNameOrRegexCollection)this[BlacklistHostsRegexConfigName] ?? new HostNameOrRegexCollection(); } + set { this[BlacklistHostsRegexConfigName] = value; } + } + } +} diff --git a/src/DotNetOpenAuth.Core/DotNetOpenAuth.Core.csproj b/src/DotNetOpenAuth.Core/DotNetOpenAuth.Core.csproj new file mode 100644 index 0000000..3692b4e --- /dev/null +++ b/src/DotNetOpenAuth.Core/DotNetOpenAuth.Core.csproj @@ -0,0 +1,168 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), EnlistmentInfo.props))\EnlistmentInfo.props" Condition=" '$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), EnlistmentInfo.props))' != '' " /> + <PropertyGroup> + <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> + <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> + </PropertyGroup> + <Import Project="$(ProjectRoot)tools\DotNetOpenAuth.props" /> + <PropertyGroup> + <SchemaVersion>2.0</SchemaVersion> + <ProjectGuid>{60426312-6AE5-4835-8667-37EDEA670222}</ProjectGuid> + <AppDesignerFolder>Properties</AppDesignerFolder> + <AssemblyName>DotNetOpenAuth.Core</AssemblyName> + </PropertyGroup> + <Import Project="$(ProjectRoot)tools\DotNetOpenAuth.Product.props" /> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> + </PropertyGroup> + <ItemGroup> + <Compile Include="Assumes.cs" /> + <Compile Include="Messaging\Bindings\AsymmetricCryptoKeyStoreWrapper.cs" /> + <Compile Include="Messaging\Bindings\CryptoKey.cs" /> + <Compile Include="Messaging\Bindings\CryptoKeyCollisionException.cs" /> + <Compile Include="Messaging\Bindings\ICryptoKeyStore.cs" /> + <Compile Include="Messaging\Bindings\MemoryCryptoKeyStore.cs" /> + <Compile Include="Messaging\BinaryDataBagFormatter.cs" /> + <Compile Include="Messaging\CachedDirectWebResponse.cs" /> + <Compile Include="Messaging\ChannelContract.cs" /> + <Compile Include="Messaging\DataBagFormatterBase.cs" /> + <Compile Include="Messaging\IHttpIndirectResponse.cs" /> + <Compile Include="Messaging\IMessageOriginalPayload.cs" /> + <Compile Include="Messaging\DirectWebRequestOptions.cs" /> + <Compile Include="Messaging\EnumerableCache.cs" /> + <Compile Include="Messaging\HostErrorException.cs" /> + <Compile Include="Messaging\IHttpDirectResponse.cs" /> + <Compile Include="Messaging\IExtensionMessage.cs" /> + <Compile Include="Messaging\IHttpDirectResponseContract.cs" /> + <Compile Include="Messaging\IMessage.cs" /> + <Compile Include="Messaging\IncomingWebResponse.cs" /> + <Compile Include="Messaging\IDirectResponseProtocolMessage.cs" /> + <Compile Include="Messaging\EmptyDictionary.cs" /> + <Compile Include="Messaging\EmptyEnumerator.cs" /> + <Compile Include="Messaging\EmptyList.cs" /> + <Compile Include="Messaging\ErrorUtilities.cs" /> + <Compile Include="Messaging\IMessageWithEvents.cs" /> + <Compile Include="Messaging\IncomingWebResponseContract.cs" /> + <Compile Include="Messaging\IProtocolMessageWithExtensions.cs" /> + <Compile Include="Messaging\InternalErrorException.cs" /> + <Compile Include="Messaging\IStreamSerializingDataBag.cs" /> + <Compile Include="Messaging\KeyedCollectionDelegate.cs" /> + <Compile Include="Messaging\MultipartPostPart.cs" /> + <Compile Include="Messaging\NetworkDirectWebResponse.cs" /> + <Compile Include="Messaging\OutgoingWebResponseActionResult.cs" /> + <Compile Include="Messaging\Reflection\IMessagePartEncoder.cs" /> + <Compile Include="Messaging\Reflection\IMessagePartNullEncoder.cs" /> + <Compile Include="Messaging\Reflection\IMessagePartOriginalEncoder.cs" /> + <Compile Include="Messaging\Reflection\MessageDescriptionCollection.cs" /> + <Compile Include="Messaging\StandardMessageFactory.cs" /> + <Compile Include="Messaging\IDataBagFormatter.cs" /> + <Compile Include="Messaging\UriStyleMessageFormatter.cs" /> + <Compile Include="Messaging\StandardMessageFactoryChannel.cs" /> + <Compile Include="Messaging\DataBag.cs" /> + <Compile Include="Messaging\TimestampEncoder.cs" /> + <Compile Include="Messaging\IMessageWithBinaryData.cs" /> + <Compile Include="Messaging\ChannelEventArgs.cs" /> + <Compile Include="Messaging\Bindings\NonceMemoryStore.cs" /> + <Compile Include="Messaging\IDirectWebRequestHandler.cs" /> + <Compile Include="Messaging\Bindings\INonceStore.cs" /> + <Compile Include="Messaging\Bindings\StandardReplayProtectionBindingElement.cs" /> + <Compile Include="Messaging\MessagePartAttribute.cs" /> + <Compile Include="Messaging\MessageProtections.cs" /> + <Compile Include="Messaging\IChannelBindingElement.cs" /> + <Compile Include="Messaging\Bindings\ReplayedMessageException.cs" /> + <Compile Include="Messaging\Bindings\ExpiredMessageException.cs" /> + <Compile Include="Messaging\Bindings\InvalidSignatureException.cs" /> + <Compile Include="Messaging\Bindings\IReplayProtectedProtocolMessage.cs" /> + <Compile Include="Messaging\Bindings\IExpiringProtocolMessage.cs" /> + <Compile Include="Messaging\Channel.cs" /> + <Compile Include="Messaging\HttpRequestInfo.cs" /> + <Compile Include="Messaging\IDirectedProtocolMessage.cs" /> + <Compile Include="Messaging\IMessageFactory.cs" /> + <Compile Include="Messaging\ITamperResistantProtocolMessage.cs" /> + <Compile Include="Messaging\MessageSerializer.cs" /> + <Compile Include="Messaging\MessagingStrings.Designer.cs"> + <AutoGen>True</AutoGen> + <DesignTime>True</DesignTime> + <DependentUpon>MessagingStrings.resx</DependentUpon> + </Compile> + <Compile Include="Messaging\MessagingUtilities.cs" /> + <Compile Include="Messaging\Bindings\StandardExpirationBindingElement.cs" /> + <Compile Include="Messaging\Reflection\ValueMapping.cs" /> + <Compile Include="Messaging\Reflection\MessageDescription.cs" /> + <Compile Include="Messaging\Reflection\MessageDictionary.cs" /> + <Compile Include="Messaging\Reflection\MessagePart.cs" /> + <Compile Include="Messaging\UnprotectedMessageException.cs" /> + <Compile Include="Messaging\OutgoingWebResponse.cs" /> + <Compile Include="Messaging\IProtocolMessage.cs" /> + <Compile Include="Messaging\HttpDeliveryMethods.cs" /> + <Compile Include="Messaging\MessageTransport.cs" /> + <Compile Include="Messaging\ProtocolException.cs" /> + <Compile Include="Messaging\TimespanSecondsEncoder.cs" /> + <Compile Include="Messaging\UntrustedWebRequestHandler.cs" /> + <Compile Include="Messaging\StandardWebRequestHandler.cs" /> + <Compile Include="Messaging\MessageReceivingEndpoint.cs" /> + </ItemGroup> + <ItemGroup> + <None Include="Messaging\Bindings\Bindings.cd" /> + <None Include="Messaging\Exceptions.cd" /> + <None Include="Messaging\Messaging.cd" /> + </ItemGroup> + <ItemGroup> + <Compile Include="Configuration\DotNetOpenAuthSection.cs" /> + <Compile Include="Configuration\MessagingElement.cs" /> + <Compile Include="Configuration\ReportingElement.cs" /> + <Compile Include="Configuration\TrustedProviderConfigurationCollection.cs" /> + <Compile Include="Configuration\TrustedProviderEndpointConfigurationElement.cs" /> + <Compile Include="Configuration\TypeConfigurationCollection.cs" /> + <Compile Include="Configuration\TypeConfigurationElement.cs" /> + <Compile Include="Configuration\UntrustedWebRequestElement.cs" /> + <Compile Include="Configuration\HostNameOrRegexCollection.cs" /> + <Compile Include="Configuration\HostNameElement.cs" /> + <Compile Include="IEmbeddedResourceRetrieval.cs" /> + <Compile Include="GlobalSuppressions.cs" /> + <Compile Include="Logger.cs" /> + <Compile Include="Loggers\ILog.cs" /> + <Compile Include="Loggers\Log4NetLogger.cs" /> + <Compile Include="Loggers\NoOpLogger.cs" /> + <Compile Include="Loggers\TraceLogger.cs" /> + <Compile Include="Properties\AssemblyInfo.cs" /> + <Compile Include="Reporting.cs" /> + <Compile Include="Requires.cs" /> + <Compile Include="Strings.Designer.cs"> + <AutoGen>True</AutoGen> + <DesignTime>True</DesignTime> + <DependentUpon>Strings.resx</DependentUpon> + </Compile> + <Compile Include="UriUtil.cs" /> + <Compile Include="Util.cs" /> + </ItemGroup> + <ItemGroup> + <None Include="Configuration\DotNetOpenAuth.xsd" /> + <None Include="Migrated rules for DotNetOpenAuth.ruleset" /> + </ItemGroup> + <ItemGroup> + <EmbeddedResource Include="Strings.resx"> + <Generator>ResXFileCodeGenerator</Generator> + <LastGenOutput>Strings.Designer.cs</LastGenOutput> + <SubType>Designer</SubType> + </EmbeddedResource> + </ItemGroup> + <ItemGroup> + <EmbeddedResource Include="Strings.sr.resx" /> + </ItemGroup> + <ItemGroup> + <EmbeddedResource Include="Messaging\MessagingStrings.resx"> + <Generator>ResXFileCodeGenerator</Generator> + <LastGenOutput>MessagingStrings.Designer.cs</LastGenOutput> + <SubType>Designer</SubType> + </EmbeddedResource> + </ItemGroup> + <ItemGroup> + <EmbeddedResource Include="Messaging\MessagingStrings.sr.resx" /> + </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))' != '' " /> +</Project>
\ No newline at end of file diff --git a/src/DotNetOpenAuth/DotNetOpenAuth.ico b/src/DotNetOpenAuth.Core/DotNetOpenAuth.ico Binary files differindex e227dbe..e227dbe 100644 --- a/src/DotNetOpenAuth/DotNetOpenAuth.ico +++ b/src/DotNetOpenAuth.Core/DotNetOpenAuth.ico diff --git a/src/DotNetOpenAuth/GlobalSuppressions.cs b/src/DotNetOpenAuth.Core/GlobalSuppressions.cs index 2bc6c04..2bc6c04 100644 --- a/src/DotNetOpenAuth/GlobalSuppressions.cs +++ b/src/DotNetOpenAuth.Core/GlobalSuppressions.cs diff --git a/src/DotNetOpenAuth/IEmbeddedResourceRetrieval.cs b/src/DotNetOpenAuth.Core/IEmbeddedResourceRetrieval.cs index b9a6fd0..b9a6fd0 100644 --- a/src/DotNetOpenAuth/IEmbeddedResourceRetrieval.cs +++ b/src/DotNetOpenAuth.Core/IEmbeddedResourceRetrieval.cs diff --git a/src/DotNetOpenAuth.Core/Logger.cs b/src/DotNetOpenAuth.Core/Logger.cs new file mode 100644 index 0000000..c9283cd --- /dev/null +++ b/src/DotNetOpenAuth.Core/Logger.cs @@ -0,0 +1,184 @@ +//----------------------------------------------------------------------- +// <copyright file="Logger.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth { + using System; + using System.Diagnostics.Contracts; + using System.Globalization; + using DotNetOpenAuth.Loggers; + using DotNetOpenAuth.Messaging; + using log4net.Core; + + /// <summary> + /// A general logger for the entire DotNetOpenAuth library. + /// </summary> + /// <remarks> + /// Because this logger is intended for use with non-localized strings, the + /// overloads that take <see cref="CultureInfo"/> have been removed, and + /// <see cref="CultureInfo.InvariantCulture"/> is used implicitly. + /// </remarks> + internal static partial class Logger { + #region Category-specific loggers + + /// <summary> + /// The <see cref="ILog"/> instance that is to be used + /// by this static Logger for the duration of the appdomain. + /// </summary> + private static readonly ILog library = CreateWithBanner("DotNetOpenAuth"); + + /// <summary> + /// Backing field for the <see cref="Yadis"/> property. + /// </summary> + private static readonly ILog yadis = Create("DotNetOpenAuth.Yadis"); + + /// <summary> + /// Backing field for the <see cref="Messaging"/> property. + /// </summary> + private static readonly ILog messaging = Create("DotNetOpenAuth.Messaging"); + + /// <summary> + /// Backing field for the <see cref="Channel"/> property. + /// </summary> + private static readonly ILog channel = Create("DotNetOpenAuth.Messaging.Channel"); + + /// <summary> + /// Backing field for the <see cref="Bindings"/> property. + /// </summary> + private static readonly ILog bindings = Create("DotNetOpenAuth.Messaging.Bindings"); + + /// <summary> + /// Backing field for the <see cref="Signatures"/> property. + /// </summary> + private static readonly ILog signatures = Create("DotNetOpenAuth.Messaging.Bindings.Signatures"); + + /// <summary> + /// Backing field for the <see cref="Http"/> property. + /// </summary> + private static readonly ILog http = Create("DotNetOpenAuth.Http"); + + /// <summary> + /// Backing field for the <see cref="Controls"/> property. + /// </summary> + private static readonly ILog controls = Create("DotNetOpenAuth.Controls"); + + /// <summary> + /// Backing field for the <see cref="OpenId"/> property. + /// </summary> + private static readonly ILog openId = Create("DotNetOpenAuth.OpenId"); + + /// <summary> + /// Backing field for the <see cref="OAuth"/> property. + /// </summary> + private static readonly ILog oauth = Create("DotNetOpenAuth.OAuth"); + + /// <summary> + /// Backing field for the <see cref="InfoCard"/> property. + /// </summary> + private static readonly ILog infocard = Create("DotNetOpenAuth.InfoCard"); + + /// <summary> + /// Gets the logger for general library logging. + /// </summary> + internal static ILog Library { get { return library; } } + + /// <summary> + /// Gets the logger for service discovery and selection events. + /// </summary> + internal static ILog Yadis { get { return yadis; } } + + /// <summary> + /// Gets the logger for Messaging events. + /// </summary> + internal static ILog Messaging { get { return messaging; } } + + /// <summary> + /// Gets the logger for Channel events. + /// </summary> + internal static ILog Channel { get { return channel; } } + + /// <summary> + /// Gets the logger for binding elements and binding-element related events on the channel. + /// </summary> + internal static ILog Bindings { get { return bindings; } } + + /// <summary> + /// Gets the logger specifically used for logging verbose text on everything about the signing process. + /// </summary> + internal static ILog Signatures { get { return signatures; } } + + /// <summary> + /// Gets the logger for HTTP-level events. + /// </summary> + internal static ILog Http { get { return http; } } + + /// <summary> + /// Gets the logger for events logged by ASP.NET controls. + /// </summary> + internal static ILog Controls { get { return controls; } } + + /// <summary> + /// Gets the logger for high-level OpenID events. + /// </summary> + internal static ILog OpenId { get { return openId; } } + + /// <summary> + /// Gets the logger for high-level OAuth events. + /// </summary> + internal static ILog OAuth { get { return oauth; } } + + /// <summary> + /// Gets the logger for high-level InfoCard events. + /// </summary> + internal static ILog InfoCard { get { return infocard; } } + + #endregion + + /// <summary> + /// Creates an additional logger on demand for a subsection of the application. + /// </summary> + /// <param name="name">A name that will be included in the log file.</param> + /// <returns>The <see cref="ILog"/> instance created with the given name.</returns> + internal static ILog Create(string name) { + Requires.NotNullOrEmpty(name, "name"); + return InitializeFacade(name); + } + + /// <summary> + /// Creates the main logger for the library, and emits an INFO message + /// that is the name and version of the library. + /// </summary> + /// <param name="name">A name that will be included in the log file.</param> + /// <returns>The <see cref="ILog"/> instance created with the given name.</returns> + internal static ILog CreateWithBanner(string name) { + Requires.NotNullOrEmpty(name, "name"); + ILog log = Create(name); + log.Info(Util.LibraryVersion); + return log; + } + + /// <summary> + /// Creates an additional logger on demand for a subsection of the application. + /// </summary> + /// <param name="type">A type whose full name that will be included in the log file.</param> + /// <returns>The <see cref="ILog"/> instance created with the given type name.</returns> + internal static ILog Create(Type type) { + Requires.NotNull(type, "type"); + + return Create(type.FullName); + } + + /// <summary> + /// Discovers the presence of Log4net.dll and other logging mechanisms + /// and returns the best available logger. + /// </summary> + /// <param name="name">The name of the log to initialize.</param> + /// <returns>The <see cref="ILog"/> instance of the logger to use.</returns> + private static ILog InitializeFacade(string name) { + ILog result = Log4NetLogger.Initialize(name) ?? TraceLogger.Initialize(name) ?? NoOpLogger.Initialize(); + return result; + } + } +} diff --git a/src/DotNetOpenAuth/Loggers/ILog.cs b/src/DotNetOpenAuth.Core/Loggers/ILog.cs index 8094296..8094296 100644 --- a/src/DotNetOpenAuth/Loggers/ILog.cs +++ b/src/DotNetOpenAuth.Core/Loggers/ILog.cs diff --git a/src/DotNetOpenAuth/Loggers/Log4NetLogger.cs b/src/DotNetOpenAuth.Core/Loggers/Log4NetLogger.cs index dd71a05..dd71a05 100644 --- a/src/DotNetOpenAuth/Loggers/Log4NetLogger.cs +++ b/src/DotNetOpenAuth.Core/Loggers/Log4NetLogger.cs diff --git a/src/DotNetOpenAuth/Loggers/NoOpLogger.cs b/src/DotNetOpenAuth.Core/Loggers/NoOpLogger.cs index 7d1b37f..7d1b37f 100644 --- a/src/DotNetOpenAuth/Loggers/NoOpLogger.cs +++ b/src/DotNetOpenAuth.Core/Loggers/NoOpLogger.cs diff --git a/src/DotNetOpenAuth/Loggers/TraceLogger.cs b/src/DotNetOpenAuth.Core/Loggers/TraceLogger.cs index 9b0bb0f..9b0bb0f 100644 --- a/src/DotNetOpenAuth/Loggers/TraceLogger.cs +++ b/src/DotNetOpenAuth.Core/Loggers/TraceLogger.cs diff --git a/src/DotNetOpenAuth.Core/Messaging/BinaryDataBagFormatter.cs b/src/DotNetOpenAuth.Core/Messaging/BinaryDataBagFormatter.cs new file mode 100644 index 0000000..0c20955 --- /dev/null +++ b/src/DotNetOpenAuth.Core/Messaging/BinaryDataBagFormatter.cs @@ -0,0 +1,80 @@ +//----------------------------------------------------------------------- +// <copyright file="BinaryDataBagFormatter.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Messaging { + using System; + using System.Collections.Generic; + using System.Diagnostics.Contracts; + using System.IO; + using System.Linq; + using System.Security.Cryptography; + using System.Text; + using DotNetOpenAuth.Messaging.Bindings; + + /// <summary> + /// A compact binary <see cref="DataBag"/> serialization class. + /// </summary> + /// <typeparam name="T">The <see cref="DataBag"/>-derived type to serialize/deserialize.</typeparam> + internal class BinaryDataBagFormatter<T> : DataBagFormatterBase<T> where T : DataBag, IStreamSerializingDataBag, new() { + /// <summary> + /// Initializes a new instance of the <see cref="BinaryDataBagFormatter<T>"/> class. + /// </summary> + /// <param name="signingKey">The crypto service provider with the asymmetric key to use for signing or verifying the token.</param> + /// <param name="encryptingKey">The crypto service provider with the asymmetric key to use for encrypting or decrypting the token.</param> + /// <param name="compressed">A value indicating whether the data in this instance will be GZip'd.</param> + /// <param name="maximumAge">The maximum age of a token that can be decoded; useful only when <paramref name="decodeOnceOnly"/> is <c>true</c>.</param> + /// <param name="decodeOnceOnly">The nonce store to use to ensure that this instance is only decoded once.</param> + protected internal BinaryDataBagFormatter(RSACryptoServiceProvider signingKey = null, RSACryptoServiceProvider encryptingKey = null, bool compressed = false, TimeSpan? maximumAge = null, INonceStore decodeOnceOnly = null) + : base(signingKey, encryptingKey, compressed, maximumAge, decodeOnceOnly) { + } + + /// <summary> + /// Initializes a new instance of the <see cref="BinaryDataBagFormatter<T>"/> class. + /// </summary> + /// <param name="cryptoKeyStore">The crypto key store used when signing or encrypting.</param> + /// <param name="bucket">The bucket in which symmetric keys are stored for signing/encrypting data.</param> + /// <param name="signed">A value indicating whether the data in this instance will be protected against tampering.</param> + /// <param name="encrypted">A value indicating whether the data in this instance will be protected against eavesdropping.</param> + /// <param name="compressed">A value indicating whether the data in this instance will be GZip'd.</param> + /// <param name="minimumAge">The minimum age.</param> + /// <param name="maximumAge">The maximum age of a token that can be decoded; useful only when <paramref name="decodeOnceOnly"/> is <c>true</c>.</param> + /// <param name="decodeOnceOnly">The nonce store to use to ensure that this instance is only decoded once.</param> + protected internal BinaryDataBagFormatter(ICryptoKeyStore cryptoKeyStore = null, string bucket = null, bool signed = false, bool encrypted = false, bool compressed = false, TimeSpan? minimumAge = null, TimeSpan? maximumAge = null, INonceStore decodeOnceOnly = null) + : base(cryptoKeyStore, bucket, signed, encrypted, compressed, minimumAge, maximumAge, decodeOnceOnly) { + Requires.True((cryptoKeyStore != null && bucket != null) || (!signed && !encrypted), null); + } + + /// <summary> + /// Serializes the <see cref="DataBag"/> instance to a buffer. + /// </summary> + /// <param name="message">The message.</param> + /// <returns>The buffer containing the serialized data.</returns> + protected override byte[] SerializeCore(T message) { + using (var stream = new MemoryStream()) { + message.Serialize(stream); + return stream.ToArray(); + } + } + + /// <summary> + /// Deserializes the <see cref="DataBag"/> instance from a buffer. + /// </summary> + /// <param name="message">The message instance to initialize with data from the buffer.</param> + /// <param name="data">The data buffer.</param> + protected override void DeserializeCore(T message, byte[] data) { + using (var stream = new MemoryStream(data)) { + message.Deserialize(stream); + } + + // Perform basic validation on message that the MessageSerializer would have normally performed. + var messageDescription = MessageDescriptions.Get(message); + var dictionary = messageDescription.GetDictionary(message); + messageDescription.EnsureMessagePartsPassBasicValidation(dictionary); + IMessage m = message; + m.EnsureValidMessage(); + } + } +} diff --git a/src/DotNetOpenAuth.Core/Messaging/Bindings/AsymmetricCryptoKeyStoreWrapper.cs b/src/DotNetOpenAuth.Core/Messaging/Bindings/AsymmetricCryptoKeyStoreWrapper.cs new file mode 100644 index 0000000..2691202 --- /dev/null +++ b/src/DotNetOpenAuth.Core/Messaging/Bindings/AsymmetricCryptoKeyStoreWrapper.cs @@ -0,0 +1,163 @@ +//----------------------------------------------------------------------- +// <copyright file="AsymmetricCryptoKeyStoreWrapper.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Messaging.Bindings { + using System; + using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; + using System.Diagnostics.Contracts; + using System.Linq; + using System.Security.Cryptography; + using System.Text; + using DotNetOpenAuth.Messaging; + + /// <summary> + /// Provides RSA encryption of symmetric keys to protect them from a theft of + /// the persistent store. + /// </summary> + public class AsymmetricCryptoKeyStoreWrapper : ICryptoKeyStore { + /// <summary> + /// The persistent store for asymmetrically encrypted symmetric keys. + /// </summary> + private readonly ICryptoKeyStore dataStore; + + /// <summary> + /// The memory cache of decrypted keys. + /// </summary> + private readonly MemoryCryptoKeyStore cache = new MemoryCryptoKeyStore(); + + /// <summary> + /// The asymmetric algorithm to use encrypting/decrypting the symmetric keys. + /// </summary> + private readonly RSACryptoServiceProvider asymmetricCrypto; + + /// <summary> + /// Initializes a new instance of the <see cref="AsymmetricCryptoKeyStoreWrapper"/> class. + /// </summary> + /// <param name="dataStore">The data store.</param> + /// <param name="asymmetricCrypto">The asymmetric protection to apply to symmetric keys. Must include the private key.</param> + public AsymmetricCryptoKeyStoreWrapper(ICryptoKeyStore dataStore, RSACryptoServiceProvider asymmetricCrypto) { + Requires.NotNull(dataStore, "dataStore"); + Requires.NotNull(asymmetricCrypto, "asymmetricCrypto"); + Requires.True(!asymmetricCrypto.PublicOnly, "asymmetricCrypto"); + this.dataStore = dataStore; + this.asymmetricCrypto = asymmetricCrypto; + } + + /// <summary> + /// Gets the key in a given bucket and handle. + /// </summary> + /// <param name="bucket">The bucket name. Case sensitive.</param> + /// <param name="handle">The key handle. Case sensitive.</param> + /// <returns> + /// The cryptographic key, or <c>null</c> if no matching key was found. + /// </returns> + public CryptoKey GetKey(string bucket, string handle) { + var key = this.dataStore.GetKey(bucket, handle); + return this.Decrypt(bucket, handle, key); + } + + /// <summary> + /// Gets a sequence of existing keys within a given bucket. + /// </summary> + /// <param name="bucket">The bucket name. Case sensitive.</param> + /// <returns> + /// A sequence of handles and keys, ordered by descending <see cref="CryptoKey.ExpiresUtc"/>. + /// </returns> + public IEnumerable<KeyValuePair<string, CryptoKey>> GetKeys(string bucket) { + return this.dataStore.GetKeys(bucket) + .Select(pair => new KeyValuePair<string, CryptoKey>(pair.Key, this.Decrypt(bucket, pair.Key, pair.Value))); + } + + /// <summary> + /// Stores a cryptographic key. + /// </summary> + /// <param name="bucket">The name of the bucket to store the key in. Case sensitive.</param> + /// <param name="handle">The handle to the key, unique within the bucket. Case sensitive.</param> + /// <param name="decryptedCryptoKey">The key to store.</param> + [SuppressMessage("Microsoft.Naming", "CA1725:ParameterNamesShouldMatchBaseDeclaration", MessageId = "2#", Justification = "Helps readability because multiple keys are involved.")] + public void StoreKey(string bucket, string handle, CryptoKey decryptedCryptoKey) { + byte[] encryptedKey = this.asymmetricCrypto.Encrypt(decryptedCryptoKey.Key, true); + var encryptedCryptoKey = new CryptoKey(encryptedKey, decryptedCryptoKey.ExpiresUtc); + this.dataStore.StoreKey(bucket, handle, encryptedCryptoKey); + + this.cache.StoreKey(bucket, handle, new CachedCryptoKey(encryptedCryptoKey, decryptedCryptoKey)); + } + + /// <summary> + /// Removes the key. + /// </summary> + /// <param name="bucket">The bucket name. Case sensitive.</param> + /// <param name="handle">The key handle. Case sensitive.</param> + public void RemoveKey(string bucket, string handle) { + this.dataStore.RemoveKey(bucket, handle); + this.cache.RemoveKey(bucket, handle); + } + + /// <summary> + /// Decrypts the specified key. + /// </summary> + /// <param name="bucket">The bucket.</param> + /// <param name="handle">The handle.</param> + /// <param name="encryptedCryptoKey">The encrypted key.</param> + /// <returns> + /// The decrypted key. + /// </returns> + [SuppressMessage("Microsoft.Naming", "CA1725:ParameterNamesShouldMatchBaseDeclaration", MessageId = "2#", Justification = "Helps readability because multiple keys are involved.")] + private CryptoKey Decrypt(string bucket, string handle, CryptoKey encryptedCryptoKey) { + if (encryptedCryptoKey == null) { + return null; + } + + // Avoid the asymmetric decryption if possible by looking up whether we have that in our cache. + CachedCryptoKey cached = (CachedCryptoKey)this.cache.GetKey(bucket, handle); + if (cached != null && MessagingUtilities.AreEquivalent(cached.EncryptedKey, encryptedCryptoKey.Key)) { + return cached; + } + + byte[] decryptedKey = this.asymmetricCrypto.Decrypt(encryptedCryptoKey.Key, true); + var decryptedCryptoKey = new CryptoKey(decryptedKey, encryptedCryptoKey.ExpiresUtc); + + // Store the decrypted version in the cache to save time next time. + this.cache.StoreKey(bucket, handle, new CachedCryptoKey(encryptedCryptoKey, decryptedCryptoKey)); + + return decryptedCryptoKey; + } + + /// <summary> + /// An encrypted key and its decrypted equivalent. + /// </summary> + private class CachedCryptoKey : CryptoKey { + /// <summary> + /// Initializes a new instance of the <see cref="CachedCryptoKey"/> class. + /// </summary> + /// <param name="encrypted">The encrypted key.</param> + /// <param name="decrypted">The decrypted key.</param> + internal CachedCryptoKey(CryptoKey encrypted, CryptoKey decrypted) + : base(decrypted.Key, decrypted.ExpiresUtc) { + Contract.Requires(encrypted != null); + Contract.Requires(decrypted != null); + Contract.Requires(encrypted.ExpiresUtc == decrypted.ExpiresUtc); + + this.EncryptedKey = encrypted.Key; + } + + /// <summary> + /// Gets the encrypted key. + /// </summary> + internal byte[] EncryptedKey { get; private set; } + + /// <summary> + /// Invariant conditions. + /// </summary> + [ContractInvariantMethod] + [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Required for code contracts.")] + private void ObjectInvariant() { + Contract.Invariant(this.EncryptedKey != null); + } + } + } +} diff --git a/src/DotNetOpenAuth/Messaging/Bindings/Bindings.cd b/src/DotNetOpenAuth.Core/Messaging/Bindings/Bindings.cd index e52e81e..e52e81e 100644 --- a/src/DotNetOpenAuth/Messaging/Bindings/Bindings.cd +++ b/src/DotNetOpenAuth.Core/Messaging/Bindings/Bindings.cd diff --git a/src/DotNetOpenAuth.Core/Messaging/Bindings/CryptoKey.cs b/src/DotNetOpenAuth.Core/Messaging/Bindings/CryptoKey.cs new file mode 100644 index 0000000..7160014 --- /dev/null +++ b/src/DotNetOpenAuth.Core/Messaging/Bindings/CryptoKey.cs @@ -0,0 +1,93 @@ +//----------------------------------------------------------------------- +// <copyright file="CryptoKey.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Messaging.Bindings { + using System; + using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; + using System.Diagnostics.Contracts; + using System.Linq; + using System.Text; + using DotNetOpenAuth.Messaging; + + /// <summary> + /// A cryptographic key and metadata concerning it. + /// </summary> + public class CryptoKey { + /// <summary> + /// Backing field for the <see cref="Key"/> property. + /// </summary> + private readonly byte[] key; + + /// <summary> + /// Backing field for the <see cref="ExpiresUtc"/> property. + /// </summary> + private readonly DateTime expiresUtc; + + /// <summary> + /// Initializes a new instance of the <see cref="CryptoKey"/> class. + /// </summary> + /// <param name="key">The cryptographic key.</param> + /// <param name="expiresUtc">The expires UTC.</param> + public CryptoKey(byte[] key, DateTime expiresUtc) { + Requires.NotNull(key, "key"); + Requires.True(expiresUtc.Kind == DateTimeKind.Utc, "expiresUtc"); + this.key = key; + this.expiresUtc = expiresUtc; + } + + /// <summary> + /// Gets the key. + /// </summary> + [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification = "It's a buffer")] + public byte[] Key { + get { + Contract.Ensures(Contract.Result<byte[]>() != null); + return this.key; + } + } + + /// <summary> + /// Gets the expiration date of this key (UTC time). + /// </summary> + public DateTime ExpiresUtc { + get { + Contract.Ensures(Contract.Result<DateTime>().Kind == DateTimeKind.Utc); + return this.expiresUtc; + } + } + + /// <summary> + /// Determines whether the specified <see cref="System.Object"/> is equal to this instance. + /// </summary> + /// <param name="obj">The <see cref="System.Object"/> to compare with this instance.</param> + /// <returns> + /// <c>true</c> if the specified <see cref="System.Object"/> is equal to this instance; otherwise, <c>false</c>. + /// </returns> + /// <exception cref="T:System.NullReferenceException"> + /// The <paramref name="obj"/> parameter is null. + /// </exception> + public override bool Equals(object obj) { + var other = obj as CryptoKey; + if (other == null) { + return false; + } + + return this.ExpiresUtc == other.ExpiresUtc + && MessagingUtilities.AreEquivalent(this.Key, other.Key); + } + + /// <summary> + /// Returns a hash code for this instance. + /// </summary> + /// <returns> + /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. + /// </returns> + public override int GetHashCode() { + return this.ExpiresUtc.GetHashCode(); + } + } +} diff --git a/src/DotNetOpenAuth.Core/Messaging/Bindings/CryptoKeyCollisionException.cs b/src/DotNetOpenAuth.Core/Messaging/Bindings/CryptoKeyCollisionException.cs new file mode 100644 index 0000000..ebd29de --- /dev/null +++ b/src/DotNetOpenAuth.Core/Messaging/Bindings/CryptoKeyCollisionException.cs @@ -0,0 +1,68 @@ +//----------------------------------------------------------------------- +// <copyright file="CryptoKeyCollisionException.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Messaging.Bindings { + using System; + using System.Diagnostics.CodeAnalysis; + using System.Security.Permissions; + + /// <summary> + /// Thrown by a hosting application or web site when a cryptographic key is created with a + /// bucket and handle that conflicts with a previously stored and unexpired key. + /// </summary> + [SuppressMessage("Microsoft.Design", "CA1032:ImplementStandardExceptionConstructors", Justification = "Specialized exception has no need of a message parameter.")] + [Serializable] + public class CryptoKeyCollisionException : ArgumentException { + /// <summary> + /// Initializes a new instance of the <see cref="CryptoKeyCollisionException"/> class. + /// </summary> + public CryptoKeyCollisionException() { + } + + /// <summary> + /// Initializes a new instance of the <see cref="CryptoKeyCollisionException"/> class. + /// </summary> + /// <param name="inner">The inner exception to include.</param> + public CryptoKeyCollisionException(Exception inner) : base(null, inner) { + } + + /// <summary> + /// Initializes a new instance of the <see cref="CryptoKeyCollisionException"/> class. + /// </summary> + /// <param name="info">The <see cref="System.Runtime.Serialization.SerializationInfo"/> + /// that holds the serialized object data about the exception being thrown.</param> + /// <param name="context">The System.Runtime.Serialization.StreamingContext + /// that contains contextual information about the source or destination.</param> + protected CryptoKeyCollisionException( + System.Runtime.Serialization.SerializationInfo info, + System.Runtime.Serialization.StreamingContext context) + : base(info, context) { + throw new NotImplementedException(); + } + + /// <summary> + /// When overridden in a derived class, sets the <see cref="T:System.Runtime.Serialization.SerializationInfo"/> with information about the exception. + /// </summary> + /// <param name="info">The <see cref="T:System.Runtime.Serialization.SerializationInfo"/> that holds the serialized object data about the exception being thrown.</param> + /// <param name="context">The <see cref="T:System.Runtime.Serialization.StreamingContext"/> that contains contextual information about the source or destination.</param> + /// <exception cref="T:System.ArgumentNullException"> + /// The <paramref name="info"/> parameter is a null reference (Nothing in Visual Basic). + /// </exception> + /// <PermissionSet> + /// <IPermission class="System.Security.Permissions.FileIOPermission, mscorlib, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Read="*AllFiles*" PathDiscovery="*AllFiles*"/> + /// <IPermission class="System.Security.Permissions.SecurityPermission, mscorlib, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Flags="SerializationFormatter"/> + /// </PermissionSet> +#if CLR4 + [System.Security.SecurityCritical] +#else + [SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.SerializationFormatter)] +#endif + public override void GetObjectData(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) { + base.GetObjectData(info, context); + throw new NotImplementedException(); + } + } +} diff --git a/src/DotNetOpenAuth.Core/Messaging/Bindings/ExpiredMessageException.cs b/src/DotNetOpenAuth.Core/Messaging/Bindings/ExpiredMessageException.cs new file mode 100644 index 0000000..196946d --- /dev/null +++ b/src/DotNetOpenAuth.Core/Messaging/Bindings/ExpiredMessageException.cs @@ -0,0 +1,40 @@ +//----------------------------------------------------------------------- +// <copyright file="ExpiredMessageException.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Messaging.Bindings { + using System; + using System.Diagnostics.Contracts; + using System.Globalization; + + /// <summary> + /// An exception thrown when a message is received that exceeds the maximum message age limit. + /// </summary> + [Serializable] + internal class ExpiredMessageException : ProtocolException { + /// <summary> + /// Initializes a new instance of the <see cref="ExpiredMessageException"/> class. + /// </summary> + /// <param name="utcExpirationDate">The date the message expired.</param> + /// <param name="faultedMessage">The expired message.</param> + public ExpiredMessageException(DateTime utcExpirationDate, IProtocolMessage faultedMessage) + : base(string.Format(CultureInfo.CurrentCulture, MessagingStrings.ExpiredMessage, utcExpirationDate.ToLocalTime(), DateTime.Now), faultedMessage) { + Requires.True(utcExpirationDate.Kind == DateTimeKind.Utc, "utcExpirationDate"); + Requires.NotNull(faultedMessage, "faultedMessage"); + } + + /// <summary> + /// Initializes a new instance of the <see cref="ExpiredMessageException"/> class. + /// </summary> + /// <param name="info">The <see cref="System.Runtime.Serialization.SerializationInfo"/> + /// that holds the serialized object data about the exception being thrown.</param> + /// <param name="context">The System.Runtime.Serialization.StreamingContext + /// that contains contextual information about the source or destination.</param> + protected ExpiredMessageException( + System.Runtime.Serialization.SerializationInfo info, + System.Runtime.Serialization.StreamingContext context) + : base(info, context) { } + } +} diff --git a/src/DotNetOpenAuth.Core/Messaging/Bindings/ICryptoKeyStore.cs b/src/DotNetOpenAuth.Core/Messaging/Bindings/ICryptoKeyStore.cs new file mode 100644 index 0000000..861ba89 --- /dev/null +++ b/src/DotNetOpenAuth.Core/Messaging/Bindings/ICryptoKeyStore.cs @@ -0,0 +1,118 @@ +//----------------------------------------------------------------------- +// <copyright file="ICryptoKeyStore.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Messaging.Bindings { + using System; + using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; + using System.Diagnostics.Contracts; + using System.Linq; + using System.Text; + using DotNetOpenAuth.Messaging; + + /// <summary> + /// A persistent store for rotating symmetric cryptographic keys. + /// </summary> + /// <remarks> + /// Implementations should persist it in such a way that the keys are shared across all servers + /// on a web farm, where applicable. + /// The store should consider protecting the persistent store against theft resulting in the loss + /// of the confidentiality of the keys. One possible mitigation is to asymmetrically encrypt + /// each key using a certificate installed in the server's certificate store. + /// </remarks> + [ContractClass(typeof(ICryptoKeyStoreContract))] + public interface ICryptoKeyStore { + /// <summary> + /// Gets the key in a given bucket and handle. + /// </summary> + /// <param name="bucket">The bucket name. Case sensitive.</param> + /// <param name="handle">The key handle. Case sensitive.</param> + /// <returns>The cryptographic key, or <c>null</c> if no matching key was found.</returns> + CryptoKey GetKey(string bucket, string handle); + + /// <summary> + /// Gets a sequence of existing keys within a given bucket. + /// </summary> + /// <param name="bucket">The bucket name. Case sensitive.</param> + /// <returns>A sequence of handles and keys, ordered by descending <see cref="CryptoKey.ExpiresUtc"/>.</returns> + [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "Important for scalability")] + IEnumerable<KeyValuePair<string, CryptoKey>> GetKeys(string bucket); + + /// <summary> + /// Stores a cryptographic key. + /// </summary> + /// <param name="bucket">The name of the bucket to store the key in. Case sensitive.</param> + /// <param name="handle">The handle to the key, unique within the bucket. Case sensitive.</param> + /// <param name="key">The key to store.</param> + /// <exception cref="CryptoKeyCollisionException">Thrown in the event of a conflict with an existing key in the same bucket and with the same handle.</exception> + void StoreKey(string bucket, string handle, CryptoKey key); + + /// <summary> + /// Removes the key. + /// </summary> + /// <param name="bucket">The bucket name. Case sensitive.</param> + /// <param name="handle">The key handle. Case sensitive.</param> + void RemoveKey(string bucket, string handle); + } + + /// <summary> + /// Code contract for the <see cref="ICryptoKeyStore"/> interface. + /// </summary> + [ContractClassFor(typeof(ICryptoKeyStore))] + internal abstract class ICryptoKeyStoreContract : ICryptoKeyStore { + /// <summary> + /// Gets the key in a given bucket and handle. + /// </summary> + /// <param name="bucket">The bucket name. Case sensitive.</param> + /// <param name="handle">The key handle. Case sensitive.</param> + /// <returns> + /// The cryptographic key, or <c>null</c> if no matching key was found. + /// </returns> + CryptoKey ICryptoKeyStore.GetKey(string bucket, string handle) { + Requires.NotNullOrEmpty(bucket, "bucket"); + Requires.NotNullOrEmpty(handle, "handle"); + throw new NotImplementedException(); + } + + /// <summary> + /// Gets a sequence of existing keys within a given bucket. + /// </summary> + /// <param name="bucket">The bucket name. Case sensitive.</param> + /// <returns> + /// A sequence of handles and keys, ordered by descending <see cref="CryptoKey.ExpiresUtc"/>. + /// </returns> + IEnumerable<KeyValuePair<string, CryptoKey>> ICryptoKeyStore.GetKeys(string bucket) { + Requires.NotNullOrEmpty(bucket, "bucket"); + Contract.Ensures(Contract.Result<IEnumerable<KeyValuePair<string, CryptoKey>>>() != null); + throw new NotImplementedException(); + } + + /// <summary> + /// Stores a cryptographic key. + /// </summary> + /// <param name="bucket">The name of the bucket to store the key in. Case sensitive.</param> + /// <param name="handle">The handle to the key, unique within the bucket. Case sensitive.</param> + /// <param name="key">The key to store.</param> + /// <exception cref="CryptoKeyCollisionException">Thrown in the event of a conflict with an existing key in the same bucket and with the same handle.</exception> + void ICryptoKeyStore.StoreKey(string bucket, string handle, CryptoKey key) { + Requires.NotNullOrEmpty(bucket, "bucket"); + Requires.NotNullOrEmpty(handle, "handle"); + Requires.NotNull(key, "key"); + throw new NotImplementedException(); + } + + /// <summary> + /// Removes the key. + /// </summary> + /// <param name="bucket">The bucket name. Case sensitive.</param> + /// <param name="handle">The key handle. Case sensitive.</param> + void ICryptoKeyStore.RemoveKey(string bucket, string handle) { + Requires.NotNullOrEmpty(bucket, "bucket"); + Requires.NotNullOrEmpty(handle, "handle"); + throw new NotImplementedException(); + } + } +} diff --git a/src/DotNetOpenAuth/Messaging/Bindings/IExpiringProtocolMessage.cs b/src/DotNetOpenAuth.Core/Messaging/Bindings/IExpiringProtocolMessage.cs index fc43ae6..fc43ae6 100644 --- a/src/DotNetOpenAuth/Messaging/Bindings/IExpiringProtocolMessage.cs +++ b/src/DotNetOpenAuth.Core/Messaging/Bindings/IExpiringProtocolMessage.cs diff --git a/src/DotNetOpenAuth.Core/Messaging/Bindings/INonceStore.cs b/src/DotNetOpenAuth.Core/Messaging/Bindings/INonceStore.cs new file mode 100644 index 0000000..7a3e8bb --- /dev/null +++ b/src/DotNetOpenAuth.Core/Messaging/Bindings/INonceStore.cs @@ -0,0 +1,39 @@ +//----------------------------------------------------------------------- +// <copyright file="INonceStore.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Messaging.Bindings { + using System; + + /// <summary> + /// Describes the contract a nonce store must fulfill. + /// </summary> + public interface INonceStore { + /// <summary> + /// Stores a given nonce and timestamp. + /// </summary> + /// <param name="context">The context, or namespace, within which the + /// <paramref name="nonce"/> must be unique. + /// The context SHOULD be treated as case-sensitive. + /// The value will never be <c>null</c> but may be the empty string.</param> + /// <param name="nonce">A series of random characters.</param> + /// <param name="timestampUtc">The UTC timestamp that together with the nonce string make it unique + /// within the given <paramref name="context"/>. + /// The timestamp may also be used by the data store to clear out old nonces.</param> + /// <returns> + /// True if the context+nonce+timestamp (combination) was not previously in the database. + /// False if the nonce was stored previously with the same timestamp and context. + /// </returns> + /// <remarks> + /// The nonce must be stored for no less than the maximum time window a message may + /// be processed within before being discarded as an expired message. + /// This maximum message age can be looked up via the + /// <see cref="DotNetOpenAuth.Configuration.MessagingElement.MaximumMessageLifetime"/> + /// property, accessible via the <see cref="DotNetOpenAuth.Configuration.MessagingElement.Configuration"/> + /// property. + /// </remarks> + bool StoreNonce(string context, string nonce, DateTime timestampUtc); + } +} diff --git a/src/DotNetOpenAuth/Messaging/Bindings/IReplayProtectedProtocolMessage.cs b/src/DotNetOpenAuth.Core/Messaging/Bindings/IReplayProtectedProtocolMessage.cs index 1edf934..1edf934 100644 --- a/src/DotNetOpenAuth/Messaging/Bindings/IReplayProtectedProtocolMessage.cs +++ b/src/DotNetOpenAuth.Core/Messaging/Bindings/IReplayProtectedProtocolMessage.cs diff --git a/src/DotNetOpenAuth/Messaging/Bindings/InvalidSignatureException.cs b/src/DotNetOpenAuth.Core/Messaging/Bindings/InvalidSignatureException.cs index 28b7e96..28b7e96 100644 --- a/src/DotNetOpenAuth/Messaging/Bindings/InvalidSignatureException.cs +++ b/src/DotNetOpenAuth.Core/Messaging/Bindings/InvalidSignatureException.cs diff --git a/src/DotNetOpenAuth/Messaging/Bindings/MemoryCryptoKeyStore.cs b/src/DotNetOpenAuth.Core/Messaging/Bindings/MemoryCryptoKeyStore.cs index 63d1953..63d1953 100644 --- a/src/DotNetOpenAuth/Messaging/Bindings/MemoryCryptoKeyStore.cs +++ b/src/DotNetOpenAuth.Core/Messaging/Bindings/MemoryCryptoKeyStore.cs diff --git a/src/DotNetOpenAuth/Messaging/Bindings/NonceMemoryStore.cs b/src/DotNetOpenAuth.Core/Messaging/Bindings/NonceMemoryStore.cs index 6e64acc..6e64acc 100644 --- a/src/DotNetOpenAuth/Messaging/Bindings/NonceMemoryStore.cs +++ b/src/DotNetOpenAuth.Core/Messaging/Bindings/NonceMemoryStore.cs diff --git a/src/DotNetOpenAuth/Messaging/Bindings/ReplayedMessageException.cs b/src/DotNetOpenAuth.Core/Messaging/Bindings/ReplayedMessageException.cs index 2b8df9d..2b8df9d 100644 --- a/src/DotNetOpenAuth/Messaging/Bindings/ReplayedMessageException.cs +++ b/src/DotNetOpenAuth.Core/Messaging/Bindings/ReplayedMessageException.cs diff --git a/src/DotNetOpenAuth.Core/Messaging/Bindings/StandardExpirationBindingElement.cs b/src/DotNetOpenAuth.Core/Messaging/Bindings/StandardExpirationBindingElement.cs new file mode 100644 index 0000000..f8c8c6a --- /dev/null +++ b/src/DotNetOpenAuth.Core/Messaging/Bindings/StandardExpirationBindingElement.cs @@ -0,0 +1,107 @@ +//----------------------------------------------------------------------- +// <copyright file="StandardExpirationBindingElement.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Messaging.Bindings { + using System; + using DotNetOpenAuth.Configuration; + + /// <summary> + /// A message expiration enforcing binding element that supports messages + /// implementing the <see cref="IExpiringProtocolMessage"/> interface. + /// </summary> + internal class StandardExpirationBindingElement : IChannelBindingElement { + /// <summary> + /// Initializes a new instance of the <see cref="StandardExpirationBindingElement"/> class. + /// </summary> + internal StandardExpirationBindingElement() { + } + + #region IChannelBindingElement Properties + + /// <summary> + /// Gets the protection offered by this binding element. + /// </summary> + /// <value><see cref="MessageProtections.Expiration"/></value> + MessageProtections IChannelBindingElement.Protection { + get { return MessageProtections.Expiration; } + } + + /// <summary> + /// Gets or sets the channel that this binding element belongs to. + /// </summary> + public Channel Channel { get; set; } + + #endregion + + /// <summary> + /// Gets the maximum age a message implementing the + /// <see cref="IExpiringProtocolMessage"/> interface can be before + /// being discarded as too old. + /// </summary> + protected internal static TimeSpan MaximumMessageAge { + get { return Configuration.DotNetOpenAuthSection.Messaging.MaximumMessageLifetime; } + } + + #region IChannelBindingElement Methods + + /// <summary> + /// Sets the timestamp on an outgoing message. + /// </summary> + /// <param name="message">The outgoing message.</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> + public MessageProtections? ProcessOutgoingMessage(IProtocolMessage message) { + IExpiringProtocolMessage expiringMessage = message as IExpiringProtocolMessage; + if (expiringMessage != null) { + expiringMessage.UtcCreationDate = DateTime.UtcNow; + return MessageProtections.Expiration; + } + + return null; + } + + /// <summary> + /// Reads the timestamp on a message and throws an exception if the message is too old. + /// </summary> + /// <param name="message">The incoming message.</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="ExpiredMessageException">Thrown if the given message has already expired.</exception> + /// <exception cref="ProtocolException"> + /// Thrown when the binding element rules indicate that this message is invalid and should + /// NOT be processed. + /// </exception> + public MessageProtections? ProcessIncomingMessage(IProtocolMessage message) { + IExpiringProtocolMessage expiringMessage = message as IExpiringProtocolMessage; + if (expiringMessage != null) { + // Yes the UtcCreationDate is supposed to always be in UTC already, + // but just in case a given message failed to guarantee that, we do it here. + DateTime creationDate = expiringMessage.UtcCreationDate.ToUniversalTimeSafe(); + DateTime expirationDate = creationDate + MaximumMessageAge; + if (expirationDate < DateTime.UtcNow) { + throw new ExpiredMessageException(expirationDate, expiringMessage); + } + + // Mitigate HMAC attacks (just guessing the signature until they get it) by + // disallowing post-dated messages. + ErrorUtilities.VerifyProtocol( + creationDate <= DateTime.UtcNow + DotNetOpenAuthSection.Messaging.MaximumClockSkew, + MessagingStrings.MessageTimestampInFuture, + creationDate); + + return MessageProtections.Expiration; + } + + return null; + } + + #endregion + } +} diff --git a/src/DotNetOpenAuth.Core/Messaging/Bindings/StandardReplayProtectionBindingElement.cs b/src/DotNetOpenAuth.Core/Messaging/Bindings/StandardReplayProtectionBindingElement.cs new file mode 100644 index 0000000..78fd1d5 --- /dev/null +++ b/src/DotNetOpenAuth.Core/Messaging/Bindings/StandardReplayProtectionBindingElement.cs @@ -0,0 +1,148 @@ +//----------------------------------------------------------------------- +// <copyright file="StandardReplayProtectionBindingElement.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Messaging.Bindings { + using System; + using System.Diagnostics; + using System.Diagnostics.Contracts; + + /// <summary> + /// A binding element that checks/verifies a nonce message part. + /// </summary> + internal class StandardReplayProtectionBindingElement : IChannelBindingElement { + /// <summary> + /// These are the characters that may be chosen from when forming a random nonce. + /// </summary> + private const string AllowedCharacters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + + /// <summary> + /// The persistent store for nonces received. + /// </summary> + private INonceStore nonceStore; + + /// <summary> + /// The length of generated nonces. + /// </summary> + private int nonceLength = 8; + + /// <summary> + /// Initializes a new instance of the <see cref="StandardReplayProtectionBindingElement"/> class. + /// </summary> + /// <param name="nonceStore">The store where nonces will be persisted and checked.</param> + internal StandardReplayProtectionBindingElement(INonceStore nonceStore) + : this(nonceStore, false) { + } + + /// <summary> + /// Initializes a new instance of the <see cref="StandardReplayProtectionBindingElement"/> class. + /// </summary> + /// <param name="nonceStore">The store where nonces will be persisted and checked.</param> + /// <param name="allowEmptyNonces">A value indicating whether zero-length nonces will be allowed.</param> + internal StandardReplayProtectionBindingElement(INonceStore nonceStore, bool allowEmptyNonces) { + Requires.NotNull(nonceStore, "nonceStore"); + + this.nonceStore = nonceStore; + this.AllowZeroLengthNonce = allowEmptyNonces; + } + + #region IChannelBindingElement Properties + + /// <summary> + /// Gets the protection that this binding element provides messages. + /// </summary> + public MessageProtections Protection { + get { return MessageProtections.ReplayProtection; } + } + + /// <summary> + /// Gets or sets the channel that this binding element belongs to. + /// </summary> + public Channel Channel { get; set; } + + #endregion + + /// <summary> + /// Gets or sets the strength of the nonce, which is measured by the number of + /// nonces that could theoretically be generated. + /// </summary> + /// <remarks> + /// The strength of the nonce is equal to the number of characters that might appear + /// in the nonce to the power of the length of the nonce. + /// </remarks> + internal double NonceStrength { + get { + return Math.Pow(AllowedCharacters.Length, this.nonceLength); + } + + set { + value = Math.Max(value, AllowedCharacters.Length); + this.nonceLength = (int)Math.Log(value, AllowedCharacters.Length); + Debug.Assert(this.nonceLength > 0, "Nonce length calculated to be below 1!"); + } + } + + /// <summary> + /// Gets or sets a value indicating whether empty nonces are allowed. + /// </summary> + /// <value>Default is <c>false</c>.</value> + internal bool AllowZeroLengthNonce { get; set; } + + #region IChannelBindingElement Methods + + /// <summary> + /// Applies a nonce to the message. + /// </summary> + /// <param name="message">The message to apply replay protection to.</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> + public MessageProtections? ProcessOutgoingMessage(IProtocolMessage message) { + IReplayProtectedProtocolMessage nonceMessage = message as IReplayProtectedProtocolMessage; + if (nonceMessage != null) { + nonceMessage.Nonce = this.GenerateUniqueFragment(); + return MessageProtections.ReplayProtection; + } + + return null; + } + + /// <summary> + /// Verifies that the nonce in an incoming message has not been seen before. + /// </summary> + /// <param name="message">The incoming message.</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="ReplayedMessageException">Thrown when the nonce check revealed a replayed message.</exception> + public MessageProtections? ProcessIncomingMessage(IProtocolMessage message) { + IReplayProtectedProtocolMessage nonceMessage = message as IReplayProtectedProtocolMessage; + if (nonceMessage != null && nonceMessage.Nonce != null) { + ErrorUtilities.VerifyProtocol(nonceMessage.Nonce.Length > 0 || this.AllowZeroLengthNonce, MessagingStrings.InvalidNonceReceived); + + if (!this.nonceStore.StoreNonce(nonceMessage.NonceContext, nonceMessage.Nonce, nonceMessage.UtcCreationDate)) { + Logger.OpenId.ErrorFormat("Replayed nonce detected ({0} {1}). Rejecting message.", nonceMessage.Nonce, nonceMessage.UtcCreationDate); + throw new ReplayedMessageException(message); + } + + return MessageProtections.ReplayProtection; + } + + return null; + } + + #endregion + + /// <summary> + /// Generates a string of random characters for use as a nonce. + /// </summary> + /// <returns>The nonce string.</returns> + private string GenerateUniqueFragment() { + return MessagingUtilities.GetRandomString(this.nonceLength, AllowedCharacters); + } + } +} diff --git a/src/DotNetOpenAuth.Core/Messaging/CachedDirectWebResponse.cs b/src/DotNetOpenAuth.Core/Messaging/CachedDirectWebResponse.cs new file mode 100644 index 0000000..2f3a1d9 --- /dev/null +++ b/src/DotNetOpenAuth.Core/Messaging/CachedDirectWebResponse.cs @@ -0,0 +1,184 @@ +//----------------------------------------------------------------------- +// <copyright file="CachedDirectWebResponse.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Messaging { + using System; + using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; + using System.Diagnostics.Contracts; + using System.IO; + using System.Net; + using System.Text; + + /// <summary> + /// Cached details on the response from a direct web request to a remote party. + /// </summary> + [ContractVerification(true)] + [DebuggerDisplay("{Status} {ContentType.MediaType}, length: {ResponseStream.Length}")] + internal class CachedDirectWebResponse : IncomingWebResponse { + /// <summary> + /// A seekable, repeatable response stream. + /// </summary> + private MemoryStream responseStream; + + /// <summary> + /// Initializes a new instance of the <see cref="CachedDirectWebResponse"/> class. + /// </summary> + internal CachedDirectWebResponse() { + } + + /// <summary> + /// Initializes a new instance of the <see cref="CachedDirectWebResponse"/> class. + /// </summary> + /// <param name="requestUri">The request URI.</param> + /// <param name="response">The response.</param> + /// <param name="maximumBytesToRead">The maximum bytes to read.</param> + internal CachedDirectWebResponse(Uri requestUri, HttpWebResponse response, int maximumBytesToRead) + : base(requestUri, response) { + Requires.NotNull(requestUri, "requestUri"); + Requires.NotNull(response, "response"); + this.responseStream = CacheNetworkStreamAndClose(response, maximumBytesToRead); + + // BUGBUG: if the response was exactly maximumBytesToRead, we'll incorrectly believe it was truncated. + this.ResponseTruncated = this.responseStream.Length == maximumBytesToRead; + } + + /// <summary> + /// Initializes a new instance of the <see cref="CachedDirectWebResponse"/> class. + /// </summary> + /// <param name="requestUri">The request URI.</param> + /// <param name="responseUri">The final URI to respond to the request.</param> + /// <param name="headers">The headers.</param> + /// <param name="statusCode">The status code.</param> + /// <param name="contentType">Type of the content.</param> + /// <param name="contentEncoding">The content encoding.</param> + /// <param name="responseStream">The response stream.</param> + internal CachedDirectWebResponse(Uri requestUri, Uri responseUri, WebHeaderCollection headers, HttpStatusCode statusCode, string contentType, string contentEncoding, MemoryStream responseStream) + : base(requestUri, responseUri, headers, statusCode, contentType, contentEncoding) { + Requires.NotNull(requestUri, "requestUri"); + Requires.NotNull(responseStream, "responseStream"); + this.responseStream = responseStream; + } + + /// <summary> + /// Gets a value indicating whether the cached response stream was + /// truncated to a maximum allowable length. + /// </summary> + public bool ResponseTruncated { get; private set; } + + /// <summary> + /// Gets the body of the HTTP response. + /// </summary> + public override Stream ResponseStream { + get { return this.responseStream; } + } + + /// <summary> + /// Gets or sets the cached response stream. + /// </summary> + internal MemoryStream CachedResponseStream { + get { return this.responseStream; } + set { this.responseStream = value; } + } + + /// <summary> + /// Creates a text reader for the response stream. + /// </summary> + /// <returns>The text reader, initialized for the proper encoding.</returns> + public override StreamReader GetResponseReader() { + this.ResponseStream.Seek(0, SeekOrigin.Begin); + string contentEncoding = this.Headers[HttpResponseHeader.ContentEncoding]; + Encoding encoding = null; + if (!string.IsNullOrEmpty(contentEncoding)) { + try { + encoding = Encoding.GetEncoding(contentEncoding); + } catch (ArgumentException ex) { + Logger.Messaging.ErrorFormat("Encoding.GetEncoding(\"{0}\") threw ArgumentException: {1}", contentEncoding, ex); + } + } + + return encoding != null ? new StreamReader(this.ResponseStream, encoding) : new StreamReader(this.ResponseStream); + } + + /// <summary> + /// Gets the body of the response as a string. + /// </summary> + /// <returns>The entire body of the response.</returns> + internal string GetResponseString() { + if (this.ResponseStream != null) { + string value = this.GetResponseReader().ReadToEnd(); + this.ResponseStream.Seek(0, SeekOrigin.Begin); + return value; + } else { + return null; + } + } + + /// <summary> + /// Gets an offline snapshot version of this instance. + /// </summary> + /// <param name="maximumBytesToCache">The maximum bytes from the response stream to cache.</param> + /// <returns>A snapshot version of this instance.</returns> + /// <remarks> + /// If this instance is a <see cref="NetworkDirectWebResponse"/> creating a snapshot + /// will automatically close and dispose of the underlying response stream. + /// If this instance is a <see cref="CachedDirectWebResponse"/>, the result will + /// be the self same instance. + /// </remarks> + internal override CachedDirectWebResponse GetSnapshot(int maximumBytesToCache) { + return this; + } + + /// <summary> + /// Sets the response to some string, encoded as UTF-8. + /// </summary> + /// <param name="body">The string to set the response to.</param> + internal void SetResponse(string body) { + if (body == null) { + this.responseStream = null; + return; + } + + Encoding encoding = Encoding.UTF8; + this.Headers[HttpResponseHeader.ContentEncoding] = encoding.HeaderName; + this.responseStream = new MemoryStream(); + StreamWriter writer = new StreamWriter(this.ResponseStream, encoding); + writer.Write(body); + writer.Flush(); + this.ResponseStream.Seek(0, SeekOrigin.Begin); + } + + /// <summary> + /// Caches the network stream and closes it if it is open. + /// </summary> + /// <param name="response">The response whose stream is to be cloned.</param> + /// <param name="maximumBytesToRead">The maximum bytes to cache.</param> + /// <returns>The seekable Stream instance that contains a copy of what was returned in the HTTP response.</returns> + [SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", MessageId = "System.Diagnostics.Contracts.__ContractsRuntime.Assume(System.Boolean,System.String,System.String)", Justification = "No localization required.")] + private static MemoryStream CacheNetworkStreamAndClose(HttpWebResponse response, int maximumBytesToRead) { + Requires.NotNull(response, "response"); + Contract.Ensures(Contract.Result<MemoryStream>() != null); + + // Now read and cache the network stream + Stream networkStream = response.GetResponseStream(); + MemoryStream cachedStream = new MemoryStream(response.ContentLength < 0 ? 4 * 1024 : Math.Min((int)response.ContentLength, maximumBytesToRead)); + try { + Contract.Assume(networkStream.CanRead, "HttpWebResponse.GetResponseStream() always returns a readable stream."); // CC missing + Contract.Assume(cachedStream.CanWrite, "This is a MemoryStream -- it's always writable."); // CC missing + networkStream.CopyTo(cachedStream); + cachedStream.Seek(0, SeekOrigin.Begin); + + networkStream.Dispose(); + response.Close(); + + return cachedStream; + } catch { + cachedStream.Dispose(); + throw; + } + } + } +} diff --git a/src/DotNetOpenAuth.Core/Messaging/Channel.cs b/src/DotNetOpenAuth.Core/Messaging/Channel.cs new file mode 100644 index 0000000..f017214 --- /dev/null +++ b/src/DotNetOpenAuth.Core/Messaging/Channel.cs @@ -0,0 +1,1406 @@ +//----------------------------------------------------------------------- +// <copyright file="Channel.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Messaging { + using System; + using System.Collections.Generic; + using System.Collections.ObjectModel; + using System.ComponentModel; + using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; + using System.Diagnostics.Contracts; + using System.Globalization; + using System.IO; + using System.Linq; + using System.Net; + using System.Net.Cache; + using System.Net.Mime; + using System.Runtime.Serialization.Json; + using System.Text; + using System.Threading; + using System.Web; + using System.Xml; + using DotNetOpenAuth.Messaging.Reflection; + + /// <summary> + /// Manages sending direct messages to a remote party and receiving responses. + /// </summary> + [SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Justification = "Unavoidable.")] + [ContractVerification(true)] + [ContractClass(typeof(ChannelContract))] + public abstract class Channel : IDisposable { + /// <summary> + /// The encoding to use when writing out POST entity strings. + /// </summary> + internal static readonly Encoding PostEntityEncoding = new UTF8Encoding(false); + + /// <summary> + /// The content-type used on HTTP POST requests where the POST entity is a + /// URL-encoded series of key=value pairs. + /// </summary> + protected internal const string HttpFormUrlEncoded = "application/x-www-form-urlencoded"; + + /// <summary> + /// The content-type used for JSON serialized objects. + /// </summary> + protected internal const string JsonEncoded = "application/json"; + + /// <summary> + /// The "text/javascript" content-type that some servers return instead of the standard <see cref="JsonEncoded"/> one. + /// </summary> + protected internal const string JsonTextEncoded = "text/javascript"; + + /// <summary> + /// The content-type for plain text. + /// </summary> + [SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "PlainText", Justification = "Not 'Plaintext' in the crypographic sense.")] + protected internal const string PlainTextEncoded = "text/plain"; + + /// <summary> + /// The content-type used on HTTP POST requests where the POST entity is a + /// URL-encoded series of key=value pairs. + /// This includes the <see cref="PostEntityEncoding"/> character encoding. + /// </summary> + protected internal static readonly ContentType HttpFormUrlEncodedContentType = new ContentType(HttpFormUrlEncoded) { CharSet = PostEntityEncoding.WebName }; + + /// <summary> + /// The HTML that should be returned to the user agent as part of a 301 Redirect. + /// </summary> + /// <value>A string that should be used as the first argument to String.Format, where the {0} should be replaced with the URL to redirect to.</value> + private const string RedirectResponseBodyFormat = @"<html><head><title>Object moved</title></head><body> +<h2>Object moved to <a href=""{0}"">here</a>.</h2> +</body></html>"; + + /// <summary> + /// A list of binding elements in the order they must be applied to outgoing messages. + /// </summary> + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private readonly List<IChannelBindingElement> outgoingBindingElements = new List<IChannelBindingElement>(); + + /// <summary> + /// A list of binding elements in the order they must be applied to incoming messages. + /// </summary> + private readonly List<IChannelBindingElement> incomingBindingElements = new List<IChannelBindingElement>(); + + /// <summary> + /// The template for indirect messages that require form POST to forward through the user agent. + /// </summary> + /// <remarks> + /// We are intentionally using " instead of the html single quote ' below because + /// the HtmlEncode'd values that we inject will only escape the double quote, so + /// only the double-quote used around these values is safe. + /// </remarks> + private const string IndirectMessageFormPostFormat = @" +<html> +<head> +</head> +<body onload=""document.body.style.display = 'none'; var btn = document.getElementById('submit_button'); btn.disabled = true; btn.value = 'Login in progress'; document.getElementById('openid_message').submit()""> +<form id=""openid_message"" action=""{0}"" method=""post"" accept-charset=""UTF-8"" enctype=""application/x-www-form-urlencoded"" onSubmit=""var btn = document.getElementById('submit_button'); btn.disabled = true; btn.value = 'Login in progress'; return true;""> +{1} + <input id=""submit_button"" type=""submit"" value=""Continue"" /> +</form> +</body> +</html> +"; + + /// <summary> + /// The default cache of message descriptions to use unless they are customized. + /// </summary> + /// <remarks> + /// This is a perf optimization, so that we don't reflect over every message type + /// every time a channel is constructed. + /// </remarks> + private static MessageDescriptionCollection defaultMessageDescriptions = new MessageDescriptionCollection(); + + /// <summary> + /// A cache of reflected message types that may be sent or received on this channel. + /// </summary> + private MessageDescriptionCollection messageDescriptions = defaultMessageDescriptions; + + /// <summary> + /// A tool that can figure out what kind of message is being received + /// so it can be deserialized. + /// </summary> + private IMessageFactory messageTypeProvider; + + /// <summary> + /// Backing store for the <see cref="CachePolicy"/> property. + /// </summary> + private RequestCachePolicy cachePolicy = new HttpRequestCachePolicy(HttpRequestCacheLevel.NoCacheNoStore); + + /// <summary> + /// Backing field for the <see cref="MaximumIndirectMessageUrlLength"/> property. + /// </summary> + private int maximumIndirectMessageUrlLength = Configuration.DotNetOpenAuthSection.Messaging.MaximumIndirectMessageUrlLength; + + /// <summary> + /// Initializes a new instance of the <see cref="Channel"/> class. + /// </summary> + /// <param name="messageTypeProvider"> + /// A class prepared to analyze incoming messages and indicate what concrete + /// message types can deserialize from it. + /// </param> + /// <param name="bindingElements">The binding elements to use in sending and receiving messages.</param> + protected Channel(IMessageFactory messageTypeProvider, params IChannelBindingElement[] bindingElements) { + Requires.NotNull(messageTypeProvider, "messageTypeProvider"); + + this.messageTypeProvider = messageTypeProvider; + this.WebRequestHandler = new StandardWebRequestHandler(); + this.XmlDictionaryReaderQuotas = new XmlDictionaryReaderQuotas { + MaxArrayLength = 1, + MaxDepth = 2, + MaxBytesPerRead = 8 * 1024, + MaxStringContentLength = 16 * 1024, + }; + + this.outgoingBindingElements = new List<IChannelBindingElement>(ValidateAndPrepareBindingElements(bindingElements)); + this.incomingBindingElements = new List<IChannelBindingElement>(this.outgoingBindingElements); + this.incomingBindingElements.Reverse(); + + foreach (var element in this.outgoingBindingElements) { + element.Channel = this; + } + } + + /// <summary> + /// An event fired whenever a message is about to be encoded and sent. + /// </summary> + internal event EventHandler<ChannelEventArgs> Sending; + + /// <summary> + /// Gets or sets an instance to a <see cref="IDirectWebRequestHandler"/> that will be used when + /// submitting HTTP requests and waiting for responses. + /// </summary> + /// <remarks> + /// This defaults to a straightforward implementation, but can be set + /// to a mock object for testing purposes. + /// </remarks> + public IDirectWebRequestHandler WebRequestHandler { get; set; } + + /// <summary> + /// Gets or sets the maximum allowable size for a 301 Redirect response before we send + /// a 200 OK response with a scripted form POST with the parameters instead + /// in order to ensure successfully sending a large payload to another server + /// that might have a maximum allowable size restriction on its GET request. + /// </summary> + /// <value>The default value is 2048.</value> + public int MaximumIndirectMessageUrlLength { + get { + return this.maximumIndirectMessageUrlLength; + } + + set { + Requires.InRange(value >= 500 && value <= 4096, "value"); + this.maximumIndirectMessageUrlLength = value; + } + } + + /// <summary> + /// Gets or sets the message descriptions. + /// </summary> + internal virtual MessageDescriptionCollection MessageDescriptions { + get { + return this.messageDescriptions; + } + + set { + Requires.NotNull(value, "value"); + this.messageDescriptions = value; + } + } + + /// <summary> + /// Gets a tool that can figure out what kind of message is being received + /// so it can be deserialized. + /// </summary> + internal IMessageFactory MessageFactoryTestHook { + get { return this.MessageFactory; } + } + + /// <summary> + /// Gets the binding elements used by this channel, in no particular guaranteed order. + /// </summary> + protected internal ReadOnlyCollection<IChannelBindingElement> BindingElements { + get { + Contract.Ensures(Contract.Result<ReadOnlyCollection<IChannelBindingElement>>() != null); + var result = this.outgoingBindingElements.AsReadOnly(); + Contract.Assume(result != null); // should be an implicit BCL contract + return result; + } + } + + /// <summary> + /// Gets the binding elements used by this channel, in the order applied to outgoing messages. + /// </summary> + protected internal ReadOnlyCollection<IChannelBindingElement> OutgoingBindingElements { + get { return this.outgoingBindingElements.AsReadOnly(); } + } + + /// <summary> + /// Gets the binding elements used by this channel, in the order applied to incoming messages. + /// </summary> + protected internal ReadOnlyCollection<IChannelBindingElement> IncomingBindingElements { + get { + Contract.Ensures(Contract.Result<ReadOnlyCollection<IChannelBindingElement>>().All(be => be.Channel != null)); + Contract.Ensures(Contract.Result<ReadOnlyCollection<IChannelBindingElement>>().All(be => be != null)); + return this.incomingBindingElements.AsReadOnly(); + } + } + + /// <summary> + /// Gets or sets a value indicating whether this instance is disposed. + /// </summary> + /// <value> + /// <c>true</c> if this instance is disposed; otherwise, <c>false</c>. + /// </value> + protected internal bool IsDisposed { get; set; } + + /// <summary> + /// Gets or sets a tool that can figure out what kind of message is being received + /// so it can be deserialized. + /// </summary> + protected virtual IMessageFactory MessageFactory { + get { return this.messageTypeProvider; } + set { this.messageTypeProvider = value; } + } + + /// <summary> + /// Gets or sets the cache policy to use for direct message requests. + /// </summary> + /// <value>Default is <see cref="HttpRequestCacheLevel.NoCacheNoStore"/>.</value> + protected RequestCachePolicy CachePolicy { + get { + return this.cachePolicy; + } + + set { + Requires.NotNull(value, "value"); + this.cachePolicy = value; + } + } + + /// <summary> + /// Gets or sets the XML dictionary reader quotas. + /// </summary> + /// <value>The XML dictionary reader quotas.</value> + protected virtual XmlDictionaryReaderQuotas XmlDictionaryReaderQuotas { get; set; } + + /// <summary> + /// Sends an indirect message (either a request or response) + /// or direct message response for transmission to a remote party + /// and ends execution on the current page or handler. + /// </summary> + /// <param name="message">The one-way message to send</param> + /// <exception cref="ThreadAbortException">Thrown by ASP.NET in order to prevent additional data from the page being sent to the client and corrupting the response.</exception> + /// <remarks> + /// Requires an HttpContext.Current context. + /// </remarks> + [EditorBrowsable(EditorBrowsableState.Never)] + public void Send(IProtocolMessage message) { + Requires.ValidState(HttpContext.Current != null, MessagingStrings.CurrentHttpContextRequired); + Requires.NotNull(message, "message"); + this.PrepareResponse(message).Respond(HttpContext.Current, true); + } + + /// <summary> + /// Sends an indirect message (either a request or response) + /// or direct message response for transmission to a remote party + /// and skips most of the remaining ASP.NET request handling pipeline. + /// Not safe to call from ASP.NET web forms. + /// </summary> + /// <param name="message">The one-way message to send</param> + /// <remarks> + /// Requires an HttpContext.Current context. + /// This call is not safe to make from an ASP.NET web form (.aspx file or code-behind) because + /// ASP.NET will render HTML after the protocol message has been sent, which will corrupt the response. + /// Use the <see cref="Send"/> method instead for web forms. + /// </remarks> + public void Respond(IProtocolMessage message) { + Requires.ValidState(HttpContext.Current != null, MessagingStrings.CurrentHttpContextRequired); + Requires.NotNull(message, "message"); + this.PrepareResponse(message).Respond(); + } + + /// <summary> + /// Prepares an indirect message (either a request or response) + /// or direct message response for transmission to a remote party. + /// </summary> + /// <param name="message">The one-way message to send</param> + /// <returns>The pending user agent redirect based message to be sent as an HttpResponse.</returns> + public OutgoingWebResponse PrepareResponse(IProtocolMessage message) { + Requires.NotNull(message, "message"); + Contract.Ensures(Contract.Result<OutgoingWebResponse>() != null); + + this.ProcessOutgoingMessage(message); + Logger.Channel.DebugFormat("Sending message: {0}", message.GetType().Name); + + OutgoingWebResponse result; + switch (message.Transport) { + case MessageTransport.Direct: + // This is a response to a direct message. + result = this.PrepareDirectResponse(message); + break; + case MessageTransport.Indirect: + var directedMessage = message as IDirectedProtocolMessage; + ErrorUtilities.VerifyArgumentNamed( + directedMessage != null, + "message", + MessagingStrings.IndirectMessagesMustImplementIDirectedProtocolMessage, + typeof(IDirectedProtocolMessage).FullName); + ErrorUtilities.VerifyArgumentNamed( + directedMessage.Recipient != null, + "message", + MessagingStrings.DirectedMessageMissingRecipient); + result = this.PrepareIndirectResponse(directedMessage); + break; + default: + throw ErrorUtilities.ThrowArgumentNamed( + "message", + MessagingStrings.UnrecognizedEnumValue, + "Transport", + message.Transport); + } + + // Apply caching policy to any response. We want to disable all caching because in auth* protocols, + // caching can be utilized in identity spoofing attacks. + result.Headers[HttpResponseHeader.CacheControl] = "no-cache, no-store, max-age=0, must-revalidate"; + result.Headers[HttpResponseHeader.Pragma] = "no-cache"; + + return result; + } + + /// <summary> + /// Gets the protocol message embedded in the given HTTP request, if present. + /// </summary> + /// <returns>The deserialized message, if one is found. Null otherwise.</returns> + /// <remarks> + /// Requires an HttpContext.Current context. + /// </remarks> + /// <exception cref="InvalidOperationException">Thrown when <see cref="HttpContext.Current"/> is null.</exception> + public IDirectedProtocolMessage ReadFromRequest() { + return this.ReadFromRequest(this.GetRequestFromContext()); + } + + /// <summary> + /// Gets the protocol message embedded in the given HTTP request, if present. + /// </summary> + /// <typeparam name="TRequest">The expected type of the message to be received.</typeparam> + /// <param name="request">The deserialized message, if one is found. Null otherwise.</param> + /// <returns>True if the expected message was recognized and deserialized. False otherwise.</returns> + /// <remarks> + /// Requires an HttpContext.Current context. + /// </remarks> + /// <exception cref="InvalidOperationException">Thrown when <see cref="HttpContext.Current"/> is null.</exception> + /// <exception cref="ProtocolException">Thrown when a request message of an unexpected type is received.</exception> + public bool TryReadFromRequest<TRequest>(out TRequest request) + where TRequest : class, IProtocolMessage { + return TryReadFromRequest<TRequest>(this.GetRequestFromContext(), out request); + } + + /// <summary> + /// Gets the protocol message embedded in the given HTTP request, if present. + /// </summary> + /// <typeparam name="TRequest">The expected type of the message to be received.</typeparam> + /// <param name="httpRequest">The request to search for an embedded message.</param> + /// <param name="request">The deserialized message, if one is found. Null otherwise.</param> + /// <returns>True if the expected message was recognized and deserialized. False otherwise.</returns> + /// <exception cref="InvalidOperationException">Thrown when <see cref="HttpContext.Current"/> is null.</exception> + /// <exception cref="ProtocolException">Thrown when a request message of an unexpected type is received.</exception> + public bool TryReadFromRequest<TRequest>(HttpRequestInfo httpRequest, out TRequest request) + where TRequest : class, IProtocolMessage { + Requires.NotNull(httpRequest, "httpRequest"); + Contract.Ensures(Contract.Result<bool>() == (Contract.ValueAtReturn<TRequest>(out request) != null)); + + IProtocolMessage untypedRequest = this.ReadFromRequest(httpRequest); + if (untypedRequest == null) { + request = null; + return false; + } + + request = untypedRequest as TRequest; + ErrorUtilities.VerifyProtocol(request != null, MessagingStrings.UnexpectedMessageReceived, typeof(TRequest), untypedRequest.GetType()); + + return true; + } + + /// <summary> + /// Gets the protocol message embedded in the current HTTP request. + /// </summary> + /// <typeparam name="TRequest">The expected type of the message to be received.</typeparam> + /// <returns>The deserialized message. Never null.</returns> + /// <remarks> + /// Requires an HttpContext.Current context. + /// </remarks> + /// <exception cref="InvalidOperationException">Thrown when <see cref="HttpContext.Current"/> is null.</exception> + /// <exception cref="ProtocolException">Thrown if the expected message was not recognized in the response.</exception> + [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "This returns and verifies the appropriate message type.")] + public TRequest ReadFromRequest<TRequest>() + where TRequest : class, IProtocolMessage { + return this.ReadFromRequest<TRequest>(this.GetRequestFromContext()); + } + + /// <summary> + /// Gets the protocol message embedded in the given HTTP request. + /// </summary> + /// <typeparam name="TRequest">The expected type of the message to be received.</typeparam> + /// <param name="httpRequest">The request to search for an embedded message.</param> + /// <returns>The deserialized message. Never null.</returns> + /// <exception cref="ProtocolException">Thrown if the expected message was not recognized in the response.</exception> + [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "This returns and verifies the appropriate message type.")] + public TRequest ReadFromRequest<TRequest>(HttpRequestInfo httpRequest) + where TRequest : class, IProtocolMessage { + Requires.NotNull(httpRequest, "httpRequest"); + TRequest request; + if (this.TryReadFromRequest<TRequest>(httpRequest, out request)) { + return request; + } else { + throw ErrorUtilities.ThrowProtocol(MessagingStrings.ExpectedMessageNotReceived, typeof(TRequest)); + } + } + + /// <summary> + /// Gets the protocol message that may be embedded in the given HTTP request. + /// </summary> + /// <param name="httpRequest">The request to search for an embedded message.</param> + /// <returns>The deserialized message, if one is found. Null otherwise.</returns> + public IDirectedProtocolMessage ReadFromRequest(HttpRequestInfo httpRequest) { + Requires.NotNull(httpRequest, "httpRequest"); + + if (Logger.Channel.IsInfoEnabled && httpRequest.UrlBeforeRewriting != null) { + Logger.Channel.InfoFormat("Scanning incoming request for messages: {0}", httpRequest.UrlBeforeRewriting.AbsoluteUri); + } + IDirectedProtocolMessage requestMessage = this.ReadFromRequestCore(httpRequest); + if (requestMessage != null) { + Logger.Channel.DebugFormat("Incoming request received: {0}", requestMessage.GetType().Name); + this.ProcessIncomingMessage(requestMessage); + } + + return requestMessage; + } + + /// <summary> + /// Sends a direct message to a remote party and waits for the response. + /// </summary> + /// <typeparam name="TResponse">The expected type of the message to be received.</typeparam> + /// <param name="requestMessage">The message to send.</param> + /// <returns>The remote party's response.</returns> + /// <exception cref="ProtocolException"> + /// Thrown if no message is recognized in the response + /// or an unexpected type of message is received. + /// </exception> + [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "This returns and verifies the appropriate message type.")] + public TResponse Request<TResponse>(IDirectedProtocolMessage requestMessage) + where TResponse : class, IProtocolMessage { + Requires.NotNull(requestMessage, "requestMessage"); + Contract.Ensures(Contract.Result<TResponse>() != null); + + IProtocolMessage response = this.Request(requestMessage); + ErrorUtilities.VerifyProtocol(response != null, MessagingStrings.ExpectedMessageNotReceived, typeof(TResponse)); + + var expectedResponse = response as TResponse; + ErrorUtilities.VerifyProtocol(expectedResponse != null, MessagingStrings.UnexpectedMessageReceived, typeof(TResponse), response.GetType()); + + return expectedResponse; + } + + /// <summary> + /// Sends a direct message to a remote party and waits for the response. + /// </summary> + /// <param name="requestMessage">The message to send.</param> + /// <returns>The remote party's response. Guaranteed to never be null.</returns> + /// <exception cref="ProtocolException">Thrown if the response does not include a protocol message.</exception> + public IProtocolMessage Request(IDirectedProtocolMessage requestMessage) { + Requires.NotNull(requestMessage, "requestMessage"); + + this.ProcessOutgoingMessage(requestMessage); + Logger.Channel.DebugFormat("Sending {0} request.", requestMessage.GetType().Name); + var responseMessage = this.RequestCore(requestMessage); + ErrorUtilities.VerifyProtocol(responseMessage != null, MessagingStrings.ExpectedMessageNotReceived, typeof(IProtocolMessage).Name); + + Logger.Channel.DebugFormat("Received {0} response.", responseMessage.GetType().Name); + this.ProcessIncomingMessage(responseMessage); + + return responseMessage; + } + + #region IDisposable Members + + /// <summary> + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// </summary> + public void Dispose() { + this.Dispose(true); + GC.SuppressFinalize(this); + } + + #endregion + + /// <summary> + /// Verifies the integrity and applicability of an incoming message. + /// </summary> + /// <param name="message">The message just received.</param> + /// <exception cref="ProtocolException"> + /// Thrown when the message is somehow invalid. + /// This can be due to tampering, replay attack or expiration, among other things. + /// </exception> + internal void ProcessIncomingMessageTestHook(IProtocolMessage message) { + this.ProcessIncomingMessage(message); + } + + /// <summary> + /// Prepares an HTTP request that carries a given message. + /// </summary> + /// <param name="request">The message to send.</param> + /// <returns>The <see cref="HttpWebRequest"/> prepared to send the request.</returns> + /// <remarks> + /// This method must be overridden by a derived class, unless the <see cref="RequestCore"/> method + /// is overridden and does not require this method. + /// </remarks> + internal HttpWebRequest CreateHttpRequestTestHook(IDirectedProtocolMessage request) { + return this.CreateHttpRequest(request); + } + + /// <summary> + /// Queues a message for sending in the response stream where the fields + /// are sent in the response stream in querystring style. + /// </summary> + /// <param name="response">The message to send as a response.</param> + /// <returns>The pending user agent redirect based message to be sent as an HttpResponse.</returns> + /// <remarks> + /// This method implements spec OAuth V1.0 section 5.3. + /// </remarks> + internal OutgoingWebResponse PrepareDirectResponseTestHook(IProtocolMessage response) { + return this.PrepareDirectResponse(response); + } + + /// <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> + internal IDictionary<string, string> ReadFromResponseCoreTestHook(IncomingWebResponse response) { + return this.ReadFromResponseCore(response); + } + + /// <remarks> + /// This method should NOT be called by derived types + /// except when sending ONE WAY request messages. + /// </remarks> + /// <summary> + /// Prepares a message for transmit by applying signatures, nonces, etc. + /// </summary> + /// <param name="message">The message to prepare for sending.</param> + internal void ProcessOutgoingMessageTestHook(IProtocolMessage message) { + this.ProcessOutgoingMessage(message); + } + + /// <summary> + /// Gets the current HTTP request being processed. + /// </summary> + /// <returns>The HttpRequestInfo for the current request.</returns> + /// <remarks> + /// Requires an <see cref="HttpContext.Current"/> context. + /// </remarks> + /// <exception cref="InvalidOperationException">Thrown if <see cref="HttpContext.Current">HttpContext.Current</see> == <c>null</c>.</exception> + [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "Costly call should not be a property.")] + protected internal virtual HttpRequestInfo GetRequestFromContext() { + Requires.ValidState(HttpContext.Current != null && HttpContext.Current.Request != null, MessagingStrings.HttpContextRequired); + Contract.Ensures(Contract.Result<HttpRequestInfo>() != null); + Contract.Ensures(Contract.Result<HttpRequestInfo>().Url != null); + Contract.Ensures(Contract.Result<HttpRequestInfo>().RawUrl != null); + Contract.Ensures(Contract.Result<HttpRequestInfo>().UrlBeforeRewriting != null); + + Contract.Assume(HttpContext.Current.Request.Url != null); + Contract.Assume(HttpContext.Current.Request.RawUrl != null); + return new HttpRequestInfo(HttpContext.Current.Request); + } + + /// <summary> + /// Checks whether a given HTTP method is expected to include an entity body in its request. + /// </summary> + /// <param name="httpMethod">The HTTP method.</param> + /// <returns><c>true</c> if the HTTP method is supposed to have an entity; <c>false</c> otherwise.</returns> + protected static bool HttpMethodHasEntity(string httpMethod) { + if (string.Equals(httpMethod, "GET", StringComparison.Ordinal) || + string.Equals(httpMethod, "HEAD", StringComparison.Ordinal) || + string.Equals(httpMethod, "DELETE", StringComparison.Ordinal)) { + return false; + } else if (string.Equals(httpMethod, "POST", StringComparison.Ordinal) || + string.Equals(httpMethod, "PUT", StringComparison.Ordinal)) { + return true; + } else { + throw ErrorUtilities.ThrowArgumentNamed("httpMethod", MessagingStrings.UnsupportedHttpVerb, httpMethod); + } + } + + /// <summary> + /// Releases unmanaged and - optionally - managed resources + /// </summary> + /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param> + protected virtual void Dispose(bool disposing) { + if (disposing) { + // Call dispose on any binding elements that need it. + foreach (IDisposable bindingElement in this.BindingElements.OfType<IDisposable>()) { + bindingElement.Dispose(); + } + + this.IsDisposed = true; + } + } + + /// <summary> + /// Fires the <see cref="Sending"/> event. + /// </summary> + /// <param name="message">The message about to be encoded and sent.</param> + protected virtual void OnSending(IProtocolMessage message) { + Requires.NotNull(message, "message"); + + var sending = this.Sending; + if (sending != null) { + sending(this, new ChannelEventArgs(message)); + } + } + + /// <summary> + /// Gets the direct response of a direct HTTP request. + /// </summary> + /// <param name="webRequest">The web request.</param> + /// <returns>The response to the web request.</returns> + /// <exception cref="ProtocolException">Thrown on network or protocol errors.</exception> + protected virtual IncomingWebResponse GetDirectResponse(HttpWebRequest webRequest) { + Requires.NotNull(webRequest, "webRequest"); + return this.WebRequestHandler.GetResponse(webRequest); + } + + /// <summary> + /// Submits a direct request message to some remote party and blocks waiting for an immediately reply. + /// </summary> + /// <param name="request">The request message.</param> + /// <returns>The response message, or null if the response did not carry a message.</returns> + /// <remarks> + /// Typically a deriving channel will override <see cref="CreateHttpRequest"/> to customize this method's + /// behavior. However in non-HTTP frameworks, such as unit test mocks, it may be appropriate to override + /// this method to eliminate all use of an HTTP transport. + /// </remarks> + protected virtual IProtocolMessage RequestCore(IDirectedProtocolMessage request) { + Requires.NotNull(request, "request"); + Requires.True(request.Recipient != null, "request", MessagingStrings.DirectedMessageMissingRecipient); + + HttpWebRequest webRequest = this.CreateHttpRequest(request); + IDictionary<string, string> responseFields; + IDirectResponseProtocolMessage responseMessage; + + using (IncomingWebResponse response = this.GetDirectResponse(webRequest)) { + if (response.ResponseStream == null) { + return null; + } + + responseFields = this.ReadFromResponseCore(response); + if (responseFields == null) { + return null; + } + + responseMessage = this.MessageFactory.GetNewResponseMessage(request, responseFields); + if (responseMessage == null) { + return null; + } + + this.OnReceivingDirectResponse(response, responseMessage); + } + + var messageAccessor = this.MessageDescriptions.GetAccessor(responseMessage); + messageAccessor.Deserialize(responseFields); + + return responseMessage; + } + + /// <summary> + /// Called when receiving a direct response message, before deserialization begins. + /// </summary> + /// <param name="response">The HTTP direct response.</param> + /// <param name="message">The newly instantiated message, prior to deserialization.</param> + protected virtual void OnReceivingDirectResponse(IncomingWebResponse response, IDirectResponseProtocolMessage message) { + } + + /// <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 virtual IDirectedProtocolMessage ReadFromRequestCore(HttpRequestInfo request) { + Requires.NotNull(request, "request"); + + Logger.Channel.DebugFormat("Incoming HTTP request: {0} {1}", request.HttpMethod, request.UrlBeforeRewriting.AbsoluteUri); + + // Search Form data first, and if nothing is there search the QueryString + Contract.Assume(request.Form != null && request.QueryStringBeforeRewriting != null); + var fields = request.Form.ToDictionary(); + if (fields.Count == 0 && request.HttpMethod != "POST") { // OpenID 2.0 section 4.1.2 + fields = request.QueryStringBeforeRewriting.ToDictionary(); + } + + MessageReceivingEndpoint recipient; + try { + recipient = request.GetRecipient(); + } catch (ArgumentException ex) { + Logger.Messaging.WarnFormat("Unrecognized HTTP request: {0}", ex); + return null; + } + + return (IDirectedProtocolMessage)this.Receive(fields, recipient); + } + + /// <summary> + /// Deserializes a dictionary of values into a message. + /// </summary> + /// <param name="fields">The dictionary of values that were read from an HTTP request or response.</param> + /// <param name="recipient">Information about where the message was directed. Null for direct response messages.</param> + /// <returns>The deserialized message, or null if no message could be recognized in the provided data.</returns> + protected virtual IProtocolMessage Receive(Dictionary<string, string> fields, MessageReceivingEndpoint recipient) { + Requires.NotNull(fields, "fields"); + + this.FilterReceivedFields(fields); + IProtocolMessage message = this.MessageFactory.GetNewRequestMessage(recipient, fields); + + // If there was no data, or we couldn't recognize it as a message, abort. + if (message == null) { + return null; + } + + // Ensure that the message came in using an allowed HTTP verb for this message type. + var directedMessage = message as IDirectedProtocolMessage; + ErrorUtilities.VerifyProtocol(recipient == null || (directedMessage != null && (recipient.AllowedMethods & directedMessage.HttpMethods) != 0), MessagingStrings.UnsupportedHttpVerbForMessageType, message.GetType().Name, recipient.AllowedMethods); + + // We have a message! Assemble it. + var messageAccessor = this.MessageDescriptions.GetAccessor(message); + messageAccessor.Deserialize(fields); + + return message; + } + + /// <summary> + /// Queues an indirect message for transmittal via the user agent. + /// </summary> + /// <param name="message">The message to send.</param> + /// <returns>The pending user agent redirect based message to be sent as an HttpResponse.</returns> + protected virtual OutgoingWebResponse PrepareIndirectResponse(IDirectedProtocolMessage message) { + Requires.NotNull(message, "message"); + Requires.True(message.Recipient != null, "message", MessagingStrings.DirectedMessageMissingRecipient); + Requires.True((message.HttpMethods & (HttpDeliveryMethods.GetRequest | HttpDeliveryMethods.PostRequest)) != 0, "message"); + Contract.Ensures(Contract.Result<OutgoingWebResponse>() != null); + + Contract.Assert(message != null && message.Recipient != null); + var messageAccessor = this.MessageDescriptions.GetAccessor(message); + Contract.Assert(message != null && message.Recipient != null); + var fields = messageAccessor.Serialize(); + + OutgoingWebResponse response = null; + bool tooLargeForGet = false; + if ((message.HttpMethods & HttpDeliveryMethods.GetRequest) == HttpDeliveryMethods.GetRequest) { + bool payloadInFragment = false; + var httpIndirect = message as IHttpIndirectResponse; + if (httpIndirect != null) { + payloadInFragment = httpIndirect.Include301RedirectPayloadInFragment; + } + + // First try creating a 301 redirect, and fallback to a form POST + // if the message is too big. + response = this.Create301RedirectResponse(message, fields, payloadInFragment); + tooLargeForGet = response.Headers[HttpResponseHeader.Location].Length > this.MaximumIndirectMessageUrlLength; + } + + // Make sure that if the message is too large for GET that POST is allowed. + if (tooLargeForGet) { + ErrorUtilities.VerifyProtocol( + (message.HttpMethods & HttpDeliveryMethods.PostRequest) == HttpDeliveryMethods.PostRequest, + "Message too large for a HTTP GET, and HTTP POST is not allowed for this message type."); + } + + // If GET didn't work out, for whatever reason... + if (response == null || tooLargeForGet) { + response = this.CreateFormPostResponse(message, fields); + } + + return response; + } + + /// <summary> + /// Encodes an HTTP response that will instruct the user agent to forward a message to + /// some remote third party using a 301 Redirect GET method. + /// </summary> + /// <param name="message">The message to forward.</param> + /// <param name="fields">The pre-serialized fields from the message.</param> + /// <param name="payloadInFragment">if set to <c>true</c> the redirect will contain the message payload in the #fragment portion of the URL rather than the ?querystring.</param> + /// <returns>The encoded HTTP response.</returns> + [Pure] + protected virtual OutgoingWebResponse Create301RedirectResponse(IDirectedProtocolMessage message, IDictionary<string, string> fields, bool payloadInFragment = false) { + Requires.NotNull(message, "message"); + Requires.True(message.Recipient != null, "message", MessagingStrings.DirectedMessageMissingRecipient); + Requires.NotNull(fields, "fields"); + Contract.Ensures(Contract.Result<OutgoingWebResponse>() != null); + + // As part of this redirect, we include an HTML body in order to get passed some proxy filters + // such as WebSense. + WebHeaderCollection headers = new WebHeaderCollection(); + UriBuilder builder = new UriBuilder(message.Recipient); + if (payloadInFragment) { + builder.AppendFragmentArgs(fields); + } else { + builder.AppendQueryArgs(fields); + } + + headers.Add(HttpResponseHeader.Location, builder.Uri.AbsoluteUri); + headers.Add(HttpResponseHeader.ContentType, "text/html; charset=utf-8"); + Logger.Http.DebugFormat("Redirecting to {0}", builder.Uri.AbsoluteUri); + OutgoingWebResponse response = new OutgoingWebResponse { + Status = HttpStatusCode.Redirect, + Headers = headers, + Body = string.Format(CultureInfo.InvariantCulture, RedirectResponseBodyFormat, builder.Uri.AbsoluteUri), + OriginalMessage = message + }; + + return response; + } + + /// <summary> + /// Encodes an HTTP response that will instruct the user agent to forward a message to + /// some remote third party using a form POST method. + /// </summary> + /// <param name="message">The message to forward.</param> + /// <param name="fields">The pre-serialized fields from the message.</param> + /// <returns>The encoded HTTP response.</returns> + protected virtual OutgoingWebResponse CreateFormPostResponse(IDirectedProtocolMessage message, IDictionary<string, string> fields) { + Requires.NotNull(message, "message"); + Requires.True(message.Recipient != null, "message", MessagingStrings.DirectedMessageMissingRecipient); + Requires.NotNull(fields, "fields"); + Contract.Ensures(Contract.Result<OutgoingWebResponse>() != null); + + WebHeaderCollection headers = new WebHeaderCollection(); + headers.Add(HttpResponseHeader.ContentType, "text/html"); + using (StringWriter bodyWriter = new StringWriter(CultureInfo.InvariantCulture)) { + StringBuilder hiddenFields = new StringBuilder(); + foreach (var field in fields) { + hiddenFields.AppendFormat( + "\t<input type=\"hidden\" name=\"{0}\" value=\"{1}\" />\r\n", + HttpUtility.HtmlEncode(field.Key), + HttpUtility.HtmlEncode(field.Value)); + } + bodyWriter.WriteLine( + IndirectMessageFormPostFormat, + HttpUtility.HtmlEncode(message.Recipient.AbsoluteUri), + hiddenFields); + bodyWriter.Flush(); + OutgoingWebResponse response = new OutgoingWebResponse { + Status = HttpStatusCode.OK, + Headers = headers, + Body = bodyWriter.ToString(), + OriginalMessage = message + }; + + return response; + } + } + + /// <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 abstract IDictionary<string, string> ReadFromResponseCore(IncomingWebResponse response); + + /// <summary> + /// Prepares an HTTP request that carries a given message. + /// </summary> + /// <param name="request">The message to send.</param> + /// <returns>The <see cref="HttpWebRequest"/> prepared to send the request.</returns> + /// <remarks> + /// This method must be overridden by a derived class, unless the <see cref="Channel.RequestCore"/> method + /// is overridden and does not require this method. + /// </remarks> + protected virtual HttpWebRequest CreateHttpRequest(IDirectedProtocolMessage request) { + Requires.NotNull(request, "request"); + Requires.True(request.Recipient != null, "request", MessagingStrings.DirectedMessageMissingRecipient); + Contract.Ensures(Contract.Result<HttpWebRequest>() != null); + throw new NotImplementedException(); + } + + /// <summary> + /// Queues a message for sending in the response stream where the fields + /// are sent in the response stream in querystring style. + /// </summary> + /// <param name="response">The message to send as a response.</param> + /// <returns>The pending user agent redirect based message to be sent as an HttpResponse.</returns> + /// <remarks> + /// This method implements spec OAuth V1.0 section 5.3. + /// </remarks> + protected abstract OutgoingWebResponse PrepareDirectResponse(IProtocolMessage response); + + /// <summary> + /// Serializes the given message as a JSON string. + /// </summary> + /// <param name="message">The message to serialize.</param> + /// <returns>A JSON string.</returns> + [SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times", Justification = "This Dispose is safe.")] + protected virtual string SerializeAsJson(IMessage message) { + Requires.NotNull(message, "message"); + + MessageDictionary messageDictionary = this.MessageDescriptions.GetAccessor(message); + using (var memoryStream = new MemoryStream()) { + using (var jsonWriter = JsonReaderWriterFactory.CreateJsonWriter(memoryStream, Encoding.UTF8)) { + MessageSerializer.Serialize(messageDictionary, jsonWriter); + jsonWriter.Flush(); + } + + string json = Encoding.UTF8.GetString(memoryStream.ToArray()); + return json; + } + } + + /// <summary> + /// Deserializes from flat data from a JSON object. + /// </summary> + /// <param name="json">A JSON string.</param> + /// <returns>The simple "key":"value" pairs from a JSON-encoded object.</returns> + protected virtual IDictionary<string, string> DeserializeFromJson(string json) { + Requires.NotNullOrEmpty(json, "json"); + + var dictionary = new Dictionary<string, string>(); + using (var jsonReader = JsonReaderWriterFactory.CreateJsonReader(Encoding.UTF8.GetBytes(json), this.XmlDictionaryReaderQuotas)) { + MessageSerializer.DeserializeJsonAsFlatDictionary(dictionary, jsonReader); + } + return dictionary; + } + + /// <summary> + /// Prepares a message for transmit by applying signatures, nonces, etc. + /// </summary> + /// <param name="message">The message to prepare for sending.</param> + /// <remarks> + /// This method should NOT be called by derived types + /// except when sending ONE WAY request messages. + /// </remarks> + protected void ProcessOutgoingMessage(IProtocolMessage message) { + Requires.NotNull(message, "message"); + + Logger.Channel.DebugFormat("Preparing to send {0} ({1}) message.", message.GetType().Name, message.Version); + this.OnSending(message); + + // Give the message a chance to do custom serialization. + IMessageWithEvents eventedMessage = message as IMessageWithEvents; + if (eventedMessage != null) { + eventedMessage.OnSending(); + } + + MessageProtections appliedProtection = MessageProtections.None; + foreach (IChannelBindingElement bindingElement in this.outgoingBindingElements) { + Contract.Assume(bindingElement.Channel != null); + MessageProtections? elementProtection = bindingElement.ProcessOutgoingMessage(message); + if (elementProtection.HasValue) { + Logger.Bindings.DebugFormat("Binding element {0} applied to message.", bindingElement.GetType().FullName); + + // Ensure that only one protection binding element applies to this message + // for each protection type. + ErrorUtilities.VerifyProtocol((appliedProtection & elementProtection.Value) == 0, MessagingStrings.TooManyBindingsOfferingSameProtection, elementProtection.Value); + appliedProtection |= elementProtection.Value; + } else { + Logger.Bindings.DebugFormat("Binding element {0} did not apply to message.", bindingElement.GetType().FullName); + } + } + + // Ensure that the message's protection requirements have been satisfied. + if ((message.RequiredProtection & appliedProtection) != message.RequiredProtection) { + throw new UnprotectedMessageException(message, appliedProtection); + } + + this.EnsureValidMessageParts(message); + message.EnsureValidMessage(); + + if (Logger.Channel.IsInfoEnabled) { + var directedMessage = message as IDirectedProtocolMessage; + string recipient = (directedMessage != null && directedMessage.Recipient != null) ? directedMessage.Recipient.AbsoluteUri : "<response>"; + var messageAccessor = this.MessageDescriptions.GetAccessor(message); + Logger.Channel.InfoFormat( + "Prepared outgoing {0} ({1}) message for {2}: {3}{4}", + message.GetType().Name, + message.Version, + recipient, + Environment.NewLine, + messageAccessor.ToStringDeferred()); + } + } + + /// <summary> + /// Prepares to send a request to the Service Provider as the query string in a GET request. + /// </summary> + /// <param name="requestMessage">The message to be transmitted to the ServiceProvider.</param> + /// <returns>The web request ready to send.</returns> + /// <remarks> + /// This method is simply a standard HTTP Get request with the message parts serialized to the query string. + /// This method satisfies OAuth 1.0 section 5.2, item #3. + /// </remarks> + protected virtual HttpWebRequest InitializeRequestAsGet(IDirectedProtocolMessage requestMessage) { + Requires.NotNull(requestMessage, "requestMessage"); + Requires.True(requestMessage.Recipient != null, "requestMessage", MessagingStrings.DirectedMessageMissingRecipient); + + var messageAccessor = this.MessageDescriptions.GetAccessor(requestMessage); + var fields = messageAccessor.Serialize(); + + UriBuilder builder = new UriBuilder(requestMessage.Recipient); + MessagingUtilities.AppendQueryArgs(builder, fields); + HttpWebRequest httpRequest = (HttpWebRequest)WebRequest.Create(builder.Uri); + + return httpRequest; + } + + /// <summary> + /// Prepares to send a request to the Service Provider as the query string in a HEAD request. + /// </summary> + /// <param name="requestMessage">The message to be transmitted to the ServiceProvider.</param> + /// <returns>The web request ready to send.</returns> + /// <remarks> + /// This method is simply a standard HTTP HEAD request with the message parts serialized to the query string. + /// This method satisfies OAuth 1.0 section 5.2, item #3. + /// </remarks> + protected virtual HttpWebRequest InitializeRequestAsHead(IDirectedProtocolMessage requestMessage) { + Requires.NotNull(requestMessage, "requestMessage"); + Requires.True(requestMessage.Recipient != null, "requestMessage", MessagingStrings.DirectedMessageMissingRecipient); + + HttpWebRequest request = this.InitializeRequestAsGet(requestMessage); + request.Method = "HEAD"; + return request; + } + + /// <summary> + /// Prepares to send a request to the Service Provider as the payload of a POST request. + /// </summary> + /// <param name="requestMessage">The message to be transmitted to the ServiceProvider.</param> + /// <returns>The web request ready to send.</returns> + /// <remarks> + /// This method is simply a standard HTTP POST request with the message parts serialized to the POST entity + /// with the application/x-www-form-urlencoded content type + /// This method satisfies OAuth 1.0 section 5.2, item #2 and OpenID 2.0 section 4.1.2. + /// </remarks> + protected virtual HttpWebRequest InitializeRequestAsPost(IDirectedProtocolMessage requestMessage) { + Requires.NotNull(requestMessage, "requestMessage"); + Contract.Ensures(Contract.Result<HttpWebRequest>() != null); + + var messageAccessor = this.MessageDescriptions.GetAccessor(requestMessage); + var fields = messageAccessor.Serialize(); + + var httpRequest = (HttpWebRequest)WebRequest.Create(requestMessage.Recipient); + httpRequest.CachePolicy = this.CachePolicy; + httpRequest.Method = "POST"; + + var requestMessageWithBinaryData = requestMessage as IMessageWithBinaryData; + if (requestMessageWithBinaryData != null && requestMessageWithBinaryData.SendAsMultipart) { + var multiPartFields = new List<MultipartPostPart>(requestMessageWithBinaryData.BinaryData); + + // When sending multi-part, all data gets send as multi-part -- even the non-binary data. + multiPartFields.AddRange(fields.Select(field => MultipartPostPart.CreateFormPart(field.Key, field.Value))); + this.SendParametersInEntityAsMultipart(httpRequest, multiPartFields); + } else { + ErrorUtilities.VerifyProtocol(requestMessageWithBinaryData == null || requestMessageWithBinaryData.BinaryData.Count == 0, MessagingStrings.BinaryDataRequiresMultipart); + this.SendParametersInEntity(httpRequest, fields); + } + + return httpRequest; + } + + /// <summary> + /// Prepares to send a request to the Service Provider as the query string in a PUT request. + /// </summary> + /// <param name="requestMessage">The message to be transmitted to the ServiceProvider.</param> + /// <returns>The web request ready to send.</returns> + /// <remarks> + /// This method is simply a standard HTTP PUT request with the message parts serialized to the query string. + /// </remarks> + protected virtual HttpWebRequest InitializeRequestAsPut(IDirectedProtocolMessage requestMessage) { + Requires.NotNull(requestMessage, "requestMessage"); + Contract.Ensures(Contract.Result<HttpWebRequest>() != null); + + HttpWebRequest request = this.InitializeRequestAsGet(requestMessage); + request.Method = "PUT"; + return request; + } + + /// <summary> + /// Prepares to send a request to the Service Provider as the query string in a DELETE request. + /// </summary> + /// <param name="requestMessage">The message to be transmitted to the ServiceProvider.</param> + /// <returns>The web request ready to send.</returns> + /// <remarks> + /// This method is simply a standard HTTP DELETE request with the message parts serialized to the query string. + /// </remarks> + protected virtual HttpWebRequest InitializeRequestAsDelete(IDirectedProtocolMessage requestMessage) { + Requires.NotNull(requestMessage, "requestMessage"); + Contract.Ensures(Contract.Result<HttpWebRequest>() != null); + + HttpWebRequest request = this.InitializeRequestAsGet(requestMessage); + request.Method = "DELETE"; + return request; + } + + /// <summary> + /// Sends the given parameters in the entity stream of an HTTP request. + /// </summary> + /// <param name="httpRequest">The HTTP request.</param> + /// <param name="fields">The parameters to send.</param> + /// <remarks> + /// This method calls <see cref="HttpWebRequest.GetRequestStream()"/> and closes + /// the request stream, but does not call <see cref="HttpWebRequest.GetResponse"/>. + /// </remarks> + protected void SendParametersInEntity(HttpWebRequest httpRequest, IDictionary<string, string> fields) { + Requires.NotNull(httpRequest, "httpRequest"); + Requires.NotNull(fields, "fields"); + + string requestBody = MessagingUtilities.CreateQueryString(fields); + byte[] requestBytes = PostEntityEncoding.GetBytes(requestBody); + httpRequest.ContentType = HttpFormUrlEncodedContentType.ToString(); + httpRequest.ContentLength = requestBytes.Length; + Stream requestStream = this.WebRequestHandler.GetRequestStream(httpRequest); + try { + requestStream.Write(requestBytes, 0, requestBytes.Length); + } finally { + // We need to be sure to close the request stream... + // unless it is a MemoryStream, which is a clue that we're in + // a mock stream situation and closing it would preclude reading it later. + if (!(requestStream is MemoryStream)) { + requestStream.Dispose(); + } + } + } + + /// <summary> + /// Sends the given parameters in the entity stream of an HTTP request in multi-part format. + /// </summary> + /// <param name="httpRequest">The HTTP request.</param> + /// <param name="fields">The parameters to send.</param> + /// <remarks> + /// This method calls <see cref="HttpWebRequest.GetRequestStream()"/> and closes + /// the request stream, but does not call <see cref="HttpWebRequest.GetResponse"/>. + /// </remarks> + protected void SendParametersInEntityAsMultipart(HttpWebRequest httpRequest, IEnumerable<MultipartPostPart> fields) { + httpRequest.PostMultipartNoGetResponse(this.WebRequestHandler, fields); + } + + /// <summary> + /// Verifies the integrity and applicability of an incoming message. + /// </summary> + /// <param name="message">The message just received.</param> + /// <exception cref="ProtocolException"> + /// Thrown when the message is somehow invalid. + /// This can be due to tampering, replay attack or expiration, among other things. + /// </exception> + protected virtual void ProcessIncomingMessage(IProtocolMessage message) { + Requires.NotNull(message, "message"); + + if (Logger.Channel.IsInfoEnabled) { + var messageAccessor = this.MessageDescriptions.GetAccessor(message, true); + Logger.Channel.InfoFormat( + "Processing incoming {0} ({1}) message:{2}{3}", + message.GetType().Name, + message.Version, + Environment.NewLine, + messageAccessor.ToStringDeferred()); + } + + MessageProtections appliedProtection = MessageProtections.None; + foreach (IChannelBindingElement bindingElement in this.IncomingBindingElements) { + Contract.Assume(bindingElement.Channel != null); // CC bug: this.IncomingBindingElements ensures this... why must we assume it here? + MessageProtections? elementProtection = bindingElement.ProcessIncomingMessage(message); + if (elementProtection.HasValue) { + Logger.Bindings.DebugFormat("Binding element {0} applied to message.", bindingElement.GetType().FullName); + + // Ensure that only one protection binding element applies to this message + // for each protection type. + if ((appliedProtection & elementProtection.Value) != 0) { + // It turns out that this MAY not be a fatal error condition. + // But it may indicate a problem. + // Specifically, when this RP uses OpenID 1.x to talk to an OP, and both invent + // their own replay protection for OpenID 1.x, and the OP happens to reuse + // openid.response_nonce, then this RP may consider both the RP's own nonce and + // the OP's nonce and "apply" replay protection twice. This actually isn't a problem. + Logger.Bindings.WarnFormat(MessagingStrings.TooManyBindingsOfferingSameProtection, elementProtection.Value); + } + + appliedProtection |= elementProtection.Value; + } else { + Logger.Bindings.DebugFormat("Binding element {0} did not apply to message.", bindingElement.GetType().FullName); + } + } + + // Ensure that the message's protection requirements have been satisfied. + if ((message.RequiredProtection & appliedProtection) != message.RequiredProtection) { + throw new UnprotectedMessageException(message, appliedProtection); + } + + // Give the message a chance to do custom serialization. + IMessageWithEvents eventedMessage = message as IMessageWithEvents; + if (eventedMessage != null) { + eventedMessage.OnReceiving(); + } + + if (Logger.Channel.IsDebugEnabled) { + var messageAccessor = this.MessageDescriptions.GetAccessor(message); + Logger.Channel.DebugFormat( + "After binding element processing, the received {0} ({1}) message is: {2}{3}", + message.GetType().Name, + message.Version, + Environment.NewLine, + messageAccessor.ToStringDeferred()); + } + + // We do NOT verify that all required message parts are present here... the + // message deserializer did for us. It would be too late to do it here since + // they might look initialized by the time we have an IProtocolMessage instance. + message.EnsureValidMessage(); + } + + /// <summary> + /// Allows preprocessing and validation of message data before an appropriate message type is + /// selected or deserialized. + /// </summary> + /// <param name="fields">The received message data.</param> + protected virtual void FilterReceivedFields(IDictionary<string, string> fields) { + } + + /// <summary> + /// Customizes the binding element order for outgoing and incoming messages. + /// </summary> + /// <param name="outgoingOrder">The outgoing order.</param> + /// <param name="incomingOrder">The incoming order.</param> + /// <remarks> + /// No binding elements can be added or removed from the channel using this method. + /// Only a customized order is allowed. + /// </remarks> + /// <exception cref="ArgumentException">Thrown if a binding element is new or missing in one of the ordered lists.</exception> + protected void CustomizeBindingElementOrder(IEnumerable<IChannelBindingElement> outgoingOrder, IEnumerable<IChannelBindingElement> incomingOrder) { + Requires.NotNull(outgoingOrder, "outgoingOrder"); + Requires.NotNull(incomingOrder, "incomingOrder"); + ErrorUtilities.VerifyArgument(this.IsBindingElementOrderValid(outgoingOrder), MessagingStrings.InvalidCustomBindingElementOrder); + ErrorUtilities.VerifyArgument(this.IsBindingElementOrderValid(incomingOrder), MessagingStrings.InvalidCustomBindingElementOrder); + + this.outgoingBindingElements.Clear(); + this.outgoingBindingElements.AddRange(outgoingOrder); + this.incomingBindingElements.Clear(); + this.incomingBindingElements.AddRange(incomingOrder); + } + + /// <summary> + /// Ensures a consistent and secure set of binding elements and + /// sorts them as necessary for a valid sequence of operations. + /// </summary> + /// <param name="elements">The binding elements provided to the channel.</param> + /// <returns>The properly ordered list of elements.</returns> + /// <exception cref="ProtocolException">Thrown when the binding elements are incomplete or inconsistent with each other.</exception> + private static IEnumerable<IChannelBindingElement> ValidateAndPrepareBindingElements(IEnumerable<IChannelBindingElement> elements) { + Requires.NullOrWithNoNullElements(elements, "elements"); + Contract.Ensures(Contract.Result<IEnumerable<IChannelBindingElement>>() != null); + if (elements == null) { + return new IChannelBindingElement[0]; + } + + // Filter the elements between the mere transforming ones and the protection ones. + var transformationElements = new List<IChannelBindingElement>( + elements.Where(element => element.Protection == MessageProtections.None)); + var protectionElements = new List<IChannelBindingElement>( + elements.Where(element => element.Protection != MessageProtections.None)); + + bool wasLastProtectionPresent = true; + foreach (MessageProtections protectionKind in Enum.GetValues(typeof(MessageProtections))) { + if (protectionKind == MessageProtections.None) { + continue; + } + + int countProtectionsOfThisKind = protectionElements.Count(element => (element.Protection & protectionKind) == protectionKind); + + // Each protection binding element is backed by the presence of its dependent protection(s). + ErrorUtilities.VerifyProtocol(!(countProtectionsOfThisKind > 0 && !wasLastProtectionPresent), MessagingStrings.RequiredProtectionMissing, protectionKind); + + wasLastProtectionPresent = countProtectionsOfThisKind > 0; + } + + // Put the binding elements in order so they are correctly applied to outgoing messages. + // Start with the transforming (non-protecting) binding elements first and preserve their original order. + var orderedList = new List<IChannelBindingElement>(transformationElements); + + // Now sort the protection binding elements among themselves and add them to the list. + orderedList.AddRange(protectionElements.OrderBy(element => element.Protection, BindingElementOutgoingMessageApplicationOrder)); + return orderedList; + } + + /// <summary> + /// Puts binding elements in their correct outgoing message processing order. + /// </summary> + /// <param name="protection1">The first protection type to compare.</param> + /// <param name="protection2">The second protection type to compare.</param> + /// <returns> + /// -1 if <paramref name="protection1"/> should be applied to an outgoing message before <paramref name="protection2"/>. + /// 1 if <paramref name="protection2"/> should be applied to an outgoing message before <paramref name="protection1"/>. + /// 0 if it doesn't matter. + /// </returns> + private static int BindingElementOutgoingMessageApplicationOrder(MessageProtections protection1, MessageProtections protection2) { + ErrorUtilities.VerifyInternal(protection1 != MessageProtections.None || protection2 != MessageProtections.None, "This comparison function should only be used to compare protection binding elements. Otherwise we change the order of user-defined message transformations."); + + // Now put the protection ones in the right order. + return -((int)protection1).CompareTo((int)protection2); // descending flag ordinal order + } + +#if CONTRACTS_FULL + /// <summary> + /// Verifies conditions that should be true for any valid state of this object. + /// </summary> + [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Called by code contracts.")] + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called by code contracts.")] + [ContractInvariantMethod] + private void ObjectInvariant() { + Contract.Invariant(this.MessageDescriptions != null); + } +#endif + + /// <summary> + /// Verifies that all required message parts are initialized to values + /// prior to sending the message to a remote party. + /// </summary> + /// <param name="message">The message to verify.</param> + /// <exception cref="ProtocolException"> + /// Thrown when any required message part does not have a value. + /// </exception> + private void EnsureValidMessageParts(IProtocolMessage message) { + Requires.NotNull(message, "message"); + MessageDictionary dictionary = this.MessageDescriptions.GetAccessor(message); + MessageDescription description = this.MessageDescriptions.Get(message); + description.EnsureMessagePartsPassBasicValidation(dictionary); + } + + /// <summary> + /// Determines whether a given ordered list of binding elements includes every + /// binding element in this channel exactly once. + /// </summary> + /// <param name="order">The list of binding elements to test.</param> + /// <returns> + /// <c>true</c> if the given list is a valid description of a binding element ordering; otherwise, <c>false</c>. + /// </returns> + [Pure] + private bool IsBindingElementOrderValid(IEnumerable<IChannelBindingElement> order) { + Requires.NotNull(order, "order"); + + // Check that the same number of binding elements are defined. + if (order.Count() != this.OutgoingBindingElements.Count) { + return false; + } + + // Check that every binding element appears exactly once. + if (order.Any(el => !this.OutgoingBindingElements.Contains(el))) { + return false; + } + + return true; + } + } +} diff --git a/src/DotNetOpenAuth.Core/Messaging/ChannelContract.cs b/src/DotNetOpenAuth.Core/Messaging/ChannelContract.cs new file mode 100644 index 0000000..bf313ef --- /dev/null +++ b/src/DotNetOpenAuth.Core/Messaging/ChannelContract.cs @@ -0,0 +1,54 @@ +//----------------------------------------------------------------------- +// <copyright file="ChannelContract.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Messaging { + using System; + using System.Collections.Generic; + using System.Diagnostics.Contracts; + + /// <summary> + /// Code contract for the <see cref="Channel"/> class. + /// </summary> + [ContractClassFor(typeof(Channel))] + internal abstract class ChannelContract : Channel { + /// <summary> + /// Prevents a default instance of the ChannelContract class from being created. + /// </summary> + private ChannelContract() + : base(null, null) { + } + + /// <summary> + /// Gets the protocol message that may be in the given HTTP response. + /// </summary> + /// <param name="response">The response that is anticipated to contain an protocol message.</param> + /// <returns> + /// The deserialized message parts, if found. Null otherwise. + /// </returns> + /// <exception cref="ProtocolException">Thrown when the response is not valid.</exception> + protected override IDictionary<string, string> ReadFromResponseCore(IncomingWebResponse response) { + Requires.NotNull(response, "response"); + throw new NotImplementedException(); + } + + /// <summary> + /// Queues a message for sending in the response stream where the fields + /// are sent in the response stream in querystring style. + /// </summary> + /// <param name="response">The message to send as a response.</param> + /// <returns> + /// The pending user agent redirect based message to be sent as an HttpResponse. + /// </returns> + /// <remarks> + /// This method implements spec V1.0 section 5.3. + /// </remarks> + protected override OutgoingWebResponse PrepareDirectResponse(IProtocolMessage response) { + Requires.NotNull(response, "response"); + Contract.Ensures(Contract.Result<OutgoingWebResponse>() != null); + throw new NotImplementedException(); + } + } +} diff --git a/src/DotNetOpenAuth.Core/Messaging/ChannelEventArgs.cs b/src/DotNetOpenAuth.Core/Messaging/ChannelEventArgs.cs new file mode 100644 index 0000000..e09e655 --- /dev/null +++ b/src/DotNetOpenAuth.Core/Messaging/ChannelEventArgs.cs @@ -0,0 +1,30 @@ +//----------------------------------------------------------------------- +// <copyright file="ChannelEventArgs.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Messaging { + using System; + using System.Diagnostics.Contracts; + + /// <summary> + /// The data packet sent with Channel events. + /// </summary> + public class ChannelEventArgs : EventArgs { + /// <summary> + /// Initializes a new instance of the <see cref="ChannelEventArgs"/> class. + /// </summary> + /// <param name="message">The message behind the fired event..</param> + internal ChannelEventArgs(IProtocolMessage message) { + Requires.NotNull(message, "message"); + + this.Message = message; + } + + /// <summary> + /// Gets the message that caused the event to fire. + /// </summary> + public IProtocolMessage Message { get; private set; } + } +} diff --git a/src/DotNetOpenAuth/Messaging/DataBag.cs b/src/DotNetOpenAuth.Core/Messaging/DataBag.cs index 17a7bda..17a7bda 100644 --- a/src/DotNetOpenAuth/Messaging/DataBag.cs +++ b/src/DotNetOpenAuth.Core/Messaging/DataBag.cs diff --git a/src/DotNetOpenAuth.Core/Messaging/DataBagFormatterBase.cs b/src/DotNetOpenAuth.Core/Messaging/DataBagFormatterBase.cs new file mode 100644 index 0000000..86ada44 --- /dev/null +++ b/src/DotNetOpenAuth.Core/Messaging/DataBagFormatterBase.cs @@ -0,0 +1,354 @@ +//----------------------------------------------------------------------- +// <copyright file="DataBagFormatterBase.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Messaging { + using System; + using System.Collections.Generic; + using System.Diagnostics.Contracts; + using System.IO; + using System.Linq; + using System.Security.Cryptography; + using System.Text; + using System.Web; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.Messaging.Bindings; + using DotNetOpenAuth.Messaging.Reflection; + + /// <summary> + /// A serializer for <see cref="DataBag"/>-derived types + /// </summary> + /// <typeparam name="T">The DataBag-derived type that is to be serialized/deserialized.</typeparam> + internal abstract class DataBagFormatterBase<T> : IDataBagFormatter<T> where T : DataBag, new() { + /// <summary> + /// The message description cache to use for data bag types. + /// </summary> + protected static readonly MessageDescriptionCollection MessageDescriptions = new MessageDescriptionCollection(); + + /// <summary> + /// The length of the nonce to include in tokens that can be decoded once only. + /// </summary> + private const int NonceLength = 6; + + /// <summary> + /// The minimum allowable lifetime for the key used to encrypt/decrypt or sign this databag. + /// </summary> + private readonly TimeSpan minimumAge = TimeSpan.FromDays(1); + + /// <summary> + /// The symmetric key store with the secret used for signing/encryption of verification codes and refresh tokens. + /// </summary> + private readonly ICryptoKeyStore cryptoKeyStore; + + /// <summary> + /// The bucket for symmetric keys. + /// </summary> + private readonly string cryptoKeyBucket; + + /// <summary> + /// The crypto to use for signing access tokens. + /// </summary> + private readonly RSACryptoServiceProvider asymmetricSigning; + + /// <summary> + /// The crypto to use for encrypting access tokens. + /// </summary> + private readonly RSACryptoServiceProvider asymmetricEncrypting; + + /// <summary> + /// A value indicating whether the data in this instance will be protected against tampering. + /// </summary> + private readonly bool signed; + + /// <summary> + /// The nonce store to use to ensure that this instance is only decoded once. + /// </summary> + private readonly INonceStore decodeOnceOnly; + + /// <summary> + /// The maximum age of a token that can be decoded; useful only when <see cref="decodeOnceOnly"/> is <c>true</c>. + /// </summary> + private readonly TimeSpan? maximumAge; + + /// <summary> + /// A value indicating whether the data in this instance will be protected against eavesdropping. + /// </summary> + private readonly bool encrypted; + + /// <summary> + /// A value indicating whether the data in this instance will be GZip'd. + /// </summary> + private readonly bool compressed; + + /// <summary> + /// Initializes a new instance of the <see cref="DataBagFormatterBase<T>"/> class. + /// </summary> + /// <param name="signingKey">The crypto service provider with the asymmetric key to use for signing or verifying the token.</param> + /// <param name="encryptingKey">The crypto service provider with the asymmetric key to use for encrypting or decrypting the token.</param> + /// <param name="compressed">A value indicating whether the data in this instance will be GZip'd.</param> + /// <param name="maximumAge">The maximum age of a token that can be decoded; useful only when <paramref name="decodeOnceOnly"/> is <c>true</c>.</param> + /// <param name="decodeOnceOnly">The nonce store to use to ensure that this instance is only decoded once.</param> + protected DataBagFormatterBase(RSACryptoServiceProvider signingKey = null, RSACryptoServiceProvider encryptingKey = null, bool compressed = false, TimeSpan? maximumAge = null, INonceStore decodeOnceOnly = null) + : this(signingKey != null, encryptingKey != null, compressed, maximumAge, decodeOnceOnly) { + this.asymmetricSigning = signingKey; + this.asymmetricEncrypting = encryptingKey; + } + + /// <summary> + /// Initializes a new instance of the <see cref="DataBagFormatterBase<T>"/> class. + /// </summary> + /// <param name="cryptoKeyStore">The crypto key store used when signing or encrypting.</param> + /// <param name="bucket">The bucket in which symmetric keys are stored for signing/encrypting data.</param> + /// <param name="signed">A value indicating whether the data in this instance will be protected against tampering.</param> + /// <param name="encrypted">A value indicating whether the data in this instance will be protected against eavesdropping.</param> + /// <param name="compressed">A value indicating whether the data in this instance will be GZip'd.</param> + /// <param name="minimumAge">The required minimum lifespan within which this token must be decodable and verifiable; useful only when <paramref name="signed"/> and/or <paramref name="encrypted"/> is true.</param> + /// <param name="maximumAge">The maximum age of a token that can be decoded; useful only when <paramref name="decodeOnceOnly"/> is <c>true</c>.</param> + /// <param name="decodeOnceOnly">The nonce store to use to ensure that this instance is only decoded once.</param> + protected DataBagFormatterBase(ICryptoKeyStore cryptoKeyStore = null, string bucket = null, bool signed = false, bool encrypted = false, bool compressed = false, TimeSpan? minimumAge = null, TimeSpan? maximumAge = null, INonceStore decodeOnceOnly = null) + : this(signed, encrypted, compressed, maximumAge, decodeOnceOnly) { + Requires.True(!String.IsNullOrEmpty(bucket) || cryptoKeyStore == null, null); + Requires.True(cryptoKeyStore != null || (!signed && !encrypted), null); + + this.cryptoKeyStore = cryptoKeyStore; + this.cryptoKeyBucket = bucket; + if (minimumAge.HasValue) { + this.minimumAge = minimumAge.Value; + } + } + + /// <summary> + /// Initializes a new instance of the <see cref="DataBagFormatterBase<T>"/> class. + /// </summary> + /// <param name="signed">A value indicating whether the data in this instance will be protected against tampering.</param> + /// <param name="encrypted">A value indicating whether the data in this instance will be protected against eavesdropping.</param> + /// <param name="compressed">A value indicating whether the data in this instance will be GZip'd.</param> + /// <param name="maximumAge">The maximum age of a token that can be decoded; useful only when <paramref name="decodeOnceOnly"/> is <c>true</c>.</param> + /// <param name="decodeOnceOnly">The nonce store to use to ensure that this instance is only decoded once.</param> + private DataBagFormatterBase(bool signed = false, bool encrypted = false, bool compressed = false, TimeSpan? maximumAge = null, INonceStore decodeOnceOnly = null) { + Requires.True(signed || decodeOnceOnly == null, null); + Requires.True(maximumAge.HasValue || decodeOnceOnly == null, null); + + this.signed = signed; + this.maximumAge = maximumAge; + this.decodeOnceOnly = decodeOnceOnly; + this.encrypted = encrypted; + this.compressed = compressed; + } + + /// <summary> + /// Serializes the specified message, including compression, encryption, signing, and nonce handling where applicable. + /// </summary> + /// <param name="message">The message to serialize. Must not be null.</param> + /// <returns>A non-null, non-empty value.</returns> + public string Serialize(T message) { + message.UtcCreationDate = DateTime.UtcNow; + + if (this.decodeOnceOnly != null) { + message.Nonce = MessagingUtilities.GetNonCryptoRandomData(NonceLength); + } + + byte[] encoded = this.SerializeCore(message); + + if (this.compressed) { + encoded = MessagingUtilities.Compress(encoded); + } + + string symmetricSecretHandle = null; + if (this.encrypted) { + encoded = this.Encrypt(encoded, out symmetricSecretHandle); + } + + if (this.signed) { + message.Signature = this.CalculateSignature(encoded, symmetricSecretHandle); + } + + int capacity = this.signed ? 4 + message.Signature.Length + 4 + encoded.Length : encoded.Length; + using (var finalStream = new MemoryStream(capacity)) { + var writer = new BinaryWriter(finalStream); + if (this.signed) { + writer.WriteBuffer(message.Signature); + } + + writer.WriteBuffer(encoded); + writer.Flush(); + + string payload = MessagingUtilities.ConvertToBase64WebSafeString(finalStream.ToArray()); + string result = payload; + if (symmetricSecretHandle != null && (this.signed || this.encrypted)) { + result = MessagingUtilities.CombineKeyHandleAndPayload(symmetricSecretHandle, payload); + } + + return result; + } + } + + /// <summary> + /// Deserializes a <see cref="DataBag"/>, including decompression, decryption, signature and nonce validation where applicable. + /// </summary> + /// <param name="containingMessage">The message that contains the <see cref="DataBag"/> serialized value. Must not be nulll.</param> + /// <param name="value">The serialized form of the <see cref="DataBag"/> to deserialize. Must not be null or empty.</param> + /// <returns>The deserialized value. Never null.</returns> + public T Deserialize(IProtocolMessage containingMessage, string value) { + string symmetricSecretHandle = null; + if (this.encrypted && this.cryptoKeyStore != null) { + string valueWithoutHandle; + MessagingUtilities.ExtractKeyHandleAndPayload(containingMessage, "<TODO>", value, out symmetricSecretHandle, out valueWithoutHandle); + value = valueWithoutHandle; + } + + var message = new T { ContainingMessage = containingMessage }; + byte[] data = MessagingUtilities.FromBase64WebSafeString(value); + + byte[] signature = null; + if (this.signed) { + using (var dataStream = new MemoryStream(data)) { + var dataReader = new BinaryReader(dataStream); + signature = dataReader.ReadBuffer(); + data = dataReader.ReadBuffer(); + } + + // Verify that the verification code was issued by message authorization server. + ErrorUtilities.VerifyProtocol(this.IsSignatureValid(data, signature, symmetricSecretHandle), MessagingStrings.SignatureInvalid); + } + + if (this.encrypted) { + data = this.Decrypt(data, symmetricSecretHandle); + } + + if (this.compressed) { + data = MessagingUtilities.Decompress(data); + } + + this.DeserializeCore(message, data); + message.Signature = signature; // TODO: we don't really need this any more, do we? + + if (this.maximumAge.HasValue) { + // Has message verification code expired? + DateTime expirationDate = message.UtcCreationDate + this.maximumAge.Value; + if (expirationDate < DateTime.UtcNow) { + throw new ExpiredMessageException(expirationDate, containingMessage); + } + } + + // Has message verification code already been used to obtain an access/refresh token? + if (this.decodeOnceOnly != null) { + ErrorUtilities.VerifyInternal(this.maximumAge.HasValue, "Oops! How can we validate a nonce without a maximum message age?"); + string context = "{" + GetType().FullName + "}"; + if (!this.decodeOnceOnly.StoreNonce(context, Convert.ToBase64String(message.Nonce), message.UtcCreationDate)) { + Logger.OpenId.ErrorFormat("Replayed nonce detected ({0} {1}). Rejecting message.", message.Nonce, message.UtcCreationDate); + throw new ReplayedMessageException(containingMessage); + } + } + + ((IMessage)message).EnsureValidMessage(); + + return message; + } + + /// <summary> + /// Serializes the <see cref="DataBag"/> instance to a buffer. + /// </summary> + /// <param name="message">The message.</param> + /// <returns>The buffer containing the serialized data.</returns> + protected abstract byte[] SerializeCore(T message); + + /// <summary> + /// Deserializes the <see cref="DataBag"/> instance from a buffer. + /// </summary> + /// <param name="message">The message instance to initialize with data from the buffer.</param> + /// <param name="data">The data buffer.</param> + protected abstract void DeserializeCore(T message, byte[] data); + + /// <summary> + /// Determines whether the signature on this instance is valid. + /// </summary> + /// <param name="signedData">The signed data.</param> + /// <param name="signature">The signature.</param> + /// <param name="symmetricSecretHandle">The symmetric secret handle. <c>null</c> when using an asymmetric algorithm.</param> + /// <returns> + /// <c>true</c> if the signature is valid; otherwise, <c>false</c>. + /// </returns> + private bool IsSignatureValid(byte[] signedData, byte[] signature, string symmetricSecretHandle) { + Requires.NotNull(signedData, "signedData"); + Requires.NotNull(signature, "signature"); + + if (this.asymmetricSigning != null) { + using (var hasher = new SHA1CryptoServiceProvider()) { + return this.asymmetricSigning.VerifyData(signedData, hasher, signature); + } + } else { + return MessagingUtilities.AreEquivalentConstantTime(signature, this.CalculateSignature(signedData, symmetricSecretHandle)); + } + } + + /// <summary> + /// Calculates the signature for the data in this verification code. + /// </summary> + /// <param name="bytesToSign">The bytes to sign.</param> + /// <param name="symmetricSecretHandle">The symmetric secret handle. <c>null</c> when using an asymmetric algorithm.</param> + /// <returns> + /// The calculated signature. + /// </returns> + private byte[] CalculateSignature(byte[] bytesToSign, string symmetricSecretHandle) { + Requires.NotNull(bytesToSign, "bytesToSign"); + Requires.ValidState(this.asymmetricSigning != null || this.cryptoKeyStore != null); + Contract.Ensures(Contract.Result<byte[]>() != null); + + if (this.asymmetricSigning != null) { + using (var hasher = new SHA1CryptoServiceProvider()) { + return this.asymmetricSigning.SignData(bytesToSign, hasher); + } + } else { + var key = this.cryptoKeyStore.GetKey(this.cryptoKeyBucket, symmetricSecretHandle); + ErrorUtilities.VerifyProtocol(key != null, "Missing decryption key."); + using (var symmetricHasher = new HMACSHA256(key.Key)) { + return symmetricHasher.ComputeHash(bytesToSign); + } + } + } + + /// <summary> + /// Encrypts the specified value using either the symmetric or asymmetric encryption algorithm as appropriate. + /// </summary> + /// <param name="value">The value.</param> + /// <param name="symmetricSecretHandle">Receives the symmetric secret handle. <c>null</c> when using an asymmetric algorithm.</param> + /// <returns> + /// The encrypted value. + /// </returns> + private byte[] Encrypt(byte[] value, out string symmetricSecretHandle) { + Requires.ValidState(this.asymmetricEncrypting != null || this.cryptoKeyStore != null); + + if (this.asymmetricEncrypting != null) { + symmetricSecretHandle = null; + return this.asymmetricEncrypting.EncryptWithRandomSymmetricKey(value); + } else { + var cryptoKey = this.cryptoKeyStore.GetCurrentKey(this.cryptoKeyBucket, this.minimumAge); + symmetricSecretHandle = cryptoKey.Key; + return MessagingUtilities.Encrypt(value, cryptoKey.Value.Key); + } + } + + /// <summary> + /// Decrypts the specified value using either the symmetric or asymmetric encryption algorithm as appropriate. + /// </summary> + /// <param name="value">The value.</param> + /// <param name="symmetricSecretHandle">The symmetric secret handle. <c>null</c> when using an asymmetric algorithm.</param> + /// <returns> + /// The decrypted value. + /// </returns> + private byte[] Decrypt(byte[] value, string symmetricSecretHandle) { + Requires.ValidState(this.asymmetricEncrypting != null || symmetricSecretHandle != null); + + if (this.asymmetricEncrypting != null) { + return this.asymmetricEncrypting.DecryptWithRandomSymmetricKey(value); + } else { + var key = this.cryptoKeyStore.GetKey(this.cryptoKeyBucket, symmetricSecretHandle); + ErrorUtilities.VerifyProtocol(key != null, "Missing decryption key."); + return MessagingUtilities.Decrypt(value, key.Key); + } + } + } +} diff --git a/src/DotNetOpenAuth/Messaging/DirectWebRequestOptions.cs b/src/DotNetOpenAuth.Core/Messaging/DirectWebRequestOptions.cs index f3ce805..f3ce805 100644 --- a/src/DotNetOpenAuth/Messaging/DirectWebRequestOptions.cs +++ b/src/DotNetOpenAuth.Core/Messaging/DirectWebRequestOptions.cs diff --git a/src/DotNetOpenAuth/Messaging/EmptyDictionary.cs b/src/DotNetOpenAuth.Core/Messaging/EmptyDictionary.cs index 9db5169..9db5169 100644 --- a/src/DotNetOpenAuth/Messaging/EmptyDictionary.cs +++ b/src/DotNetOpenAuth.Core/Messaging/EmptyDictionary.cs diff --git a/src/DotNetOpenAuth/Messaging/EmptyEnumerator.cs b/src/DotNetOpenAuth.Core/Messaging/EmptyEnumerator.cs index f37e3d4..f37e3d4 100644 --- a/src/DotNetOpenAuth/Messaging/EmptyEnumerator.cs +++ b/src/DotNetOpenAuth.Core/Messaging/EmptyEnumerator.cs diff --git a/src/DotNetOpenAuth/Messaging/EmptyList.cs b/src/DotNetOpenAuth.Core/Messaging/EmptyList.cs index 68cdabd..68cdabd 100644 --- a/src/DotNetOpenAuth/Messaging/EmptyList.cs +++ b/src/DotNetOpenAuth.Core/Messaging/EmptyList.cs diff --git a/src/DotNetOpenAuth.Core/Messaging/EnumerableCache.cs b/src/DotNetOpenAuth.Core/Messaging/EnumerableCache.cs new file mode 100644 index 0000000..f6ea55e --- /dev/null +++ b/src/DotNetOpenAuth.Core/Messaging/EnumerableCache.cs @@ -0,0 +1,243 @@ +//----------------------------------------------------------------------- +// <copyright file="EnumerableCache.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// This code is released under the Microsoft Public License (Ms-PL). +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Messaging { + using System; + using System.Collections; + using System.Collections.Generic; + using System.Diagnostics.Contracts; + + /// <summary> + /// Extension methods for <see cref="IEnumerable<T>"/> types. + /// </summary> + public static class EnumerableCacheExtensions { + /// <summary> + /// Caches the results of enumerating over a given object so that subsequence enumerations + /// don't require interacting with the object a second time. + /// </summary> + /// <typeparam name="T">The type of element found in the enumeration.</typeparam> + /// <param name="sequence">The enumerable object.</param> + /// <returns> + /// Either a new enumerable object that caches enumerated results, or the original, <paramref name="sequence"/> + /// object if no caching is necessary to avoid additional CPU work. + /// </returns> + /// <remarks> + /// <para>This is designed for use on the results of generator methods (the ones with <c>yield return</c> in them) + /// so that only those elements in the sequence that are needed are ever generated, while not requiring + /// regeneration of elements that are enumerated over multiple times.</para> + /// <para>This can be a huge performance gain if enumerating multiple times over an expensive generator method.</para> + /// <para>Some enumerable types such as collections, lists, and already-cached generators do not require + /// any (additional) caching, and this method will simply return those objects rather than caching them + /// to avoid double-caching.</para> + /// </remarks> + public static IEnumerable<T> CacheGeneratedResults<T>(this IEnumerable<T> sequence) { + Requires.NotNull(sequence, "sequence"); + + // Don't create a cache for types that don't need it. + if (sequence is IList<T> || + sequence is ICollection<T> || + sequence is Array || + sequence is EnumerableCache<T>) { + return sequence; + } + + return new EnumerableCache<T>(sequence); + } + + /// <summary> + /// A wrapper for <see cref="IEnumerable<T>"/> types and returns a caching <see cref="IEnumerator<T>"/> + /// from its <see cref="IEnumerable<T>.GetEnumerator"/> method. + /// </summary> + /// <typeparam name="T">The type of element in the sequence.</typeparam> + private class EnumerableCache<T> : IEnumerable<T> { + /// <summary> + /// The results from enumeration of the live object that have been collected thus far. + /// </summary> + private List<T> cache; + + /// <summary> + /// The original generator method or other enumerable object whose contents should only be enumerated once. + /// </summary> + private IEnumerable<T> generator; + + /// <summary> + /// The enumerator we're using over the generator method's results. + /// </summary> + private IEnumerator<T> generatorEnumerator; + + /// <summary> + /// The sync object our caching enumerators use when adding a new live generator method result to the cache. + /// </summary> + /// <remarks> + /// Although individual enumerators are not thread-safe, this <see cref="IEnumerable<T>"/> should be + /// thread safe so that multiple enumerators can be created from it and used from different threads. + /// </remarks> + private object generatorLock = new object(); + + /// <summary> + /// Initializes a new instance of the EnumerableCache class. + /// </summary> + /// <param name="generator">The generator.</param> + internal EnumerableCache(IEnumerable<T> generator) { + Requires.NotNull(generator, "generator"); + + this.generator = generator; + } + + #region IEnumerable<T> Members + + /// <summary> + /// Returns an enumerator that iterates through the collection. + /// </summary> + /// <returns> + /// A <see cref="T:System.Collections.Generic.IEnumerator`1"/> that can be used to iterate through the collection. + /// </returns> + public IEnumerator<T> GetEnumerator() { + if (this.generatorEnumerator == null) { + this.cache = new List<T>(); + this.generatorEnumerator = this.generator.GetEnumerator(); + } + + return new EnumeratorCache(this); + } + + #endregion + + #region IEnumerable Members + + /// <summary> + /// Returns an enumerator that iterates through a collection. + /// </summary> + /// <returns> + /// An <see cref="T:System.Collections.IEnumerator"/> object that can be used to iterate through the collection. + /// </returns> + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { + return this.GetEnumerator(); + } + + #endregion + + /// <summary> + /// An enumerator that uses cached enumeration results whenever they are available, + /// and caches whatever results it has to pull from the original <see cref="IEnumerable<T>"/> object. + /// </summary> + private class EnumeratorCache : IEnumerator<T> { + /// <summary> + /// The parent enumeration wrapper class that stores the cached results. + /// </summary> + private EnumerableCache<T> parent; + + /// <summary> + /// The position of this enumerator in the cached list. + /// </summary> + private int cachePosition = -1; + + /// <summary> + /// Initializes a new instance of the EnumeratorCache class. + /// </summary> + /// <param name="parent">The parent cached enumerable whose GetEnumerator method is calling this constructor.</param> + internal EnumeratorCache(EnumerableCache<T> parent) { + Requires.NotNull(parent, "parent"); + + this.parent = parent; + } + + #region IEnumerator<T> Members + + /// <summary> + /// Gets the element in the collection at the current position of the enumerator. + /// </summary> + /// <returns> + /// The element in the collection at the current position of the enumerator. + /// </returns> + public T Current { + get { + if (this.cachePosition < 0 || this.cachePosition >= this.parent.cache.Count) { + throw new InvalidOperationException(); + } + + return this.parent.cache[this.cachePosition]; + } + } + + #endregion + + #region IEnumerator Properties + + /// <summary> + /// Gets the element in the collection at the current position of the enumerator. + /// </summary> + /// <returns> + /// The element in the collection at the current position of the enumerator. + /// </returns> + object System.Collections.IEnumerator.Current { + get { return this.Current; } + } + + #endregion + + #region IDisposable Members + + /// <summary> + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// </summary> + public void Dispose() { + this.Dispose(true); + GC.SuppressFinalize(this); + } + + #endregion + + #region IEnumerator Methods + + /// <summary> + /// Advances the enumerator to the next element of the collection. + /// </summary> + /// <returns> + /// true if the enumerator was successfully advanced to the next element; false if the enumerator has passed the end of the collection. + /// </returns> + /// <exception cref="T:System.InvalidOperationException"> + /// The collection was modified after the enumerator was created. + /// </exception> + public bool MoveNext() { + this.cachePosition++; + if (this.cachePosition >= this.parent.cache.Count) { + lock (this.parent.generatorLock) { + if (this.parent.generatorEnumerator.MoveNext()) { + this.parent.cache.Add(this.parent.generatorEnumerator.Current); + } else { + return false; + } + } + } + + return true; + } + + /// <summary> + /// Sets the enumerator to its initial position, which is before the first element in the collection. + /// </summary> + /// <exception cref="T:System.InvalidOperationException"> + /// The collection was modified after the enumerator was created. + /// </exception> + public void Reset() { + this.cachePosition = -1; + } + + #endregion + + /// <summary> + /// Releases unmanaged and - optionally - managed resources + /// </summary> + /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param> + protected virtual void Dispose(bool disposing) { + // Nothing to do here. + } + } + } + } +} diff --git a/src/DotNetOpenAuth.Core/Messaging/ErrorUtilities.cs b/src/DotNetOpenAuth.Core/Messaging/ErrorUtilities.cs new file mode 100644 index 0000000..129a03d --- /dev/null +++ b/src/DotNetOpenAuth.Core/Messaging/ErrorUtilities.cs @@ -0,0 +1,385 @@ +//----------------------------------------------------------------------- +// <copyright file="ErrorUtilities.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Messaging { + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Diagnostics.Contracts; + using System.Globalization; + using System.Web; + + /// <summary> + /// A collection of error checking and reporting methods. + /// </summary> + [ContractVerification(true)] + [Pure] + internal static class ErrorUtilities { + /// <summary> + /// Wraps an exception in a new <see cref="ProtocolException"/>. + /// </summary> + /// <param name="inner">The inner exception to wrap.</param> + /// <param name="errorMessage">The error message for the outer exception.</param> + /// <param name="args">The string formatting arguments, if any.</param> + /// <returns>The newly constructed (unthrown) exception.</returns> + [Pure] + internal static Exception Wrap(Exception inner, string errorMessage, params object[] args) { + Requires.NotNull(args, "args"); + Contract.Assume(errorMessage != null); + return new ProtocolException(string.Format(CultureInfo.CurrentCulture, errorMessage, args), inner); + } + + /// <summary> + /// Throws an internal error exception. + /// </summary> + /// <param name="errorMessage">The error message.</param> + /// <returns>Nothing. But included here so callers can "throw" this method for C# safety.</returns> + /// <exception cref="InternalErrorException">Always thrown.</exception> + [Pure] + internal static Exception ThrowInternal(string errorMessage) { + // Since internal errors are really bad, take this chance to + // help the developer find the cause by breaking into the + // debugger if one is attached. + if (Debugger.IsAttached) { + Debugger.Break(); + } + + throw new InternalErrorException(errorMessage); + } + + /// <summary> + /// Checks a condition and throws an internal error exception if it evaluates to false. + /// </summary> + /// <param name="condition">The condition to check.</param> + /// <param name="errorMessage">The message to include in the exception, if created.</param> + /// <exception cref="InternalErrorException">Thrown if <paramref name="condition"/> evaluates to <c>false</c>.</exception> + [Pure] + internal static void VerifyInternal(bool condition, string errorMessage) { + Contract.Ensures(condition); + Contract.EnsuresOnThrow<InternalErrorException>(!condition); + if (!condition) { + ThrowInternal(errorMessage); + } + } + + /// <summary> + /// Checks a condition and throws an internal error exception if it evaluates to false. + /// </summary> + /// <param name="condition">The condition to check.</param> + /// <param name="errorMessage">The message to include in the exception, if created.</param> + /// <param name="args">The formatting arguments.</param> + /// <exception cref="InternalErrorException">Thrown if <paramref name="condition"/> evaluates to <c>false</c>.</exception> + [Pure] + internal static void VerifyInternal(bool condition, string errorMessage, params object[] args) { + Requires.NotNull(args, "args"); + Contract.Ensures(condition); + Contract.EnsuresOnThrow<InternalErrorException>(!condition); + Contract.Assume(errorMessage != null); + if (!condition) { + errorMessage = string.Format(CultureInfo.CurrentCulture, errorMessage, args); + throw new InternalErrorException(errorMessage); + } + } + + /// <summary> + /// Checks a condition and throws an <see cref="InvalidOperationException"/> if it evaluates to false. + /// </summary> + /// <param name="condition">The condition to check.</param> + /// <param name="errorMessage">The message to include in the exception, if created.</param> + /// <exception cref="InvalidOperationException">Thrown if <paramref name="condition"/> evaluates to <c>false</c>.</exception> + [Pure] + internal static void VerifyOperation(bool condition, string errorMessage) { + Contract.Ensures(condition); + Contract.EnsuresOnThrow<InvalidOperationException>(!condition); + if (!condition) { + throw new InvalidOperationException(errorMessage); + } + } + + /// <summary> + /// Checks a condition and throws a <see cref="NotSupportedException"/> if it evaluates to false. + /// </summary> + /// <param name="condition">The condition to check.</param> + /// <param name="errorMessage">The message to include in the exception, if created.</param> + /// <exception cref="NotSupportedException">Thrown if <paramref name="condition"/> evaluates to <c>false</c>.</exception> + [Pure] + internal static void VerifySupported(bool condition, string errorMessage) { + Contract.Ensures(condition); + Contract.EnsuresOnThrow<NotSupportedException>(!condition); + if (!condition) { + throw new NotSupportedException(errorMessage); + } + } + + /// <summary> + /// Checks a condition and throws a <see cref="NotSupportedException"/> if it evaluates to false. + /// </summary> + /// <param name="condition">The condition to check.</param> + /// <param name="errorMessage">The message to include in the exception, if created.</param> + /// <param name="args">The string formatting arguments for <paramref name="errorMessage"/>.</param> + /// <exception cref="NotSupportedException">Thrown if <paramref name="condition"/> evaluates to <c>false</c>.</exception> + [Pure] + internal static void VerifySupported(bool condition, string errorMessage, params object[] args) { + Requires.NotNull(args, "args"); + Contract.Ensures(condition); + Contract.EnsuresOnThrow<NotSupportedException>(!condition); + Contract.Assume(errorMessage != null); + if (!condition) { + throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, errorMessage, args)); + } + } + + /// <summary> + /// Checks a condition and throws an <see cref="InvalidOperationException"/> if it evaluates to false. + /// </summary> + /// <param name="condition">The condition to check.</param> + /// <param name="errorMessage">The message to include in the exception, if created.</param> + /// <param name="args">The formatting arguments.</param> + /// <exception cref="InvalidOperationException">Thrown if <paramref name="condition"/> evaluates to <c>false</c>.</exception> + [Pure] + internal static void VerifyOperation(bool condition, string errorMessage, params object[] args) { + Requires.NotNull(args, "args"); + Contract.Ensures(condition); + Contract.EnsuresOnThrow<InvalidOperationException>(!condition); + Contract.Assume(errorMessage != null); + if (!condition) { + errorMessage = string.Format(CultureInfo.CurrentCulture, errorMessage, args); + throw new InvalidOperationException(errorMessage); + } + } + + /// <summary> + /// Throws a <see cref="HostErrorException"/> if some <paramref name="condition"/> evaluates to false. + /// </summary> + /// <param name="condition">True to do nothing; false to throw the exception.</param> + /// <param name="errorMessage">The error message for the exception.</param> + /// <param name="args">The string formatting arguments, if any.</param> + /// <exception cref="HostErrorException">Thrown if <paramref name="condition"/> evaluates to <c>false</c>.</exception> + [Pure] + internal static void VerifyHost(bool condition, string errorMessage, params object[] args) { + Requires.NotNull(args, "args"); + Contract.Ensures(condition); + Contract.EnsuresOnThrow<ProtocolException>(!condition); + Contract.Assume(errorMessage != null); + if (!condition) { + throw new HostErrorException(string.Format(CultureInfo.CurrentCulture, errorMessage, args)); + } + } + + /// <summary> + /// Throws a <see cref="ProtocolException"/> if some <paramref name="condition"/> evaluates to false. + /// </summary> + /// <param name="condition">True to do nothing; false to throw the exception.</param> + /// <param name="faultedMessage">The message being processed that would be responsible for the exception if thrown.</param> + /// <param name="errorMessage">The error message for the exception.</param> + /// <param name="args">The string formatting arguments, if any.</param> + /// <exception cref="ProtocolException">Thrown if <paramref name="condition"/> evaluates to <c>false</c>.</exception> + [Pure] + internal static void VerifyProtocol(bool condition, IProtocolMessage faultedMessage, string errorMessage, params object[] args) { + Requires.NotNull(args, "args"); + Requires.NotNull(faultedMessage, "faultedMessage"); + Contract.Ensures(condition); + Contract.EnsuresOnThrow<ProtocolException>(!condition); + Contract.Assume(errorMessage != null); + if (!condition) { + throw new ProtocolException(string.Format(CultureInfo.CurrentCulture, errorMessage, args), faultedMessage); + } + } + + /// <summary> + /// Throws a <see cref="ProtocolException"/> if some <paramref name="condition"/> evaluates to false. + /// </summary> + /// <param name="condition">True to do nothing; false to throw the exception.</param> + /// <param name="message">The error message for the exception.</param> + /// <param name="args">The string formatting arguments, if any.</param> + /// <exception cref="ProtocolException">Thrown if <paramref name="condition"/> evaluates to <c>false</c>.</exception> + [Pure] + internal static void VerifyProtocol(bool condition, string message, params object[] args) { + Requires.NotNull(args, "args"); + Contract.Ensures(condition); + Contract.EnsuresOnThrow<ProtocolException>(!condition); + Contract.Assume(message != null); + if (!condition) { + var exception = new ProtocolException(string.Format(CultureInfo.CurrentCulture, message, args)); + if (Logger.Messaging.IsErrorEnabled) { + Logger.Messaging.Error( + string.Format( + CultureInfo.CurrentCulture, + "Protocol error: {0}{1}{2}", + exception.Message, + Environment.NewLine, + new StackTrace())); + } + throw exception; + } + } + + /// <summary> + /// Throws a <see cref="ProtocolException"/>. + /// </summary> + /// <param name="message">The message to set in the exception.</param> + /// <param name="args">The formatting arguments of the message.</param> + /// <returns> + /// An InternalErrorException, which may be "thrown" by the caller in order + /// to satisfy C# rules to show that code will never be reached, but no value + /// actually is ever returned because this method guarantees to throw. + /// </returns> + /// <exception cref="ProtocolException">Always thrown.</exception> + [Pure] + internal static Exception ThrowProtocol(string message, params object[] args) { + Requires.NotNull(args, "args"); + Contract.Assume(message != null); + VerifyProtocol(false, message, args); + + // we never reach here, but this allows callers to "throw" this method. + return new InternalErrorException(); + } + + /// <summary> + /// Throws a <see cref="FormatException"/>. + /// </summary> + /// <param name="message">The message for the exception.</param> + /// <param name="args">The string formatting arguments for <paramref name="message"/>.</param> + /// <returns>Nothing. It's just here so the caller can throw this method for C# compilation check.</returns> + [Pure] + internal static Exception ThrowFormat(string message, params object[] args) { + Requires.NotNull(args, "args"); + Contract.Assume(message != null); + throw new FormatException(string.Format(CultureInfo.CurrentCulture, message, args)); + } + + /// <summary> + /// Throws a <see cref="FormatException"/> if some condition is false. + /// </summary> + /// <param name="condition">The expression to evaluate. A value of <c>false</c> will cause the exception to be thrown.</param> + /// <param name="message">The message for the exception.</param> + /// <param name="args">The string formatting arguments for <paramref name="message"/>.</param> + /// <exception cref="FormatException">Thrown when <paramref name="condition"/> is <c>false</c>.</exception> + [Pure] + internal static void VerifyFormat(bool condition, string message, params object[] args) { + Requires.NotNull(args, "args"); + Contract.Ensures(condition); + Contract.EnsuresOnThrow<FormatException>(!condition); + Contract.Assume(message != null); + if (!condition) { + throw ThrowFormat(message, args); + } + } + + /// <summary> + /// Verifies something about the argument supplied to a method. + /// </summary> + /// <param name="condition">The condition that must evaluate to true to avoid an exception.</param> + /// <param name="message">The message to use in the exception if the condition is false.</param> + /// <param name="args">The string formatting arguments, if any.</param> + /// <exception cref="ArgumentException">Thrown if <paramref name="condition"/> evaluates to <c>false</c>.</exception> + [Pure] + internal static void VerifyArgument(bool condition, string message, params object[] args) { + Requires.NotNull(args, "args"); + Contract.Ensures(condition); + Contract.EnsuresOnThrow<ArgumentException>(!condition); + Contract.Assume(message != null); + if (!condition) { + throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, message, args)); + } + } + + /// <summary> + /// Throws an <see cref="ArgumentException"/>. + /// </summary> + /// <param name="parameterName">Name of the parameter.</param> + /// <param name="message">The message to use in the exception if the condition is false.</param> + /// <param name="args">The string formatting arguments, if any.</param> + /// <returns>Never returns anything. It always throws.</returns> + [Pure] + internal static Exception ThrowArgumentNamed(string parameterName, string message, params object[] args) { + Requires.NotNull(args, "args"); + Contract.Assume(message != null); + throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, message, args), parameterName); + } + + /// <summary> + /// Verifies something about the argument supplied to a method. + /// </summary> + /// <param name="condition">The condition that must evaluate to true to avoid an exception.</param> + /// <param name="parameterName">Name of the parameter.</param> + /// <param name="message">The message to use in the exception if the condition is false.</param> + /// <param name="args">The string formatting arguments, if any.</param> + /// <exception cref="ArgumentException">Thrown if <paramref name="condition"/> evaluates to <c>false</c>.</exception> + [Pure] + internal static void VerifyArgumentNamed(bool condition, string parameterName, string message, params object[] args) { + Requires.NotNull(args, "args"); + Contract.Ensures(condition); + Contract.EnsuresOnThrow<ArgumentException>(!condition); + Contract.Assume(message != null); + if (!condition) { + throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, message, args), parameterName); + } + } + + /// <summary> + /// Verifies that some given value is not null. + /// </summary> + /// <param name="value">The value to check.</param> + /// <param name="paramName">Name of the parameter, which will be used in the <see cref="ArgumentException"/>, if thrown.</param> + /// <exception cref="ArgumentNullException">Thrown if <paramref name="value"/> is null.</exception> + [Pure] + internal static void VerifyArgumentNotNull(object value, string paramName) { + Contract.Ensures(value != null); + Contract.EnsuresOnThrow<ArgumentNullException>(value == null); + if (value == null) { + throw new ArgumentNullException(paramName); + } + } + + /// <summary> + /// Verifies that some string is not null and has non-zero length. + /// </summary> + /// <param name="value">The value to check.</param> + /// <param name="paramName">Name of the parameter, which will be used in the <see cref="ArgumentException"/>, if thrown.</param> + /// <exception cref="ArgumentNullException">Thrown if <paramref name="value"/> is null.</exception> + /// <exception cref="ArgumentException">Thrown if <paramref name="value"/> has zero length.</exception> + [Pure] + internal static void VerifyNonZeroLength(string value, string paramName) { + Contract.Ensures((value != null && value.Length > 0) && !string.IsNullOrEmpty(value)); + Contract.EnsuresOnThrow<ArgumentException>(value == null || value.Length == 0); + VerifyArgumentNotNull(value, paramName); + if (value.Length == 0) { + throw new ArgumentException(MessagingStrings.UnexpectedEmptyString, paramName); + } + } + + /// <summary> + /// Verifies that <see cref="HttpContext.Current"/> != <c>null</c>. + /// </summary> + /// <exception cref="InvalidOperationException">Thrown if <see cref="HttpContext.Current"/> == <c>null</c></exception> + [Pure] + internal static void VerifyHttpContext() { + Contract.Ensures(HttpContext.Current != null); + Contract.Ensures(HttpContext.Current.Request != null); + ErrorUtilities.VerifyOperation(HttpContext.Current != null && HttpContext.Current.Request != null, MessagingStrings.HttpContextRequired); + } + + /// <summary> + /// Obtains a value from the dictionary if possible, or throws a <see cref="ProtocolException"/> if it's missing. + /// </summary> + /// <typeparam name="TKey">The type of key in the dictionary.</typeparam> + /// <typeparam name="TValue">The type of value in the dictionary.</typeparam> + /// <param name="dictionary">The dictionary.</param> + /// <param name="key">The key to use to look up the value.</param> + /// <param name="message">The message to claim is invalid if the key cannot be found.</param> + /// <returns>The value for the given key.</returns> + [Pure] + internal static TValue GetValueOrThrow<TKey, TValue>(this IDictionary<TKey, TValue> dictionary, TKey key, IMessage message) { + Requires.NotNull(dictionary, "dictionary"); + Requires.NotNull(message, "message"); + + TValue value; + VerifyProtocol(dictionary.TryGetValue(key, out value), MessagingStrings.ExpectedParameterWasMissing, key, message.GetType().Name); + return value; + } + } +} diff --git a/src/DotNetOpenAuth/Messaging/Exceptions.cd b/src/DotNetOpenAuth.Core/Messaging/Exceptions.cd index 0119753..0119753 100644 --- a/src/DotNetOpenAuth/Messaging/Exceptions.cd +++ b/src/DotNetOpenAuth.Core/Messaging/Exceptions.cd diff --git a/src/DotNetOpenAuth/Messaging/HostErrorException.cs b/src/DotNetOpenAuth.Core/Messaging/HostErrorException.cs index 81691b0..81691b0 100644 --- a/src/DotNetOpenAuth/Messaging/HostErrorException.cs +++ b/src/DotNetOpenAuth.Core/Messaging/HostErrorException.cs diff --git a/src/DotNetOpenAuth/Messaging/HttpDeliveryMethods.cs b/src/DotNetOpenAuth.Core/Messaging/HttpDeliveryMethods.cs index 1443fff..1443fff 100644 --- a/src/DotNetOpenAuth/Messaging/HttpDeliveryMethods.cs +++ b/src/DotNetOpenAuth.Core/Messaging/HttpDeliveryMethods.cs diff --git a/src/DotNetOpenAuth.Core/Messaging/HttpRequestInfo.cs b/src/DotNetOpenAuth.Core/Messaging/HttpRequestInfo.cs new file mode 100644 index 0000000..0cf37a5 --- /dev/null +++ b/src/DotNetOpenAuth.Core/Messaging/HttpRequestInfo.cs @@ -0,0 +1,423 @@ +//----------------------------------------------------------------------- +// <copyright file="HttpRequestInfo.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Messaging { + using System; + using System.Collections.Specialized; + using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; + using System.Diagnostics.Contracts; + using System.Globalization; + using System.IO; + using System.Net; + using System.Net.Mime; + using System.ServiceModel.Channels; + using System.Web; + + /// <summary> + /// A property store of details of an incoming HTTP request. + /// </summary> + /// <remarks> + /// This serves a very similar purpose to <see cref="HttpRequest"/>, except that + /// ASP.NET does not let us fully initialize that class, so we have to write one + /// of our one. + /// </remarks> + public class HttpRequestInfo { + /// <summary> + /// The key/value pairs found in the entity of a POST request. + /// </summary> + private NameValueCollection form; + + /// <summary> + /// The key/value pairs found in the querystring of the incoming request. + /// </summary> + private NameValueCollection queryString; + + /// <summary> + /// Backing field for the <see cref="QueryStringBeforeRewriting"/> property. + /// </summary> + private NameValueCollection queryStringBeforeRewriting; + + /// <summary> + /// Backing field for the <see cref="Message"/> property. + /// </summary> + private IDirectedProtocolMessage message; + + /// <summary> + /// Initializes a new instance of the <see cref="HttpRequestInfo"/> class. + /// </summary> + /// <param name="request">The ASP.NET structure to copy from.</param> + public HttpRequestInfo(HttpRequest request) { + Requires.NotNull(request, "request"); + Contract.Ensures(this.HttpMethod == request.HttpMethod); + Contract.Ensures(this.Url == request.Url); + Contract.Ensures(this.RawUrl == request.RawUrl); + Contract.Ensures(this.UrlBeforeRewriting != null); + Contract.Ensures(this.Headers != null); + Contract.Ensures(this.InputStream == request.InputStream); + Contract.Ensures(this.form == request.Form); + Contract.Ensures(this.queryString == request.QueryString); + + this.HttpMethod = request.HttpMethod; + this.Url = request.Url; + this.UrlBeforeRewriting = GetPublicFacingUrl(request); + this.RawUrl = request.RawUrl; + this.Headers = GetHeaderCollection(request.Headers); + this.InputStream = request.InputStream; + + // These values would normally be calculated, but we'll reuse them from + // HttpRequest since they're already calculated, and there's a chance (<g>) + // that ASP.NET does a better job of being comprehensive about gathering + // these as well. + this.form = request.Form; + this.queryString = request.QueryString; + + Reporting.RecordRequestStatistics(this); + } + + /// <summary> + /// Initializes a new instance of the <see cref="HttpRequestInfo"/> class. + /// </summary> + /// <param name="httpMethod">The HTTP method (i.e. GET or POST) of the incoming request.</param> + /// <param name="requestUrl">The URL being requested.</param> + /// <param name="rawUrl">The raw URL that appears immediately following the HTTP verb in the request, + /// before any URL rewriting takes place.</param> + /// <param name="headers">Headers in the HTTP request.</param> + /// <param name="inputStream">The entity stream, if any. (POST requests typically have these). Use <c>null</c> for GET requests.</param> + public HttpRequestInfo(string httpMethod, Uri requestUrl, string rawUrl, WebHeaderCollection headers, Stream inputStream) { + Requires.NotNullOrEmpty(httpMethod, "httpMethod"); + Requires.NotNull(requestUrl, "requestUrl"); + Requires.NotNull(rawUrl, "rawUrl"); + Requires.NotNull(headers, "headers"); + + this.HttpMethod = httpMethod; + this.Url = requestUrl; + this.UrlBeforeRewriting = requestUrl; + this.RawUrl = rawUrl; + this.Headers = headers; + this.InputStream = inputStream; + + Reporting.RecordRequestStatistics(this); + } + + /// <summary> + /// Initializes a new instance of the <see cref="HttpRequestInfo"/> class. + /// </summary> + /// <param name="listenerRequest">Details on the incoming HTTP request.</param> + public HttpRequestInfo(HttpListenerRequest listenerRequest) { + Requires.NotNull(listenerRequest, "listenerRequest"); + + this.HttpMethod = listenerRequest.HttpMethod; + this.Url = listenerRequest.Url; + this.UrlBeforeRewriting = listenerRequest.Url; + this.RawUrl = listenerRequest.RawUrl; + this.Headers = new WebHeaderCollection(); + foreach (string key in listenerRequest.Headers) { + this.Headers[key] = listenerRequest.Headers[key]; + } + + this.InputStream = listenerRequest.InputStream; + + Reporting.RecordRequestStatistics(this); + } + + /// <summary> + /// Initializes a new instance of the <see cref="HttpRequestInfo"/> class. + /// </summary> + /// <param name="request">The WCF incoming request structure to get the HTTP information from.</param> + /// <param name="requestUri">The URI of the service endpoint.</param> + public HttpRequestInfo(HttpRequestMessageProperty request, Uri requestUri) { + Requires.NotNull(request, "request"); + Requires.NotNull(requestUri, "requestUri"); + + this.HttpMethod = request.Method; + this.Headers = request.Headers; + this.Url = requestUri; + this.UrlBeforeRewriting = requestUri; + this.RawUrl = MakeUpRawUrlFromUrl(requestUri); + + Reporting.RecordRequestStatistics(this); + } + + /// <summary> + /// Initializes a new instance of the <see cref="HttpRequestInfo"/> class. + /// </summary> + internal HttpRequestInfo() { + Contract.Ensures(this.HttpMethod == "GET"); + Contract.Ensures(this.Headers != null); + + this.HttpMethod = "GET"; + this.Headers = new WebHeaderCollection(); + } + + /// <summary> + /// Initializes a new instance of the <see cref="HttpRequestInfo"/> class. + /// </summary> + /// <param name="request">The HttpWebRequest (that was never used) to copy from.</param> + internal HttpRequestInfo(WebRequest request) { + Requires.NotNull(request, "request"); + + this.HttpMethod = request.Method; + this.Url = request.RequestUri; + this.UrlBeforeRewriting = request.RequestUri; + this.RawUrl = MakeUpRawUrlFromUrl(request.RequestUri); + this.Headers = GetHeaderCollection(request.Headers); + this.InputStream = null; + + Reporting.RecordRequestStatistics(this); + } + + /// <summary> + /// Initializes a new instance of the <see cref="HttpRequestInfo"/> class. + /// </summary> + /// <param name="message">The message being passed in through a mock transport. May be null.</param> + /// <param name="httpMethod">The HTTP method that the incoming request came in on, whether or not <paramref name="message"/> is null.</param> + internal HttpRequestInfo(IDirectedProtocolMessage message, HttpDeliveryMethods httpMethod) { + this.message = message; + this.HttpMethod = MessagingUtilities.GetHttpVerb(httpMethod); + } + + /// <summary> + /// Gets or sets the message that is being sent over a mock transport (for testing). + /// </summary> + internal virtual IDirectedProtocolMessage Message { + get { return this.message; } + set { this.message = value; } + } + + /// <summary> + /// Gets or sets the verb in the request (i.e. GET, POST, etc.) + /// </summary> + internal string HttpMethod { get; set; } + + /// <summary> + /// Gets or sets the entire URL of the request, after any URL rewriting. + /// </summary> + internal Uri Url { get; set; } + + /// <summary> + /// Gets or sets the raw URL that appears immediately following the HTTP verb in the request, + /// before any URL rewriting takes place. + /// </summary> + internal string RawUrl { get; set; } + + /// <summary> + /// Gets or sets the full public URL used by the remote client to initiate this request, + /// before any URL rewriting and before any changes made by web farm load distributors. + /// </summary> + internal Uri UrlBeforeRewriting { get; set; } + + /// <summary> + /// Gets the query part of the URL (The ? and everything after it), after URL rewriting. + /// </summary> + internal string Query { + get { return this.Url != null ? this.Url.Query : null; } + } + + /// <summary> + /// Gets or sets the collection of headers that came in with the request. + /// </summary> + internal WebHeaderCollection Headers { get; set; } + + /// <summary> + /// Gets or sets the entity, or body of the request, if any. + /// </summary> + internal Stream InputStream { get; set; } + + /// <summary> + /// Gets the key/value pairs found in the entity of a POST request. + /// </summary> + internal NameValueCollection Form { + get { + Contract.Ensures(Contract.Result<NameValueCollection>() != null); + if (this.form == null) { + ContentType contentType = string.IsNullOrEmpty(this.Headers[HttpRequestHeader.ContentType]) ? null : new ContentType(this.Headers[HttpRequestHeader.ContentType]); + if (this.HttpMethod == "POST" && contentType != null && string.Equals(contentType.MediaType, Channel.HttpFormUrlEncoded, StringComparison.Ordinal)) { + StreamReader reader = new StreamReader(this.InputStream); + long originalPosition = 0; + if (this.InputStream.CanSeek) { + originalPosition = this.InputStream.Position; + } + this.form = HttpUtility.ParseQueryString(reader.ReadToEnd()); + if (this.InputStream.CanSeek) { + this.InputStream.Seek(originalPosition, SeekOrigin.Begin); + } + } else { + this.form = new NameValueCollection(); + } + } + + return this.form; + } + } + + /// <summary> + /// Gets the key/value pairs found in the querystring of the incoming request. + /// </summary> + internal NameValueCollection QueryString { + get { + if (this.queryString == null) { + this.queryString = this.Query != null ? HttpUtility.ParseQueryString(this.Query) : new NameValueCollection(); + } + + return this.queryString; + } + } + + /// <summary> + /// Gets the query data from the original request (before any URL rewriting has occurred.) + /// </summary> + /// <returns>A <see cref="NameValueCollection"/> containing all the parameters in the query string.</returns> + internal NameValueCollection QueryStringBeforeRewriting { + get { + if (this.queryStringBeforeRewriting == null) { + // This request URL may have been rewritten by the host site. + // For openid protocol purposes, we really need to look at + // the original query parameters before any rewriting took place. + if (!this.IsUrlRewritten) { + // No rewriting has taken place. + this.queryStringBeforeRewriting = this.QueryString; + } else { + // Rewriting detected! Recover the original request URI. + ErrorUtilities.VerifyInternal(this.UrlBeforeRewriting != null, "UrlBeforeRewriting is null, so the query string cannot be determined."); + this.queryStringBeforeRewriting = HttpUtility.ParseQueryString(this.UrlBeforeRewriting.Query); + } + } + + return this.queryStringBeforeRewriting; + } + } + + /// <summary> + /// Gets a value indicating whether the request's URL was rewritten by ASP.NET + /// or some other module. + /// </summary> + /// <value> + /// <c>true</c> if this request's URL was rewritten; otherwise, <c>false</c>. + /// </value> + internal bool IsUrlRewritten { + get { return this.Url != this.UrlBeforeRewriting; } + } + + /// <summary> + /// Gets the public facing URL for the given incoming HTTP request. + /// </summary> + /// <param name="request">The request.</param> + /// <param name="serverVariables">The server variables to consider part of the request.</param> + /// <returns> + /// The URI that the outside world used to create this request. + /// </returns> + /// <remarks> + /// Although the <paramref name="serverVariables"/> value can be obtained from + /// <see cref="HttpRequest.ServerVariables"/>, it's useful to be able to pass them + /// in so we can simulate injected values from our unit tests since the actual property + /// is a read-only kind of <see cref="NameValueCollection"/>. + /// </remarks> + internal static Uri GetPublicFacingUrl(HttpRequest request, NameValueCollection serverVariables) { + Requires.NotNull(request, "request"); + Requires.NotNull(serverVariables, "serverVariables"); + + // Due to URL rewriting, cloud computing (i.e. Azure) + // and web farms, etc., we have to be VERY careful about what + // we consider the incoming URL. We want to see the URL as it would + // appear on the public-facing side of the hosting web site. + // HttpRequest.Url gives us the internal URL in a cloud environment, + // So we use a variable that (at least from what I can tell) gives us + // the public URL: + if (serverVariables["HTTP_HOST"] != null) { + ErrorUtilities.VerifySupported(request.Url.Scheme == Uri.UriSchemeHttps || request.Url.Scheme == Uri.UriSchemeHttp, "Only HTTP and HTTPS are supported protocols."); + string scheme = serverVariables["HTTP_X_FORWARDED_PROTO"] ?? request.Url.Scheme; + Uri hostAndPort = new Uri(scheme + Uri.SchemeDelimiter + serverVariables["HTTP_HOST"]); + UriBuilder publicRequestUri = new UriBuilder(request.Url); + publicRequestUri.Scheme = scheme; + publicRequestUri.Host = hostAndPort.Host; + publicRequestUri.Port = hostAndPort.Port; // CC missing Uri.Port contract that's on UriBuilder.Port + return publicRequestUri.Uri; + } else { + // Failover to the method that works for non-web farm enviroments. + // We use Request.Url for the full path to the server, and modify it + // with Request.RawUrl to capture both the cookieless session "directory" if it exists + // and the original path in case URL rewriting is going on. We don't want to be + // fooled by URL rewriting because we're comparing the actual URL with what's in + // the return_to parameter in some cases. + // Response.ApplyAppPathModifier(builder.Path) would have worked for the cookieless + // session, but not the URL rewriting problem. + return new Uri(request.Url, request.RawUrl); + } + } + + /// <summary> + /// Gets the query or form data from the original request (before any URL rewriting has occurred.) + /// </summary> + /// <returns>A set of name=value pairs.</returns> + [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "Expensive call")] + internal NameValueCollection GetQueryOrFormFromContext() { + NameValueCollection query; + if (this.HttpMethod == "GET") { + query = this.QueryStringBeforeRewriting; + } else { + query = this.Form; + } + return query; + } + + /// <summary> + /// Gets the public facing URL for the given incoming HTTP request. + /// </summary> + /// <param name="request">The request.</param> + /// <returns>The URI that the outside world used to create this request.</returns> + private static Uri GetPublicFacingUrl(HttpRequest request) { + Requires.NotNull(request, "request"); + return GetPublicFacingUrl(request, request.ServerVariables); + } + + /// <summary> + /// Makes up a reasonable guess at the raw URL from the possibly rewritten URL. + /// </summary> + /// <param name="url">A full URL.</param> + /// <returns>A raw URL that might have come in on the HTTP verb.</returns> + private static string MakeUpRawUrlFromUrl(Uri url) { + Requires.NotNull(url, "url"); + return url.AbsolutePath + url.Query + url.Fragment; + } + + /// <summary> + /// Converts a NameValueCollection to a WebHeaderCollection. + /// </summary> + /// <param name="pairs">The collection a HTTP headers.</param> + /// <returns>A new collection of the given headers.</returns> + private static WebHeaderCollection GetHeaderCollection(NameValueCollection pairs) { + Requires.NotNull(pairs, "pairs"); + + WebHeaderCollection headers = new WebHeaderCollection(); + foreach (string key in pairs) { + try { + headers.Add(key, pairs[key]); + } catch (ArgumentException ex) { + Logger.Messaging.WarnFormat( + "{0} thrown when trying to add web header \"{1}: {2}\". {3}", + ex.GetType().Name, + key, + pairs[key], + ex.Message); + } + } + + return headers; + } + +#if CONTRACTS_FULL + /// <summary> + /// Verifies conditions that should be true for any valid state of this object. + /// </summary> + [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Called by code contracts.")] + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called by code contracts.")] + [ContractInvariantMethod] + private void ObjectInvariant() { + } +#endif + } +} diff --git a/src/DotNetOpenAuth.Core/Messaging/IChannelBindingElement.cs b/src/DotNetOpenAuth.Core/Messaging/IChannelBindingElement.cs new file mode 100644 index 0000000..9dac9b3 --- /dev/null +++ b/src/DotNetOpenAuth.Core/Messaging/IChannelBindingElement.cs @@ -0,0 +1,146 @@ +//----------------------------------------------------------------------- +// <copyright file="IChannelBindingElement.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Messaging { + using System; + using System.Diagnostics.Contracts; + + /// <summary> + /// An interface that must be implemented by message transforms/validators in order + /// to be included in the channel stack. + /// </summary> + [ContractClass(typeof(IChannelBindingElementContract))] + public interface IChannelBindingElement { + /// <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> + 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> + MessageProtections Protection { get; } + + /// <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> + 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> + MessageProtections? ProcessIncomingMessage(IProtocolMessage message); + } + + /// <summary> + /// Code Contract for the <see cref="IChannelBindingElement"/> interface. + /// </summary> + [ContractClassFor(typeof(IChannelBindingElement))] + internal abstract class IChannelBindingElementContract : IChannelBindingElement { + /// <summary> + /// Prevents a default instance of the <see cref="IChannelBindingElementContract"/> class from being created. + /// </summary> + private IChannelBindingElementContract() { + } + + #region IChannelBindingElement Members + + /// <summary> + /// Gets or sets the channel that this binding element belongs to. + /// </summary> + /// <value></value> + /// <remarks> + /// This property is set by the channel when it is first constructed. + /// </remarks> + Channel IChannelBindingElement.Channel { + get { throw new NotImplementedException(); } + set { throw new NotImplementedException(); } + } + + /// <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> + MessageProtections IChannelBindingElement.Protection { + get { throw new NotImplementedException(); } + } + + /// <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> + MessageProtections? IChannelBindingElement.ProcessOutgoingMessage(IProtocolMessage message) { + Requires.NotNull(message, "message"); + Requires.ValidState(((IChannelBindingElement)this).Channel != null); + throw new NotImplementedException(); + } + + /// <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> + MessageProtections? IChannelBindingElement.ProcessIncomingMessage(IProtocolMessage message) { + Requires.NotNull(message, "message"); + Requires.ValidState(((IChannelBindingElement)this).Channel != null); + throw new NotImplementedException(); + } + + #endregion + } +} diff --git a/src/DotNetOpenAuth.Core/Messaging/IDataBagFormatter.cs b/src/DotNetOpenAuth.Core/Messaging/IDataBagFormatter.cs new file mode 100644 index 0000000..fd1c15d --- /dev/null +++ b/src/DotNetOpenAuth.Core/Messaging/IDataBagFormatter.cs @@ -0,0 +1,75 @@ +//----------------------------------------------------------------------- +// <copyright file="IDataBagFormatter.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Messaging { + using System; + using System.Diagnostics.Contracts; + + /// <summary> + /// A serializer for <see cref="DataBag"/>-derived types + /// </summary> + /// <typeparam name="T">The DataBag-derived type that is to be serialized/deserialized.</typeparam> + [ContractClass(typeof(IDataBagFormatterContract<>))] + internal interface IDataBagFormatter<T> where T : DataBag, new() { + /// <summary> + /// Serializes the specified message. + /// </summary> + /// <param name="message">The message to serialize. Must not be null.</param> + /// <returns>A non-null, non-empty value.</returns> + string Serialize(T message); + + /// <summary> + /// Deserializes a <see cref="DataBag"/>. + /// </summary> + /// <param name="containingMessage">The message that contains the <see cref="DataBag"/> serialized value. Must not be nulll.</param> + /// <param name="data">The serialized form of the <see cref="DataBag"/> to deserialize. Must not be null or empty.</param> + /// <returns>The deserialized value. Never null.</returns> + T Deserialize(IProtocolMessage containingMessage, string data); + } + + /// <summary> + /// Contract class for the IDataBagFormatter interface. + /// </summary> + /// <typeparam name="T">The type of DataBag to serialize.</typeparam> + [ContractClassFor(typeof(IDataBagFormatter<>))] + internal abstract class IDataBagFormatterContract<T> : IDataBagFormatter<T> where T : DataBag, new() { + /// <summary> + /// Prevents a default instance of the <see cref="IDataBagFormatterContract<T>"/> class from being created. + /// </summary> + private IDataBagFormatterContract() { + } + + #region IDataBagFormatter<T> Members + + /// <summary> + /// Serializes the specified message. + /// </summary> + /// <param name="message">The message to serialize. Must not be null.</param> + /// <returns>A non-null, non-empty value.</returns> + string IDataBagFormatter<T>.Serialize(T message) { + Requires.NotNull(message, "message"); + Contract.Ensures(!String.IsNullOrEmpty(Contract.Result<string>())); + + throw new System.NotImplementedException(); + } + + /// <summary> + /// Deserializes a <see cref="DataBag"/>. + /// </summary> + /// <param name="containingMessage">The message that contains the <see cref="DataBag"/> serialized value. Must not be nulll.</param> + /// <param name="data">The serialized form of the <see cref="DataBag"/> to deserialize. Must not be null or empty.</param> + /// <returns>The deserialized value. Never null.</returns> + T IDataBagFormatter<T>.Deserialize(IProtocolMessage containingMessage, string data) { + Requires.NotNull(containingMessage, "containingMessage"); + Requires.NotNullOrEmpty(data, "data"); + Contract.Ensures(Contract.Result<T>() != null); + + throw new System.NotImplementedException(); + } + + #endregion + } +}
\ No newline at end of file diff --git a/src/DotNetOpenAuth/Messaging/IDirectResponseProtocolMessage.cs b/src/DotNetOpenAuth.Core/Messaging/IDirectResponseProtocolMessage.cs index 3b4da6c..3b4da6c 100644 --- a/src/DotNetOpenAuth/Messaging/IDirectResponseProtocolMessage.cs +++ b/src/DotNetOpenAuth.Core/Messaging/IDirectResponseProtocolMessage.cs diff --git a/src/DotNetOpenAuth.Core/Messaging/IDirectWebRequestHandler.cs b/src/DotNetOpenAuth.Core/Messaging/IDirectWebRequestHandler.cs new file mode 100644 index 0000000..add35f9 --- /dev/null +++ b/src/DotNetOpenAuth.Core/Messaging/IDirectWebRequestHandler.cs @@ -0,0 +1,223 @@ +//----------------------------------------------------------------------- +// <copyright file="IDirectWebRequestHandler.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Messaging { + using System; + using System.Collections.Generic; + using System.Diagnostics.Contracts; + using System.IO; + using System.Net; + using DotNetOpenAuth.Messaging; + + /// <summary> + /// A contract for <see cref="HttpWebRequest"/> handling. + /// </summary> + /// <remarks> + /// Implementations of this interface must be thread safe. + /// </remarks> + [ContractClass(typeof(IDirectWebRequestHandlerContract))] + public interface IDirectWebRequestHandler { + /// <summary> + /// Determines whether this instance can support the specified options. + /// </summary> + /// <param name="options">The set of options that might be given in a subsequent web request.</param> + /// <returns> + /// <c>true</c> if this instance can support the specified options; otherwise, <c>false</c>. + /// </returns> + [Pure] + bool CanSupport(DirectWebRequestOptions options); + + /// <summary> + /// Prepares an <see cref="HttpWebRequest"/> that contains an POST entity for sending the entity. + /// </summary> + /// <param name="request">The <see cref="HttpWebRequest"/> that should contain the entity.</param> + /// <returns> + /// The stream the caller should write out the entity data to. + /// </returns> + /// <exception cref="ProtocolException">Thrown for any network error.</exception> + /// <remarks> + /// <para>The caller should have set the <see cref="HttpWebRequest.ContentLength"/> + /// and any other appropriate properties <i>before</i> calling this method. + /// Callers <i>must</i> close and dispose of the request stream when they are done + /// writing to it to avoid taking up the connection too long and causing long waits on + /// subsequent requests.</para> + /// <para>Implementations should catch <see cref="WebException"/> and wrap it in a + /// <see cref="ProtocolException"/> to abstract away the transport and provide + /// a single exception type for hosts to catch.</para> + /// </remarks> + Stream GetRequestStream(HttpWebRequest request); + + /// <summary> + /// Prepares an <see cref="HttpWebRequest"/> that contains an POST entity for sending the entity. + /// </summary> + /// <param name="request">The <see cref="HttpWebRequest"/> that should contain the entity.</param> + /// <param name="options">The options to apply to this web request.</param> + /// <returns> + /// The stream the caller should write out the entity data to. + /// </returns> + /// <exception cref="ProtocolException">Thrown for any network error.</exception> + /// <remarks> + /// <para>The caller should have set the <see cref="HttpWebRequest.ContentLength"/> + /// and any other appropriate properties <i>before</i> calling this method. + /// Callers <i>must</i> close and dispose of the request stream when they are done + /// writing to it to avoid taking up the connection too long and causing long waits on + /// subsequent requests.</para> + /// <para>Implementations should catch <see cref="WebException"/> and wrap it in a + /// <see cref="ProtocolException"/> to abstract away the transport and provide + /// a single exception type for hosts to catch.</para> + /// </remarks> + Stream GetRequestStream(HttpWebRequest request, DirectWebRequestOptions options); + + /// <summary> + /// Processes an <see cref="HttpWebRequest"/> and converts the + /// <see cref="HttpWebResponse"/> to a <see cref="IncomingWebResponse"/> instance. + /// </summary> + /// <param name="request">The <see cref="HttpWebRequest"/> to handle.</param> + /// <returns>An instance of <see cref="IncomingWebResponse"/> describing the response.</returns> + /// <exception cref="ProtocolException">Thrown for any network error.</exception> + /// <remarks> + /// <para>Implementations should catch <see cref="WebException"/> and wrap it in a + /// <see cref="ProtocolException"/> to abstract away the transport and provide + /// a single exception type for hosts to catch. The <see cref="WebException.Response"/> + /// value, if set, should be Closed before throwing.</para> + /// </remarks> + IncomingWebResponse GetResponse(HttpWebRequest request); + + /// <summary> + /// Processes an <see cref="HttpWebRequest"/> and converts the + /// <see cref="HttpWebResponse"/> to a <see cref="IncomingWebResponse"/> instance. + /// </summary> + /// <param name="request">The <see cref="HttpWebRequest"/> to handle.</param> + /// <param name="options">The options to apply to this web request.</param> + /// <returns>An instance of <see cref="IncomingWebResponse"/> describing the response.</returns> + /// <exception cref="ProtocolException">Thrown for any network error.</exception> + /// <remarks> + /// <para>Implementations should catch <see cref="WebException"/> and wrap it in a + /// <see cref="ProtocolException"/> to abstract away the transport and provide + /// a single exception type for hosts to catch. The <see cref="WebException.Response"/> + /// value, if set, should be Closed before throwing.</para> + /// </remarks> + IncomingWebResponse GetResponse(HttpWebRequest request, DirectWebRequestOptions options); + } + + /// <summary> + /// Code contract for the <see cref="IDirectWebRequestHandler"/> type. + /// </summary> + [ContractClassFor(typeof(IDirectWebRequestHandler))] + internal abstract class IDirectWebRequestHandlerContract : IDirectWebRequestHandler { + #region IDirectWebRequestHandler Members + + /// <summary> + /// Determines whether this instance can support the specified options. + /// </summary> + /// <param name="options">The set of options that might be given in a subsequent web request.</param> + /// <returns> + /// <c>true</c> if this instance can support the specified options; otherwise, <c>false</c>. + /// </returns> + bool IDirectWebRequestHandler.CanSupport(DirectWebRequestOptions options) { + throw new System.NotImplementedException(); + } + + /// <summary> + /// Prepares an <see cref="HttpWebRequest"/> that contains an POST entity for sending the entity. + /// </summary> + /// <param name="request">The <see cref="HttpWebRequest"/> that should contain the entity.</param> + /// <returns> + /// The stream the caller should write out the entity data to. + /// </returns> + /// <exception cref="ProtocolException">Thrown for any network error.</exception> + /// <remarks> + /// <para>The caller should have set the <see cref="HttpWebRequest.ContentLength"/> + /// and any other appropriate properties <i>before</i> calling this method. + /// Callers <i>must</i> close and dispose of the request stream when they are done + /// writing to it to avoid taking up the connection too long and causing long waits on + /// subsequent requests.</para> + /// <para>Implementations should catch <see cref="WebException"/> and wrap it in a + /// <see cref="ProtocolException"/> to abstract away the transport and provide + /// a single exception type for hosts to catch.</para> + /// </remarks> + Stream IDirectWebRequestHandler.GetRequestStream(HttpWebRequest request) { + Requires.NotNull(request, "request"); + throw new System.NotImplementedException(); + } + + /// <summary> + /// Prepares an <see cref="HttpWebRequest"/> that contains an POST entity for sending the entity. + /// </summary> + /// <param name="request">The <see cref="HttpWebRequest"/> that should contain the entity.</param> + /// <param name="options">The options to apply to this web request.</param> + /// <returns> + /// The stream the caller should write out the entity data to. + /// </returns> + /// <exception cref="ProtocolException">Thrown for any network error.</exception> + /// <remarks> + /// <para>The caller should have set the <see cref="HttpWebRequest.ContentLength"/> + /// and any other appropriate properties <i>before</i> calling this method. + /// Callers <i>must</i> close and dispose of the request stream when they are done + /// writing to it to avoid taking up the connection too long and causing long waits on + /// subsequent requests.</para> + /// <para>Implementations should catch <see cref="WebException"/> and wrap it in a + /// <see cref="ProtocolException"/> to abstract away the transport and provide + /// a single exception type for hosts to catch.</para> + /// </remarks> + Stream IDirectWebRequestHandler.GetRequestStream(HttpWebRequest request, DirectWebRequestOptions options) { + Requires.NotNull(request, "request"); + Requires.Support(((IDirectWebRequestHandler)this).CanSupport(options), MessagingStrings.DirectWebRequestOptionsNotSupported); + ////ErrorUtilities.VerifySupported(((IDirectWebRequestHandler)this).CanSupport(options), string.Format(MessagingStrings.DirectWebRequestOptionsNotSupported, options, this.GetType().Name)); + throw new System.NotImplementedException(); + } + + /// <summary> + /// Processes an <see cref="HttpWebRequest"/> and converts the + /// <see cref="HttpWebResponse"/> to a <see cref="IncomingWebResponse"/> instance. + /// </summary> + /// <param name="request">The <see cref="HttpWebRequest"/> to handle.</param> + /// <returns> + /// An instance of <see cref="IncomingWebResponse"/> describing the response. + /// </returns> + /// <exception cref="ProtocolException">Thrown for any network error.</exception> + /// <remarks> + /// Implementations should catch <see cref="WebException"/> and wrap it in a + /// <see cref="ProtocolException"/> to abstract away the transport and provide + /// a single exception type for hosts to catch. The <see cref="WebException.Response"/> + /// value, if set, should be Closed before throwing. + /// </remarks> + IncomingWebResponse IDirectWebRequestHandler.GetResponse(HttpWebRequest request) { + Requires.NotNull(request, "request"); + Contract.Ensures(Contract.Result<IncomingWebResponse>() != null); + Contract.Ensures(Contract.Result<IncomingWebResponse>().ResponseStream != null); + throw new System.NotImplementedException(); + } + + /// <summary> + /// Processes an <see cref="HttpWebRequest"/> and converts the + /// <see cref="HttpWebResponse"/> to a <see cref="IncomingWebResponse"/> instance. + /// </summary> + /// <param name="request">The <see cref="HttpWebRequest"/> to handle.</param> + /// <param name="options">The options to apply to this web request.</param> + /// <returns> + /// An instance of <see cref="IncomingWebResponse"/> describing the response. + /// </returns> + /// <exception cref="ProtocolException">Thrown for any network error.</exception> + /// <remarks> + /// Implementations should catch <see cref="WebException"/> and wrap it in a + /// <see cref="ProtocolException"/> to abstract away the transport and provide + /// a single exception type for hosts to catch. The <see cref="WebException.Response"/> + /// value, if set, should be Closed before throwing. + /// </remarks> + IncomingWebResponse IDirectWebRequestHandler.GetResponse(HttpWebRequest request, DirectWebRequestOptions options) { + Requires.NotNull(request, "request"); + Contract.Ensures(Contract.Result<IncomingWebResponse>() != null); + Contract.Ensures(Contract.Result<IncomingWebResponse>().ResponseStream != null); + Requires.Support(((IDirectWebRequestHandler)this).CanSupport(options), MessagingStrings.DirectWebRequestOptionsNotSupported); + + ////ErrorUtilities.VerifySupported(((IDirectWebRequestHandler)this).CanSupport(options), string.Format(MessagingStrings.DirectWebRequestOptionsNotSupported, options, this.GetType().Name)); + throw new System.NotImplementedException(); + } + + #endregion + } +} diff --git a/src/DotNetOpenAuth/Messaging/IDirectWebRequestHandler.cs.orig b/src/DotNetOpenAuth.Core/Messaging/IDirectWebRequestHandler.cs.orig index a17b379..a17b379 100644 --- a/src/DotNetOpenAuth/Messaging/IDirectWebRequestHandler.cs.orig +++ b/src/DotNetOpenAuth.Core/Messaging/IDirectWebRequestHandler.cs.orig diff --git a/src/DotNetOpenAuth/Messaging/IDirectedProtocolMessage.cs b/src/DotNetOpenAuth.Core/Messaging/IDirectedProtocolMessage.cs index 4342d45..4342d45 100644 --- a/src/DotNetOpenAuth/Messaging/IDirectedProtocolMessage.cs +++ b/src/DotNetOpenAuth.Core/Messaging/IDirectedProtocolMessage.cs diff --git a/src/DotNetOpenAuth/Messaging/IExtensionMessage.cs b/src/DotNetOpenAuth.Core/Messaging/IExtensionMessage.cs index 5fc05a6..5fc05a6 100644 --- a/src/DotNetOpenAuth/Messaging/IExtensionMessage.cs +++ b/src/DotNetOpenAuth.Core/Messaging/IExtensionMessage.cs diff --git a/src/DotNetOpenAuth/Messaging/IHttpDirectResponse.cs b/src/DotNetOpenAuth.Core/Messaging/IHttpDirectResponse.cs index 20c3d6f..20c3d6f 100644 --- a/src/DotNetOpenAuth/Messaging/IHttpDirectResponse.cs +++ b/src/DotNetOpenAuth.Core/Messaging/IHttpDirectResponse.cs diff --git a/src/DotNetOpenAuth/Messaging/IHttpDirectResponseContract.cs b/src/DotNetOpenAuth.Core/Messaging/IHttpDirectResponseContract.cs index b1ddba2..b1ddba2 100644 --- a/src/DotNetOpenAuth/Messaging/IHttpDirectResponseContract.cs +++ b/src/DotNetOpenAuth.Core/Messaging/IHttpDirectResponseContract.cs diff --git a/src/DotNetOpenAuth/Messaging/IHttpIndirectResponse.cs b/src/DotNetOpenAuth.Core/Messaging/IHttpIndirectResponse.cs index 7d0fe0c..7d0fe0c 100644 --- a/src/DotNetOpenAuth/Messaging/IHttpIndirectResponse.cs +++ b/src/DotNetOpenAuth.Core/Messaging/IHttpIndirectResponse.cs diff --git a/src/DotNetOpenAuth/Messaging/IMessage.cs b/src/DotNetOpenAuth.Core/Messaging/IMessage.cs index e91a160..e91a160 100644 --- a/src/DotNetOpenAuth/Messaging/IMessage.cs +++ b/src/DotNetOpenAuth.Core/Messaging/IMessage.cs diff --git a/src/DotNetOpenAuth.Core/Messaging/IMessageFactory.cs b/src/DotNetOpenAuth.Core/Messaging/IMessageFactory.cs new file mode 100644 index 0000000..b44bbbf --- /dev/null +++ b/src/DotNetOpenAuth.Core/Messaging/IMessageFactory.cs @@ -0,0 +1,93 @@ +//----------------------------------------------------------------------- +// <copyright file="IMessageFactory.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Messaging { + using System; + using System.Collections.Generic; + using System.Diagnostics.Contracts; + + /// <summary> + /// A tool to analyze an incoming message to figure out what concrete class + /// is designed to deserialize it and instantiates that class. + /// </summary> + [ContractClass(typeof(IMessageFactoryContract))] + public interface IMessageFactory { + /// <summary> + /// Analyzes an incoming request message payload to discover what kind of + /// message is embedded in it and returns the type, or null if no match is found. + /// </summary> + /// <param name="recipient">The intended or actual recipient of the request message.</param> + /// <param name="fields">The name/value pairs that make up the message payload.</param> + /// <returns> + /// A newly instantiated <see cref="IProtocolMessage"/>-derived object that this message can + /// deserialize to. Null if the request isn't recognized as a valid protocol message. + /// </returns> + IDirectedProtocolMessage GetNewRequestMessage(MessageReceivingEndpoint recipient, IDictionary<string, string> fields); + + /// <summary> + /// Analyzes an incoming request message payload to discover what kind of + /// message is embedded in it and returns the type, or null if no match is found. + /// </summary> + /// <param name="request"> + /// The message that was sent as a request that resulted in the response. + /// </param> + /// <param name="fields">The name/value pairs that make up the message payload.</param> + /// <returns> + /// A newly instantiated <see cref="IProtocolMessage"/>-derived object that this message can + /// deserialize to. Null if the request isn't recognized as a valid protocol message. + /// </returns> + IDirectResponseProtocolMessage GetNewResponseMessage(IDirectedProtocolMessage request, IDictionary<string, string> fields); + } + + /// <summary> + /// Code contract for the <see cref="IMessageFactory"/> interface. + /// </summary> + [ContractClassFor(typeof(IMessageFactory))] + internal abstract class IMessageFactoryContract : IMessageFactory { + /// <summary> + /// Prevents a default instance of the <see cref="IMessageFactoryContract"/> class from being created. + /// </summary> + private IMessageFactoryContract() { + } + + #region IMessageFactory Members + + /// <summary> + /// Analyzes an incoming request message payload to discover what kind of + /// message is embedded in it and returns the type, or null if no match is found. + /// </summary> + /// <param name="recipient">The intended or actual recipient of the request message.</param> + /// <param name="fields">The name/value pairs that make up the message payload.</param> + /// <returns> + /// A newly instantiated <see cref="IProtocolMessage"/>-derived object that this message can + /// deserialize to. Null if the request isn't recognized as a valid protocol message. + /// </returns> + IDirectedProtocolMessage IMessageFactory.GetNewRequestMessage(MessageReceivingEndpoint recipient, IDictionary<string, string> fields) { + Requires.NotNull(recipient, "recipient"); + Requires.NotNull(fields, "fields"); + + throw new NotImplementedException(); + } + + /// <summary> + /// Analyzes an incoming request message payload to discover what kind of + /// message is embedded in it and returns the type, or null if no match is found. + /// </summary> + /// <param name="request">The message that was sent as a request that resulted in the response.</param> + /// <param name="fields">The name/value pairs that make up the message payload.</param> + /// <returns> + /// A newly instantiated <see cref="IProtocolMessage"/>-derived object that this message can + /// deserialize to. Null if the request isn't recognized as a valid protocol message. + /// </returns> + IDirectResponseProtocolMessage IMessageFactory.GetNewResponseMessage(IDirectedProtocolMessage request, IDictionary<string, string> fields) { + Requires.NotNull(request, "request"); + Requires.NotNull(fields, "fields"); + throw new NotImplementedException(); + } + + #endregion + } +} diff --git a/src/DotNetOpenAuth/Messaging/IMessageOriginalPayload.cs b/src/DotNetOpenAuth.Core/Messaging/IMessageOriginalPayload.cs index d18be20..d18be20 100644 --- a/src/DotNetOpenAuth/Messaging/IMessageOriginalPayload.cs +++ b/src/DotNetOpenAuth.Core/Messaging/IMessageOriginalPayload.cs diff --git a/src/DotNetOpenAuth/Messaging/IMessageWithBinaryData.cs b/src/DotNetOpenAuth.Core/Messaging/IMessageWithBinaryData.cs index 32ae227..32ae227 100644 --- a/src/DotNetOpenAuth/Messaging/IMessageWithBinaryData.cs +++ b/src/DotNetOpenAuth.Core/Messaging/IMessageWithBinaryData.cs diff --git a/src/DotNetOpenAuth/Messaging/IMessageWithEvents.cs b/src/DotNetOpenAuth.Core/Messaging/IMessageWithEvents.cs index 51e00fc..51e00fc 100644 --- a/src/DotNetOpenAuth/Messaging/IMessageWithEvents.cs +++ b/src/DotNetOpenAuth.Core/Messaging/IMessageWithEvents.cs diff --git a/src/DotNetOpenAuth/Messaging/IProtocolMessage.cs b/src/DotNetOpenAuth.Core/Messaging/IProtocolMessage.cs index cf43360..cf43360 100644 --- a/src/DotNetOpenAuth/Messaging/IProtocolMessage.cs +++ b/src/DotNetOpenAuth.Core/Messaging/IProtocolMessage.cs diff --git a/src/DotNetOpenAuth/Messaging/IProtocolMessageWithExtensions.cs b/src/DotNetOpenAuth.Core/Messaging/IProtocolMessageWithExtensions.cs index 44c4cbb..44c4cbb 100644 --- a/src/DotNetOpenAuth/Messaging/IProtocolMessageWithExtensions.cs +++ b/src/DotNetOpenAuth.Core/Messaging/IProtocolMessageWithExtensions.cs diff --git a/src/DotNetOpenAuth/Messaging/IStreamSerializingDataBag.cs b/src/DotNetOpenAuth.Core/Messaging/IStreamSerializingDataBag.cs index 2003f9e..2003f9e 100644 --- a/src/DotNetOpenAuth/Messaging/IStreamSerializingDataBag.cs +++ b/src/DotNetOpenAuth.Core/Messaging/IStreamSerializingDataBag.cs diff --git a/src/DotNetOpenAuth/Messaging/ITamperResistantProtocolMessage.cs b/src/DotNetOpenAuth.Core/Messaging/ITamperResistantProtocolMessage.cs index 0da6303..0da6303 100644 --- a/src/DotNetOpenAuth/Messaging/ITamperResistantProtocolMessage.cs +++ b/src/DotNetOpenAuth.Core/Messaging/ITamperResistantProtocolMessage.cs diff --git a/src/DotNetOpenAuth.Core/Messaging/IncomingWebResponse.cs b/src/DotNetOpenAuth.Core/Messaging/IncomingWebResponse.cs new file mode 100644 index 0000000..90d2f1f --- /dev/null +++ b/src/DotNetOpenAuth.Core/Messaging/IncomingWebResponse.cs @@ -0,0 +1,191 @@ +//----------------------------------------------------------------------- +// <copyright file="IncomingWebResponse.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Messaging { + using System; + using System.Diagnostics.CodeAnalysis; + using System.Diagnostics.Contracts; + using System.Globalization; + using System.IO; + using System.Net; + using System.Net.Mime; + using System.Text; + + /// <summary> + /// Details on the incoming response from a direct web request to a remote party. + /// </summary> + [ContractVerification(true)] + [ContractClass(typeof(IncomingWebResponseContract))] + public abstract class IncomingWebResponse : IDisposable { + /// <summary> + /// The encoding to use in reading a response that does not declare its own content encoding. + /// </summary> + private const string DefaultContentEncoding = "ISO-8859-1"; + + /// <summary> + /// Initializes a new instance of the <see cref="IncomingWebResponse"/> class. + /// </summary> + protected internal IncomingWebResponse() { + this.Status = HttpStatusCode.OK; + this.Headers = new WebHeaderCollection(); + } + + /// <summary> + /// Initializes a new instance of the <see cref="IncomingWebResponse"/> class. + /// </summary> + /// <param name="requestUri">The original request URI.</param> + /// <param name="response">The response to initialize from. The network stream is used by this class directly.</param> + protected IncomingWebResponse(Uri requestUri, HttpWebResponse response) { + Requires.NotNull(requestUri, "requestUri"); + Requires.NotNull(response, "response"); + + this.RequestUri = requestUri; + if (!string.IsNullOrEmpty(response.ContentType)) { + try { + this.ContentType = new ContentType(response.ContentType); + } catch (FormatException) { + Logger.Messaging.ErrorFormat("HTTP response to {0} included an invalid Content-Type header value: {1}", response.ResponseUri.AbsoluteUri, response.ContentType); + } + } + this.ContentEncoding = string.IsNullOrEmpty(response.ContentEncoding) ? DefaultContentEncoding : response.ContentEncoding; + this.FinalUri = response.ResponseUri; + this.Status = response.StatusCode; + this.Headers = response.Headers; + } + + /// <summary> + /// Initializes a new instance of the <see cref="IncomingWebResponse"/> class. + /// </summary> + /// <param name="requestUri">The request URI.</param> + /// <param name="responseUri">The final URI to respond to the request.</param> + /// <param name="headers">The headers.</param> + /// <param name="statusCode">The status code.</param> + /// <param name="contentType">Type of the content.</param> + /// <param name="contentEncoding">The content encoding.</param> + protected IncomingWebResponse(Uri requestUri, Uri responseUri, WebHeaderCollection headers, HttpStatusCode statusCode, string contentType, string contentEncoding) { + Requires.NotNull(requestUri, "requestUri"); + + this.RequestUri = requestUri; + this.Status = statusCode; + if (!string.IsNullOrEmpty(contentType)) { + try { + this.ContentType = new ContentType(contentType); + } catch (FormatException) { + Logger.Messaging.ErrorFormat("HTTP response to {0} included an invalid Content-Type header value: {1}", responseUri.AbsoluteUri, contentType); + } + } + this.ContentEncoding = string.IsNullOrEmpty(contentEncoding) ? DefaultContentEncoding : contentEncoding; + this.Headers = headers; + this.FinalUri = responseUri; + } + + /// <summary> + /// Gets the type of the content. + /// </summary> + public ContentType ContentType { get; private set; } + + /// <summary> + /// Gets the content encoding. + /// </summary> + public string ContentEncoding { get; private set; } + + /// <summary> + /// Gets the URI of the initial request. + /// </summary> + public Uri RequestUri { get; private set; } + + /// <summary> + /// Gets the URI that finally responded to the request. + /// </summary> + /// <remarks> + /// This can be different from the <see cref="RequestUri"/> in cases of + /// redirection during the request. + /// </remarks> + public Uri FinalUri { get; internal set; } + + /// <summary> + /// Gets the headers that must be included in the response to the user agent. + /// </summary> + /// <remarks> + /// The headers in this collection are not meant to be a comprehensive list + /// of exactly what should be sent, but are meant to augment whatever headers + /// are generally included in a typical response. + /// </remarks> + public WebHeaderCollection Headers { get; internal set; } + + /// <summary> + /// Gets the HTTP status code to use in the HTTP response. + /// </summary> + public HttpStatusCode Status { get; internal set; } + + /// <summary> + /// Gets the body of the HTTP response. + /// </summary> + public abstract Stream ResponseStream { get; } + + /// <summary> + /// Returns a <see cref="T:System.String"/> that represents the current <see cref="T:System.Object"/>. + /// </summary> + /// <returns> + /// A <see cref="T:System.String"/> that represents the current <see cref="T:System.Object"/>. + /// </returns> + public override string ToString() { + StringBuilder sb = new StringBuilder(); + sb.AppendLine(string.Format(CultureInfo.CurrentCulture, "RequestUri = {0}", this.RequestUri)); + sb.AppendLine(string.Format(CultureInfo.CurrentCulture, "ResponseUri = {0}", this.FinalUri)); + sb.AppendLine(string.Format(CultureInfo.CurrentCulture, "StatusCode = {0}", this.Status)); + sb.AppendLine(string.Format(CultureInfo.CurrentCulture, "ContentType = {0}", this.ContentType)); + sb.AppendLine(string.Format(CultureInfo.CurrentCulture, "ContentEncoding = {0}", this.ContentEncoding)); + sb.AppendLine("Headers:"); + foreach (string header in this.Headers) { + sb.AppendLine(string.Format(CultureInfo.CurrentCulture, "\t{0}: {1}", header, this.Headers[header])); + } + + return sb.ToString(); + } + + /// <summary> + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// </summary> + public void Dispose() { + this.Dispose(true); + GC.SuppressFinalize(this); + } + + /// <summary> + /// Creates a text reader for the response stream. + /// </summary> + /// <returns>The text reader, initialized for the proper encoding.</returns> + [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "Costly operation")] + public abstract StreamReader GetResponseReader(); + + /// <summary> + /// Gets an offline snapshot version of this instance. + /// </summary> + /// <param name="maximumBytesToCache">The maximum bytes from the response stream to cache.</param> + /// <returns>A snapshot version of this instance.</returns> + /// <remarks> + /// If this instance is a <see cref="NetworkDirectWebResponse"/> creating a snapshot + /// will automatically close and dispose of the underlying response stream. + /// If this instance is a <see cref="CachedDirectWebResponse"/>, the result will + /// be the self same instance. + /// </remarks> + internal abstract CachedDirectWebResponse GetSnapshot(int maximumBytesToCache); + + /// <summary> + /// Releases unmanaged and - optionally - managed resources + /// </summary> + /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param> + protected virtual void Dispose(bool disposing) { + if (disposing) { + Stream responseStream = this.ResponseStream; + if (responseStream != null) { + responseStream.Dispose(); + } + } + } + } +} diff --git a/src/DotNetOpenAuth.Core/Messaging/IncomingWebResponseContract.cs b/src/DotNetOpenAuth.Core/Messaging/IncomingWebResponseContract.cs new file mode 100644 index 0000000..8c9a6df --- /dev/null +++ b/src/DotNetOpenAuth.Core/Messaging/IncomingWebResponseContract.cs @@ -0,0 +1,54 @@ +//----------------------------------------------------------------------- +// <copyright file="IncomingWebResponseContract.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Messaging { + using System; + using System.Diagnostics.Contracts; + using System.IO; + + /// <summary> + /// Code contract for the <see cref="IncomingWebResponse"/> class. + /// </summary> + [ContractClassFor(typeof(IncomingWebResponse))] + internal abstract class IncomingWebResponseContract : IncomingWebResponse { + /// <summary> + /// Gets the body of the HTTP response. + /// </summary> + /// <value></value> + public override Stream ResponseStream { + get { throw new NotImplementedException(); } + } + + /// <summary> + /// Creates a text reader for the response stream. + /// </summary> + /// <returns> + /// The text reader, initialized for the proper encoding. + /// </returns> + public override StreamReader GetResponseReader() { + Contract.Ensures(Contract.Result<StreamReader>() != null); + throw new NotImplementedException(); + } + + /// <summary> + /// Gets an offline snapshot version of this instance. + /// </summary> + /// <param name="maximumBytesToCache">The maximum bytes from the response stream to cache.</param> + /// <returns>A snapshot version of this instance.</returns> + /// <remarks> + /// If this instance is a <see cref="NetworkDirectWebResponse"/> creating a snapshot + /// will automatically close and dispose of the underlying response stream. + /// If this instance is a <see cref="CachedDirectWebResponse"/>, the result will + /// be the self same instance. + /// </remarks> + internal override CachedDirectWebResponse GetSnapshot(int maximumBytesToCache) { + Requires.InRange(maximumBytesToCache >= 0, "maximumBytesToCache"); + Requires.ValidState(this.RequestUri != null); + Contract.Ensures(Contract.Result<CachedDirectWebResponse>() != null); + throw new NotImplementedException(); + } + } +} diff --git a/src/DotNetOpenAuth/Messaging/InternalErrorException.cs b/src/DotNetOpenAuth.Core/Messaging/InternalErrorException.cs index 32b44f2..32b44f2 100644 --- a/src/DotNetOpenAuth/Messaging/InternalErrorException.cs +++ b/src/DotNetOpenAuth.Core/Messaging/InternalErrorException.cs diff --git a/src/DotNetOpenAuth.Core/Messaging/KeyedCollectionDelegate.cs b/src/DotNetOpenAuth.Core/Messaging/KeyedCollectionDelegate.cs new file mode 100644 index 0000000..c0a08df --- /dev/null +++ b/src/DotNetOpenAuth.Core/Messaging/KeyedCollectionDelegate.cs @@ -0,0 +1,45 @@ +//----------------------------------------------------------------------- +// <copyright file="KeyedCollectionDelegate.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Messaging { + using System; + using System.Collections.ObjectModel; + using System.Diagnostics.Contracts; + + /// <summary> + /// A KeyedCollection whose item -> key transform is provided via a delegate + /// to its constructor, and null items are disallowed. + /// </summary> + /// <typeparam name="TKey">The type of the key.</typeparam> + /// <typeparam name="TItem">The type of the item.</typeparam> + [Serializable] + internal class KeyedCollectionDelegate<TKey, TItem> : KeyedCollection<TKey, TItem> { + /// <summary> + /// The delegate that returns a key for the given item. + /// </summary> + private Func<TItem, TKey> getKeyForItemDelegate; + + /// <summary> + /// Initializes a new instance of the KeyedCollectionDelegate class. + /// </summary> + /// <param name="getKeyForItemDelegate">The delegate that gets the key for a given item.</param> + internal KeyedCollectionDelegate(Func<TItem, TKey> getKeyForItemDelegate) { + Requires.NotNull(getKeyForItemDelegate, "getKeyForItemDelegate"); + + this.getKeyForItemDelegate = getKeyForItemDelegate; + } + + /// <summary> + /// When implemented in a derived class, extracts the key from the specified element. + /// </summary> + /// <param name="item">The element from which to extract the key.</param> + /// <returns>The key for the specified element.</returns> + protected override TKey GetKeyForItem(TItem item) { + ErrorUtilities.VerifyArgumentNotNull(item, "item"); // null items not supported. + return this.getKeyForItemDelegate(item); + } + } +} diff --git a/src/DotNetOpenAuth/Messaging/MessagePartAttribute.cs b/src/DotNetOpenAuth.Core/Messaging/MessagePartAttribute.cs index 22c660c..22c660c 100644 --- a/src/DotNetOpenAuth/Messaging/MessagePartAttribute.cs +++ b/src/DotNetOpenAuth.Core/Messaging/MessagePartAttribute.cs diff --git a/src/DotNetOpenAuth/Messaging/MessageProtections.cs b/src/DotNetOpenAuth.Core/Messaging/MessageProtections.cs index c78c92f..c78c92f 100644 --- a/src/DotNetOpenAuth/Messaging/MessageProtections.cs +++ b/src/DotNetOpenAuth.Core/Messaging/MessageProtections.cs diff --git a/src/DotNetOpenAuth.Core/Messaging/MessageReceivingEndpoint.cs b/src/DotNetOpenAuth.Core/Messaging/MessageReceivingEndpoint.cs new file mode 100644 index 0000000..ca7c5df --- /dev/null +++ b/src/DotNetOpenAuth.Core/Messaging/MessageReceivingEndpoint.cs @@ -0,0 +1,54 @@ +//----------------------------------------------------------------------- +// <copyright file="MessageReceivingEndpoint.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Messaging { + using System; + using System.Diagnostics; + using System.Diagnostics.Contracts; + + /// <summary> + /// An immutable description of a URL that receives messages. + /// </summary> + [DebuggerDisplay("{AllowedMethods} {Location}")] + [Serializable] + public class MessageReceivingEndpoint { + /// <summary> + /// Initializes a new instance of the <see cref="MessageReceivingEndpoint"/> class. + /// </summary> + /// <param name="locationUri">The URL of this endpoint.</param> + /// <param name="method">The HTTP method(s) allowed.</param> + public MessageReceivingEndpoint(string locationUri, HttpDeliveryMethods method) + : this(new Uri(locationUri), method) { + Requires.NotNull(locationUri, "locationUri"); + Requires.InRange(method != HttpDeliveryMethods.None, "method"); + Requires.InRange((method & HttpDeliveryMethods.HttpVerbMask) != 0, "method", MessagingStrings.GetOrPostFlagsRequired); + } + + /// <summary> + /// Initializes a new instance of the <see cref="MessageReceivingEndpoint"/> class. + /// </summary> + /// <param name="location">The URL of this endpoint.</param> + /// <param name="method">The HTTP method(s) allowed.</param> + public MessageReceivingEndpoint(Uri location, HttpDeliveryMethods method) { + Requires.NotNull(location, "location"); + Requires.InRange(method != HttpDeliveryMethods.None, "method"); + Requires.InRange((method & HttpDeliveryMethods.HttpVerbMask) != 0, "method", MessagingStrings.GetOrPostFlagsRequired); + + this.Location = location; + this.AllowedMethods = method; + } + + /// <summary> + /// Gets the URL of this endpoint. + /// </summary> + public Uri Location { get; private set; } + + /// <summary> + /// Gets the HTTP method(s) allowed. + /// </summary> + public HttpDeliveryMethods AllowedMethods { get; private set; } + } +} diff --git a/src/DotNetOpenAuth.Core/Messaging/MessageSerializer.cs b/src/DotNetOpenAuth.Core/Messaging/MessageSerializer.cs new file mode 100644 index 0000000..957ea41 --- /dev/null +++ b/src/DotNetOpenAuth.Core/Messaging/MessageSerializer.cs @@ -0,0 +1,236 @@ +//----------------------------------------------------------------------- +// <copyright file="MessageSerializer.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Messaging { + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; + using System.Diagnostics.Contracts; + using System.Globalization; + using System.Reflection; + using System.Xml; + using DotNetOpenAuth.Messaging.Reflection; + + /// <summary> + /// Serializes/deserializes OAuth messages for/from transit. + /// </summary> + [ContractVerification(true)] + internal class MessageSerializer { + /// <summary> + /// The specific <see cref="IMessage"/>-derived type + /// that will be serialized and deserialized using this class. + /// </summary> + private readonly Type messageType; + + /// <summary> + /// Initializes a new instance of the MessageSerializer class. + /// </summary> + /// <param name="messageType">The specific <see cref="IMessage"/>-derived type + /// that will be serialized and deserialized using this class.</param> + [ContractVerification(false)] // bugs/limitations in CC static analysis + private MessageSerializer(Type messageType) { + Requires.NotNullSubtype<IMessage>(messageType, "messageType"); + Contract.Ensures(this.messageType != null); + this.messageType = messageType; + } + + /// <summary> + /// Creates or reuses a message serializer for a given message type. + /// </summary> + /// <param name="messageType">The type of message that will be serialized/deserialized.</param> + /// <returns>A message serializer for the given message type.</returns> + [ContractVerification(false)] // bugs/limitations in CC static analysis + internal static MessageSerializer Get(Type messageType) { + Requires.NotNullSubtype<IMessage>(messageType, "messageType"); + + return new MessageSerializer(messageType); + } + + /// <summary> + /// Reads JSON as a flat dictionary into a message. + /// </summary> + /// <param name="messageDictionary">The message dictionary to fill with the JSON-deserialized data.</param> + /// <param name="reader">The JSON reader.</param> + internal static void DeserializeJsonAsFlatDictionary(IDictionary<string, string> messageDictionary, XmlDictionaryReader reader) { + Requires.NotNull(messageDictionary, "messageDictionary"); + Requires.NotNull(reader, "reader"); + + reader.Read(); // one extra one to skip the root node. + while (reader.Read()) { + if (reader.NodeType == XmlNodeType.EndElement) { + // This is likely the closing </root> tag. + continue; + } + + string key = reader.Name; + reader.Read(); + string value = reader.ReadContentAsString(); + messageDictionary[key] = value; + } + } + + /// <summary> + /// Reads the data from a message instance and writes a XML/JSON encoding of it. + /// </summary> + /// <param name="messageDictionary">The message to be serialized.</param> + /// <param name="writer">The writer to use for the serialized form.</param> + /// <remarks> + /// Use <see cref="System.Runtime.Serialization.Json.JsonReaderWriterFactory.CreateJsonWriter(System.IO.Stream)"/> + /// to create the <see cref="XmlDictionaryWriter"/> instance capable of emitting JSON. + /// </remarks> + [Pure] + internal static void Serialize(MessageDictionary messageDictionary, XmlDictionaryWriter writer) { + Requires.NotNull(messageDictionary, "messageDictionary"); + Requires.NotNull(writer, "writer"); + + writer.WriteStartElement("root"); + writer.WriteAttributeString("type", "object"); + foreach (var pair in messageDictionary) { + bool include = false; + string type = "string"; + MessagePart partDescription; + if (messageDictionary.Description.Mapping.TryGetValue(pair.Key, out partDescription)) { + Contract.Assume(partDescription != null); + if (partDescription.IsRequired || partDescription.IsNondefaultValueSet(messageDictionary.Message)) { + include = true; + if (IsNumeric(partDescription.MemberDeclaredType)) { + type = "number"; + } else if (partDescription.MemberDeclaredType.IsAssignableFrom(typeof(bool))) { + type = "boolean"; + } + } + } else { + // This is extra data. We always write it out. + include = true; + } + + if (include) { + writer.WriteStartElement(pair.Key); + writer.WriteAttributeString("type", type); + writer.WriteString(pair.Value); + writer.WriteEndElement(); + } + } + + writer.WriteEndElement(); + } + + /// <summary> + /// Reads XML/JSON into a message dictionary. + /// </summary> + /// <param name="messageDictionary">The message to deserialize into.</param> + /// <param name="reader">The XML/JSON to read into the message.</param> + /// <exception cref="ProtocolException">Thrown when protocol rules are broken by the incoming message.</exception> + /// <remarks> + /// Use <see cref="System.Runtime.Serialization.Json.JsonReaderWriterFactory.CreateJsonReader(System.IO.Stream, System.Xml.XmlDictionaryReaderQuotas)"/> + /// to create the <see cref="XmlDictionaryReader"/> instance capable of reading JSON. + /// </remarks> + internal static void Deserialize(MessageDictionary messageDictionary, XmlDictionaryReader reader) { + Requires.NotNull(messageDictionary, "messageDictionary"); + Requires.NotNull(reader, "reader"); + + DeserializeJsonAsFlatDictionary(messageDictionary, reader); + + // Make sure all the required parts are present and valid. + messageDictionary.Description.EnsureMessagePartsPassBasicValidation(messageDictionary); + messageDictionary.Message.EnsureValidMessage(); + } + + /// <summary> + /// Reads the data from a message instance and returns a series of name=value pairs for the fields that must be included in the message. + /// </summary> + /// <param name="messageDictionary">The message to be serialized.</param> + /// <returns>The dictionary of values to send for the message.</returns> + [Pure] + [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Parallel design with Deserialize method.")] + internal IDictionary<string, string> Serialize(MessageDictionary messageDictionary) { + Requires.NotNull(messageDictionary, "messageDictionary"); + Contract.Ensures(Contract.Result<IDictionary<string, string>>() != null); + + // Rather than hand back the whole message dictionary (which + // includes keys with blank values), create a new dictionary + // that only has required keys, and optional keys whose + // values are not empty (or default). + var result = new Dictionary<string, string>(); + foreach (var pair in messageDictionary) { + MessagePart partDescription; + if (messageDictionary.Description.Mapping.TryGetValue(pair.Key, out partDescription)) { + Contract.Assume(partDescription != null); + if (partDescription.IsRequired || partDescription.IsNondefaultValueSet(messageDictionary.Message)) { + result.Add(pair.Key, pair.Value); + } + } else { + // This is extra data. We always write it out. + result.Add(pair.Key, pair.Value); + } + } + + return result; + } + + /// <summary> + /// Reads name=value pairs into a message. + /// </summary> + /// <param name="fields">The name=value pairs that were read in from the transport.</param> + /// <param name="messageDictionary">The message to deserialize into.</param> + /// <exception cref="ProtocolException">Thrown when protocol rules are broken by the incoming message.</exception> + internal void Deserialize(IDictionary<string, string> fields, MessageDictionary messageDictionary) { + Requires.NotNull(fields, "fields"); + Requires.NotNull(messageDictionary, "messageDictionary"); + + var messageDescription = messageDictionary.Description; + + // Before we deserialize the message, make sure all the required parts are present. + messageDescription.EnsureMessagePartsPassBasicValidation(fields); + + try { + foreach (var pair in fields) { + messageDictionary[pair.Key] = pair.Value; + } + } catch (ArgumentException ex) { + throw ErrorUtilities.Wrap(ex, MessagingStrings.ErrorDeserializingMessage, this.messageType.Name); + } + + messageDictionary.Message.EnsureValidMessage(); + + var originalPayloadMessage = messageDictionary.Message as IMessageOriginalPayload; + if (originalPayloadMessage != null) { + originalPayloadMessage.OriginalPayload = fields; + } + } + + /// <summary> + /// Determines whether the specified type is numeric. + /// </summary> + /// <param name="type">The type to test.</param> + /// <returns> + /// <c>true</c> if the specified type is numeric; otherwise, <c>false</c>. + /// </returns> + private static bool IsNumeric(Type type) { + return type.IsAssignableFrom(typeof(double)) + || type.IsAssignableFrom(typeof(float)) + || type.IsAssignableFrom(typeof(short)) + || type.IsAssignableFrom(typeof(int)) + || type.IsAssignableFrom(typeof(long)) + || type.IsAssignableFrom(typeof(ushort)) + || type.IsAssignableFrom(typeof(uint)) + || type.IsAssignableFrom(typeof(ulong)); + } + +#if CONTRACTS_FULL + /// <summary> + /// Verifies conditions that should be true for any valid state of this object. + /// </summary> + [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Called by code contracts.")] + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called by code contracts.")] + [ContractInvariantMethod] + private void ObjectInvariant() { + Contract.Invariant(this.messageType != null); + } +#endif + } +} diff --git a/src/DotNetOpenAuth/Messaging/MessageTransport.cs b/src/DotNetOpenAuth.Core/Messaging/MessageTransport.cs index ee06c95..ee06c95 100644 --- a/src/DotNetOpenAuth/Messaging/MessageTransport.cs +++ b/src/DotNetOpenAuth.Core/Messaging/MessageTransport.cs diff --git a/src/DotNetOpenAuth/Messaging/Messaging.cd b/src/DotNetOpenAuth.Core/Messaging/Messaging.cd index 0c22565..0c22565 100644 --- a/src/DotNetOpenAuth/Messaging/Messaging.cd +++ b/src/DotNetOpenAuth.Core/Messaging/Messaging.cd diff --git a/src/DotNetOpenAuth.Core/Messaging/MessagingStrings.Designer.cs b/src/DotNetOpenAuth.Core/Messaging/MessagingStrings.Designer.cs new file mode 100644 index 0000000..3ad2bdd --- /dev/null +++ b/src/DotNetOpenAuth.Core/Messaging/MessagingStrings.Designer.cs @@ -0,0 +1,684 @@ +//------------------------------------------------------------------------------ +// <auto-generated> +// This code was generated by a tool. +// Runtime Version:4.0.30319.239 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// </auto-generated> +//------------------------------------------------------------------------------ + +namespace DotNetOpenAuth.Messaging { + 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 MessagingStrings { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal MessagingStrings() { + } + + /// <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.Messaging.MessagingStrings", typeof(MessagingStrings).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 Argument's {0}.{1} property is required but is empty or null.. + /// </summary> + internal static string ArgumentPropertyMissing { + get { + return ResourceManager.GetString("ArgumentPropertyMissing", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Unable to send all message data because some of it requires multi-part POST, but IMessageWithBinaryData.SendAsMultipart was false.. + /// </summary> + internal static string BinaryDataRequiresMultipart { + get { + return ResourceManager.GetString("BinaryDataRequiresMultipart", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to HttpContext.Current is null. There must be an ASP.NET request in process for this operation to succeed.. + /// </summary> + internal static string CurrentHttpContextRequired { + get { + return ResourceManager.GetString("CurrentHttpContextRequired", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to DataContractSerializer could not be initialized on message type {0}. Is it missing a [DataContract] attribute?. + /// </summary> + internal static string DataContractMissingFromMessageType { + get { + return ResourceManager.GetString("DataContractMissingFromMessageType", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to DataContractSerializer could not be initialized on message type {0} because the DataContractAttribute.Namespace property is not set.. + /// </summary> + internal static string DataContractMissingNamespace { + get { + return ResourceManager.GetString("DataContractMissingNamespace", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to An instance of type {0} was expected, but received unexpected derived type {1}.. + /// </summary> + internal static string DerivedTypeNotExpected { + get { + return ResourceManager.GetString("DerivedTypeNotExpected", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to The directed message's Recipient property must not be null.. + /// </summary> + internal static string DirectedMessageMissingRecipient { + get { + return ResourceManager.GetString("DirectedMessageMissingRecipient", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to The given set of options is not supported by this web request handler.. + /// </summary> + internal static string DirectWebRequestOptionsNotSupported { + get { + return ResourceManager.GetString("DirectWebRequestOptionsNotSupported", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Unable to instantiate the message part encoder/decoder type {0}.. + /// </summary> + internal static string EncoderInstantiationFailed { + get { + return ResourceManager.GetString("EncoderInstantiationFailed", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Error while deserializing message {0}.. + /// </summary> + internal static string ErrorDeserializingMessage { + get { + return ResourceManager.GetString("ErrorDeserializingMessage", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Error occurred while sending a direct message or getting the response.. + /// </summary> + internal static string ErrorInRequestReplyMessage { + get { + return ResourceManager.GetString("ErrorInRequestReplyMessage", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to This exception was not constructed with a root request message that caused it.. + /// </summary> + internal static string ExceptionNotConstructedForTransit { + get { + return ResourceManager.GetString("ExceptionNotConstructedForTransit", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to This exception must be instantiated with a recipient that will receive the error message, or a direct request message instance that this exception will respond to.. + /// </summary> + internal static string ExceptionUndeliverable { + get { + return ResourceManager.GetString("ExceptionUndeliverable", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Expected {0} message but received no recognizable message.. + /// </summary> + internal static string ExpectedMessageNotReceived { + get { + return ResourceManager.GetString("ExpectedMessageNotReceived", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to The message part {0} was expected in the {1} message but was not found.. + /// </summary> + internal static string ExpectedParameterWasMissing { + get { + return ResourceManager.GetString("ExpectedParameterWasMissing", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to The message expired at {0} and it is now {1}.. + /// </summary> + internal static string ExpiredMessage { + get { + return ResourceManager.GetString("ExpiredMessage", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Failed to add extra parameter '{0}' with value '{1}'.. + /// </summary> + internal static string ExtraParameterAddFailure { + get { + return ResourceManager.GetString("ExtraParameterAddFailure", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to At least one of GET or POST flags must be present.. + /// </summary> + internal static string GetOrPostFlagsRequired { + get { + return ResourceManager.GetString("GetOrPostFlagsRequired", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to This method requires a current HttpContext. Alternatively, use an overload of this method that allows you to pass in information without an HttpContext.. + /// </summary> + internal static string HttpContextRequired { + get { + return ResourceManager.GetString("HttpContextRequired", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Messages that indicate indirect transport must implement the {0} interface.. + /// </summary> + internal static string IndirectMessagesMustImplementIDirectedProtocolMessage { + get { + return ResourceManager.GetString("IndirectMessagesMustImplementIDirectedProtocolMessage", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Insecure web request for '{0}' aborted due to security requirements demanding HTTPS.. + /// </summary> + internal static string InsecureWebRequestWithSslRequired { + get { + return ResourceManager.GetString("InsecureWebRequestWithSslRequired", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to The {0} message required protections {{{1}}} but the channel could only apply {{{2}}}.. + /// </summary> + internal static string InsufficientMessageProtection { + get { + return ResourceManager.GetString("InsufficientMessageProtection", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to The customized binding element ordering is invalid.. + /// </summary> + internal static string InvalidCustomBindingElementOrder { + get { + return ResourceManager.GetString("InvalidCustomBindingElementOrder", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Some part(s) of the message have invalid values: {0}. + /// </summary> + internal static string InvalidMessageParts { + get { + return ResourceManager.GetString("InvalidMessageParts", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to The incoming message had an invalid or missing nonce.. + /// </summary> + internal static string InvalidNonceReceived { + get { + return ResourceManager.GetString("InvalidNonceReceived", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to An item with the same key has already been added.. + /// </summary> + internal static string KeyAlreadyExists { + get { + return ResourceManager.GetString("KeyAlreadyExists", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to The {0} message does not support extensions.. + /// </summary> + internal static string MessageNotExtensible { + get { + return ResourceManager.GetString("MessageNotExtensible", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to The value for {0}.{1} on member {1} was expected to derive from {2} but was {3}.. + /// </summary> + internal static string MessagePartEncoderWrongType { + get { + return ResourceManager.GetString("MessagePartEncoderWrongType", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Error while reading message '{0}' parameter '{1}' with value '{2}'.. + /// </summary> + internal static string MessagePartReadFailure { + get { + return ResourceManager.GetString("MessagePartReadFailure", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Message parameter '{0}' with value '{1}' failed to base64 decode.. + /// </summary> + internal static string MessagePartValueBase64DecodingFault { + get { + return ResourceManager.GetString("MessagePartValueBase64DecodingFault", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Error while preparing message '{0}' parameter '{1}' for sending.. + /// </summary> + internal static string MessagePartWriteFailure { + get { + return ResourceManager.GetString("MessagePartWriteFailure", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to This message has a timestamp of {0}, which is beyond the allowable clock skew for in the future.. + /// </summary> + internal static string MessageTimestampInFuture { + get { + return ResourceManager.GetString("MessageTimestampInFuture", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to A non-empty string was expected.. + /// </summary> + internal static string NonEmptyStringExpected { + get { + return ResourceManager.GetString("NonEmptyStringExpected", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to A message response is already queued for sending in the response stream.. + /// </summary> + internal static string QueuedMessageResponseAlreadyExists { + get { + return ResourceManager.GetString("QueuedMessageResponseAlreadyExists", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to This message has already been processed. This could indicate a replay attack in progress.. + /// </summary> + internal static string ReplayAttackDetected { + get { + return ResourceManager.GetString("ReplayAttackDetected", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to This channel does not support replay protection.. + /// </summary> + internal static string ReplayProtectionNotSupported { + get { + return ResourceManager.GetString("ReplayProtectionNotSupported", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to The following message parts had constant value requirements that were unsatisfied: {0}. + /// </summary> + internal static string RequiredMessagePartConstantIncorrect { + get { + return ResourceManager.GetString("RequiredMessagePartConstantIncorrect", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to The following required non-empty parameters were empty in the {0} message: {1}. + /// </summary> + internal static string RequiredNonEmptyParameterWasEmpty { + get { + return ResourceManager.GetString("RequiredNonEmptyParameterWasEmpty", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to The following required parameters were missing from the {0} message: {1}. + /// </summary> + internal static string RequiredParametersMissing { + get { + return ResourceManager.GetString("RequiredParametersMissing", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to The binding element offering the {0} protection requires other protection that is not provided.. + /// </summary> + internal static string RequiredProtectionMissing { + get { + return ResourceManager.GetString("RequiredProtectionMissing", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to The list is empty.. + /// </summary> + internal static string SequenceContainsNoElements { + get { + return ResourceManager.GetString("SequenceContainsNoElements", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to The list contains a null element.. + /// </summary> + internal static string SequenceContainsNullElement { + get { + return ResourceManager.GetString("SequenceContainsNullElement", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to An HttpContext.Current.Session object is required.. + /// </summary> + internal static string SessionRequired { + get { + return ResourceManager.GetString("SessionRequired", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Message signature was incorrect.. + /// </summary> + internal static string SignatureInvalid { + get { + return ResourceManager.GetString("SignatureInvalid", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to This channel does not support signing messages. To support signing messages, a derived Channel type must override the Sign and IsSignatureValid methods.. + /// </summary> + internal static string SigningNotSupported { + get { + return ResourceManager.GetString("SigningNotSupported", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to This message factory does not support message type(s): {0}. + /// </summary> + internal static string StandardMessageFactoryUnsupportedMessageType { + get { + return ResourceManager.GetString("StandardMessageFactoryUnsupportedMessageType", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to The stream must have a known length.. + /// </summary> + internal static string StreamMustHaveKnownLength { + get { + return ResourceManager.GetString("StreamMustHaveKnownLength", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to The stream's CanRead property returned false.. + /// </summary> + internal static string StreamUnreadable { + get { + return ResourceManager.GetString("StreamUnreadable", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to The stream's CanWrite property returned false.. + /// </summary> + internal static string StreamUnwritable { + get { + return ResourceManager.GetString("StreamUnwritable", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Expected at most 1 binding element to apply the {0} protection, but more than one applied.. + /// </summary> + internal static string TooManyBindingsOfferingSameProtection { + get { + return ResourceManager.GetString("TooManyBindingsOfferingSameProtection", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to The maximum allowable number of redirects were exceeded while requesting '{0}'.. + /// </summary> + internal static string TooManyRedirects { + get { + return ResourceManager.GetString("TooManyRedirects", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to The array must not be empty.. + /// </summary> + internal static string UnexpectedEmptyArray { + get { + return ResourceManager.GetString("UnexpectedEmptyArray", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to The empty string is not allowed.. + /// </summary> + internal static string UnexpectedEmptyString { + get { + return ResourceManager.GetString("UnexpectedEmptyString", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Expected direct response to use HTTP status code {0} but was {1} instead.. + /// </summary> + internal static string UnexpectedHttpStatusCode { + get { + return ResourceManager.GetString("UnexpectedHttpStatusCode", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Message parameter '{0}' had unexpected value '{1}'.. + /// </summary> + internal static string UnexpectedMessagePartValue { + get { + return ResourceManager.GetString("UnexpectedMessagePartValue", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Expected message {0} parameter '{1}' to have value '{2}' but had '{3}' instead.. + /// </summary> + internal static string UnexpectedMessagePartValueForConstant { + get { + return ResourceManager.GetString("UnexpectedMessagePartValueForConstant", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Expected message {0} but received {1} instead.. + /// </summary> + internal static string UnexpectedMessageReceived { + get { + return ResourceManager.GetString("UnexpectedMessageReceived", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Unexpected message type received.. + /// </summary> + internal static string UnexpectedMessageReceivedOfMany { + get { + return ResourceManager.GetString("UnexpectedMessageReceivedOfMany", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to A null key was included and is not allowed.. + /// </summary> + internal static string UnexpectedNullKey { + get { + return ResourceManager.GetString("UnexpectedNullKey", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to A null or empty key was included and is not allowed.. + /// </summary> + internal static string UnexpectedNullOrEmptyKey { + get { + return ResourceManager.GetString("UnexpectedNullOrEmptyKey", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to A null value was included for key '{0}' and is not allowed.. + /// </summary> + internal static string UnexpectedNullValue { + get { + return ResourceManager.GetString("UnexpectedNullValue", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to The type {0} or a derived type was expected, but {1} was given.. + /// </summary> + internal static string UnexpectedType { + get { + return ResourceManager.GetString("UnexpectedType", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to {0} property has unrecognized value {1}.. + /// </summary> + internal static string UnrecognizedEnumValue { + get { + return ResourceManager.GetString("UnrecognizedEnumValue", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to The URL '{0}' is rated unsafe and cannot be requested this way.. + /// </summary> + internal static string UnsafeWebRequestDetected { + get { + return ResourceManager.GetString("UnsafeWebRequestDetected", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to This blob is not a recognized encryption format.. + /// </summary> + internal static string UnsupportedEncryptionAlgorithm { + get { + return ResourceManager.GetString("UnsupportedEncryptionAlgorithm", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to The HTTP verb '{0}' is unrecognized and unsupported.. + /// </summary> + internal static string UnsupportedHttpVerb { + get { + return ResourceManager.GetString("UnsupportedHttpVerb", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to '{0}' messages cannot be received with HTTP verb '{1}'.. + /// </summary> + internal static string UnsupportedHttpVerbForMessageType { + get { + return ResourceManager.GetString("UnsupportedHttpVerbForMessageType", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Redirects on POST requests that are to untrusted servers is not supported.. + /// </summary> + internal static string UntrustedRedirectsOnPOSTNotSupported { + get { + return ResourceManager.GetString("UntrustedRedirectsOnPOSTNotSupported", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Web request to '{0}' failed.. + /// </summary> + internal static string WebRequestFailed { + get { + return ResourceManager.GetString("WebRequestFailed", resourceCulture); + } + } + } +} diff --git a/src/DotNetOpenAuth.Core/Messaging/MessagingStrings.resx b/src/DotNetOpenAuth.Core/Messaging/MessagingStrings.resx new file mode 100644 index 0000000..5f3f79a --- /dev/null +++ b/src/DotNetOpenAuth.Core/Messaging/MessagingStrings.resx @@ -0,0 +1,327 @@ +<?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="ArgumentPropertyMissing" xml:space="preserve"> + <value>Argument's {0}.{1} property is required but is empty or null.</value> + </data> + <data name="CurrentHttpContextRequired" xml:space="preserve"> + <value>HttpContext.Current is null. There must be an ASP.NET request in process for this operation to succeed.</value> + </data> + <data name="DataContractMissingFromMessageType" xml:space="preserve"> + <value>DataContractSerializer could not be initialized on message type {0}. Is it missing a [DataContract] attribute?</value> + </data> + <data name="DataContractMissingNamespace" xml:space="preserve"> + <value>DataContractSerializer could not be initialized on message type {0} because the DataContractAttribute.Namespace property is not set.</value> + </data> + <data name="DerivedTypeNotExpected" xml:space="preserve"> + <value>An instance of type {0} was expected, but received unexpected derived type {1}.</value> + </data> + <data name="DirectedMessageMissingRecipient" xml:space="preserve"> + <value>The directed message's Recipient property must not be null.</value> + </data> + <data name="DirectWebRequestOptionsNotSupported" xml:space="preserve"> + <value>The given set of options is not supported by this web request handler.</value> + </data> + <data name="ErrorDeserializingMessage" xml:space="preserve"> + <value>Error while deserializing message {0}.</value> + </data> + <data name="ErrorInRequestReplyMessage" xml:space="preserve"> + <value>Error occurred while sending a direct message or getting the response.</value> + </data> + <data name="ExceptionNotConstructedForTransit" xml:space="preserve"> + <value>This exception was not constructed with a root request message that caused it.</value> + </data> + <data name="ExpectedMessageNotReceived" xml:space="preserve"> + <value>Expected {0} message but received no recognizable message.</value> + </data> + <data name="ExpiredMessage" xml:space="preserve"> + <value>The message expired at {0} and it is now {1}.</value> + </data> + <data name="GetOrPostFlagsRequired" xml:space="preserve"> + <value>At least one of GET or POST flags must be present.</value> + </data> + <data name="HttpContextRequired" xml:space="preserve"> + <value>This method requires a current HttpContext. Alternatively, use an overload of this method that allows you to pass in information without an HttpContext.</value> + </data> + <data name="IndirectMessagesMustImplementIDirectedProtocolMessage" xml:space="preserve"> + <value>Messages that indicate indirect transport must implement the {0} interface.</value> + </data> + <data name="InsecureWebRequestWithSslRequired" xml:space="preserve"> + <value>Insecure web request for '{0}' aborted due to security requirements demanding HTTPS.</value> + </data> + <data name="InsufficientMessageProtection" xml:space="preserve"> + <value>The {0} message required protections {{{1}}} but the channel could only apply {{{2}}}.</value> + </data> + <data name="InvalidCustomBindingElementOrder" xml:space="preserve"> + <value>The customized binding element ordering is invalid.</value> + </data> + <data name="InvalidMessageParts" xml:space="preserve"> + <value>Some part(s) of the message have invalid values: {0}</value> + </data> + <data name="InvalidNonceReceived" xml:space="preserve"> + <value>The incoming message had an invalid or missing nonce.</value> + </data> + <data name="KeyAlreadyExists" xml:space="preserve"> + <value>An item with the same key has already been added.</value> + </data> + <data name="MessageNotExtensible" xml:space="preserve"> + <value>The {0} message does not support extensions.</value> + </data> + <data name="MessagePartEncoderWrongType" xml:space="preserve"> + <value>The value for {0}.{1} on member {1} was expected to derive from {2} but was {3}.</value> + </data> + <data name="MessagePartReadFailure" xml:space="preserve"> + <value>Error while reading message '{0}' parameter '{1}' with value '{2}'.</value> + </data> + <data name="MessagePartValueBase64DecodingFault" xml:space="preserve"> + <value>Message parameter '{0}' with value '{1}' failed to base64 decode.</value> + </data> + <data name="MessagePartWriteFailure" xml:space="preserve"> + <value>Error while preparing message '{0}' parameter '{1}' for sending.</value> + </data> + <data name="QueuedMessageResponseAlreadyExists" xml:space="preserve"> + <value>A message response is already queued for sending in the response stream.</value> + </data> + <data name="ReplayAttackDetected" xml:space="preserve"> + <value>This message has already been processed. This could indicate a replay attack in progress.</value> + </data> + <data name="ReplayProtectionNotSupported" xml:space="preserve"> + <value>This channel does not support replay protection.</value> + </data> + <data name="RequiredNonEmptyParameterWasEmpty" xml:space="preserve"> + <value>The following required non-empty parameters were empty in the {0} message: {1}</value> + </data> + <data name="RequiredParametersMissing" xml:space="preserve"> + <value>The following required parameters were missing from the {0} message: {1}</value> + </data> + <data name="RequiredProtectionMissing" xml:space="preserve"> + <value>The binding element offering the {0} protection requires other protection that is not provided.</value> + </data> + <data name="SequenceContainsNoElements" xml:space="preserve"> + <value>The list is empty.</value> + </data> + <data name="SequenceContainsNullElement" xml:space="preserve"> + <value>The list contains a null element.</value> + </data> + <data name="SignatureInvalid" xml:space="preserve"> + <value>Message signature was incorrect.</value> + </data> + <data name="SigningNotSupported" xml:space="preserve"> + <value>This channel does not support signing messages. To support signing messages, a derived Channel type must override the Sign and IsSignatureValid methods.</value> + </data> + <data name="StreamUnreadable" xml:space="preserve"> + <value>The stream's CanRead property returned false.</value> + </data> + <data name="StreamUnwritable" xml:space="preserve"> + <value>The stream's CanWrite property returned false.</value> + </data> + <data name="TooManyBindingsOfferingSameProtection" xml:space="preserve"> + <value>Expected at most 1 binding element to apply the {0} protection, but more than one applied.</value> + </data> + <data name="TooManyRedirects" xml:space="preserve"> + <value>The maximum allowable number of redirects were exceeded while requesting '{0}'.</value> + </data> + <data name="UnexpectedEmptyArray" xml:space="preserve"> + <value>The array must not be empty.</value> + </data> + <data name="UnexpectedEmptyString" xml:space="preserve"> + <value>The empty string is not allowed.</value> + </data> + <data name="UnexpectedMessagePartValue" xml:space="preserve"> + <value>Message parameter '{0}' had unexpected value '{1}'.</value> + </data> + <data name="UnexpectedMessagePartValueForConstant" xml:space="preserve"> + <value>Expected message {0} parameter '{1}' to have value '{2}' but had '{3}' instead.</value> + </data> + <data name="UnexpectedMessageReceived" xml:space="preserve"> + <value>Expected message {0} but received {1} instead.</value> + </data> + <data name="UnexpectedMessageReceivedOfMany" xml:space="preserve"> + <value>Unexpected message type received.</value> + </data> + <data name="UnexpectedNullKey" xml:space="preserve"> + <value>A null key was included and is not allowed.</value> + </data> + <data name="UnexpectedNullOrEmptyKey" xml:space="preserve"> + <value>A null or empty key was included and is not allowed.</value> + </data> + <data name="UnexpectedNullValue" xml:space="preserve"> + <value>A null value was included for key '{0}' and is not allowed.</value> + </data> + <data name="UnexpectedType" xml:space="preserve"> + <value>The type {0} or a derived type was expected, but {1} was given.</value> + </data> + <data name="UnrecognizedEnumValue" xml:space="preserve"> + <value>{0} property has unrecognized value {1}.</value> + </data> + <data name="UnsafeWebRequestDetected" xml:space="preserve"> + <value>The URL '{0}' is rated unsafe and cannot be requested this way.</value> + </data> + <data name="UntrustedRedirectsOnPOSTNotSupported" xml:space="preserve"> + <value>Redirects on POST requests that are to untrusted servers is not supported.</value> + </data> + <data name="WebRequestFailed" xml:space="preserve"> + <value>Web request to '{0}' failed.</value> + </data> + <data name="ExceptionUndeliverable" xml:space="preserve"> + <value>This exception must be instantiated with a recipient that will receive the error message, or a direct request message instance that this exception will respond to.</value> + </data> + <data name="UnsupportedHttpVerbForMessageType" xml:space="preserve"> + <value>'{0}' messages cannot be received with HTTP verb '{1}'.</value> + </data> + <data name="UnexpectedHttpStatusCode" xml:space="preserve"> + <value>Expected direct response to use HTTP status code {0} but was {1} instead.</value> + </data> + <data name="UnsupportedHttpVerb" xml:space="preserve"> + <value>The HTTP verb '{0}' is unrecognized and unsupported.</value> + </data> + <data name="NonEmptyStringExpected" xml:space="preserve"> + <value>A non-empty string was expected.</value> + </data> + <data name="StreamMustHaveKnownLength" xml:space="preserve"> + <value>The stream must have a known length.</value> + </data> + <data name="BinaryDataRequiresMultipart" xml:space="preserve"> + <value>Unable to send all message data because some of it requires multi-part POST, but IMessageWithBinaryData.SendAsMultipart was false.</value> + </data> + <data name="SessionRequired" xml:space="preserve"> + <value>An HttpContext.Current.Session object is required.</value> + </data> + <data name="StandardMessageFactoryUnsupportedMessageType" xml:space="preserve"> + <value>This message factory does not support message type(s): {0}</value> + </data> + <data name="RequiredMessagePartConstantIncorrect" xml:space="preserve"> + <value>The following message parts had constant value requirements that were unsatisfied: {0}</value> + </data> + <data name="EncoderInstantiationFailed" xml:space="preserve"> + <value>Unable to instantiate the message part encoder/decoder type {0}.</value> + </data> + <data name="MessageTimestampInFuture" xml:space="preserve"> + <value>This message has a timestamp of {0}, which is beyond the allowable clock skew for in the future.</value> + </data> + <data name="UnsupportedEncryptionAlgorithm" xml:space="preserve"> + <value>This blob is not a recognized encryption format.</value> + </data> + <data name="ExtraParameterAddFailure" xml:space="preserve"> + <value>Failed to add extra parameter '{0}' with value '{1}'.</value> + </data> + <data name="ExpectedParameterWasMissing" xml:space="preserve"> + <value>The message part {0} was expected in the {1} message but was not found.</value> + </data> +</root>
\ No newline at end of file diff --git a/src/DotNetOpenAuth/Messaging/MessagingStrings.sr.resx b/src/DotNetOpenAuth.Core/Messaging/MessagingStrings.sr.resx index 5b7b716..5b7b716 100644 --- a/src/DotNetOpenAuth/Messaging/MessagingStrings.sr.resx +++ b/src/DotNetOpenAuth.Core/Messaging/MessagingStrings.sr.resx diff --git a/src/DotNetOpenAuth.Core/Messaging/MessagingUtilities.cs b/src/DotNetOpenAuth.Core/Messaging/MessagingUtilities.cs new file mode 100644 index 0000000..2a94791 --- /dev/null +++ b/src/DotNetOpenAuth.Core/Messaging/MessagingUtilities.cs @@ -0,0 +1,1709 @@ +//----------------------------------------------------------------------- +// <copyright file="MessagingUtilities.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Messaging { + using System; + using System.Collections.Generic; + using System.Collections.Specialized; + using System.Diagnostics.CodeAnalysis; + using System.Diagnostics.Contracts; + using System.Globalization; + using System.IO; + using System.IO.Compression; + using System.Linq; + using System.Net; + using System.Net.Mime; + using System.Security; + using System.Security.Cryptography; + using System.Text; + using System.Web; + using System.Web.Mvc; + using DotNetOpenAuth.Messaging.Bindings; + using DotNetOpenAuth.Messaging.Reflection; + + /// <summary> + /// A grab-bag of utility methods useful for the channel stack of the protocol. + /// </summary> + [SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Justification = "Utility class touches lots of surface area")] + public static class MessagingUtilities { + /// <summary> + /// The cryptographically strong random data generator used for creating secrets. + /// </summary> + /// <remarks>The random number generator is thread-safe.</remarks> + internal static readonly RandomNumberGenerator CryptoRandomDataGenerator = new RNGCryptoServiceProvider(); + + /// <summary> + /// A pseudo-random data generator (NOT cryptographically strong random data) + /// </summary> + internal static readonly Random NonCryptoRandomDataGenerator = new Random(); + + /// <summary> + /// The uppercase alphabet. + /// </summary> + internal const string UppercaseLetters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + + /// <summary> + /// The lowercase alphabet. + /// </summary> + internal const string LowercaseLetters = "abcdefghijklmnopqrstuvwxyz"; + + /// <summary> + /// The set of base 10 digits. + /// </summary> + internal const string Digits = "0123456789"; + + /// <summary> + /// The set of digits and alphabetic letters (upper and lowercase). + /// </summary> + internal const string AlphaNumeric = UppercaseLetters + LowercaseLetters + Digits; + + /// <summary> + /// All the characters that are allowed for use as a base64 encoding character. + /// </summary> + internal const string Base64Characters = AlphaNumeric + "+" + "/"; + + /// <summary> + /// All the characters that are allowed for use as a base64 encoding character + /// in the "web safe" context. + /// </summary> + internal const string Base64WebSafeCharacters = AlphaNumeric + "-" + "_"; + + /// <summary> + /// The set of digits, and alphabetic letters (upper and lowercase) that are clearly + /// visually distinguishable. + /// </summary> + internal const string AlphaNumericNoLookAlikes = "23456789abcdefghjkmnpqrstwxyzABCDEFGHJKMNPQRSTWXYZ"; + + /// <summary> + /// The length of private symmetric secret handles. + /// </summary> + /// <remarks> + /// This value needn't be high, as we only expect to have a small handful of unexpired secrets at a time, + /// and handle recycling is permissible. + /// </remarks> + private const int SymmetricSecretHandleLength = 4; + + /// <summary> + /// The default lifetime of a private secret. + /// </summary> + private static readonly TimeSpan SymmetricSecretKeyLifespan = Configuration.DotNetOpenAuthSection.Messaging.PrivateSecretMaximumAge; + + /// <summary> + /// A character array containing just the = character. + /// </summary> + private static readonly char[] EqualsArray = new char[] { '=' }; + + /// <summary> + /// A character array containing just the , character. + /// </summary> + private static readonly char[] CommaArray = new char[] { ',' }; + + /// <summary> + /// A character array containing just the " character. + /// </summary> + private static readonly char[] QuoteArray = new char[] { '"' }; + + /// <summary> + /// The set of characters that are unreserved in RFC 2396 but are NOT unreserved in RFC 3986. + /// </summary> + private static readonly string[] UriRfc3986CharsToEscape = new[] { "!", "*", "'", "(", ")" }; + + /// <summary> + /// A set of escaping mappings that help secure a string from javscript execution. + /// </summary> + /// <remarks> + /// The characters to escape here are inspired by + /// http://code.google.com/p/doctype/wiki/ArticleXSSInJavaScript + /// </remarks> + private static readonly Dictionary<string, string> javascriptStaticStringEscaping = new Dictionary<string, string> { + { "\\", @"\\" }, // this WAS just above the & substitution but we moved it here to prevent double-escaping + { "\t", @"\t" }, + { "\n", @"\n" }, + { "\r", @"\r" }, + { "\u0085", @"\u0085" }, + { "\u2028", @"\u2028" }, + { "\u2029", @"\u2029" }, + { "'", @"\x27" }, + { "\"", @"\x22" }, + { "&", @"\x26" }, + { "<", @"\x3c" }, + { ">", @"\x3e" }, + { "=", @"\x3d" }, + }; + + /// <summary> + /// Transforms an OutgoingWebResponse to an MVC-friendly ActionResult. + /// </summary> + /// <param name="response">The response to send to the user agent.</param> + /// <returns>The <see cref="ActionResult"/> instance to be returned by the Controller's action method.</returns> + public static ActionResult AsActionResult(this OutgoingWebResponse response) { + Requires.NotNull(response, "response"); + return new OutgoingWebResponseActionResult(response); + } + + /// <summary> + /// Gets the original request URL, as seen from the browser before any URL rewrites on the server if any. + /// Cookieless session directory (if applicable) is also included. + /// </summary> + /// <returns>The URL in the user agent's Location bar.</returns> + [SuppressMessage("Microsoft.Usage", "CA2234:PassSystemUriObjectsInsteadOfStrings", Justification = "The Uri merging requires use of a string value.")] + [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "Expensive call should not be a property.")] + public static Uri GetRequestUrlFromContext() { + Requires.ValidState(HttpContext.Current != null && HttpContext.Current.Request != null, MessagingStrings.HttpContextRequired); + HttpContext context = HttpContext.Current; + + return HttpRequestInfo.GetPublicFacingUrl(context.Request, context.Request.ServerVariables); + } + + /// <summary> + /// Strips any and all URI query parameters that start with some prefix. + /// </summary> + /// <param name="uri">The URI that may have a query with parameters to remove.</param> + /// <param name="prefix">The prefix for parameters to remove. A period is NOT automatically appended.</param> + /// <returns>Either a new Uri with the parameters removed if there were any to remove, or the same Uri instance if no parameters needed to be removed.</returns> + public static Uri StripQueryArgumentsWithPrefix(this Uri uri, string prefix) { + Requires.NotNull(uri, "uri"); + Requires.NotNullOrEmpty(prefix, "prefix"); + + NameValueCollection queryArgs = HttpUtility.ParseQueryString(uri.Query); + var matchingKeys = queryArgs.Keys.OfType<string>().Where(key => key.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)).ToList(); + if (matchingKeys.Count > 0) { + UriBuilder builder = new UriBuilder(uri); + foreach (string key in matchingKeys) { + queryArgs.Remove(key); + } + builder.Query = CreateQueryString(queryArgs.ToDictionary()); + return builder.Uri; + } else { + return uri; + } + } + + /// <summary> + /// Sends a multipart HTTP POST request (useful for posting files). + /// </summary> + /// <param name="request">The HTTP request.</param> + /// <param name="requestHandler">The request handler.</param> + /// <param name="parts">The parts to include in the POST entity.</param> + /// <returns>The HTTP response.</returns> + public static IncomingWebResponse PostMultipart(this HttpWebRequest request, IDirectWebRequestHandler requestHandler, IEnumerable<MultipartPostPart> parts) { + Requires.NotNull(request, "request"); + Requires.NotNull(requestHandler, "requestHandler"); + Requires.NotNull(parts, "parts"); + + PostMultipartNoGetResponse(request, requestHandler, parts); + return requestHandler.GetResponse(request); + } + + /// <summary> + /// Assembles a message comprised of the message on a given exception and all inner exceptions. + /// </summary> + /// <param name="exception">The exception.</param> + /// <returns>The assembled message.</returns> + public static string ToStringDescriptive(this Exception exception) { + // The input being null is probably bad, but since this method is called + // from a catch block, we don't really want to throw a new exception and + // hide the details of this one. + if (exception == null) { + Logger.Messaging.Error("MessagingUtilities.GetAllMessages called with null input."); + } + + StringBuilder message = new StringBuilder(); + while (exception != null) { + message.Append(exception.Message); + exception = exception.InnerException; + if (exception != null) { + message.Append(" "); + } + } + + return message.ToString(); + } + + /// <summary> + /// Flattens the specified sequence of sequences. + /// </summary> + /// <typeparam name="T">The type of element contained in the sequence.</typeparam> + /// <param name="sequence">The sequence of sequences to flatten.</param> + /// <returns>A sequence of the contained items.</returns> + [Obsolete("Use Enumerable.SelectMany instead.")] + public static IEnumerable<T> Flatten<T>(this IEnumerable<IEnumerable<T>> sequence) { + ErrorUtilities.VerifyArgumentNotNull(sequence, "sequence"); + + foreach (IEnumerable<T> subsequence in sequence) { + foreach (T item in subsequence) { + yield return item; + } + } + } + + /// <summary> + /// Cuts off precision beyond a second on a DateTime value. + /// </summary> + /// <param name="value">The value.</param> + /// <returns>A DateTime with a 0 millisecond component.</returns> + public static DateTime CutToSecond(this DateTime value) { + return value - TimeSpan.FromMilliseconds(value.Millisecond); + } + + /// <summary> + /// Adds a name-value pair to the end of a given URL + /// as part of the querystring piece. Prefixes a ? or & before + /// first element as necessary. + /// </summary> + /// <param name="builder">The UriBuilder to add arguments to.</param> + /// <param name="name">The name of the parameter to add.</param> + /// <param name="value">The value of the argument.</param> + /// <remarks> + /// If the parameters to add match names of parameters that already are defined + /// in the query string, the existing ones are <i>not</i> replaced. + /// </remarks> + public static void AppendQueryArgument(this UriBuilder builder, string name, string value) { + AppendQueryArgs(builder, new[] { new KeyValuePair<string, string>(name, value) }); + } + + /// <summary> + /// Adds a set of values to a collection. + /// </summary> + /// <typeparam name="T">The type of value kept in the collection.</typeparam> + /// <param name="collection">The collection to add to.</param> + /// <param name="values">The values to add to the collection.</param> + public static void AddRange<T>(this ICollection<T> collection, IEnumerable<T> values) { + Requires.NotNull(collection, "collection"); + Requires.NotNull(values, "values"); + + foreach (var value in values) { + collection.Add(value); + } + } + + /// <summary> + /// Tests whether two timespans are within reasonable approximation of each other. + /// </summary> + /// <param name="self">One TimeSpan.</param> + /// <param name="other">The other TimeSpan.</param> + /// <param name="marginOfError">The allowable margin of error.</param> + /// <returns><c>true</c> if the two TimeSpans are within <paramref name="marginOfError"/> of each other.</returns> + public static bool Equals(this TimeSpan self, TimeSpan other, TimeSpan marginOfError) { + return TimeSpan.FromMilliseconds(Math.Abs((self - other).TotalMilliseconds)) < marginOfError; + } + + /// <summary> + /// Clears any existing elements in a collection and fills the collection with a given set of values. + /// </summary> + /// <typeparam name="T">The type of value kept in the collection.</typeparam> + /// <param name="collection">The collection to modify.</param> + /// <param name="values">The new values to fill the collection.</param> + internal static void ResetContents<T>(this ICollection<T> collection, IEnumerable<T> values) { + Requires.NotNull(collection, "collection"); + + collection.Clear(); + if (values != null) { + AddRange(collection, values); + } + } + + /// <summary> + /// Strips any and all URI query parameters that serve as parts of a message. + /// </summary> + /// <param name="uri">The URI that may contain query parameters to remove.</param> + /// <param name="messageDescription">The message description whose parts should be removed from the URL.</param> + /// <returns>A cleaned URL.</returns> + internal static Uri StripMessagePartsFromQueryString(this Uri uri, MessageDescription messageDescription) { + Requires.NotNull(uri, "uri"); + Requires.NotNull(messageDescription, "messageDescription"); + + NameValueCollection queryArgs = HttpUtility.ParseQueryString(uri.Query); + var matchingKeys = queryArgs.Keys.OfType<string>().Where(key => messageDescription.Mapping.ContainsKey(key)).ToList(); + if (matchingKeys.Count > 0) { + var builder = new UriBuilder(uri); + foreach (string key in matchingKeys) { + queryArgs.Remove(key); + } + builder.Query = CreateQueryString(queryArgs.ToDictionary()); + return builder.Uri; + } else { + return uri; + } + } + + /// <summary> + /// Sends a multipart HTTP POST request (useful for posting files) but doesn't call GetResponse on it. + /// </summary> + /// <param name="request">The HTTP request.</param> + /// <param name="requestHandler">The request handler.</param> + /// <param name="parts">The parts to include in the POST entity.</param> + internal static void PostMultipartNoGetResponse(this HttpWebRequest request, IDirectWebRequestHandler requestHandler, IEnumerable<MultipartPostPart> parts) { + Requires.NotNull(request, "request"); + Requires.NotNull(requestHandler, "requestHandler"); + Requires.NotNull(parts, "parts"); + + Reporting.RecordFeatureUse("MessagingUtilities.PostMultipart"); + parts = parts.CacheGeneratedResults(); + string boundary = Guid.NewGuid().ToString(); + string initialPartLeadingBoundary = string.Format(CultureInfo.InvariantCulture, "--{0}\r\n", boundary); + string partLeadingBoundary = string.Format(CultureInfo.InvariantCulture, "\r\n--{0}\r\n", boundary); + string finalTrailingBoundary = string.Format(CultureInfo.InvariantCulture, "\r\n--{0}--\r\n", boundary); + var contentType = new ContentType("multipart/form-data") { + Boundary = boundary, + CharSet = Channel.PostEntityEncoding.WebName, + }; + + request.Method = "POST"; + request.ContentType = contentType.ToString(); + long contentLength = parts.Sum(p => partLeadingBoundary.Length + p.Length) + finalTrailingBoundary.Length; + if (parts.Any()) { + contentLength -= 2; // the initial part leading boundary has no leading \r\n + } + request.ContentLength = contentLength; + + var requestStream = requestHandler.GetRequestStream(request); + try { + StreamWriter writer = new StreamWriter(requestStream, Channel.PostEntityEncoding); + bool firstPart = true; + foreach (var part in parts) { + writer.Write(firstPart ? initialPartLeadingBoundary : partLeadingBoundary); + firstPart = false; + part.Serialize(writer); + part.Dispose(); + } + + writer.Write(finalTrailingBoundary); + writer.Flush(); + } finally { + // We need to be sure to close the request stream... + // unless it is a MemoryStream, which is a clue that we're in + // a mock stream situation and closing it would preclude reading it later. + if (!(requestStream is MemoryStream)) { + requestStream.Dispose(); + } + } + } + + /// <summary> + /// Assembles the content of the HTTP Authorization or WWW-Authenticate header. + /// </summary> + /// <param name="scheme">The scheme.</param> + /// <param name="fields">The fields to include.</param> + /// <returns>A value prepared for an HTTP header.</returns> + internal static string AssembleAuthorizationHeader(string scheme, IEnumerable<KeyValuePair<string, string>> fields) { + Requires.NotNullOrEmpty(scheme, "scheme"); + Requires.NotNull(fields, "fields"); + + var authorization = new StringBuilder(); + authorization.Append(scheme); + authorization.Append(" "); + foreach (var pair in fields) { + string key = MessagingUtilities.EscapeUriDataStringRfc3986(pair.Key); + string value = MessagingUtilities.EscapeUriDataStringRfc3986(pair.Value); + authorization.Append(key); + authorization.Append("=\""); + authorization.Append(value); + authorization.Append("\","); + } + authorization.Length--; // remove trailing comma + return authorization.ToString(); + } + + /// <summary> + /// Parses the authorization header. + /// </summary> + /// <param name="scheme">The scheme. Must not be null or empty.</param> + /// <param name="authorizationHeader">The authorization header. May be null or empty.</param> + /// <returns>A sequence of key=value pairs discovered in the header. Never null, but may be empty.</returns> + internal static IEnumerable<KeyValuePair<string, string>> ParseAuthorizationHeader(string scheme, string authorizationHeader) { + Requires.NotNullOrEmpty(scheme, "scheme"); + Contract.Ensures(Contract.Result<IEnumerable<KeyValuePair<string, string>>>() != null); + + string prefix = scheme + " "; + if (authorizationHeader != null) { + // The authorization header may have multiple sections. Look for the appropriate one. + string[] authorizationSections = new string[] { authorizationHeader }; // what is the right delimiter, if any? + foreach (string authorization in authorizationSections) { + string trimmedAuth = authorization.Trim(); + if (trimmedAuth.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)) { // RFC 2617 says this is case INsensitive + string data = trimmedAuth.Substring(prefix.Length); + return from element in data.Split(CommaArray) + let parts = element.Split(EqualsArray, 2) + let key = Uri.UnescapeDataString(parts[0]) + let value = Uri.UnescapeDataString(parts[1].Trim(QuoteArray)) + select new KeyValuePair<string, string>(key, value); + } + } + } + + return Enumerable.Empty<KeyValuePair<string, string>>(); + } + + /// <summary> + /// Encodes a symmetric key handle and the blob that is encrypted/signed with that key into a single string + /// that can be decoded by <see cref="ExtractKeyHandleAndPayload"/>. + /// </summary> + /// <param name="handle">The cryptographic key handle.</param> + /// <param name="payload">The encrypted/signed blob.</param> + /// <returns>The combined encoded value.</returns> + internal static string CombineKeyHandleAndPayload(string handle, string payload) { + Requires.NotNullOrEmpty(handle, "handle"); + Requires.NotNullOrEmpty(payload, "payload"); + Contract.Ensures(!String.IsNullOrEmpty(Contract.Result<string>())); + + return handle + "!" + payload; + } + + /// <summary> + /// Extracts the key handle and encrypted blob from a string previously returned from <see cref="CombineKeyHandleAndPayload"/>. + /// </summary> + /// <param name="containingMessage">The containing message.</param> + /// <param name="messagePart">The message part.</param> + /// <param name="keyHandleAndBlob">The value previously returned from <see cref="CombineKeyHandleAndPayload"/>.</param> + /// <param name="handle">The crypto key handle.</param> + /// <param name="dataBlob">The encrypted/signed data.</param> + internal static void ExtractKeyHandleAndPayload(IProtocolMessage containingMessage, string messagePart, string keyHandleAndBlob, out string handle, out string dataBlob) { + Requires.NotNull(containingMessage, "containingMessage"); + Requires.NotNullOrEmpty(messagePart, "messagePart"); + Requires.NotNullOrEmpty(keyHandleAndBlob, "keyHandleAndBlob"); + + int privateHandleIndex = keyHandleAndBlob.IndexOf('!'); + ErrorUtilities.VerifyProtocol(privateHandleIndex > 0, MessagingStrings.UnexpectedMessagePartValue, messagePart, keyHandleAndBlob); + handle = keyHandleAndBlob.Substring(0, privateHandleIndex); + dataBlob = keyHandleAndBlob.Substring(privateHandleIndex + 1); + } + + /// <summary> + /// Gets a buffer of random data (not cryptographically strong). + /// </summary> + /// <param name="length">The length of the sequence to generate.</param> + /// <returns>The generated values, which may contain zeros.</returns> + internal static byte[] GetNonCryptoRandomData(int length) { + byte[] buffer = new byte[length]; + NonCryptoRandomDataGenerator.NextBytes(buffer); + return buffer; + } + + /// <summary> + /// Gets a cryptographically strong random sequence of values. + /// </summary> + /// <param name="length">The length of the sequence to generate.</param> + /// <returns>The generated values, which may contain zeros.</returns> + internal static byte[] GetCryptoRandomData(int length) { + byte[] buffer = new byte[length]; + CryptoRandomDataGenerator.GetBytes(buffer); + return buffer; + } + + /// <summary> + /// Gets a cryptographically strong random sequence of values. + /// </summary> + /// <param name="binaryLength">The length of the byte sequence to generate.</param> + /// <returns>A base64 encoding of the generated random data, + /// whose length in characters will likely be greater than <paramref name="binaryLength"/>.</returns> + internal static string GetCryptoRandomDataAsBase64(int binaryLength) { + byte[] uniq_bytes = GetCryptoRandomData(binaryLength); + string uniq = Convert.ToBase64String(uniq_bytes); + return uniq; + } + + /// <summary> + /// Gets a random string made up of a given set of allowable characters. + /// </summary> + /// <param name="length">The length of the desired random string.</param> + /// <param name="allowableCharacters">The allowable characters.</param> + /// <returns>A random string.</returns> + internal static string GetRandomString(int length, string allowableCharacters) { + Requires.InRange(length >= 0, "length"); + Requires.True(allowableCharacters != null && allowableCharacters.Length >= 2, "allowableCharacters"); + + char[] randomString = new char[length]; + for (int i = 0; i < length; i++) { + randomString[i] = allowableCharacters[NonCryptoRandomDataGenerator.Next(allowableCharacters.Length)]; + } + + return new string(randomString); + } + + /// <summary> + /// Computes the hash of a string. + /// </summary> + /// <param name="algorithm">The hash algorithm to use.</param> + /// <param name="value">The value to hash.</param> + /// <param name="encoding">The encoding to use when converting the string to a byte array.</param> + /// <returns>A base64 encoded string.</returns> + internal static string ComputeHash(this HashAlgorithm algorithm, string value, Encoding encoding = null) { + Requires.NotNull(algorithm, "algorithm"); + Requires.NotNull(value, "value"); + Contract.Ensures(Contract.Result<string>() != null); + + encoding = encoding ?? Encoding.UTF8; + byte[] bytesToHash = encoding.GetBytes(value); + byte[] hash = algorithm.ComputeHash(bytesToHash); + string base64Hash = Convert.ToBase64String(hash); + return base64Hash; + } + + /// <summary> + /// Computes the hash of a sequence of key=value pairs. + /// </summary> + /// <param name="algorithm">The hash algorithm to use.</param> + /// <param name="data">The data to hash.</param> + /// <param name="encoding">The encoding to use when converting the string to a byte array.</param> + /// <returns>A base64 encoded string.</returns> + internal static string ComputeHash(this HashAlgorithm algorithm, IDictionary<string, string> data, Encoding encoding = null) { + Requires.NotNull(algorithm, "algorithm"); + Requires.NotNull(data, "data"); + Contract.Ensures(Contract.Result<string>() != null); + + // Assemble the dictionary to sign, taking care to remove the signature itself + // in order to accurately reproduce the original signature (which of course didn't include + // the signature). + // Also we need to sort the dictionary's keys so that we sign in the same order as we did + // the last time. + var sortedData = new SortedDictionary<string, string>(data, StringComparer.OrdinalIgnoreCase); + return ComputeHash(algorithm, (IEnumerable<KeyValuePair<string, string>>)sortedData, encoding); + } + + /// <summary> + /// Computes the hash of a sequence of key=value pairs. + /// </summary> + /// <param name="algorithm">The hash algorithm to use.</param> + /// <param name="sortedData">The data to hash.</param> + /// <param name="encoding">The encoding to use when converting the string to a byte array.</param> + /// <returns>A base64 encoded string.</returns> + internal static string ComputeHash(this HashAlgorithm algorithm, IEnumerable<KeyValuePair<string, string>> sortedData, Encoding encoding = null) { + Requires.NotNull(algorithm, "algorithm"); + Requires.NotNull(sortedData, "sortedData"); + Contract.Ensures(Contract.Result<string>() != null); + + return ComputeHash(algorithm, CreateQueryString(sortedData), encoding); + } + + /// <summary> + /// Encrypts a byte buffer. + /// </summary> + /// <param name="buffer">The buffer to encrypt.</param> + /// <param name="key">The symmetric secret to use to encrypt the buffer. Allowed values are 128, 192, or 256 bytes in length.</param> + /// <returns>The encrypted buffer</returns> + internal static byte[] Encrypt(byte[] buffer, byte[] key) { + using (SymmetricAlgorithm crypto = CreateSymmetricAlgorithm(key)) { + using (var ms = new MemoryStream()) { + var binaryWriter = new BinaryWriter(ms); + binaryWriter.Write((byte)1); // version of encryption algorithm + binaryWriter.Write(crypto.IV); + binaryWriter.Flush(); + + var cryptoStream = new CryptoStream(ms, crypto.CreateEncryptor(), CryptoStreamMode.Write); + cryptoStream.Write(buffer, 0, buffer.Length); + cryptoStream.FlushFinalBlock(); + + return ms.ToArray(); + } + } + } + + /// <summary> + /// Decrypts a byte buffer. + /// </summary> + /// <param name="buffer">The buffer to decrypt.</param> + /// <param name="key">The symmetric secret to use to decrypt the buffer. Allowed values are 128, 192, and 256.</param> + /// <returns>The encrypted buffer</returns> + [SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times", Justification = "This Dispose is safe.")] + internal static byte[] Decrypt(byte[] buffer, byte[] key) { + using (SymmetricAlgorithm crypto = CreateSymmetricAlgorithm(key)) { + using (var ms = new MemoryStream(buffer)) { + var binaryReader = new BinaryReader(ms); + int algorithmVersion = binaryReader.ReadByte(); + ErrorUtilities.VerifyProtocol(algorithmVersion == 1, MessagingStrings.UnsupportedEncryptionAlgorithm); + crypto.IV = binaryReader.ReadBytes(crypto.IV.Length); + + // Allocate space for the decrypted buffer. We don't know how long it will be yet, + // but it will never be larger than the encrypted buffer. + var decryptedBuffer = new byte[buffer.Length]; + int actualDecryptedLength; + + using (var cryptoStream = new CryptoStream(ms, crypto.CreateDecryptor(), CryptoStreamMode.Read)) { + actualDecryptedLength = cryptoStream.Read(decryptedBuffer, 0, decryptedBuffer.Length); + } + + // Create a new buffer with only the decrypted data. + var finalDecryptedBuffer = new byte[actualDecryptedLength]; + Array.Copy(decryptedBuffer, finalDecryptedBuffer, actualDecryptedLength); + return finalDecryptedBuffer; + } + } + } + + /// <summary> + /// Encrypts a string. + /// </summary> + /// <param name="plainText">The text to encrypt.</param> + /// <param name="key">The symmetric secret to use to encrypt the buffer. Allowed values are 128, 192, and 256.</param> + /// <returns>The encrypted buffer</returns> + internal static string Encrypt(string plainText, byte[] key) { + byte[] buffer = Encoding.UTF8.GetBytes(plainText); + byte[] cipher = Encrypt(buffer, key); + return Convert.ToBase64String(cipher); + } + + /// <summary> + /// Decrypts a string previously encrypted with <see cref="Encrypt(string, byte[])"/>. + /// </summary> + /// <param name="cipherText">The text to decrypt.</param> + /// <param name="key">The symmetric secret to use to decrypt the buffer. Allowed values are 128, 192, and 256.</param> + /// <returns>The encrypted buffer</returns> + internal static string Decrypt(string cipherText, byte[] key) { + byte[] cipher = Convert.FromBase64String(cipherText); + byte[] plainText = Decrypt(cipher, key); + return Encoding.UTF8.GetString(plainText); + } + + /// <summary> + /// Performs asymmetric encryption of a given buffer. + /// </summary> + /// <param name="crypto">The asymmetric encryption provider to use for encryption.</param> + /// <param name="buffer">The buffer to encrypt.</param> + /// <returns>The encrypted data.</returns> + internal static byte[] EncryptWithRandomSymmetricKey(this RSACryptoServiceProvider crypto, byte[] buffer) { + Requires.NotNull(crypto, "crypto"); + Requires.NotNull(buffer, "buffer"); + + using (var symmetricCrypto = new RijndaelManaged()) { + symmetricCrypto.Mode = CipherMode.CBC; + + using (var encryptedStream = new MemoryStream()) { + var encryptedStreamWriter = new BinaryWriter(encryptedStream); + + byte[] prequel = new byte[symmetricCrypto.Key.Length + symmetricCrypto.IV.Length]; + Array.Copy(symmetricCrypto.Key, prequel, symmetricCrypto.Key.Length); + Array.Copy(symmetricCrypto.IV, 0, prequel, symmetricCrypto.Key.Length, symmetricCrypto.IV.Length); + byte[] encryptedPrequel = crypto.Encrypt(prequel, false); + + encryptedStreamWriter.Write(encryptedPrequel.Length); + encryptedStreamWriter.Write(encryptedPrequel); + encryptedStreamWriter.Flush(); + + var cryptoStream = new CryptoStream(encryptedStream, symmetricCrypto.CreateEncryptor(), CryptoStreamMode.Write); + cryptoStream.Write(buffer, 0, buffer.Length); + cryptoStream.FlushFinalBlock(); + + return encryptedStream.ToArray(); + } + } + } + + /// <summary> + /// Performs asymmetric decryption of a given buffer. + /// </summary> + /// <param name="crypto">The asymmetric encryption provider to use for decryption.</param> + /// <param name="buffer">The buffer to decrypt.</param> + /// <returns>The decrypted data.</returns> + [SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times", Justification = "This Dispose is safe.")] + internal static byte[] DecryptWithRandomSymmetricKey(this RSACryptoServiceProvider crypto, byte[] buffer) { + Requires.NotNull(crypto, "crypto"); + Requires.NotNull(buffer, "buffer"); + + using (var encryptedStream = new MemoryStream(buffer)) { + var encryptedStreamReader = new BinaryReader(encryptedStream); + + byte[] encryptedPrequel = encryptedStreamReader.ReadBytes(encryptedStreamReader.ReadInt32()); + byte[] prequel = crypto.Decrypt(encryptedPrequel, false); + + using (var symmetricCrypto = new RijndaelManaged()) { + symmetricCrypto.Mode = CipherMode.CBC; + + byte[] symmetricKey = new byte[symmetricCrypto.Key.Length]; + byte[] symmetricIV = new byte[symmetricCrypto.IV.Length]; + Array.Copy(prequel, symmetricKey, symmetricKey.Length); + Array.Copy(prequel, symmetricKey.Length, symmetricIV, 0, symmetricIV.Length); + symmetricCrypto.Key = symmetricKey; + symmetricCrypto.IV = symmetricIV; + + // Allocate space for the decrypted buffer. We don't know how long it will be yet, + // but it will never be larger than the encrypted buffer. + var decryptedBuffer = new byte[encryptedStream.Length - encryptedStream.Position]; + int actualDecryptedLength; + + using (var cryptoStream = new CryptoStream(encryptedStream, symmetricCrypto.CreateDecryptor(), CryptoStreamMode.Read)) { + actualDecryptedLength = cryptoStream.Read(decryptedBuffer, 0, decryptedBuffer.Length); + } + + // Create a new buffer with only the decrypted data. + var finalDecryptedBuffer = new byte[actualDecryptedLength]; + Array.Copy(decryptedBuffer, finalDecryptedBuffer, actualDecryptedLength); + return finalDecryptedBuffer; + } + } + } + + /// <summary> + /// Gets a key from a given bucket with the longest remaining life, or creates a new one if necessary. + /// </summary> + /// <param name="cryptoKeyStore">The crypto key store.</param> + /// <param name="bucket">The bucket where the key should be found or stored.</param> + /// <param name="minimumRemainingLife">The minimum remaining life required on the returned key.</param> + /// <param name="keySize">The required size of the key, in bits.</param> + /// <returns> + /// A key-value pair whose key is the secret's handle and whose value is the cryptographic key. + /// </returns> + internal static KeyValuePair<string, CryptoKey> GetCurrentKey(this ICryptoKeyStore cryptoKeyStore, string bucket, TimeSpan minimumRemainingLife, int keySize = 256) { + Requires.NotNull(cryptoKeyStore, "cryptoKeyStore"); + Requires.NotNullOrEmpty(bucket, "bucket"); + Requires.True(keySize % 8 == 0, "keySize"); + + var cryptoKeyPair = cryptoKeyStore.GetKeys(bucket).FirstOrDefault(pair => pair.Value.Key.Length == keySize / 8); + if (cryptoKeyPair.Value == null || cryptoKeyPair.Value.ExpiresUtc < DateTime.UtcNow + minimumRemainingLife) { + // No key exists with enough remaining life for the required purpose. Create a new key. + ErrorUtilities.VerifyHost(minimumRemainingLife <= SymmetricSecretKeyLifespan, "Unable to create a new symmetric key with the required lifespan of {0} because it is beyond the limit of {1}.", minimumRemainingLife, SymmetricSecretKeyLifespan); + byte[] secret = GetCryptoRandomData(keySize / 8); + DateTime expires = DateTime.UtcNow + SymmetricSecretKeyLifespan; + var cryptoKey = new CryptoKey(secret, expires); + + // Store this key so we can find and use it later. + int failedAttempts = 0; + tryAgain: + try { + string handle = GetRandomString(SymmetricSecretHandleLength, Base64WebSafeCharacters); + cryptoKeyPair = new KeyValuePair<string, CryptoKey>(handle, cryptoKey); + cryptoKeyStore.StoreKey(bucket, handle, cryptoKey); + } catch (CryptoKeyCollisionException) { + ErrorUtilities.VerifyInternal(++failedAttempts < 3, "Unable to derive a unique handle to a private symmetric key."); + goto tryAgain; + } + } + + return cryptoKeyPair; + } + + /// <summary> + /// Compresses a given buffer. + /// </summary> + /// <param name="buffer">The buffer to compress.</param> + /// <returns>The compressed data.</returns> + [SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times", Justification = "This Dispose is safe.")] + internal static byte[] Compress(byte[] buffer) { + Requires.NotNull(buffer, "buffer"); + Contract.Ensures(Contract.Result<byte[]>() != null); + + using (var ms = new MemoryStream()) { + using (var compressingStream = new DeflateStream(ms, CompressionMode.Compress, true)) { + compressingStream.Write(buffer, 0, buffer.Length); + } + + return ms.ToArray(); + } + } + + /// <summary> + /// Decompresses a given buffer. + /// </summary> + /// <param name="buffer">The buffer to decompress.</param> + /// <returns>The decompressed data.</returns> + internal static byte[] Decompress(byte[] buffer) { + Requires.NotNull(buffer, "buffer"); + Contract.Ensures(Contract.Result<byte[]>() != null); + + using (var compressedDataStream = new MemoryStream(buffer)) { + using (var decompressedDataStream = new MemoryStream()) { + using (var decompressingStream = new DeflateStream(compressedDataStream, CompressionMode.Decompress, true)) { + decompressingStream.CopyTo(decompressedDataStream); + } + + return decompressedDataStream.ToArray(); + } + } + } + + /// <summary> + /// Converts to data buffer to a base64-encoded string, using web safe characters and with the padding removed. + /// </summary> + /// <param name="data">The data buffer.</param> + /// <returns>A web-safe base64-encoded string without padding.</returns> + internal static string ConvertToBase64WebSafeString(byte[] data) { + var builder = new StringBuilder(Convert.ToBase64String(data)); + + // Swap out the URL-unsafe characters, and trim the padding characters. + builder.Replace('+', '-').Replace('/', '_'); + while (builder[builder.Length - 1] == '=') { // should happen at most twice. + builder.Length -= 1; + } + + return builder.ToString(); + } + + /// <summary> + /// Decodes a (web-safe) base64-string back to its binary buffer form. + /// </summary> + /// <param name="base64WebSafe">The base64-encoded string. May be web-safe encoded.</param> + /// <returns>A data buffer.</returns> + internal static byte[] FromBase64WebSafeString(string base64WebSafe) { + Requires.NotNullOrEmpty(base64WebSafe, "base64WebSafe"); + Contract.Ensures(Contract.Result<byte[]>() != null); + + // Restore the padding characters and original URL-unsafe characters. + int missingPaddingCharacters; + switch (base64WebSafe.Length % 4) { + case 3: + missingPaddingCharacters = 1; + break; + case 2: + missingPaddingCharacters = 2; + break; + case 0: + missingPaddingCharacters = 0; + break; + default: + throw ErrorUtilities.ThrowInternal("No more than two padding characters should be present for base64."); + } + var builder = new StringBuilder(base64WebSafe, base64WebSafe.Length + missingPaddingCharacters); + builder.Replace('-', '+').Replace('_', '/'); + builder.Append('=', missingPaddingCharacters); + + return Convert.FromBase64String(builder.ToString()); + } + + /// <summary> + /// Compares to string values for ordinal equality in such a way that its execution time does not depend on how much of the value matches. + /// </summary> + /// <param name="value1">The first value.</param> + /// <param name="value2">The second value.</param> + /// <returns>A value indicating whether the two strings share ordinal equality.</returns> + /// <remarks> + /// In signature equality checks, a difference in execution time based on how many initial characters match MAY + /// be used as an attack to figure out the expected signature. It is therefore important to make a signature + /// equality check's execution time independent of how many characters match the expected value. + /// See http://codahale.com/a-lesson-in-timing-attacks/ for more information. + /// </remarks> + internal static bool EqualsConstantTime(string value1, string value2) { + // If exactly one value is null, they don't match. + if (value1 == null ^ value2 == null) { + return false; + } + + // If both values are null (since if one is at this point then they both are), it's a match. + if (value1 == null) { + return true; + } + + if (value1.Length != value2.Length) { + return false; + } + + // This looks like a pretty crazy way to compare values, but it provides a constant time equality check, + // and is more resistant to compiler optimizations than simply setting a boolean flag and returning the boolean after the loop. + int result = 0; + for (int i = 0; i < value1.Length; i++) { + result |= value1[i] ^ value2[i]; + } + + return result == 0; + } + + /// <summary> + /// Adds a set of HTTP headers to an <see cref="HttpResponse"/> instance, + /// taking care to set some headers to the appropriate properties of + /// <see cref="HttpResponse" /> + /// </summary> + /// <param name="headers">The headers to add.</param> + /// <param name="response">The <see cref="HttpResponse"/> instance to set the appropriate values to.</param> + internal static void ApplyHeadersToResponse(WebHeaderCollection headers, HttpResponseBase response) { + Requires.NotNull(headers, "headers"); + Requires.NotNull(response, "response"); + + foreach (string headerName in headers) { + switch (headerName) { + case "Content-Type": + response.ContentType = headers[HttpResponseHeader.ContentType]; + break; + + // Add more special cases here as necessary. + default: + response.AddHeader(headerName, headers[headerName]); + break; + } + } + } + + /// <summary> + /// Adds a set of HTTP headers to an <see cref="HttpResponse"/> instance, + /// taking care to set some headers to the appropriate properties of + /// <see cref="HttpResponse" /> + /// </summary> + /// <param name="headers">The headers to add.</param> + /// <param name="response">The <see cref="HttpListenerResponse"/> instance to set the appropriate values to.</param> + internal static void ApplyHeadersToResponse(WebHeaderCollection headers, HttpListenerResponse response) { + Requires.NotNull(headers, "headers"); + Requires.NotNull(response, "response"); + + foreach (string headerName in headers) { + switch (headerName) { + case "Content-Type": + response.ContentType = headers[HttpResponseHeader.ContentType]; + break; + + // Add more special cases here as necessary. + default: + response.AddHeader(headerName, headers[headerName]); + break; + } + } + } + +#if !CLR4 + /// <summary> + /// Copies the contents of one stream to another. + /// </summary> + /// <param name="copyFrom">The stream to copy from, at the position where copying should begin.</param> + /// <param name="copyTo">The stream to copy to, at the position where bytes should be written.</param> + /// <returns>The total number of bytes copied.</returns> + /// <remarks> + /// Copying begins at the streams' current positions. + /// The positions are NOT reset after copying is complete. + /// </remarks> + internal static int CopyTo(this Stream copyFrom, Stream copyTo) { + Requires.NotNull(copyFrom, "copyFrom"); + Requires.NotNull(copyTo, "copyTo"); + Requires.True(copyFrom.CanRead, "copyFrom", MessagingStrings.StreamUnreadable); + Requires.True(copyTo.CanWrite, "copyTo", MessagingStrings.StreamUnwritable); + return CopyUpTo(copyFrom, copyTo, int.MaxValue); + } +#endif + + /// <summary> + /// Copies the contents of one stream to another. + /// </summary> + /// <param name="copyFrom">The stream to copy from, at the position where copying should begin.</param> + /// <param name="copyTo">The stream to copy to, at the position where bytes should be written.</param> + /// <param name="maximumBytesToCopy">The maximum bytes to copy.</param> + /// <returns>The total number of bytes copied.</returns> + /// <remarks> + /// Copying begins at the streams' current positions. + /// The positions are NOT reset after copying is complete. + /// </remarks> + internal static int CopyUpTo(this Stream copyFrom, Stream copyTo, int maximumBytesToCopy) { + Requires.NotNull(copyFrom, "copyFrom"); + Requires.NotNull(copyTo, "copyTo"); + Requires.True(copyFrom.CanRead, "copyFrom", MessagingStrings.StreamUnreadable); + Requires.True(copyTo.CanWrite, "copyTo", MessagingStrings.StreamUnwritable); + + byte[] buffer = new byte[1024]; + int readBytes; + int totalCopiedBytes = 0; + while ((readBytes = copyFrom.Read(buffer, 0, Math.Min(1024, maximumBytesToCopy))) > 0) { + int writeBytes = Math.Min(maximumBytesToCopy, readBytes); + copyTo.Write(buffer, 0, writeBytes); + totalCopiedBytes += writeBytes; + maximumBytesToCopy -= writeBytes; + } + + return totalCopiedBytes; + } + + /// <summary> + /// Creates a snapshot of some stream so it is seekable, and the original can be closed. + /// </summary> + /// <param name="copyFrom">The stream to copy bytes from.</param> + /// <returns>A seekable stream with the same contents as the original.</returns> + internal static Stream CreateSnapshot(this Stream copyFrom) { + Requires.NotNull(copyFrom, "copyFrom"); + Requires.True(copyFrom.CanRead, "copyFrom", MessagingStrings.StreamUnreadable); + + MemoryStream copyTo = new MemoryStream(copyFrom.CanSeek ? (int)copyFrom.Length : 4 * 1024); + try { + copyFrom.CopyTo(copyTo); + copyTo.Position = 0; + return copyTo; + } catch { + copyTo.Dispose(); + throw; + } + } + + /// <summary> + /// Clones an <see cref="HttpWebRequest"/> in order to send it again. + /// </summary> + /// <param name="request">The request to clone.</param> + /// <returns>The newly created instance.</returns> + internal static HttpWebRequest Clone(this HttpWebRequest request) { + Requires.NotNull(request, "request"); + Requires.True(request.RequestUri != null, "request"); + return Clone(request, request.RequestUri); + } + + /// <summary> + /// Clones an <see cref="HttpWebRequest"/> in order to send it again. + /// </summary> + /// <param name="request">The request to clone.</param> + /// <param name="newRequestUri">The new recipient of the request.</param> + /// <returns>The newly created instance.</returns> + internal static HttpWebRequest Clone(this HttpWebRequest request, Uri newRequestUri) { + Requires.NotNull(request, "request"); + Requires.NotNull(newRequestUri, "newRequestUri"); + + var newRequest = (HttpWebRequest)WebRequest.Create(newRequestUri); + + // First copy headers. Only set those that are explicitly set on the original request, + // because some properties (like IfModifiedSince) activate special behavior when set, + // even when set to their "original" values. + foreach (string headerName in request.Headers) { + switch (headerName) { + case "Accept": newRequest.Accept = request.Accept; break; + case "Connection": break; // Keep-Alive controls this + case "Content-Length": newRequest.ContentLength = request.ContentLength; break; + case "Content-Type": newRequest.ContentType = request.ContentType; break; + case "Expect": newRequest.Expect = request.Expect; break; + case "Host": break; // implicitly copied as part of the RequestUri + case "If-Modified-Since": newRequest.IfModifiedSince = request.IfModifiedSince; break; + case "Keep-Alive": newRequest.KeepAlive = request.KeepAlive; break; + case "Proxy-Connection": break; // no property equivalent? + case "Referer": newRequest.Referer = request.Referer; break; + case "Transfer-Encoding": newRequest.TransferEncoding = request.TransferEncoding; break; + case "User-Agent": newRequest.UserAgent = request.UserAgent; break; + default: newRequest.Headers[headerName] = request.Headers[headerName]; break; + } + } + + newRequest.AllowAutoRedirect = request.AllowAutoRedirect; + newRequest.AllowWriteStreamBuffering = request.AllowWriteStreamBuffering; + newRequest.AuthenticationLevel = request.AuthenticationLevel; + newRequest.AutomaticDecompression = request.AutomaticDecompression; + newRequest.CachePolicy = request.CachePolicy; + newRequest.ClientCertificates = request.ClientCertificates; + newRequest.ConnectionGroupName = request.ConnectionGroupName; + newRequest.ContinueDelegate = request.ContinueDelegate; + newRequest.CookieContainer = request.CookieContainer; + newRequest.Credentials = request.Credentials; + newRequest.ImpersonationLevel = request.ImpersonationLevel; + newRequest.MaximumAutomaticRedirections = request.MaximumAutomaticRedirections; + newRequest.MaximumResponseHeadersLength = request.MaximumResponseHeadersLength; + newRequest.MediaType = request.MediaType; + newRequest.Method = request.Method; + newRequest.Pipelined = request.Pipelined; + newRequest.PreAuthenticate = request.PreAuthenticate; + newRequest.ProtocolVersion = request.ProtocolVersion; + newRequest.ReadWriteTimeout = request.ReadWriteTimeout; + newRequest.SendChunked = request.SendChunked; + newRequest.Timeout = request.Timeout; + newRequest.UseDefaultCredentials = request.UseDefaultCredentials; + + try { + newRequest.Proxy = request.Proxy; + newRequest.UnsafeAuthenticatedConnectionSharing = request.UnsafeAuthenticatedConnectionSharing; + } catch (SecurityException) { + Logger.Messaging.Warn("Unable to clone some HttpWebRequest properties due to partial trust."); + } + + return newRequest; + } + + /// <summary> + /// Tests whether two arrays are equal in contents and ordering. + /// </summary> + /// <typeparam name="T">The type of elements in the arrays.</typeparam> + /// <param name="first">The first array in the comparison. May not be null.</param> + /// <param name="second">The second array in the comparison. May not be null.</param> + /// <returns>True if the arrays equal; false otherwise.</returns> + internal static bool AreEquivalent<T>(T[] first, T[] second) { + Requires.NotNull(first, "first"); + Requires.NotNull(second, "second"); + if (first.Length != second.Length) { + return false; + } + for (int i = 0; i < first.Length; i++) { + if (!first[i].Equals(second[i])) { + return false; + } + } + return true; + } + + /// <summary> + /// Tests whether two arrays are equal in contents and ordering, + /// guaranteeing roughly equivalent execution time regardless of where a signature mismatch may exist. + /// </summary> + /// <param name="first">The first array in the comparison. May not be null.</param> + /// <param name="second">The second array in the comparison. May not be null.</param> + /// <returns>True if the arrays equal; false otherwise.</returns> + /// <remarks> + /// Guaranteeing equal execution time is useful in mitigating against timing attacks on a signature + /// or other secret. + /// </remarks> + internal static bool AreEquivalentConstantTime(byte[] first, byte[] second) { + Requires.NotNull(first, "first"); + Requires.NotNull(second, "second"); + if (first.Length != second.Length) { + return false; + } + + int result = 0; + for (int i = 0; i < first.Length; i++) { + result |= first[i] ^ second[i]; + } + return result == 0; + } + + /// <summary> + /// Tests two sequences for same contents and ordering. + /// </summary> + /// <typeparam name="T">The type of elements in the arrays.</typeparam> + /// <param name="sequence1">The first sequence in the comparison. May not be null.</param> + /// <param name="sequence2">The second sequence in the comparison. May not be null.</param> + /// <returns>True if the arrays equal; false otherwise.</returns> + internal static bool AreEquivalent<T>(IEnumerable<T> sequence1, IEnumerable<T> sequence2) { + if (sequence1 == null && sequence2 == null) { + return true; + } + if ((sequence1 == null) ^ (sequence2 == null)) { + return false; + } + + IEnumerator<T> iterator1 = sequence1.GetEnumerator(); + IEnumerator<T> iterator2 = sequence2.GetEnumerator(); + bool movenext1, movenext2; + while (true) { + movenext1 = iterator1.MoveNext(); + movenext2 = iterator2.MoveNext(); + if (!movenext1 || !movenext2) { // if we've reached the end of at least one sequence + break; + } + object obj1 = iterator1.Current; + object obj2 = iterator2.Current; + if (obj1 == null && obj2 == null) { + continue; // both null is ok + } + if (obj1 == null ^ obj2 == null) { + return false; // exactly one null is different + } + if (!obj1.Equals(obj2)) { + return false; // if they're not equal to each other + } + } + + return movenext1 == movenext2; // did they both reach the end together? + } + + /// <summary> + /// Tests two unordered collections for same contents. + /// </summary> + /// <typeparam name="T">The type of elements in the collections.</typeparam> + /// <param name="first">The first collection in the comparison. May not be null.</param> + /// <param name="second">The second collection in the comparison. May not be null.</param> + /// <returns>True if the collections have the same contents; false otherwise.</returns> + internal static bool AreEquivalentUnordered<T>(ICollection<T> first, ICollection<T> second) { + if (first == null && second == null) { + return true; + } + if ((first == null) ^ (second == null)) { + return false; + } + + if (first.Count != second.Count) { + return false; + } + + foreach (T value in first) { + if (!second.Contains(value)) { + return false; + } + } + + return true; + } + + /// <summary> + /// Tests whether two dictionaries are equal in length and contents. + /// </summary> + /// <typeparam name="TKey">The type of keys in the dictionaries.</typeparam> + /// <typeparam name="TValue">The type of values in the dictionaries.</typeparam> + /// <param name="first">The first dictionary in the comparison. May not be null.</param> + /// <param name="second">The second dictionary in the comparison. May not be null.</param> + /// <returns>True if the arrays equal; false otherwise.</returns> + internal static bool AreEquivalent<TKey, TValue>(IDictionary<TKey, TValue> first, IDictionary<TKey, TValue> second) { + Requires.NotNull(first, "first"); + Requires.NotNull(second, "second"); + return AreEquivalent(first.ToArray(), second.ToArray()); + } + + /// <summary> + /// Concatenates a list of name-value pairs as key=value&key=value, + /// taking care to properly encode each key and value for URL + /// transmission according to RFC 3986. No ? is prefixed to the string. + /// </summary> + /// <param name="args">The dictionary of key/values to read from.</param> + /// <returns>The formulated querystring style string.</returns> + internal static string CreateQueryString(IEnumerable<KeyValuePair<string, string>> args) { + Requires.NotNull(args, "args"); + Contract.Ensures(Contract.Result<string>() != null); + + if (args.Count() == 0) { + return string.Empty; + } + StringBuilder sb = new StringBuilder(args.Count() * 10); + + foreach (var p in args) { + ErrorUtilities.VerifyArgument(!string.IsNullOrEmpty(p.Key), MessagingStrings.UnexpectedNullOrEmptyKey); + ErrorUtilities.VerifyArgument(p.Value != null, MessagingStrings.UnexpectedNullValue, p.Key); + sb.Append(EscapeUriDataStringRfc3986(p.Key)); + sb.Append('='); + sb.Append(EscapeUriDataStringRfc3986(p.Value)); + sb.Append('&'); + } + sb.Length--; // remove trailing & + + return sb.ToString(); + } + + /// <summary> + /// Adds a set of name-value pairs to the end of a given URL + /// as part of the querystring piece. Prefixes a ? or & before + /// first element as necessary. + /// </summary> + /// <param name="builder">The UriBuilder to add arguments to.</param> + /// <param name="args"> + /// The arguments to add to the query. + /// If null, <paramref name="builder"/> is not changed. + /// </param> + /// <remarks> + /// If the parameters to add match names of parameters that already are defined + /// in the query string, the existing ones are <i>not</i> replaced. + /// </remarks> + internal static void AppendQueryArgs(this UriBuilder builder, IEnumerable<KeyValuePair<string, string>> args) { + Requires.NotNull(builder, "builder"); + + if (args != null && args.Count() > 0) { + StringBuilder sb = new StringBuilder(50 + (args.Count() * 10)); + if (!string.IsNullOrEmpty(builder.Query)) { + sb.Append(builder.Query.Substring(1)); + sb.Append('&'); + } + sb.Append(CreateQueryString(args)); + + builder.Query = sb.ToString(); + } + } + + /// <summary> + /// Adds a set of name-value pairs to the end of a given URL + /// as part of the fragment piece. Prefixes a # or & before + /// first element as necessary. + /// </summary> + /// <param name="builder">The UriBuilder to add arguments to.</param> + /// <param name="args"> + /// The arguments to add to the query. + /// If null, <paramref name="builder"/> is not changed. + /// </param> + /// <remarks> + /// If the parameters to add match names of parameters that already are defined + /// in the fragment, the existing ones are <i>not</i> replaced. + /// </remarks> + internal static void AppendFragmentArgs(this UriBuilder builder, IEnumerable<KeyValuePair<string, string>> args) { + Requires.NotNull(builder, "builder"); + + if (args != null && args.Count() > 0) { + StringBuilder sb = new StringBuilder(50 + (args.Count() * 10)); + if (!string.IsNullOrEmpty(builder.Fragment)) { + sb.Append(builder.Fragment); + sb.Append('&'); + } + sb.Append(CreateQueryString(args)); + + builder.Fragment = sb.ToString(); + } + } + + /// <summary> + /// Adds parameters to a query string, replacing parameters that + /// match ones that already exist in the query string. + /// </summary> + /// <param name="builder">The UriBuilder to add arguments to.</param> + /// <param name="args"> + /// The arguments to add to the query. + /// If null, <paramref name="builder"/> is not changed. + /// </param> + internal static void AppendAndReplaceQueryArgs(this UriBuilder builder, IEnumerable<KeyValuePair<string, string>> args) { + Requires.NotNull(builder, "builder"); + + if (args != null && args.Count() > 0) { + NameValueCollection aggregatedArgs = HttpUtility.ParseQueryString(builder.Query); + foreach (var pair in args) { + aggregatedArgs[pair.Key] = pair.Value; + } + + builder.Query = CreateQueryString(aggregatedArgs.ToDictionary()); + } + } + + /// <summary> + /// Extracts the recipient from an HttpRequestInfo. + /// </summary> + /// <param name="request">The request to get recipient information from.</param> + /// <returns>The recipient.</returns> + /// <exception cref="ArgumentException">Thrown if the HTTP request is something we can't handle.</exception> + internal static MessageReceivingEndpoint GetRecipient(this HttpRequestInfo request) { + return new MessageReceivingEndpoint(request.UrlBeforeRewriting, GetHttpDeliveryMethod(request.HttpMethod)); + } + + /// <summary> + /// Gets the <see cref="HttpDeliveryMethods"/> enum value for a given HTTP verb. + /// </summary> + /// <param name="httpVerb">The HTTP verb.</param> + /// <returns>A <see cref="HttpDeliveryMethods"/> enum value that is within the <see cref="HttpDeliveryMethods.HttpVerbMask"/>.</returns> + /// <exception cref="ArgumentException">Thrown if the HTTP request is something we can't handle.</exception> + internal static HttpDeliveryMethods GetHttpDeliveryMethod(string httpVerb) { + if (httpVerb == "GET") { + return HttpDeliveryMethods.GetRequest; + } else if (httpVerb == "POST") { + return HttpDeliveryMethods.PostRequest; + } else if (httpVerb == "PUT") { + return HttpDeliveryMethods.PutRequest; + } else if (httpVerb == "DELETE") { + return HttpDeliveryMethods.DeleteRequest; + } else if (httpVerb == "HEAD") { + return HttpDeliveryMethods.HeadRequest; + } else { + throw ErrorUtilities.ThrowArgumentNamed("httpVerb", MessagingStrings.UnsupportedHttpVerb, httpVerb); + } + } + + /// <summary> + /// Gets the HTTP verb to use for a given <see cref="HttpDeliveryMethods"/> enum value. + /// </summary> + /// <param name="httpMethod">The HTTP method.</param> + /// <returns>An HTTP verb, such as GET, POST, PUT, or DELETE.</returns> + internal static string GetHttpVerb(HttpDeliveryMethods httpMethod) { + if ((httpMethod & HttpDeliveryMethods.HttpVerbMask) == HttpDeliveryMethods.GetRequest) { + return "GET"; + } else if ((httpMethod & HttpDeliveryMethods.HttpVerbMask) == HttpDeliveryMethods.PostRequest) { + return "POST"; + } else if ((httpMethod & HttpDeliveryMethods.HttpVerbMask) == HttpDeliveryMethods.PutRequest) { + return "PUT"; + } else if ((httpMethod & HttpDeliveryMethods.HttpVerbMask) == HttpDeliveryMethods.DeleteRequest) { + return "DELETE"; + } else if ((httpMethod & HttpDeliveryMethods.HttpVerbMask) == HttpDeliveryMethods.HeadRequest) { + return "HEAD"; + } else if ((httpMethod & HttpDeliveryMethods.AuthorizationHeaderRequest) != 0) { + return "GET"; // if AuthorizationHeaderRequest is specified without an explicit HTTP verb, assume GET. + } else { + throw ErrorUtilities.ThrowArgumentNamed("httpMethod", MessagingStrings.UnsupportedHttpVerb, httpMethod); + } + } + + /// <summary> + /// Copies some extra parameters into a message. + /// </summary> + /// <param name="messageDictionary">The message to copy the extra data into.</param> + /// <param name="extraParameters">The extra data to copy into the message. May be null to do nothing.</param> + internal static void AddExtraParameters(this MessageDictionary messageDictionary, IDictionary<string, string> extraParameters) { + Requires.NotNull(messageDictionary, "messageDictionary"); + + if (extraParameters != null) { + foreach (var pair in extraParameters) { + try { + messageDictionary.Add(pair); + } catch (ArgumentException ex) { + throw ErrorUtilities.Wrap(ex, MessagingStrings.ExtraParameterAddFailure, pair.Key, pair.Value); + } + } + } + } + + /// <summary> + /// Collects a sequence of key=value pairs into a dictionary. + /// </summary> + /// <typeparam name="TKey">The type of the key.</typeparam> + /// <typeparam name="TValue">The type of the value.</typeparam> + /// <param name="sequence">The sequence.</param> + /// <returns>A dictionary.</returns> + internal static Dictionary<TKey, TValue> ToDictionary<TKey, TValue>(this IEnumerable<KeyValuePair<TKey, TValue>> sequence) { + Requires.NotNull(sequence, "sequence"); + return sequence.ToDictionary(pair => pair.Key, pair => pair.Value); + } + + /// <summary> + /// Converts a <see cref="NameValueCollection"/> to an IDictionary<string, string>. + /// </summary> + /// <param name="nvc">The NameValueCollection to convert. May be null.</param> + /// <returns>The generated dictionary, or null if <paramref name="nvc"/> is null.</returns> + /// <remarks> + /// If a <c>null</c> key is encountered, its value is ignored since + /// <c>Dictionary<string, string></c> does not allow null keys. + /// </remarks> + internal static Dictionary<string, string> ToDictionary(this NameValueCollection nvc) { + Contract.Ensures((nvc != null && Contract.Result<Dictionary<string, string>>() != null) || (nvc == null && Contract.Result<Dictionary<string, string>>() == null)); + return ToDictionary(nvc, false); + } + + /// <summary> + /// Converts a <see cref="NameValueCollection"/> to an IDictionary<string, string>. + /// </summary> + /// <param name="nvc">The NameValueCollection to convert. May be null.</param> + /// <param name="throwOnNullKey"> + /// A value indicating whether a null key in the <see cref="NameValueCollection"/> should be silently skipped since it is not a valid key in a Dictionary. + /// Use <c>true</c> to throw an exception if a null key is encountered. + /// Use <c>false</c> to silently continue converting the valid keys. + /// </param> + /// <returns>The generated dictionary, or null if <paramref name="nvc"/> is null.</returns> + /// <exception cref="ArgumentException">Thrown if <paramref name="throwOnNullKey"/> is <c>true</c> and a null key is encountered.</exception> + internal static Dictionary<string, string> ToDictionary(this NameValueCollection nvc, bool throwOnNullKey) { + Contract.Ensures((nvc != null && Contract.Result<Dictionary<string, string>>() != null) || (nvc == null && Contract.Result<Dictionary<string, string>>() == null)); + if (nvc == null) { + return null; + } + + var dictionary = new Dictionary<string, string>(); + foreach (string key in nvc) { + // NameValueCollection supports a null key, but Dictionary<K,V> does not. + if (key == null) { + if (throwOnNullKey) { + throw new ArgumentException(MessagingStrings.UnexpectedNullKey); + } else { + // Only emit a warning if there was a non-empty value. + if (!string.IsNullOrEmpty(nvc[key])) { + Logger.OpenId.WarnFormat("Null key with value {0} encountered while translating NameValueCollection to Dictionary.", nvc[key]); + } + } + } else { + dictionary.Add(key, nvc[key]); + } + } + + return dictionary; + } + + /// <summary> + /// Sorts the elements of a sequence in ascending order by using a specified comparer. + /// </summary> + /// <typeparam name="TSource">The type of the elements of source.</typeparam> + /// <typeparam name="TKey">The type of the key returned by keySelector.</typeparam> + /// <param name="source">A sequence of values to order.</param> + /// <param name="keySelector">A function to extract a key from an element.</param> + /// <param name="comparer">A comparison function to compare keys.</param> + /// <returns>An System.Linq.IOrderedEnumerable<TElement> whose elements are sorted according to a key.</returns> + internal static IOrderedEnumerable<TSource> OrderBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, Comparison<TKey> comparer) { + Requires.NotNull(source, "source"); + Requires.NotNull(comparer, "comparer"); + Requires.NotNull(keySelector, "keySelector"); + Contract.Ensures(Contract.Result<IOrderedEnumerable<TSource>>() != null); + return System.Linq.Enumerable.OrderBy<TSource, TKey>(source, keySelector, new ComparisonHelper<TKey>(comparer)); + } + + /// <summary> + /// Determines whether the specified message is a request (indirect message or direct request). + /// </summary> + /// <param name="message">The message in question.</param> + /// <returns> + /// <c>true</c> if the specified message is a request; otherwise, <c>false</c>. + /// </returns> + /// <remarks> + /// Although an <see cref="IProtocolMessage"/> may implement the <see cref="IDirectedProtocolMessage"/> + /// interface, it may only be doing that for its derived classes. These objects are only requests + /// if their <see cref="IDirectedProtocolMessage.Recipient"/> property is non-null. + /// </remarks> + internal static bool IsRequest(this IDirectedProtocolMessage message) { + Requires.NotNull(message, "message"); + return message.Recipient != null; + } + + /// <summary> + /// Determines whether the specified message is a direct response. + /// </summary> + /// <param name="message">The message in question.</param> + /// <returns> + /// <c>true</c> if the specified message is a direct response; otherwise, <c>false</c>. + /// </returns> + /// <remarks> + /// Although an <see cref="IProtocolMessage"/> may implement the + /// <see cref="IDirectResponseProtocolMessage"/> interface, it may only be doing + /// that for its derived classes. These objects are only requests if their + /// <see cref="IDirectResponseProtocolMessage.OriginatingRequest"/> property is non-null. + /// </remarks> + internal static bool IsDirectResponse(this IDirectResponseProtocolMessage message) { + Requires.NotNull(message, "message"); + return message.OriginatingRequest != null; + } + + /// <summary> + /// Writes a buffer, prefixed with its own length. + /// </summary> + /// <param name="writer">The binary writer.</param> + /// <param name="buffer">The buffer.</param> + internal static void WriteBuffer(this BinaryWriter writer, byte[] buffer) { + Requires.NotNull(writer, "writer"); + Requires.NotNull(buffer, "buffer"); + writer.Write(buffer.Length); + writer.Write(buffer, 0, buffer.Length); + } + + /// <summary> + /// Reads a buffer that is prefixed with its own length. + /// </summary> + /// <param name="reader">The binary reader positioned at the buffer length.</param> + /// <returns>The read buffer.</returns> + internal static byte[] ReadBuffer(this BinaryReader reader) { + Requires.NotNull(reader, "reader"); + int length = reader.ReadInt32(); + byte[] buffer = new byte[length]; + ErrorUtilities.VerifyProtocol(reader.Read(buffer, 0, length) == length, "Unexpected buffer length."); + return buffer; + } + + /// <summary> + /// Constructs a Javascript expression that will create an object + /// on the user agent when assigned to a variable. + /// </summary> + /// <param name="namesAndValues">The untrusted names and untrusted values to inject into the JSON object.</param> + /// <param name="valuesPreEncoded">if set to <c>true</c> the values will NOT be escaped as if it were a pure string.</param> + /// <returns>The Javascript JSON object as a string.</returns> + internal static string CreateJsonObject(IEnumerable<KeyValuePair<string, string>> namesAndValues, bool valuesPreEncoded) { + StringBuilder builder = new StringBuilder(); + builder.Append("{ "); + + foreach (var pair in namesAndValues) { + builder.Append(MessagingUtilities.GetSafeJavascriptValue(pair.Key)); + builder.Append(": "); + builder.Append(valuesPreEncoded ? pair.Value : MessagingUtilities.GetSafeJavascriptValue(pair.Value)); + builder.Append(","); + } + + if (builder[builder.Length - 1] == ',') { + builder.Length -= 1; + } + builder.Append("}"); + return builder.ToString(); + } + + /// <summary> + /// Prepares what SHOULD be simply a string value for safe injection into Javascript + /// by using appropriate character escaping. + /// </summary> + /// <param name="value">The untrusted string value to be escaped to protected against XSS attacks. May be null.</param> + /// <returns>The escaped string, surrounded by single-quotes.</returns> + internal static string GetSafeJavascriptValue(string value) { + if (value == null) { + return "null"; + } + + // We use a StringBuilder because we have potentially many replacements to do, + // and we don't want to create a new string for every intermediate replacement step. + StringBuilder builder = new StringBuilder(value); + foreach (var pair in javascriptStaticStringEscaping) { + builder.Replace(pair.Key, pair.Value); + } + builder.Insert(0, '\''); + builder.Append('\''); + return builder.ToString(); + } + + /// <summary> + /// Escapes a string according to the URI data string rules given in RFC 3986. + /// </summary> + /// <param name="value">The value to escape.</param> + /// <returns>The escaped value.</returns> + /// <remarks> + /// The <see cref="Uri.EscapeDataString"/> method is <i>supposed</i> to take on + /// RFC 3986 behavior if certain elements are present in a .config file. Even if this + /// actually worked (which in my experiments it <i>doesn't</i>), we can't rely on every + /// host actually having this configuration element present. + /// </remarks> + internal static string EscapeUriDataStringRfc3986(string value) { + Requires.NotNull(value, "value"); + + // Start with RFC 2396 escaping by calling the .NET method to do the work. + // This MAY sometimes exhibit RFC 3986 behavior (according to the documentation). + // If it does, the escaping we do that follows it will be a no-op since the + // characters we search for to replace can't possibly exist in the string. + StringBuilder escaped = new StringBuilder(Uri.EscapeDataString(value)); + + // Upgrade the escaping to RFC 3986, if necessary. + for (int i = 0; i < UriRfc3986CharsToEscape.Length; i++) { + escaped.Replace(UriRfc3986CharsToEscape[i], Uri.HexEscape(UriRfc3986CharsToEscape[i][0])); + } + + // Return the fully-RFC3986-escaped string. + return escaped.ToString(); + } + + /// <summary> + /// Ensures that UTC times are converted to local times. Unspecified kinds are unchanged. + /// </summary> + /// <param name="value">The date-time to convert.</param> + /// <returns>The date-time in local time.</returns> + internal static DateTime ToLocalTimeSafe(this DateTime value) { + if (value.Kind == DateTimeKind.Unspecified) { + return value; + } + + return value.ToLocalTime(); + } + + /// <summary> + /// Ensures that local times are converted to UTC times. Unspecified kinds are unchanged. + /// </summary> + /// <param name="value">The date-time to convert.</param> + /// <returns>The date-time in UTC time.</returns> + internal static DateTime ToUniversalTimeSafe(this DateTime value) { + if (value.Kind == DateTimeKind.Unspecified) { + return value; + } + + return value.ToUniversalTime(); + } + + /// <summary> + /// Creates a symmetric algorithm for use in encryption/decryption. + /// </summary> + /// <param name="key">The symmetric key to use for encryption/decryption.</param> + /// <returns>A symmetric algorithm.</returns> + private static SymmetricAlgorithm CreateSymmetricAlgorithm(byte[] key) { + SymmetricAlgorithm result = null; + try { + result = new RijndaelManaged(); + result.Mode = CipherMode.CBC; + result.Key = key; + return result; + } catch { + IDisposable disposableResult = result; + if (disposableResult != null) { + disposableResult.Dispose(); + } + + throw; + } + } + + /// <summary> + /// A class to convert a <see cref="Comparison<T>"/> into an <see cref="IComparer<T>"/>. + /// </summary> + /// <typeparam name="T">The type of objects being compared.</typeparam> + private class ComparisonHelper<T> : IComparer<T> { + /// <summary> + /// The comparison method to use. + /// </summary> + private Comparison<T> comparison; + + /// <summary> + /// Initializes a new instance of the ComparisonHelper class. + /// </summary> + /// <param name="comparison">The comparison method to use.</param> + internal ComparisonHelper(Comparison<T> comparison) { + Requires.NotNull(comparison, "comparison"); + + this.comparison = comparison; + } + + #region IComparer<T> Members + + /// <summary> + /// Compares two instances of <typeparamref name="T"/>. + /// </summary> + /// <param name="x">The first object to compare.</param> + /// <param name="y">The second object to compare.</param> + /// <returns>Any of -1, 0, or 1 according to standard comparison rules.</returns> + public int Compare(T x, T y) { + return this.comparison(x, y); + } + + #endregion + } + } +} diff --git a/src/DotNetOpenAuth.Core/Messaging/MultipartPostPart.cs b/src/DotNetOpenAuth.Core/Messaging/MultipartPostPart.cs new file mode 100644 index 0000000..f72ad6c --- /dev/null +++ b/src/DotNetOpenAuth.Core/Messaging/MultipartPostPart.cs @@ -0,0 +1,223 @@ +//----------------------------------------------------------------------- +// <copyright file="MultipartPostPart.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Messaging { + using System; + using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; + using System.Diagnostics.Contracts; + using System.IO; + using System.Net; + using System.Text; + + /// <summary> + /// Represents a single part in a HTTP multipart POST request. + /// </summary> + public class MultipartPostPart : IDisposable { + /// <summary> + /// The "Content-Disposition" string. + /// </summary> + private const string ContentDispositionHeader = "Content-Disposition"; + + /// <summary> + /// The two-character \r\n newline character sequence to use. + /// </summary> + private const string NewLine = "\r\n"; + + /// <summary> + /// Initializes a new instance of the <see cref="MultipartPostPart"/> class. + /// </summary> + /// <param name="contentDisposition">The content disposition of the part.</param> + public MultipartPostPart(string contentDisposition) { + Requires.NotNullOrEmpty(contentDisposition, "contentDisposition"); + + this.ContentDisposition = contentDisposition; + this.ContentAttributes = new Dictionary<string, string>(); + this.PartHeaders = new WebHeaderCollection(); + } + + /// <summary> + /// Gets or sets the content disposition. + /// </summary> + /// <value>The content disposition.</value> + public string ContentDisposition { get; set; } + + /// <summary> + /// Gets the key=value attributes that appear on the same line as the Content-Disposition. + /// </summary> + /// <value>The content attributes.</value> + public IDictionary<string, string> ContentAttributes { get; private set; } + + /// <summary> + /// Gets the headers that appear on subsequent lines after the Content-Disposition. + /// </summary> + public WebHeaderCollection PartHeaders { get; private set; } + + /// <summary> + /// Gets or sets the content of the part. + /// </summary> + public Stream Content { get; set; } + + /// <summary> + /// Gets the length of this entire part. + /// </summary> + /// <remarks>Useful for calculating the ContentLength HTTP header to send before actually serializing the content.</remarks> + public long Length { + get { + ErrorUtilities.VerifyOperation(this.Content != null && this.Content.Length >= 0, MessagingStrings.StreamMustHaveKnownLength); + + long length = 0; + length += ContentDispositionHeader.Length; + length += ": ".Length; + length += this.ContentDisposition.Length; + foreach (var pair in this.ContentAttributes) { + length += "; ".Length + pair.Key.Length + "=\"".Length + pair.Value.Length + "\"".Length; + } + + length += NewLine.Length; + foreach (string headerName in this.PartHeaders) { + length += headerName.Length; + length += ": ".Length; + length += this.PartHeaders[headerName].Length; + length += NewLine.Length; + } + + length += NewLine.Length; + length += this.Content.Length; + + return length; + } + } + + /// <summary> + /// Creates a part that represents a simple form field. + /// </summary> + /// <param name="name">The name of the form field.</param> + /// <param name="value">The value.</param> + /// <returns>The constructed part.</returns> + public static MultipartPostPart CreateFormPart(string name, string value) { + Requires.NotNullOrEmpty(name, "name"); + Requires.NotNull(value, "value"); + + var part = new MultipartPostPart("form-data"); + try { + part.ContentAttributes["name"] = name; + part.Content = new MemoryStream(Encoding.UTF8.GetBytes(value)); + return part; + } catch { + part.Dispose(); + throw; + } + } + + /// <summary> + /// Creates a part that represents a file attachment. + /// </summary> + /// <param name="name">The name of the form field.</param> + /// <param name="filePath">The path to the file to send.</param> + /// <param name="contentType">Type of the content in HTTP Content-Type format.</param> + /// <returns>The constructed part.</returns> + public static MultipartPostPart CreateFormFilePart(string name, string filePath, string contentType) { + Requires.NotNullOrEmpty(name, "name"); + Requires.NotNullOrEmpty(filePath, "filePath"); + Requires.NotNullOrEmpty(contentType, "contentType"); + + string fileName = Path.GetFileName(filePath); + var fileStream = File.OpenRead(filePath); + try { + return CreateFormFilePart(name, fileName, contentType, fileStream); + } catch { + fileStream.Dispose(); + throw; + } + } + + /// <summary> + /// Creates a part that represents a file attachment. + /// </summary> + /// <param name="name">The name of the form field.</param> + /// <param name="fileName">Name of the file as the server should see it.</param> + /// <param name="contentType">Type of the content in HTTP Content-Type format.</param> + /// <param name="content">The content of the file.</param> + /// <returns>The constructed part.</returns> + public static MultipartPostPart CreateFormFilePart(string name, string fileName, string contentType, Stream content) { + Requires.NotNullOrEmpty(name, "name"); + Requires.NotNullOrEmpty(fileName, "fileName"); + Requires.NotNullOrEmpty(contentType, "contentType"); + Requires.NotNull(content, "content"); + + var part = new MultipartPostPart("file"); + try { + part.ContentAttributes["name"] = name; + part.ContentAttributes["filename"] = fileName; + part.PartHeaders[HttpRequestHeader.ContentType] = contentType; + if (!contentType.StartsWith("text/", StringComparison.Ordinal)) { + part.PartHeaders["Content-Transfer-Encoding"] = "binary"; + } + + part.Content = content; + return part; + } catch { + part.Dispose(); + throw; + } + } + + /// <summary> + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// </summary> + public void Dispose() { + this.Dispose(true); + GC.SuppressFinalize(this); + } + + /// <summary> + /// Serializes the part to a stream. + /// </summary> + /// <param name="streamWriter">The stream writer.</param> + internal void Serialize(StreamWriter streamWriter) { + // VERY IMPORTANT: any changes at all made to this must be kept in sync with the + // Length property which calculates exactly how many bytes this method will write. + streamWriter.NewLine = NewLine; + streamWriter.Write("{0}: {1}", ContentDispositionHeader, this.ContentDisposition); + foreach (var pair in this.ContentAttributes) { + streamWriter.Write("; {0}=\"{1}\"", pair.Key, pair.Value); + } + + streamWriter.WriteLine(); + foreach (string headerName in this.PartHeaders) { + streamWriter.WriteLine("{0}: {1}", headerName, this.PartHeaders[headerName]); + } + + streamWriter.WriteLine(); + streamWriter.Flush(); + this.Content.CopyTo(streamWriter.BaseStream); + } + + /// <summary> + /// Releases unmanaged and - optionally - managed resources + /// </summary> + /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param> + protected virtual void Dispose(bool disposing) { + if (disposing) { + this.Content.Dispose(); + } + } + +#if CONTRACTS_FULL + /// <summary> + /// Verifies conditions that should be true for any valid state of this object. + /// </summary> + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called by code contracts.")] + [ContractInvariantMethod] + private void Invariant() { + Contract.Invariant(!string.IsNullOrEmpty(this.ContentDisposition)); + Contract.Invariant(this.PartHeaders != null); + Contract.Invariant(this.ContentAttributes != null); + } +#endif + } +} diff --git a/src/DotNetOpenAuth.Core/Messaging/NetworkDirectWebResponse.cs b/src/DotNetOpenAuth.Core/Messaging/NetworkDirectWebResponse.cs new file mode 100644 index 0000000..8fb69a1 --- /dev/null +++ b/src/DotNetOpenAuth.Core/Messaging/NetworkDirectWebResponse.cs @@ -0,0 +1,116 @@ +//----------------------------------------------------------------------- +// <copyright file="NetworkDirectWebResponse.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Messaging { + using System; + using System.Diagnostics; + using System.Diagnostics.Contracts; + using System.IO; + using System.Net; + using System.Text; + + /// <summary> + /// A live network HTTP response + /// </summary> + [DebuggerDisplay("{Status} {ContentType.MediaType}")] + [ContractVerification(true)] + internal class NetworkDirectWebResponse : IncomingWebResponse, IDisposable { + /// <summary> + /// The network response object, used to initialize this instance, that still needs + /// to be closed if applicable. + /// </summary> + private HttpWebResponse httpWebResponse; + + /// <summary> + /// The incoming network response stream. + /// </summary> + private Stream responseStream; + + /// <summary> + /// A value indicating whether a stream reader has already been + /// created on this instance. + /// </summary> + private bool streamReadBegun; + + /// <summary> + /// Initializes a new instance of the <see cref="NetworkDirectWebResponse"/> class. + /// </summary> + /// <param name="requestUri">The request URI.</param> + /// <param name="response">The response.</param> + internal NetworkDirectWebResponse(Uri requestUri, HttpWebResponse response) + : base(requestUri, response) { + Requires.NotNull(requestUri, "requestUri"); + Requires.NotNull(response, "response"); + this.httpWebResponse = response; + this.responseStream = response.GetResponseStream(); + } + + /// <summary> + /// Gets the body of the HTTP response. + /// </summary> + public override Stream ResponseStream { + get { return this.responseStream; } + } + + /// <summary> + /// Creates a text reader for the response stream. + /// </summary> + /// <returns>The text reader, initialized for the proper encoding.</returns> + public override StreamReader GetResponseReader() { + this.streamReadBegun = true; + if (this.responseStream == null) { + throw new ObjectDisposedException(GetType().Name); + } + + string contentEncoding = this.Headers[HttpResponseHeader.ContentEncoding]; + if (string.IsNullOrEmpty(contentEncoding)) { + return new StreamReader(this.ResponseStream); + } else { + return new StreamReader(this.ResponseStream, Encoding.GetEncoding(contentEncoding)); + } + } + + /// <summary> + /// Gets an offline snapshot version of this instance. + /// </summary> + /// <param name="maximumBytesToCache">The maximum bytes from the response stream to cache.</param> + /// <returns>A snapshot version of this instance.</returns> + /// <remarks> + /// If this instance is a <see cref="NetworkDirectWebResponse"/> creating a snapshot + /// will automatically close and dispose of the underlying response stream. + /// If this instance is a <see cref="CachedDirectWebResponse"/>, the result will + /// be the self same instance. + /// </remarks> + internal override CachedDirectWebResponse GetSnapshot(int maximumBytesToCache) { + ErrorUtilities.VerifyOperation(!this.streamReadBegun, "Network stream reading has already begun."); + ErrorUtilities.VerifyOperation(this.httpWebResponse != null, "httpWebResponse != null"); + + this.streamReadBegun = true; + var result = new CachedDirectWebResponse(this.RequestUri, this.httpWebResponse, maximumBytesToCache); + this.Dispose(); + return result; + } + + /// <summary> + /// Releases unmanaged and - optionally - managed resources + /// </summary> + /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param> + protected override void Dispose(bool disposing) { + if (disposing) { + if (this.responseStream != null) { + this.responseStream.Dispose(); + this.responseStream = null; + } + if (this.httpWebResponse != null) { + this.httpWebResponse.Close(); + this.httpWebResponse = null; + } + } + + base.Dispose(disposing); + } + } +} diff --git a/src/DotNetOpenAuth.Core/Messaging/OutgoingWebResponse.cs b/src/DotNetOpenAuth.Core/Messaging/OutgoingWebResponse.cs new file mode 100644 index 0000000..026b7c2 --- /dev/null +++ b/src/DotNetOpenAuth.Core/Messaging/OutgoingWebResponse.cs @@ -0,0 +1,344 @@ +//----------------------------------------------------------------------- +// <copyright file="OutgoingWebResponse.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Messaging { + using System; + using System.ComponentModel; + using System.Diagnostics.CodeAnalysis; + using System.Diagnostics.Contracts; + using System.IO; + using System.Net; + using System.Net.Mime; + using System.Text; + using System.Threading; + using System.Web; + + /// <summary> + /// A protocol message (request or response) that passes from this + /// to a remote party via the user agent using a redirect or form + /// POST submission, OR a direct message response. + /// </summary> + /// <remarks> + /// <para>An instance of this type describes the HTTP response that must be sent + /// in response to the current HTTP request.</para> + /// <para>It is important that this response make up the entire HTTP response. + /// A hosting ASPX page should not be allowed to render its normal HTML output + /// after this response is sent. The normal rendered output of an ASPX page + /// can be canceled by calling <see cref="HttpResponse.End"/> after this message + /// is sent on the response stream.</para> + /// </remarks> + public class OutgoingWebResponse { + /// <summary> + /// The encoder to use for serializing the response body. + /// </summary> + private static Encoding bodyStringEncoder = new UTF8Encoding(false); + + /// <summary> + /// Initializes a new instance of the <see cref="OutgoingWebResponse"/> class. + /// </summary> + internal OutgoingWebResponse() { + this.Status = HttpStatusCode.OK; + this.Headers = new WebHeaderCollection(); + } + + /// <summary> + /// Initializes a new instance of the <see cref="OutgoingWebResponse"/> class + /// based on the contents of an <see cref="HttpWebResponse"/>. + /// </summary> + /// <param name="response">The <see cref="HttpWebResponse"/> to clone.</param> + /// <param name="maximumBytesToRead">The maximum bytes to read from the response stream.</param> + protected internal OutgoingWebResponse(HttpWebResponse response, int maximumBytesToRead) { + Requires.NotNull(response, "response"); + + this.Status = response.StatusCode; + this.Headers = response.Headers; + this.ResponseStream = new MemoryStream(response.ContentLength < 0 ? 4 * 1024 : (int)response.ContentLength); + using (Stream responseStream = response.GetResponseStream()) { + // BUGBUG: strictly speaking, is the response were exactly the limit, we'd report it as truncated here. + this.IsResponseTruncated = responseStream.CopyUpTo(this.ResponseStream, maximumBytesToRead) == maximumBytesToRead; + this.ResponseStream.Seek(0, SeekOrigin.Begin); + } + } + + /// <summary> + /// Gets the headers that must be included in the response to the user agent. + /// </summary> + /// <remarks> + /// The headers in this collection are not meant to be a comprehensive list + /// of exactly what should be sent, but are meant to augment whatever headers + /// are generally included in a typical response. + /// </remarks> + public WebHeaderCollection Headers { get; internal set; } + + /// <summary> + /// Gets the body of the HTTP response. + /// </summary> + public Stream ResponseStream { get; internal set; } + + /// <summary> + /// Gets a value indicating whether the response stream is incomplete due + /// to a length limitation imposed by the HttpWebRequest or calling method. + /// </summary> + public bool IsResponseTruncated { get; internal set; } + + /// <summary> + /// Gets or sets the body of the response as a string. + /// </summary> + public string Body { + get { return this.ResponseStream != null ? this.GetResponseReader().ReadToEnd() : null; } + set { this.SetResponse(value, null); } + } + + /// <summary> + /// Gets the HTTP status code to use in the HTTP response. + /// </summary> + public HttpStatusCode Status { get; internal set; } + + /// <summary> + /// Gets or sets a reference to the actual protocol message that + /// is being sent via the user agent. + /// </summary> + internal IProtocolMessage OriginalMessage { get; set; } + + /// <summary> + /// Creates a text reader for the response stream. + /// </summary> + /// <returns>The text reader, initialized for the proper encoding.</returns> + [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "Costly operation")] + public StreamReader GetResponseReader() { + this.ResponseStream.Seek(0, SeekOrigin.Begin); + string contentEncoding = this.Headers[HttpResponseHeader.ContentEncoding]; + if (string.IsNullOrEmpty(contentEncoding)) { + return new StreamReader(this.ResponseStream); + } else { + return new StreamReader(this.ResponseStream, Encoding.GetEncoding(contentEncoding)); + } + } + + /// <summary> + /// Automatically sends the appropriate response to the user agent + /// and ends execution on the current page or handler. + /// </summary> + /// <exception cref="ThreadAbortException">Typically thrown by ASP.NET in order to prevent additional data from the page being sent to the client and corrupting the response.</exception> + /// <remarks> + /// Requires a current HttpContext. + /// </remarks> + [EditorBrowsable(EditorBrowsableState.Never)] + public virtual void Send() { + Requires.ValidState(HttpContext.Current != null && HttpContext.Current.Request != null, MessagingStrings.HttpContextRequired); + + this.Send(HttpContext.Current); + } + + /// <summary> + /// Automatically sends the appropriate response to the user agent + /// and ends execution on the current page or handler. + /// </summary> + /// <param name="context">The context of the HTTP request whose response should be set. + /// Typically this is <see cref="HttpContext.Current"/>.</param> + /// <exception cref="ThreadAbortException">Typically thrown by ASP.NET in order to prevent additional data from the page being sent to the client and corrupting the response.</exception> + [EditorBrowsable(EditorBrowsableState.Never)] + public virtual void Send(HttpContext context) { + this.Respond(new HttpContextWrapper(context), true); + } + + /// <summary> + /// Automatically sends the appropriate response to the user agent + /// and ends execution on the current page or handler. + /// </summary> + /// <param name="context">The context of the HTTP request whose response should be set. + /// Typically this is <see cref="HttpContext.Current"/>.</param> + /// <exception cref="ThreadAbortException">Typically thrown by ASP.NET in order to prevent additional data from the page being sent to the client and corrupting the response.</exception> + [EditorBrowsable(EditorBrowsableState.Never)] + public virtual void Send(HttpContextBase context) { + this.Respond(context, true); + } + + /// <summary> + /// Automatically sends the appropriate response to the user agent + /// and signals ASP.NET to short-circuit the page execution pipeline + /// now that the response has been completed. + /// Not safe to call from ASP.NET web forms. + /// </summary> + /// <remarks> + /// Requires a current HttpContext. + /// This call is not safe to make from an ASP.NET web form (.aspx file or code-behind) because + /// ASP.NET will render HTML after the protocol message has been sent, which will corrupt the response. + /// Use the <see cref="Send()"/> method instead for web forms. + /// </remarks> + public virtual void Respond() { + Requires.ValidState(HttpContext.Current != null && HttpContext.Current.Request != null, MessagingStrings.HttpContextRequired); + + this.Respond(HttpContext.Current); + } + + /// <summary> + /// Automatically sends the appropriate response to the user agent + /// and signals ASP.NET to short-circuit the page execution pipeline + /// now that the response has been completed. + /// Not safe to call from ASP.NET web forms. + /// </summary> + /// <param name="context">The context of the HTTP request whose response should be set. + /// Typically this is <see cref="HttpContext.Current"/>.</param> + /// <remarks> + /// This call is not safe to make from an ASP.NET web form (.aspx file or code-behind) because + /// ASP.NET will render HTML after the protocol message has been sent, which will corrupt the response. + /// Use the <see cref="Send()"/> method instead for web forms. + /// </remarks> + public void Respond(HttpContext context) { + Requires.NotNull(context, "context"); + this.Respond(new HttpContextWrapper(context)); + } + + /// <summary> + /// Automatically sends the appropriate response to the user agent + /// and signals ASP.NET to short-circuit the page execution pipeline + /// now that the response has been completed. + /// Not safe to call from ASP.NET web forms. + /// </summary> + /// <param name="context">The context of the HTTP request whose response should be set. + /// Typically this is <see cref="HttpContext.Current"/>.</param> + /// <remarks> + /// This call is not safe to make from an ASP.NET web form (.aspx file or code-behind) because + /// ASP.NET will render HTML after the protocol message has been sent, which will corrupt the response. + /// Use the <see cref="Send()"/> method instead for web forms. + /// </remarks> + public virtual void Respond(HttpContextBase context) { + Requires.NotNull(context, "context"); + + this.Respond(context, false); + } + + /// <summary> + /// Automatically sends the appropriate response to the user agent. + /// </summary> + /// <param name="response">The response to set to this message.</param> + public virtual void Send(HttpListenerResponse response) { + Requires.NotNull(response, "response"); + + response.StatusCode = (int)this.Status; + MessagingUtilities.ApplyHeadersToResponse(this.Headers, response); + if (this.ResponseStream != null) { + response.ContentLength64 = this.ResponseStream.Length; + this.ResponseStream.CopyTo(response.OutputStream); + } + + response.OutputStream.Close(); + } + + /// <summary> + /// Gets the URI that, when requested with an HTTP GET request, + /// would transmit the message that normally would be transmitted via a user agent redirect. + /// </summary> + /// <param name="channel">The channel to use for encoding.</param> + /// <returns> + /// The URL that would transmit the original message. This URL may exceed the normal 2K limit, + /// and should therefore be broken up manually and POSTed as form fields when it exceeds this length. + /// </returns> + /// <remarks> + /// This is useful for desktop applications that will spawn a user agent to transmit the message + /// rather than cause a redirect. + /// </remarks> + internal Uri GetDirectUriRequest(Channel channel) { + Requires.NotNull(channel, "channel"); + + var message = this.OriginalMessage as IDirectedProtocolMessage; + if (message == null) { + throw new InvalidOperationException(); // this only makes sense for directed messages (indirect responses) + } + + var fields = channel.MessageDescriptions.GetAccessor(message).Serialize(); + UriBuilder builder = new UriBuilder(message.Recipient); + MessagingUtilities.AppendQueryArgs(builder, fields); + return builder.Uri; + } + + /// <summary> + /// Sets the response to some string, encoded as UTF-8. + /// </summary> + /// <param name="body">The string to set the response to.</param> + /// <param name="contentType">Type of the content. May be null.</param> + internal void SetResponse(string body, ContentType contentType) { + if (body == null) { + this.ResponseStream = null; + return; + } + + if (contentType == null) { + contentType = new ContentType("text/html"); + contentType.CharSet = bodyStringEncoder.WebName; + } else if (contentType.CharSet != bodyStringEncoder.WebName) { + // clone the original so we're not tampering with our inputs if it came as a parameter. + contentType = new ContentType(contentType.ToString()); + contentType.CharSet = bodyStringEncoder.WebName; + } + + this.Headers[HttpResponseHeader.ContentType] = contentType.ToString(); + this.ResponseStream = new MemoryStream(); + StreamWriter writer = new StreamWriter(this.ResponseStream, bodyStringEncoder); + writer.Write(body); + writer.Flush(); + this.ResponseStream.Seek(0, SeekOrigin.Begin); + } + + /// <summary> + /// Automatically sends the appropriate response to the user agent + /// and signals ASP.NET to short-circuit the page execution pipeline + /// now that the response has been completed. + /// </summary> + /// <param name="context">The context of the HTTP request whose response should be set. + /// Typically this is <see cref="HttpContext.Current"/>.</param> + /// <param name="endRequest">If set to <c>false</c>, this method calls + /// <see cref="HttpApplication.CompleteRequest"/> rather than <see cref="HttpResponse.End"/> + /// to avoid a <see cref="ThreadAbortException"/>.</param> + protected internal void Respond(HttpContext context, bool endRequest) { + this.Respond(new HttpContextWrapper(context), endRequest); + } + + /// <summary> + /// Automatically sends the appropriate response to the user agent + /// and signals ASP.NET to short-circuit the page execution pipeline + /// now that the response has been completed. + /// </summary> + /// <param name="context">The context of the HTTP request whose response should be set. + /// Typically this is <see cref="HttpContext.Current"/>.</param> + /// <param name="endRequest">If set to <c>false</c>, this method calls + /// <see cref="HttpApplication.CompleteRequest"/> rather than <see cref="HttpResponse.End"/> + /// to avoid a <see cref="ThreadAbortException"/>.</param> + protected internal virtual void Respond(HttpContextBase context, bool endRequest) { + Requires.NotNull(context, "context"); + + context.Response.Clear(); + context.Response.StatusCode = (int)this.Status; + MessagingUtilities.ApplyHeadersToResponse(this.Headers, context.Response); + if (this.ResponseStream != null) { + try { + this.ResponseStream.CopyTo(context.Response.OutputStream); + } catch (HttpException ex) { + if (ex.ErrorCode == -2147467259 && context.Response.Output != null) { + // Test scenarios can generate this, since the stream is being spoofed: + // System.Web.HttpException: OutputStream is not available when a custom TextWriter is used. + context.Response.Output.Write(this.Body); + } else { + throw; + } + } + } + + if (endRequest) { + // This approach throws an exception in order that + // no more code is executed in the calling page. + // Microsoft no longer recommends this approach. + context.Response.End(); + } else if (context.ApplicationInstance != null) { + // This approach doesn't throw an exception, but + // still tells ASP.NET to short-circuit most of the + // request handling pipeline to speed things up. + context.ApplicationInstance.CompleteRequest(); + } + } + } +} diff --git a/src/DotNetOpenAuth.Core/Messaging/OutgoingWebResponseActionResult.cs b/src/DotNetOpenAuth.Core/Messaging/OutgoingWebResponseActionResult.cs new file mode 100644 index 0000000..86dbb58 --- /dev/null +++ b/src/DotNetOpenAuth.Core/Messaging/OutgoingWebResponseActionResult.cs @@ -0,0 +1,40 @@ +//----------------------------------------------------------------------- +// <copyright file="OutgoingWebResponseActionResult.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Messaging { + using System; + using System.Diagnostics.Contracts; + using System.Web.Mvc; + using DotNetOpenAuth.Messaging; + + /// <summary> + /// An ASP.NET MVC structure to represent the response to send + /// to the user agent when the controller has finished its work. + /// </summary> + internal class OutgoingWebResponseActionResult : ActionResult { + /// <summary> + /// The outgoing web response to send when the ActionResult is executed. + /// </summary> + private readonly OutgoingWebResponse response; + + /// <summary> + /// Initializes a new instance of the <see cref="OutgoingWebResponseActionResult"/> class. + /// </summary> + /// <param name="response">The response.</param> + internal OutgoingWebResponseActionResult(OutgoingWebResponse response) { + Requires.NotNull(response, "response"); + this.response = response; + } + + /// <summary> + /// Enables processing of the result of an action method by a custom type that inherits from <see cref="T:System.Web.Mvc.ActionResult"/>. + /// </summary> + /// <param name="context">The context in which to set the response.</param> + public override void ExecuteResult(ControllerContext context) { + this.response.Respond(); + } + } +} diff --git a/src/DotNetOpenAuth.Core/Messaging/ProtocolException.cs b/src/DotNetOpenAuth.Core/Messaging/ProtocolException.cs new file mode 100644 index 0000000..cf3ccb8 --- /dev/null +++ b/src/DotNetOpenAuth.Core/Messaging/ProtocolException.cs @@ -0,0 +1,93 @@ +//----------------------------------------------------------------------- +// <copyright file="ProtocolException.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Messaging { + using System; + using System.Collections.Generic; + using System.Diagnostics.Contracts; + using System.Security; + using System.Security.Permissions; + + /// <summary> + /// An exception to represent errors in the local or remote implementation of the protocol. + /// </summary> + [Serializable] + public class ProtocolException : Exception { + /// <summary> + /// Initializes a new instance of the <see cref="ProtocolException"/> class. + /// </summary> + public ProtocolException() { + } + + /// <summary> + /// Initializes a new instance of the <see cref="ProtocolException"/> class. + /// </summary> + /// <param name="message">A message describing the specific error the occurred or was detected.</param> + public ProtocolException(string message) : base(message) { + } + + /// <summary> + /// Initializes a new instance of the <see cref="ProtocolException"/> class. + /// </summary> + /// <param name="message">A message describing the specific error the occurred or was detected.</param> + /// <param name="inner">The inner exception to include.</param> + public ProtocolException(string message, Exception inner) : base(message, inner) { + } + + /// <summary> + /// Initializes a new instance of the <see cref="ProtocolException"/> class + /// such that it can be sent as a protocol message response to a remote caller. + /// </summary> + /// <param name="message">The human-readable exception message.</param> + /// <param name="faultedMessage">The message that was the cause of the exception. Must not be null.</param> + protected internal ProtocolException(string message, IProtocolMessage faultedMessage) + : base(message) { + Requires.NotNull(faultedMessage, "faultedMessage"); + this.FaultedMessage = faultedMessage; + } + + /// <summary> + /// Initializes a new instance of the <see cref="ProtocolException"/> class. + /// </summary> + /// <param name="info">The <see cref="System.Runtime.Serialization.SerializationInfo"/> + /// that holds the serialized object data about the exception being thrown.</param> + /// <param name="context">The System.Runtime.Serialization.StreamingContext + /// that contains contextual information about the source or destination.</param> + protected ProtocolException( + System.Runtime.Serialization.SerializationInfo info, + System.Runtime.Serialization.StreamingContext context) + : base(info, context) { + throw new NotImplementedException(); + } + + /// <summary> + /// Gets the message that caused the exception. + /// </summary> + internal IProtocolMessage FaultedMessage { get; private set; } + + /// <summary> + /// When overridden in a derived class, sets the <see cref="T:System.Runtime.Serialization.SerializationInfo"/> with information about the exception. + /// </summary> + /// <param name="info">The <see cref="T:System.Runtime.Serialization.SerializationInfo"/> that holds the serialized object data about the exception being thrown.</param> + /// <param name="context">The <see cref="T:System.Runtime.Serialization.StreamingContext"/> that contains contextual information about the source or destination.</param> + /// <exception cref="T:System.ArgumentNullException"> + /// The <paramref name="info"/> parameter is a null reference (Nothing in Visual Basic). + /// </exception> + /// <PermissionSet> + /// <IPermission class="System.Security.Permissions.FileIOPermission, mscorlib, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Read="*AllFiles*" PathDiscovery="*AllFiles*"/> + /// <IPermission class="System.Security.Permissions.SecurityPermission, mscorlib, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Flags="SerializationFormatter"/> + /// </PermissionSet> +#if CLR4 + [SecurityCritical] +#else + [SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.SerializationFormatter)] +#endif + public override void GetObjectData(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) { + base.GetObjectData(info, context); + throw new NotImplementedException(); + } + } +} diff --git a/src/DotNetOpenAuth.Core/Messaging/Reflection/IMessagePartEncoder.cs b/src/DotNetOpenAuth.Core/Messaging/Reflection/IMessagePartEncoder.cs new file mode 100644 index 0000000..bbb3737 --- /dev/null +++ b/src/DotNetOpenAuth.Core/Messaging/Reflection/IMessagePartEncoder.cs @@ -0,0 +1,78 @@ +//----------------------------------------------------------------------- +// <copyright file="IMessagePartEncoder.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Messaging.Reflection { + using System; + using System.Collections.Generic; + using System.Diagnostics.Contracts; + using System.Linq; + using System.Text; + + /// <summary> + /// An interface describing how various objects can be serialized and deserialized between their object and string forms. + /// </summary> + /// <remarks> + /// Implementations of this interface must include a default constructor and must be thread-safe. + /// </remarks> + [ContractClass(typeof(IMessagePartEncoderContract))] + public interface IMessagePartEncoder { + /// <summary> + /// Encodes the specified value. + /// </summary> + /// <param name="value">The value. Guaranteed to never be null.</param> + /// <returns>The <paramref name="value"/> in string form, ready for message transport.</returns> + string Encode(object value); + + /// <summary> + /// Decodes the specified value. + /// </summary> + /// <param name="value">The string value carried by the transport. Guaranteed to never be null, although it may be empty.</param> + /// <returns>The deserialized form of the given string.</returns> + /// <exception cref="FormatException">Thrown when the string value given cannot be decoded into the required object type.</exception> + object Decode(string value); + } + + /// <summary> + /// Code contract for the <see cref="IMessagePartEncoder"/> type. + /// </summary> + [ContractClassFor(typeof(IMessagePartEncoder))] + internal abstract class IMessagePartEncoderContract : IMessagePartEncoder { + /// <summary> + /// Initializes a new instance of the <see cref="IMessagePartEncoderContract"/> class. + /// </summary> + protected IMessagePartEncoderContract() { + } + + #region IMessagePartEncoder Members + + /// <summary> + /// Encodes the specified value. + /// </summary> + /// <param name="value">The value. Guaranteed to never be null.</param> + /// <returns> + /// The <paramref name="value"/> in string form, ready for message transport. + /// </returns> + string IMessagePartEncoder.Encode(object value) { + Requires.NotNull(value, "value"); + throw new NotImplementedException(); + } + + /// <summary> + /// Decodes the specified value. + /// </summary> + /// <param name="value">The string value carried by the transport. Guaranteed to never be null, although it may be empty.</param> + /// <returns> + /// The deserialized form of the given string. + /// </returns> + /// <exception cref="FormatException">Thrown when the string value given cannot be decoded into the required object type.</exception> + object IMessagePartEncoder.Decode(string value) { + Requires.NotNull(value, "value"); + throw new NotImplementedException(); + } + + #endregion + } +} diff --git a/src/DotNetOpenAuth/Messaging/Reflection/IMessagePartNullEncoder.cs b/src/DotNetOpenAuth.Core/Messaging/Reflection/IMessagePartNullEncoder.cs index 7581550..7581550 100644 --- a/src/DotNetOpenAuth/Messaging/Reflection/IMessagePartNullEncoder.cs +++ b/src/DotNetOpenAuth.Core/Messaging/Reflection/IMessagePartNullEncoder.cs diff --git a/src/DotNetOpenAuth/Messaging/Reflection/IMessagePartOriginalEncoder.cs b/src/DotNetOpenAuth.Core/Messaging/Reflection/IMessagePartOriginalEncoder.cs index 9ad55c9..9ad55c9 100644 --- a/src/DotNetOpenAuth/Messaging/Reflection/IMessagePartOriginalEncoder.cs +++ b/src/DotNetOpenAuth.Core/Messaging/Reflection/IMessagePartOriginalEncoder.cs diff --git a/src/DotNetOpenAuth.Core/Messaging/Reflection/MessageDescription.cs b/src/DotNetOpenAuth.Core/Messaging/Reflection/MessageDescription.cs new file mode 100644 index 0000000..9a8098b --- /dev/null +++ b/src/DotNetOpenAuth.Core/Messaging/Reflection/MessageDescription.cs @@ -0,0 +1,283 @@ +//----------------------------------------------------------------------- +// <copyright file="MessageDescription.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Messaging.Reflection { + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Diagnostics.Contracts; + using System.Globalization; + using System.Linq; + using System.Reflection; + + /// <summary> + /// A mapping between serialized key names and <see cref="MessagePart"/> instances describing + /// those key/values pairs. + /// </summary> + internal class MessageDescription { + /// <summary> + /// A mapping between the serialized key names and their + /// describing <see cref="MessagePart"/> instances. + /// </summary> + private Dictionary<string, MessagePart> mapping; + + /// <summary> + /// Initializes a new instance of the <see cref="MessageDescription"/> class. + /// </summary> + /// <param name="messageType">Type of the message.</param> + /// <param name="messageVersion">The message version.</param> + internal MessageDescription(Type messageType, Version messageVersion) { + Requires.NotNullSubtype<IMessage>(messageType, "messageType"); + Requires.NotNull(messageVersion, "messageVersion"); + + this.MessageType = messageType; + this.MessageVersion = messageVersion; + this.ReflectMessageType(); + } + + /// <summary> + /// Gets the mapping between the serialized key names and their describing + /// <see cref="MessagePart"/> instances. + /// </summary> + internal IDictionary<string, MessagePart> Mapping { + get { return this.mapping; } + } + + /// <summary> + /// Gets the message version this instance was generated from. + /// </summary> + internal Version MessageVersion { get; private set; } + + /// <summary> + /// Gets the type of message this instance was generated from. + /// </summary> + /// <value>The type of the described message.</value> + internal Type MessageType { get; private set; } + + /// <summary> + /// Gets the constructors available on the message type. + /// </summary> + internal ConstructorInfo[] Constructors { get; private set; } + + /// <summary> + /// Returns a <see cref="System.String"/> that represents this instance. + /// </summary> + /// <returns> + /// A <see cref="System.String"/> that represents this instance. + /// </returns> + public override string ToString() { + return this.MessageType.Name + " (" + this.MessageVersion + ")"; + } + + /// <summary> + /// Gets a dictionary that provides read/write access to a message. + /// </summary> + /// <param name="message">The message the dictionary should provide access to.</param> + /// <returns>The dictionary accessor to the message</returns> + [Pure] + internal MessageDictionary GetDictionary(IMessage message) { + Requires.NotNull(message, "message"); + Contract.Ensures(Contract.Result<MessageDictionary>() != null); + return this.GetDictionary(message, false); + } + + /// <summary> + /// Gets a dictionary that provides read/write access to a message. + /// </summary> + /// <param name="message">The message the dictionary should provide access to.</param> + /// <param name="getOriginalValues">A value indicating whether this message dictionary will retrieve original values instead of normalized ones.</param> + /// <returns>The dictionary accessor to the message</returns> + [Pure] + internal MessageDictionary GetDictionary(IMessage message, bool getOriginalValues) { + Requires.NotNull(message, "message"); + Contract.Ensures(Contract.Result<MessageDictionary>() != null); + return new MessageDictionary(message, this, getOriginalValues); + } + + /// <summary> + /// Ensures the message parts pass basic validation. + /// </summary> + /// <param name="parts">The key/value pairs of the serialized message.</param> + internal void EnsureMessagePartsPassBasicValidation(IDictionary<string, string> parts) { + try { + this.CheckRequiredMessagePartsArePresent(parts.Keys, true); + this.CheckRequiredProtocolMessagePartsAreNotEmpty(parts, true); + this.CheckMessagePartsConstantValues(parts, true); + } catch (ProtocolException) { + Logger.Messaging.ErrorFormat( + "Error while performing basic validation of {0} with these message parts:{1}{2}", + this.MessageType.Name, + Environment.NewLine, + parts.ToStringDeferred()); + throw; + } + } + + /// <summary> + /// Tests whether all the required message parts pass basic validation for the given data. + /// </summary> + /// <param name="parts">The key/value pairs of the serialized message.</param> + /// <returns>A value indicating whether the provided data fits the message's basic requirements.</returns> + internal bool CheckMessagePartsPassBasicValidation(IDictionary<string, string> parts) { + Requires.NotNull(parts, "parts"); + + return this.CheckRequiredMessagePartsArePresent(parts.Keys, false) && + this.CheckRequiredProtocolMessagePartsAreNotEmpty(parts, false) && + this.CheckMessagePartsConstantValues(parts, false); + } + + /// <summary> + /// Verifies that a given set of keys include all the required parameters + /// for this message type or throws an exception. + /// </summary> + /// <param name="keys">The names of all parameters included in a message.</param> + /// <param name="throwOnFailure">if set to <c>true</c> an exception is thrown on failure with details.</param> + /// <returns>A value indicating whether the provided data fits the message's basic requirements.</returns> + /// <exception cref="ProtocolException"> + /// Thrown when required parts of a message are not in <paramref name="keys"/> + /// if <paramref name="throwOnFailure"/> is <c>true</c>. + /// </exception> + private bool CheckRequiredMessagePartsArePresent(IEnumerable<string> keys, bool throwOnFailure) { + Requires.NotNull(keys, "keys"); + + var missingKeys = (from part in this.Mapping.Values + where part.IsRequired && !keys.Contains(part.Name) + select part.Name).ToArray(); + if (missingKeys.Length > 0) { + if (throwOnFailure) { + ErrorUtilities.ThrowProtocol( + MessagingStrings.RequiredParametersMissing, + this.MessageType.FullName, + string.Join(", ", missingKeys)); + } else { + Logger.Messaging.DebugFormat( + MessagingStrings.RequiredParametersMissing, + this.MessageType.FullName, + missingKeys.ToStringDeferred()); + return false; + } + } + + return true; + } + + /// <summary> + /// Ensures the protocol message parts that must not be empty are in fact not empty. + /// </summary> + /// <param name="partValues">A dictionary of key/value pairs that make up the serialized message.</param> + /// <param name="throwOnFailure">if set to <c>true</c> an exception is thrown on failure with details.</param> + /// <returns>A value indicating whether the provided data fits the message's basic requirements.</returns> + /// <exception cref="ProtocolException"> + /// Thrown when required parts of a message are not in <paramref name="partValues"/> + /// if <paramref name="throwOnFailure"/> is <c>true</c>. + /// </exception> + private bool CheckRequiredProtocolMessagePartsAreNotEmpty(IDictionary<string, string> partValues, bool throwOnFailure) { + Requires.NotNull(partValues, "partValues"); + + string value; + var emptyValuedKeys = (from part in this.Mapping.Values + where !part.AllowEmpty && partValues.TryGetValue(part.Name, out value) && value != null && value.Length == 0 + select part.Name).ToArray(); + if (emptyValuedKeys.Length > 0) { + if (throwOnFailure) { + ErrorUtilities.ThrowProtocol( + MessagingStrings.RequiredNonEmptyParameterWasEmpty, + this.MessageType.FullName, + string.Join(", ", emptyValuedKeys)); + } else { + Logger.Messaging.DebugFormat( + MessagingStrings.RequiredNonEmptyParameterWasEmpty, + this.MessageType.FullName, + emptyValuedKeys.ToStringDeferred()); + return false; + } + } + + return true; + } + + /// <summary> + /// Checks that a bunch of message part values meet the constant value requirements of this message description. + /// </summary> + /// <param name="partValues">The part values.</param> + /// <param name="throwOnFailure">if set to <c>true</c>, this method will throw on failure.</param> + /// <returns>A value indicating whether all the requirements are met.</returns> + private bool CheckMessagePartsConstantValues(IDictionary<string, string> partValues, bool throwOnFailure) { + Requires.NotNull(partValues, "partValues"); + + var badConstantValues = (from part in this.Mapping.Values + where part.IsConstantValueAvailableStatically + where partValues.ContainsKey(part.Name) + where !string.Equals(partValues[part.Name], part.StaticConstantValue, StringComparison.Ordinal) + select part.Name).ToArray(); + if (badConstantValues.Length > 0) { + if (throwOnFailure) { + ErrorUtilities.ThrowProtocol( + MessagingStrings.RequiredMessagePartConstantIncorrect, + this.MessageType.FullName, + string.Join(", ", badConstantValues)); + } else { + Logger.Messaging.DebugFormat( + MessagingStrings.RequiredMessagePartConstantIncorrect, + this.MessageType.FullName, + badConstantValues.ToStringDeferred()); + return false; + } + } + + return true; + } + + /// <summary> + /// Reflects over some <see cref="IMessage"/>-implementing type + /// and prepares to serialize/deserialize instances of that type. + /// </summary> + private void ReflectMessageType() { + this.mapping = new Dictionary<string, MessagePart>(); + + Type currentType = this.MessageType; + do { + foreach (MemberInfo member in currentType.GetMembers(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.DeclaredOnly)) { + if (member is PropertyInfo || member is FieldInfo) { + MessagePartAttribute partAttribute = + (from a in member.GetCustomAttributes(typeof(MessagePartAttribute), true).OfType<MessagePartAttribute>() + orderby a.MinVersionValue descending + where a.MinVersionValue <= this.MessageVersion + where a.MaxVersionValue >= this.MessageVersion + select a).FirstOrDefault(); + if (partAttribute != null) { + MessagePart part = new MessagePart(member, partAttribute); + if (this.mapping.ContainsKey(part.Name)) { + Logger.Messaging.WarnFormat( + "Message type {0} has more than one message part named {1}. Inherited members will be hidden.", + this.MessageType.Name, + part.Name); + } else { + this.mapping.Add(part.Name, part); + } + } + } + } + currentType = currentType.BaseType; + } while (currentType != null); + + BindingFlags flags = BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public; + this.Constructors = this.MessageType.GetConstructors(flags); + } + +#if CONTRACTS_FULL + /// <summary> + /// Describes traits of this class that are always true. + /// </summary> + [ContractInvariantMethod] + private void Invariant() { + Contract.Invariant(this.MessageType != null); + Contract.Invariant(this.MessageVersion != null); + Contract.Invariant(this.Constructors != null); + } +#endif + } +} diff --git a/src/DotNetOpenAuth.Core/Messaging/Reflection/MessageDescriptionCollection.cs b/src/DotNetOpenAuth.Core/Messaging/Reflection/MessageDescriptionCollection.cs new file mode 100644 index 0000000..79ef172 --- /dev/null +++ b/src/DotNetOpenAuth.Core/Messaging/Reflection/MessageDescriptionCollection.cs @@ -0,0 +1,217 @@ +//----------------------------------------------------------------------- +// <copyright file="MessageDescriptionCollection.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Messaging.Reflection { + using System; + using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; + using System.Diagnostics.Contracts; + + /// <summary> + /// A cache of <see cref="MessageDescription"/> instances. + /// </summary> + [ContractVerification(true)] + internal class MessageDescriptionCollection : IEnumerable<MessageDescription> { + /// <summary> + /// A dictionary of reflected message types and the generated reflection information. + /// </summary> + private readonly Dictionary<MessageTypeAndVersion, MessageDescription> reflectedMessageTypes = new Dictionary<MessageTypeAndVersion, MessageDescription>(); + + /// <summary> + /// Initializes a new instance of the <see cref="MessageDescriptionCollection"/> class. + /// </summary> + internal MessageDescriptionCollection() { + } + + #region IEnumerable<MessageDescription> Members + + /// <summary> + /// Returns an enumerator that iterates through a collection. + /// </summary> + /// <returns> + /// An <see cref="T:System.Collections.IEnumerator"/> object that can be used to iterate through the collection. + /// </returns> + public IEnumerator<MessageDescription> GetEnumerator() { + return this.reflectedMessageTypes.Values.GetEnumerator(); + } + + #endregion + + #region IEnumerable Members + + /// <summary> + /// Returns an enumerator that iterates through a collection. + /// </summary> + /// <returns> + /// An <see cref="T:System.Collections.IEnumerator"/> object that can be used to iterate through the collection. + /// </returns> + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { + return this.reflectedMessageTypes.Values.GetEnumerator(); + } + + #endregion + + /// <summary> + /// Gets a <see cref="MessageDescription"/> instance prepared for the + /// given message type. + /// </summary> + /// <param name="messageType">A type that implements <see cref="IMessage"/>.</param> + /// <param name="messageVersion">The protocol version of the message.</param> + /// <returns>A <see cref="MessageDescription"/> instance.</returns> + [SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", MessageId = "System.Diagnostics.Contracts.__ContractsRuntime.Assume(System.Boolean,System.String,System.String)", Justification = "No localization required.")] + [Pure] + internal MessageDescription Get(Type messageType, Version messageVersion) { + Requires.NotNullSubtype<IMessage>(messageType, "messageType"); + Requires.NotNull(messageVersion, "messageVersion"); + Contract.Ensures(Contract.Result<MessageDescription>() != null); + + MessageTypeAndVersion key = new MessageTypeAndVersion(messageType, messageVersion); + + MessageDescription result; + if (!this.reflectedMessageTypes.TryGetValue(key, out result)) { + lock (this.reflectedMessageTypes) { + if (!this.reflectedMessageTypes.TryGetValue(key, out result)) { + this.reflectedMessageTypes[key] = result = new MessageDescription(messageType, messageVersion); + } + } + } + + Contract.Assume(result != null, "We should never assign null values to this dictionary."); + return result; + } + + /// <summary> + /// Gets a <see cref="MessageDescription"/> instance prepared for the + /// given message type. + /// </summary> + /// <param name="message">The message for which a <see cref="MessageDescription"/> should be obtained.</param> + /// <returns> + /// A <see cref="MessageDescription"/> instance. + /// </returns> + [Pure] + internal MessageDescription Get(IMessage message) { + Requires.NotNull(message, "message"); + Contract.Ensures(Contract.Result<MessageDescription>() != null); + return this.Get(message.GetType(), message.Version); + } + + /// <summary> + /// Gets the dictionary that provides read/write access to a message. + /// </summary> + /// <param name="message">The message.</param> + /// <returns>The dictionary.</returns> + [Pure] + internal MessageDictionary GetAccessor(IMessage message) { + Requires.NotNull(message, "message"); + return this.GetAccessor(message, false); + } + + /// <summary> + /// Gets the dictionary that provides read/write access to a message. + /// </summary> + /// <param name="message">The message.</param> + /// <param name="getOriginalValues">A value indicating whether this message dictionary will retrieve original values instead of normalized ones.</param> + /// <returns>The dictionary.</returns> + [Pure] + internal MessageDictionary GetAccessor(IMessage message, bool getOriginalValues) { + Requires.NotNull(message, "message"); + return this.Get(message).GetDictionary(message, getOriginalValues); + } + + /// <summary> + /// A struct used as the key to bundle message type and version. + /// </summary> + [ContractVerification(true)] + private struct MessageTypeAndVersion { + /// <summary> + /// Backing store for the <see cref="Type"/> property. + /// </summary> + private readonly Type type; + + /// <summary> + /// Backing store for the <see cref="Version"/> property. + /// </summary> + private readonly Version version; + + /// <summary> + /// Initializes a new instance of the <see cref="MessageTypeAndVersion"/> struct. + /// </summary> + /// <param name="messageType">Type of the message.</param> + /// <param name="messageVersion">The message version.</param> + internal MessageTypeAndVersion(Type messageType, Version messageVersion) { + Requires.NotNull(messageType, "messageType"); + Requires.NotNull(messageVersion, "messageVersion"); + + this.type = messageType; + this.version = messageVersion; + } + + /// <summary> + /// Gets the message type. + /// </summary> + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Exposes basic identity on the type.")] + internal Type Type { + get { return this.type; } + } + + /// <summary> + /// Gets the message version. + /// </summary> + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Exposes basic identity on the type.")] + internal Version Version { + get { return this.version; } + } + + /// <summary> + /// Implements the operator ==. + /// </summary> + /// <param name="first">The first object to compare.</param> + /// <param name="second">The second object to compare.</param> + /// <returns>The result of the operator.</returns> + public static bool operator ==(MessageTypeAndVersion first, MessageTypeAndVersion second) { + // structs cannot be null, so this is safe + return first.Equals(second); + } + + /// <summary> + /// Implements the operator !=. + /// </summary> + /// <param name="first">The first object to compare.</param> + /// <param name="second">The second object to compare.</param> + /// <returns>The result of the operator.</returns> + public static bool operator !=(MessageTypeAndVersion first, MessageTypeAndVersion second) { + // structs cannot be null, so this is safe + return !first.Equals(second); + } + + /// <summary> + /// Indicates whether this instance and a specified object are equal. + /// </summary> + /// <param name="obj">Another object to compare to.</param> + /// <returns> + /// true if <paramref name="obj"/> and this instance are the same type and represent the same value; otherwise, false. + /// </returns> + public override bool Equals(object obj) { + if (obj is MessageTypeAndVersion) { + MessageTypeAndVersion other = (MessageTypeAndVersion)obj; + return this.type == other.type && this.version == other.version; + } else { + return false; + } + } + + /// <summary> + /// Returns the hash code for this instance. + /// </summary> + /// <returns> + /// A 32-bit signed integer that is the hash code for this instance. + /// </returns> + public override int GetHashCode() { + return this.type.GetHashCode(); + } + } + } +} diff --git a/src/DotNetOpenAuth.Core/Messaging/Reflection/MessageDictionary.cs b/src/DotNetOpenAuth.Core/Messaging/Reflection/MessageDictionary.cs new file mode 100644 index 0000000..54e2dd5 --- /dev/null +++ b/src/DotNetOpenAuth.Core/Messaging/Reflection/MessageDictionary.cs @@ -0,0 +1,409 @@ +//----------------------------------------------------------------------- +// <copyright file="MessageDictionary.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Messaging.Reflection { + using System; + using System.Collections; + using System.Collections.Generic; + using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; + using System.Diagnostics.Contracts; + + /// <summary> + /// Wraps an <see cref="IMessage"/> instance in a dictionary that + /// provides access to both well-defined message properties and "extra" + /// name/value pairs that have no properties associated with them. + /// </summary> + [ContractVerification(false)] + internal class MessageDictionary : IDictionary<string, string> { + /// <summary> + /// The <see cref="IMessage"/> instance manipulated by this dictionary. + /// </summary> + private readonly IMessage message; + + /// <summary> + /// The <see cref="MessageDescription"/> instance that describes the message type. + /// </summary> + private readonly MessageDescription description; + + /// <summary> + /// Whether original string values should be retrieved instead of normalized ones. + /// </summary> + private readonly bool getOriginalValues; + + /// <summary> + /// Initializes a new instance of the <see cref="MessageDictionary"/> class. + /// </summary> + /// <param name="message">The message instance whose values will be manipulated by this dictionary.</param> + /// <param name="description">The message description.</param> + /// <param name="getOriginalValues">A value indicating whether this message dictionary will retrieve original values instead of normalized ones.</param> + [Pure] + internal MessageDictionary(IMessage message, MessageDescription description, bool getOriginalValues) { + Requires.NotNull(message, "message"); + Requires.NotNull(description, "description"); + + this.message = message; + this.description = description; + this.getOriginalValues = getOriginalValues; + } + + /// <summary> + /// Gets the message this dictionary provides access to. + /// </summary> + public IMessage Message { + get { + Contract.Ensures(Contract.Result<IMessage>() != null); + return this.message; + } + } + + /// <summary> + /// Gets the description of the type of message this dictionary provides access to. + /// </summary> + public MessageDescription Description { + get { + Contract.Ensures(Contract.Result<MessageDescription>() != null); + return this.description; + } + } + + #region ICollection<KeyValuePair<string,string>> Properties + + /// <summary> + /// Gets the number of explicitly set values in the message. + /// </summary> + public int Count { + get { return this.Keys.Count; } + } + + /// <summary> + /// Gets a value indicating whether this message is read only. + /// </summary> + bool ICollection<KeyValuePair<string, string>>.IsReadOnly { + get { return false; } + } + + #endregion + + #region IDictionary<string,string> Properties + + /// <summary> + /// Gets all the keys that have values associated with them. + /// </summary> + public ICollection<string> Keys { + get { + List<string> keys = new List<string>(this.message.ExtraData.Count + this.description.Mapping.Count); + keys.AddRange(this.DeclaredKeys); + keys.AddRange(this.AdditionalKeys); + return keys.AsReadOnly(); + } + } + + /// <summary> + /// Gets the set of official message part names that have non-null values associated with them. + /// </summary> + public ICollection<string> DeclaredKeys { + get { + List<string> keys = new List<string>(this.description.Mapping.Count); + foreach (var pair in this.description.Mapping) { + // Don't include keys with null values, but default values for structs is ok + if (pair.Value.GetValue(this.message, this.getOriginalValues) != null) { + keys.Add(pair.Key); + } + } + + return keys.AsReadOnly(); + } + } + + /// <summary> + /// Gets the keys that are in the message but not declared as official OAuth properties. + /// </summary> + public ICollection<string> AdditionalKeys { + get { return this.message.ExtraData.Keys; } + } + + /// <summary> + /// Gets all the values. + /// </summary> + public ICollection<string> Values { + get { + List<string> values = new List<string>(this.message.ExtraData.Count + this.description.Mapping.Count); + foreach (MessagePart part in this.description.Mapping.Values) { + if (part.GetValue(this.message, this.getOriginalValues) != null) { + values.Add(part.GetValue(this.message, this.getOriginalValues)); + } + } + + foreach (string value in this.message.ExtraData.Values) { + Debug.Assert(value != null, "Null values should never be allowed in the extra data dictionary."); + values.Add(value); + } + + return values.AsReadOnly(); + } + } + + #endregion + + /// <summary> + /// Gets the serializer for the message this dictionary provides access to. + /// </summary> + private MessageSerializer Serializer { + get { return MessageSerializer.Get(this.Message.GetType()); } + } + + #region IDictionary<string,string> Indexers + + /// <summary> + /// Gets or sets a value for some named value. + /// </summary> + /// <param name="key">The serialized form of a name for the value to read or write.</param> + /// <returns>The named value.</returns> + /// <remarks> + /// If the key matches a declared property or field on the message type, + /// that type member is set. Otherwise the key/value is stored in a + /// dictionary for extra (weakly typed) strings. + /// </remarks> + /// <exception cref="ArgumentException">Thrown when setting a value that is not allowed for a given <paramref name="key"/>.</exception> + public string this[string key] { + get { + MessagePart part; + if (this.description.Mapping.TryGetValue(key, out part)) { + // Never throw KeyNotFoundException for declared properties. + return part.GetValue(this.message, this.getOriginalValues); + } else { + return this.message.ExtraData[key]; + } + } + + set { + MessagePart part; + if (this.description.Mapping.TryGetValue(key, out part)) { + part.SetValue(this.message, value); + } else { + if (value == null) { + this.message.ExtraData.Remove(key); + } else { + this.message.ExtraData[key] = value; + } + } + } + } + + #endregion + + #region IDictionary<string,string> Methods + + /// <summary> + /// Adds a named value to the message. + /// </summary> + /// <param name="key">The serialized form of the name whose value is being set.</param> + /// <param name="value">The serialized form of the value.</param> + /// <exception cref="ArgumentException"> + /// Thrown if <paramref name="key"/> already has a set value in this message. + /// </exception> + /// <exception cref="ArgumentNullException"> + /// Thrown if <paramref name="value"/> is null. + /// </exception> + public void Add(string key, string value) { + ErrorUtilities.VerifyArgumentNotNull(value, "value"); + + MessagePart part; + if (this.description.Mapping.TryGetValue(key, out part)) { + if (part.IsNondefaultValueSet(this.message)) { + throw new ArgumentException(MessagingStrings.KeyAlreadyExists); + } + part.SetValue(this.message, value); + } else { + this.message.ExtraData.Add(key, value); + } + } + + /// <summary> + /// Checks whether some named parameter has a value set in the message. + /// </summary> + /// <param name="key">The serialized form of the message part's name.</param> + /// <returns>True if the parameter by the given name has a set value. False otherwise.</returns> + public bool ContainsKey(string key) { + return this.message.ExtraData.ContainsKey(key) || + (this.description.Mapping.ContainsKey(key) && this.description.Mapping[key].GetValue(this.message, this.getOriginalValues) != null); + } + + /// <summary> + /// Removes a name and value from the message given its name. + /// </summary> + /// <param name="key">The serialized form of the name to remove.</param> + /// <returns>True if a message part by the given name was found and removed. False otherwise.</returns> + public bool Remove(string key) { + if (this.message.ExtraData.Remove(key)) { + return true; + } else { + MessagePart part; + if (this.description.Mapping.TryGetValue(key, out part)) { + if (part.GetValue(this.message, this.getOriginalValues) != null) { + part.SetValue(this.message, null); + return true; + } + } + return false; + } + } + + /// <summary> + /// Gets some named value if the key has a value. + /// </summary> + /// <param name="key">The name (in serialized form) of the value being sought.</param> + /// <param name="value">The variable where the value will be set.</param> + /// <returns>True if the key was found and <paramref name="value"/> was set. False otherwise.</returns> + public bool TryGetValue(string key, out string value) { + MessagePart part; + if (this.description.Mapping.TryGetValue(key, out part)) { + value = part.GetValue(this.message, this.getOriginalValues); + return value != null; + } + return this.message.ExtraData.TryGetValue(key, out value); + } + + #endregion + + #region ICollection<KeyValuePair<string,string>> Methods + + /// <summary> + /// Sets a named value in the message. + /// </summary> + /// <param name="item">The name-value pair to add. The name is the serialized form of the key.</param> + [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Code Contracts ccrewrite does this.")] + public void Add(KeyValuePair<string, string> item) { + this.Add(item.Key, item.Value); + } + + /// <summary> + /// Removes all values in the message. + /// </summary> + public void ClearValues() { + foreach (string key in this.Keys) { + this.Remove(key); + } + } + + /// <summary> + /// Removes all items from the <see cref="T:System.Collections.Generic.ICollection`1"/>. + /// </summary> + /// <exception cref="T:System.NotSupportedException"> + /// The <see cref="T:System.Collections.Generic.ICollection`1"/> is read-only. + /// </exception> + /// <remarks> + /// This method cannot be implemented because keys are not guaranteed to be removed + /// since some are inherent to the type of message that this dictionary provides + /// access to. + /// </remarks> + public void Clear() { + throw new NotSupportedException(); + } + + /// <summary> + /// Checks whether a named value has been set on the message. + /// </summary> + /// <param name="item">The name/value pair.</param> + /// <returns>True if the key exists and has the given value. False otherwise.</returns> + public bool Contains(KeyValuePair<string, string> item) { + MessagePart part; + if (this.description.Mapping.TryGetValue(item.Key, out part)) { + return string.Equals(part.GetValue(this.message, this.getOriginalValues), item.Value, StringComparison.Ordinal); + } else { + return this.message.ExtraData.Contains(item); + } + } + + /// <summary> + /// Copies all the serializable data from the message to a key/value array. + /// </summary> + /// <param name="array">The array to copy to.</param> + /// <param name="arrayIndex">The index in the <paramref name="array"/> to begin copying to.</param> + void ICollection<KeyValuePair<string, string>>.CopyTo(KeyValuePair<string, string>[] array, int arrayIndex) { + foreach (var pair in (IDictionary<string, string>)this) { + array[arrayIndex++] = pair; + } + } + + /// <summary> + /// Removes a named value from the message if it exists. + /// </summary> + /// <param name="item">The serialized form of the name and value to remove.</param> + /// <returns>True if the name/value was found and removed. False otherwise.</returns> + public bool Remove(KeyValuePair<string, string> item) { + // We use contains because that checks that the value is equal as well. + if (((ICollection<KeyValuePair<string, string>>)this).Contains(item)) { + ((IDictionary<string, string>)this).Remove(item.Key); + return true; + } + return false; + } + + #endregion + + #region IEnumerable<KeyValuePair<string,string>> Members + + /// <summary> + /// Gets an enumerator that generates KeyValuePair<string, string> instances + /// for all the key/value pairs that are set in the message. + /// </summary> + /// <returns>The enumerator that can generate the name/value pairs.</returns> + public IEnumerator<KeyValuePair<string, string>> GetEnumerator() { + foreach (string key in this.Keys) { + yield return new KeyValuePair<string, string>(key, this[key]); + } + } + + #endregion + + #region IEnumerable Members + + /// <summary> + /// Gets an enumerator that generates KeyValuePair<string, string> instances + /// for all the key/value pairs that are set in the message. + /// </summary> + /// <returns>The enumerator that can generate the name/value pairs.</returns> + IEnumerator System.Collections.IEnumerable.GetEnumerator() { + return ((IEnumerable<KeyValuePair<string, string>>)this).GetEnumerator(); + } + + #endregion + + /// <summary> + /// Saves the data in a message to a standard dictionary. + /// </summary> + /// <returns>The generated dictionary.</returns> + [Pure] + public IDictionary<string, string> Serialize() { + Contract.Ensures(Contract.Result<IDictionary<string, string>>() != null); + return this.Serializer.Serialize(this); + } + + /// <summary> + /// Loads data from a dictionary into the message. + /// </summary> + /// <param name="fields">The data to load into the message.</param> + public void Deserialize(IDictionary<string, string> fields) { + Requires.NotNull(fields, "fields"); + this.Serializer.Deserialize(fields, this); + } + +#if CONTRACTS_FULL + /// <summary> + /// Verifies conditions that should be true for any valid state of this object. + /// </summary> + [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Called by code contracts.")] + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called by code contracts.")] + [ContractInvariantMethod] + private void ObjectInvariant() { + Contract.Invariant(this.Message != null); + Contract.Invariant(this.Description != null); + } +#endif + } +} diff --git a/src/DotNetOpenAuth.Core/Messaging/Reflection/MessagePart.cs b/src/DotNetOpenAuth.Core/Messaging/Reflection/MessagePart.cs new file mode 100644 index 0000000..f439c4d --- /dev/null +++ b/src/DotNetOpenAuth.Core/Messaging/Reflection/MessagePart.cs @@ -0,0 +1,428 @@ +//----------------------------------------------------------------------- +// <copyright file="MessagePart.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Messaging.Reflection { + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; + using System.Diagnostics.Contracts; + using System.Globalization; + using System.Linq; + using System.Net.Security; + using System.Reflection; + using System.Xml; + using DotNetOpenAuth.Configuration; + + /// <summary> + /// Describes an individual member of a message and assists in its serialization. + /// </summary> + [ContractVerification(true)] + [DebuggerDisplay("MessagePart {Name}")] + internal class MessagePart { + /// <summary> + /// A map of converters that help serialize custom objects to string values and back again. + /// </summary> + private static readonly Dictionary<Type, ValueMapping> converters = new Dictionary<Type, ValueMapping>(); + + /// <summary> + /// A map of instantiated custom encoders used to encode/decode message parts. + /// </summary> + private static readonly Dictionary<Type, IMessagePartEncoder> encoders = new Dictionary<Type, IMessagePartEncoder>(); + + /// <summary> + /// The string-object conversion routines to use for this individual message part. + /// </summary> + private ValueMapping converter; + + /// <summary> + /// The property that this message part is associated with, if aplicable. + /// </summary> + private PropertyInfo property; + + /// <summary> + /// The field that this message part is associated with, if aplicable. + /// </summary> + private FieldInfo field; + + /// <summary> + /// The type of the message part. (Not the type of the message itself). + /// </summary> + private Type memberDeclaredType; + + /// <summary> + /// The default (uninitialized) value of the member inherent in its type. + /// </summary> + private object defaultMemberValue; + + /// <summary> + /// Initializes static members of the <see cref="MessagePart"/> class. + /// </summary> + [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Justification = "This simplifies the rest of the code.")] + [SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase", Justification = "By design.")] + [SuppressMessage("Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline", Justification = "Much more efficient initialization when we can call methods.")] + static MessagePart() { + Func<string, Uri> safeUri = str => { + Contract.Assume(str != null); + return new Uri(str); + }; + Func<string, bool> safeBool = str => { + Contract.Assume(str != null); + return bool.Parse(str); + }; + + Func<byte[], string> safeFromByteArray = bytes => { + Contract.Assume(bytes != null); + return Convert.ToBase64String(bytes); + }; + Func<string, byte[]> safeToByteArray = str => { + Contract.Assume(str != null); + return Convert.FromBase64String(str); + }; + Map<Uri>(uri => uri.AbsoluteUri, uri => uri.OriginalString, safeUri); + Map<DateTime>(dt => XmlConvert.ToString(dt, XmlDateTimeSerializationMode.Utc), null, str => XmlConvert.ToDateTime(str, XmlDateTimeSerializationMode.Utc)); + Map<TimeSpan>(ts => ts.ToString(), null, str => TimeSpan.Parse(str)); + Map<byte[]>(safeFromByteArray, null, safeToByteArray); + Map<bool>(value => value.ToString().ToLowerInvariant(), null, safeBool); + Map<CultureInfo>(c => c.Name, null, str => new CultureInfo(str)); + Map<CultureInfo[]>(cs => string.Join(",", cs.Select(c => c.Name).ToArray()), null, str => str.Split(',').Select(s => new CultureInfo(s)).ToArray()); + Map<Type>(t => t.FullName, null, str => Type.GetType(str)); + } + + /// <summary> + /// Initializes a new instance of the <see cref="MessagePart"/> class. + /// </summary> + /// <param name="member"> + /// A property or field of an <see cref="IMessage"/> implementing type + /// that has a <see cref="MessagePartAttribute"/> attached to it. + /// </param> + /// <param name="attribute"> + /// The attribute discovered on <paramref name="member"/> that describes the + /// serialization requirements of the message part. + /// </param> + [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Justification = "Unavoidable"), SuppressMessage("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily", Justification = "Code contracts requires it.")] + internal MessagePart(MemberInfo member, MessagePartAttribute attribute) { + Requires.NotNull(member, "member"); + Requires.True(member is FieldInfo || member is PropertyInfo, "member"); + Requires.NotNull(attribute, "attribute"); + + this.field = member as FieldInfo; + this.property = member as PropertyInfo; + this.Name = attribute.Name ?? member.Name; + this.RequiredProtection = attribute.RequiredProtection; + this.IsRequired = attribute.IsRequired; + this.AllowEmpty = attribute.AllowEmpty; + this.memberDeclaredType = (this.field != null) ? this.field.FieldType : this.property.PropertyType; + this.defaultMemberValue = DeriveDefaultValue(this.memberDeclaredType); + + Contract.Assume(this.memberDeclaredType != null); // CC missing PropertyInfo.PropertyType ensures result != null + if (attribute.Encoder == null) { + if (!converters.TryGetValue(this.memberDeclaredType, out this.converter)) { + if (this.memberDeclaredType.IsGenericType && + this.memberDeclaredType.GetGenericTypeDefinition() == typeof(Nullable<>)) { + // It's a nullable type. Try again to look up an appropriate converter for the underlying type. + Type underlyingType = Nullable.GetUnderlyingType(this.memberDeclaredType); + ValueMapping underlyingMapping; + if (converters.TryGetValue(underlyingType, out underlyingMapping)) { + this.converter = new ValueMapping( + underlyingMapping.ValueToString, + null, + str => str != null ? underlyingMapping.StringToValue(str) : null); + } else { + this.converter = new ValueMapping( + obj => obj != null ? obj.ToString() : null, + null, + str => str != null ? Convert.ChangeType(str, underlyingType, CultureInfo.InvariantCulture) : null); + } + } else { + this.converter = new ValueMapping( + obj => obj != null ? obj.ToString() : null, + null, + str => str != null ? Convert.ChangeType(str, this.memberDeclaredType, CultureInfo.InvariantCulture) : null); + } + } + } else { + this.converter = new ValueMapping(GetEncoder(attribute.Encoder)); + } + + // readonly and const fields are considered legal, and "constants" for message transport. + FieldAttributes constAttributes = FieldAttributes.Static | FieldAttributes.Literal | FieldAttributes.HasDefault; + if (this.field != null && ( + (this.field.Attributes & FieldAttributes.InitOnly) == FieldAttributes.InitOnly || + (this.field.Attributes & constAttributes) == constAttributes)) { + this.IsConstantValue = true; + this.IsConstantValueAvailableStatically = this.field.IsStatic; + } else if (this.property != null && !this.property.CanWrite) { + this.IsConstantValue = true; + } + + // Validate a sane combination of settings + this.ValidateSettings(); + } + + /// <summary> + /// Gets or sets the name to use when serializing or deserializing this parameter in a message. + /// </summary> + internal string Name { get; set; } + + /// <summary> + /// Gets or sets whether this message part must be signed. + /// </summary> + internal ProtectionLevel RequiredProtection { get; set; } + + /// <summary> + /// Gets or sets a value indicating whether this message part is required for the + /// containing message to be valid. + /// </summary> + internal bool IsRequired { get; set; } + + /// <summary> + /// Gets or sets a value indicating whether the string value is allowed to be empty in the serialized message. + /// </summary> + internal bool AllowEmpty { get; set; } + + /// <summary> + /// Gets or sets a value indicating whether the field or property must remain its default value. + /// </summary> + internal bool IsConstantValue { get; set; } + + /// <summary> + /// Gets or sets a value indicating whether this part is defined as a constant field and can be read without a message instance. + /// </summary> + internal bool IsConstantValueAvailableStatically { get; set; } + + /// <summary> + /// Gets the static constant value for this message part without a message instance. + /// </summary> + internal string StaticConstantValue { + get { + Requires.ValidState(this.IsConstantValueAvailableStatically); + return this.ToString(this.field.GetValue(null), false); + } + } + + /// <summary> + /// Gets the type of the declared member. + /// </summary> + internal Type MemberDeclaredType { + get { return this.memberDeclaredType; } + } + + /// <summary> + /// Adds a pair of type conversion functions to the static conversion map. + /// </summary> + /// <typeparam name="T">The custom type to convert to and from strings.</typeparam> + /// <param name="toString">The function to convert the custom type to a string.</param> + /// <param name="toOriginalString">The mapping function that converts some custom value to its original (non-normalized) string. May be null if the same as the <paramref name="toString"/> function.</param> + /// <param name="toValue">The function to convert a string to the custom type.</param> + [SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", MessageId = "System.Diagnostics.Contracts.__ContractsRuntime.Requires<System.ArgumentNullException>(System.Boolean,System.String,System.String)", Justification = "Code contracts"), SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "toString", Justification = "Code contracts"), SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "toValue", Justification = "Code contracts")] + internal static void Map<T>(Func<T, string> toString, Func<T, string> toOriginalString, Func<string, T> toValue) { + Requires.NotNull(toString, "toString"); + Requires.NotNull(toValue, "toValue"); + + if (toOriginalString == null) { + toOriginalString = toString; + } + + Func<object, string> safeToString = obj => obj != null ? toString((T)obj) : null; + Func<object, string> safeToOriginalString = obj => obj != null ? toOriginalString((T)obj) : null; + Func<string, object> safeToT = str => str != null ? toValue(str) : default(T); + converters.Add(typeof(T), new ValueMapping(safeToString, safeToOriginalString, safeToT)); + } + + /// <summary> + /// Sets the member of a given message to some given value. + /// Used in deserialization. + /// </summary> + /// <param name="message">The message instance containing the member whose value should be set.</param> + /// <param name="value">The string representation of the value to set.</param> + internal void SetValue(IMessage message, string value) { + Requires.NotNull(message, "message"); + + try { + if (this.IsConstantValue) { + string constantValue = this.GetValue(message); + var caseSensitivity = DotNetOpenAuthSection.Messaging.Strict ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase; + if (!string.Equals(constantValue, value, caseSensitivity)) { + throw new ArgumentException(string.Format( + CultureInfo.CurrentCulture, + MessagingStrings.UnexpectedMessagePartValueForConstant, + message.GetType().Name, + this.Name, + constantValue, + value)); + } + } else { + this.SetValueAsObject(message, this.ToValue(value)); + } + } catch (Exception ex) { + throw ErrorUtilities.Wrap(ex, MessagingStrings.MessagePartReadFailure, message.GetType(), this.Name, value); + } + } + + /// <summary> + /// Gets the normalized form of a value of a member of a given message. + /// Used in serialization. + /// </summary> + /// <param name="message">The message instance to read the value from.</param> + /// <returns>The string representation of the member's value.</returns> + internal string GetValue(IMessage message) { + try { + object value = this.GetValueAsObject(message); + return this.ToString(value, false); + } catch (FormatException ex) { + throw ErrorUtilities.Wrap(ex, MessagingStrings.MessagePartWriteFailure, message.GetType(), this.Name); + } + } + + /// <summary> + /// Gets the value of a member of a given message. + /// Used in serialization. + /// </summary> + /// <param name="message">The message instance to read the value from.</param> + /// <param name="originalValue">A value indicating whether the original value should be retrieved (as opposed to a normalized form of it).</param> + /// <returns>The string representation of the member's value.</returns> + internal string GetValue(IMessage message, bool originalValue) { + try { + object value = this.GetValueAsObject(message); + return this.ToString(value, originalValue); + } catch (FormatException ex) { + throw ErrorUtilities.Wrap(ex, MessagingStrings.MessagePartWriteFailure, message.GetType(), this.Name); + } + } + + /// <summary> + /// Gets whether the value has been set to something other than its CLR type default value. + /// </summary> + /// <param name="message">The message instance to check the value on.</param> + /// <returns>True if the value is not the CLR default value.</returns> + internal bool IsNondefaultValueSet(IMessage message) { + if (this.memberDeclaredType.IsValueType) { + return !this.GetValueAsObject(message).Equals(this.defaultMemberValue); + } else { + return this.defaultMemberValue != this.GetValueAsObject(message); + } + } + + /// <summary> + /// Figures out the CLR default value for a given type. + /// </summary> + /// <param name="type">The type whose default value is being sought.</param> + /// <returns>Either null, or some default value like 0 or 0.0.</returns> + private static object DeriveDefaultValue(Type type) { + if (type.IsValueType) { + return Activator.CreateInstance(type); + } else { + return null; + } + } + + /// <summary> + /// Checks whether a type is a nullable value type (i.e. int?) + /// </summary> + /// <param name="type">The type in question.</param> + /// <returns>True if this is a nullable value type.</returns> + private static bool IsNonNullableValueType(Type type) { + if (!type.IsValueType) { + return false; + } + + if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>)) { + return false; + } + + return true; + } + + /// <summary> + /// Retrieves a previously instantiated encoder of a given type, or creates a new one and stores it for later retrieval as well. + /// </summary> + /// <param name="messagePartEncoder">The message part encoder type.</param> + /// <returns>An instance of the desired encoder.</returns> + private static IMessagePartEncoder GetEncoder(Type messagePartEncoder) { + Requires.NotNull(messagePartEncoder, "messagePartEncoder"); + Contract.Ensures(Contract.Result<IMessagePartEncoder>() != null); + + IMessagePartEncoder encoder; + if (!encoders.TryGetValue(messagePartEncoder, out encoder)) { + try { + encoder = encoders[messagePartEncoder] = (IMessagePartEncoder)Activator.CreateInstance(messagePartEncoder); + } catch (MissingMethodException ex) { + throw ErrorUtilities.Wrap(ex, MessagingStrings.EncoderInstantiationFailed, messagePartEncoder.FullName); + } + } + + return encoder; + } + + /// <summary> + /// Gets the value of the message part, without converting it to/from a string. + /// </summary> + /// <param name="message">The message instance to read from.</param> + /// <returns>The value of the member.</returns> + private object GetValueAsObject(IMessage message) { + if (this.property != null) { + return this.property.GetValue(message, null); + } else { + return this.field.GetValue(message); + } + } + + /// <summary> + /// Sets the value of a message part directly with a given value. + /// </summary> + /// <param name="message">The message instance to read from.</param> + /// <param name="value">The value to set on the this part.</param> + private void SetValueAsObject(IMessage message, object value) { + if (this.property != null) { + this.property.SetValue(message, value, null); + } else { + this.field.SetValue(message, value); + } + } + + /// <summary> + /// Converts a string representation of the member's value to the appropriate type. + /// </summary> + /// <param name="value">The string representation of the member's value.</param> + /// <returns> + /// An instance of the appropriate type for setting the member. + /// </returns> + private object ToValue(string value) { + return this.converter.StringToValue(value); + } + + /// <summary> + /// Converts the member's value to its string representation. + /// </summary> + /// <param name="value">The value of the member.</param> + /// <param name="originalString">A value indicating whether a string matching the originally decoded string should be returned (as opposed to a normalized string).</param> + /// <returns> + /// The string representation of the member's value. + /// </returns> + private string ToString(object value, bool originalString) { + return originalString ? this.converter.ValueToOriginalString(value) : this.converter.ValueToString(value); + } + + /// <summary> + /// Validates that the message part and its attribute have agreeable settings. + /// </summary> + /// <exception cref="ArgumentException"> + /// Thrown when a non-nullable value type is set as optional. + /// </exception> + private void ValidateSettings() { + if (!this.IsRequired && IsNonNullableValueType(this.memberDeclaredType)) { + MemberInfo member = (MemberInfo)this.field ?? this.property; + throw new ArgumentException( + string.Format( + CultureInfo.CurrentCulture, + "Invalid combination: {0} on message type {1} is a non-nullable value type but is marked as optional.", + member.Name, + member.DeclaringType)); + } + } + } +} diff --git a/src/DotNetOpenAuth.Core/Messaging/Reflection/ValueMapping.cs b/src/DotNetOpenAuth.Core/Messaging/Reflection/ValueMapping.cs new file mode 100644 index 0000000..9c0fa83 --- /dev/null +++ b/src/DotNetOpenAuth.Core/Messaging/Reflection/ValueMapping.cs @@ -0,0 +1,67 @@ +//----------------------------------------------------------------------- +// <copyright file="ValueMapping.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Messaging.Reflection { + using System; + using System.Diagnostics.Contracts; + + /// <summary> + /// A pair of conversion functions to map some type to a string and back again. + /// </summary> + [ContractVerification(true)] + internal struct ValueMapping { + /// <summary> + /// The mapping function that converts some custom type to a string. + /// </summary> + internal readonly Func<object, string> ValueToString; + + /// <summary> + /// The mapping function that converts some custom type to the original string + /// (possibly non-normalized) that represents it. + /// </summary> + internal readonly Func<object, string> ValueToOriginalString; + + /// <summary> + /// The mapping function that converts a string to some custom type. + /// </summary> + internal readonly Func<string, object> StringToValue; + + /// <summary> + /// Initializes a new instance of the <see cref="ValueMapping"/> struct. + /// </summary> + /// <param name="toString">The mapping function that converts some custom value to a string.</param> + /// <param name="toOriginalString">The mapping function that converts some custom value to its original (non-normalized) string. May be null if the same as the <paramref name="toString"/> function.</param> + /// <param name="toValue">The mapping function that converts a string to some custom value.</param> + internal ValueMapping(Func<object, string> toString, Func<object, string> toOriginalString, Func<string, object> toValue) { + Requires.NotNull(toString, "toString"); + Requires.NotNull(toValue, "toValue"); + + this.ValueToString = toString; + this.ValueToOriginalString = toOriginalString ?? toString; + this.StringToValue = toValue; + } + + /// <summary> + /// Initializes a new instance of the <see cref="ValueMapping"/> struct. + /// </summary> + /// <param name="encoder">The encoder.</param> + internal ValueMapping(IMessagePartEncoder encoder) { + Requires.NotNull(encoder, "encoder"); + var nullEncoder = encoder as IMessagePartNullEncoder; + string nullString = nullEncoder != null ? nullEncoder.EncodedNullValue : null; + + var originalStringEncoder = encoder as IMessagePartOriginalEncoder; + Func<object, string> originalStringEncode = encoder.Encode; + if (originalStringEncoder != null) { + originalStringEncode = originalStringEncoder.EncodeAsOriginalString; + } + + this.ValueToString = obj => (obj != null) ? encoder.Encode(obj) : nullString; + this.StringToValue = str => (str != null) ? encoder.Decode(str) : null; + this.ValueToOriginalString = obj => (obj != null) ? originalStringEncode(obj) : nullString; + } + } +} diff --git a/src/DotNetOpenAuth.Core/Messaging/StandardMessageFactory.cs b/src/DotNetOpenAuth.Core/Messaging/StandardMessageFactory.cs new file mode 100644 index 0000000..5db206e --- /dev/null +++ b/src/DotNetOpenAuth.Core/Messaging/StandardMessageFactory.cs @@ -0,0 +1,298 @@ +//----------------------------------------------------------------------- +// <copyright file="StandardMessageFactory.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Messaging { + using System; + using System.Collections.Generic; + using System.Diagnostics.Contracts; + using System.Linq; + using System.Reflection; + using System.Text; + using DotNetOpenAuth.Messaging.Reflection; + + /// <summary> + /// A message factory that automatically selects the message type based on the incoming data. + /// </summary> + internal class StandardMessageFactory : IMessageFactory { + /// <summary> + /// The request message types and their constructors to use for instantiating the messages. + /// </summary> + private readonly Dictionary<MessageDescription, ConstructorInfo> requestMessageTypes = new Dictionary<MessageDescription, ConstructorInfo>(); + + /// <summary> + /// The response message types and their constructors to use for instantiating the messages. + /// </summary> + /// <value> + /// The value is a dictionary, whose key is the type of the constructor's lone parameter. + /// </value> + private readonly Dictionary<MessageDescription, Dictionary<Type, ConstructorInfo>> responseMessageTypes = new Dictionary<MessageDescription, Dictionary<Type, ConstructorInfo>>(); + + /// <summary> + /// Initializes a new instance of the <see cref="StandardMessageFactory"/> class. + /// </summary> + internal StandardMessageFactory() { + } + + /// <summary> + /// Adds message types to the set that this factory can create. + /// </summary> + /// <param name="messageTypes">The message types that this factory may instantiate.</param> + public virtual void AddMessageTypes(IEnumerable<MessageDescription> messageTypes) { + Requires.NotNull(messageTypes, "messageTypes"); + Requires.True(messageTypes.All(msg => msg != null), "messageTypes"); + + var unsupportedMessageTypes = new List<MessageDescription>(0); + foreach (MessageDescription messageDescription in messageTypes) { + bool supportedMessageType = false; + + // First see whether this message fits the recognized pattern for request messages. + if (typeof(IDirectedProtocolMessage).IsAssignableFrom(messageDescription.MessageType)) { + foreach (ConstructorInfo ctor in messageDescription.Constructors) { + ParameterInfo[] parameters = ctor.GetParameters(); + if (parameters.Length == 2 && parameters[0].ParameterType == typeof(Uri) && parameters[1].ParameterType == typeof(Version)) { + supportedMessageType = true; + this.requestMessageTypes.Add(messageDescription, ctor); + break; + } + } + } + + // Also see if this message fits the recognized pattern for response messages. + if (typeof(IDirectResponseProtocolMessage).IsAssignableFrom(messageDescription.MessageType)) { + var responseCtors = new Dictionary<Type, ConstructorInfo>(messageDescription.Constructors.Length); + foreach (ConstructorInfo ctor in messageDescription.Constructors) { + ParameterInfo[] parameters = ctor.GetParameters(); + if (parameters.Length == 1 && typeof(IDirectedProtocolMessage).IsAssignableFrom(parameters[0].ParameterType)) { + responseCtors.Add(parameters[0].ParameterType, ctor); + } + } + + if (responseCtors.Count > 0) { + supportedMessageType = true; + this.responseMessageTypes.Add(messageDescription, responseCtors); + } + } + + if (!supportedMessageType) { + unsupportedMessageTypes.Add(messageDescription); + } + } + + ErrorUtilities.VerifySupported( + !unsupportedMessageTypes.Any(), + MessagingStrings.StandardMessageFactoryUnsupportedMessageType, + unsupportedMessageTypes.ToStringDeferred()); + } + + #region IMessageFactory Members + + /// <summary> + /// Analyzes an incoming request message payload to discover what kind of + /// message is embedded in it and returns the type, or null if no match is found. + /// </summary> + /// <param name="recipient">The intended or actual recipient of the request message.</param> + /// <param name="fields">The name/value pairs that make up the message payload.</param> + /// <returns> + /// A newly instantiated <see cref="IProtocolMessage"/>-derived object that this message can + /// deserialize to. Null if the request isn't recognized as a valid protocol message. + /// </returns> + public virtual IDirectedProtocolMessage GetNewRequestMessage(MessageReceivingEndpoint recipient, IDictionary<string, string> fields) { + MessageDescription matchingType = this.GetMessageDescription(recipient, fields); + if (matchingType != null) { + return this.InstantiateAsRequest(matchingType, recipient); + } else { + return null; + } + } + + /// <summary> + /// Analyzes an incoming request message payload to discover what kind of + /// message is embedded in it and returns the type, or null if no match is found. + /// </summary> + /// <param name="request">The message that was sent as a request that resulted in the response.</param> + /// <param name="fields">The name/value pairs that make up the message payload.</param> + /// <returns> + /// A newly instantiated <see cref="IProtocolMessage"/>-derived object that this message can + /// deserialize to. Null if the request isn't recognized as a valid protocol message. + /// </returns> + public virtual IDirectResponseProtocolMessage GetNewResponseMessage(IDirectedProtocolMessage request, IDictionary<string, string> fields) { + MessageDescription matchingType = this.GetMessageDescription(request, fields); + if (matchingType != null) { + return this.InstantiateAsResponse(matchingType, request); + } else { + return null; + } + } + + #endregion + + /// <summary> + /// Gets the message type that best fits the given incoming request data. + /// </summary> + /// <param name="recipient">The recipient of the incoming data. Typically not used, but included just in case.</param> + /// <param name="fields">The data of the incoming message.</param> + /// <returns> + /// The message type that matches the incoming data; or <c>null</c> if no match. + /// </returns> + /// <exception cref="ProtocolException">May be thrown if the incoming data is ambiguous.</exception> + protected virtual MessageDescription GetMessageDescription(MessageReceivingEndpoint recipient, IDictionary<string, string> fields) { + Requires.NotNull(recipient, "recipient"); + Requires.NotNull(fields, "fields"); + + var matches = this.requestMessageTypes.Keys + .Where(message => message.CheckMessagePartsPassBasicValidation(fields)) + .OrderByDescending(message => CountInCommon(message.Mapping.Keys, fields.Keys)) + .ThenByDescending(message => message.Mapping.Count) + .CacheGeneratedResults(); + var match = matches.FirstOrDefault(); + if (match != null) { + if (Logger.Messaging.IsWarnEnabled && matches.Count() > 1) { + Logger.Messaging.WarnFormat( + "Multiple message types seemed to fit the incoming data: {0}", + matches.ToStringDeferred()); + } + + return match; + } else { + // No message type matches the incoming data. + return null; + } + } + + /// <summary> + /// Gets the message type that best fits the given incoming direct response data. + /// </summary> + /// <param name="request">The request message that prompted the response data.</param> + /// <param name="fields">The data of the incoming message.</param> + /// <returns> + /// The message type that matches the incoming data; or <c>null</c> if no match. + /// </returns> + /// <exception cref="ProtocolException">May be thrown if the incoming data is ambiguous.</exception> + protected virtual MessageDescription GetMessageDescription(IDirectedProtocolMessage request, IDictionary<string, string> fields) { + Requires.NotNull(request, "request"); + Requires.NotNull(fields, "fields"); + + var matches = (from responseMessageType in this.responseMessageTypes + let message = responseMessageType.Key + where message.CheckMessagePartsPassBasicValidation(fields) + let ctors = this.FindMatchingResponseConstructors(message, request.GetType()) + where ctors.Any() + orderby GetDerivationDistance(ctors.First().GetParameters()[0].ParameterType, request.GetType()), + CountInCommon(message.Mapping.Keys, fields.Keys) descending, + message.Mapping.Count descending + select message).CacheGeneratedResults(); + var match = matches.FirstOrDefault(); + if (match != null) { + if (Logger.Messaging.IsWarnEnabled && matches.Count() > 1) { + Logger.Messaging.WarnFormat( + "Multiple message types seemed to fit the incoming data: {0}", + matches.ToStringDeferred()); + } + + return match; + } else { + // No message type matches the incoming data. + return null; + } + } + + /// <summary> + /// Instantiates the given request message type. + /// </summary> + /// <param name="messageDescription">The message description.</param> + /// <param name="recipient">The recipient.</param> + /// <returns>The instantiated message. Never null.</returns> + protected virtual IDirectedProtocolMessage InstantiateAsRequest(MessageDescription messageDescription, MessageReceivingEndpoint recipient) { + Requires.NotNull(messageDescription, "messageDescription"); + Requires.NotNull(recipient, "recipient"); + Contract.Ensures(Contract.Result<IDirectedProtocolMessage>() != null); + + ConstructorInfo ctor = this.requestMessageTypes[messageDescription]; + return (IDirectedProtocolMessage)ctor.Invoke(new object[] { recipient.Location, messageDescription.MessageVersion }); + } + + /// <summary> + /// Instantiates the given request message type. + /// </summary> + /// <param name="messageDescription">The message description.</param> + /// <param name="request">The request that resulted in this response.</param> + /// <returns>The instantiated message. Never null.</returns> + protected virtual IDirectResponseProtocolMessage InstantiateAsResponse(MessageDescription messageDescription, IDirectedProtocolMessage request) { + Requires.NotNull(messageDescription, "messageDescription"); + Requires.NotNull(request, "request"); + Contract.Ensures(Contract.Result<IDirectResponseProtocolMessage>() != null); + + Type requestType = request.GetType(); + var ctors = this.FindMatchingResponseConstructors(messageDescription, requestType); + ConstructorInfo ctor = null; + try { + ctor = ctors.Single(); + } catch (InvalidOperationException) { + if (ctors.Any()) { + ErrorUtilities.ThrowInternal("More than one matching constructor for request type " + requestType.Name + " and response type " + messageDescription.MessageType.Name); + } else { + ErrorUtilities.ThrowInternal("Unexpected request message type " + requestType.FullName + " for response type " + messageDescription.MessageType.Name); + } + } + return (IDirectResponseProtocolMessage)ctor.Invoke(new object[] { request }); + } + + /// <summary> + /// Gets the hierarchical distance between a type and a type it derives from or implements. + /// </summary> + /// <param name="assignableType">The base type or interface.</param> + /// <param name="derivedType">The concrete class that implements the <paramref name="assignableType"/>.</param> + /// <returns>The distance between the two types. 0 if the types are equivalent, 1 if the type immediately derives from or implements the base type, or progressively higher integers.</returns> + private static int GetDerivationDistance(Type assignableType, Type derivedType) { + Requires.NotNull(assignableType, "assignableType"); + Requires.NotNull(derivedType, "derivedType"); + Requires.True(assignableType.IsAssignableFrom(derivedType), "assignableType"); + + // If this is the two types are equivalent... + if (derivedType.IsAssignableFrom(assignableType)) + { + return 0; + } + + int steps; + derivedType = derivedType.BaseType; + for (steps = 1; assignableType.IsAssignableFrom(derivedType); steps++) + { + derivedType = derivedType.BaseType; + } + + return steps; + } + + /// <summary> + /// Counts how many strings are in the intersection of two collections. + /// </summary> + /// <param name="collection1">The first collection.</param> + /// <param name="collection2">The second collection.</param> + /// <param name="comparison">The string comparison method to use.</param> + /// <returns>A non-negative integer no greater than the count of elements in the smallest collection.</returns> + private static int CountInCommon(ICollection<string> collection1, ICollection<string> collection2, StringComparison comparison = StringComparison.Ordinal) { + Requires.NotNull(collection1, "collection1"); + Requires.NotNull(collection2, "collection2"); + Contract.Ensures(Contract.Result<int>() >= 0 && Contract.Result<int>() <= Math.Min(collection1.Count, collection2.Count)); + + return collection1.Count(value1 => collection2.Any(value2 => string.Equals(value1, value2, comparison))); + } + + /// <summary> + /// Finds constructors for response messages that take a given request message type. + /// </summary> + /// <param name="messageDescription">The message description.</param> + /// <param name="requestType">Type of the request message.</param> + /// <returns>A sequence of matching constructors.</returns> + private IEnumerable<ConstructorInfo> FindMatchingResponseConstructors(MessageDescription messageDescription, Type requestType) { + Requires.NotNull(messageDescription, "messageDescription"); + Requires.NotNull(requestType, "requestType"); + + return this.responseMessageTypes[messageDescription].Where(pair => pair.Key.IsAssignableFrom(requestType)).Select(pair => pair.Value); + } + } +} diff --git a/src/DotNetOpenAuth.Core/Messaging/StandardMessageFactoryChannel.cs b/src/DotNetOpenAuth.Core/Messaging/StandardMessageFactoryChannel.cs new file mode 100644 index 0000000..acfc004 --- /dev/null +++ b/src/DotNetOpenAuth.Core/Messaging/StandardMessageFactoryChannel.cs @@ -0,0 +1,111 @@ +//----------------------------------------------------------------------- +// <copyright file="StandardMessageFactoryChannel.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Messaging { + using System; + using System.Collections.Generic; + using System.Diagnostics.Contracts; + using System.Linq; + using System.Text; + using Reflection; + + /// <summary> + /// A channel that uses the standard message factory. + /// </summary> + public abstract class StandardMessageFactoryChannel : Channel { + /// <summary> + /// The message types receivable by this channel. + /// </summary> + private readonly ICollection<Type> messageTypes; + + /// <summary> + /// The protocol versions supported by this channel. + /// </summary> + private readonly ICollection<Version> versions; + + /// <summary> + /// Initializes a new instance of the <see cref="StandardMessageFactoryChannel"/> class. + /// </summary> + /// <param name="messageTypes">The message types that might be encountered.</param> + /// <param name="versions">All the possible message versions that might be encountered.</param> + /// <param name="bindingElements">The binding elements to apply to the channel.</param> + protected StandardMessageFactoryChannel(ICollection<Type> messageTypes, ICollection<Version> versions, params IChannelBindingElement[] bindingElements) + : base(new StandardMessageFactory(), bindingElements) { + Requires.NotNull(messageTypes, "messageTypes"); + Requires.NotNull(versions, "versions"); + + this.messageTypes = messageTypes; + this.versions = versions; + this.StandardMessageFactory.AddMessageTypes(GetMessageDescriptions(this.messageTypes, this.versions, this.MessageDescriptions)); + } + + /// <summary> + /// Gets or sets a tool that can figure out what kind of message is being received + /// so it can be deserialized. + /// </summary> + internal StandardMessageFactory StandardMessageFactory { + get { return (Messaging.StandardMessageFactory)this.MessageFactory; } + set { this.MessageFactory = value; } + } + + /// <summary> + /// Gets or sets the message descriptions. + /// </summary> + internal sealed override MessageDescriptionCollection MessageDescriptions { + get { + return base.MessageDescriptions; + } + + set { + base.MessageDescriptions = value; + + // We must reinitialize the message factory so it can use the new message descriptions. + var factory = new StandardMessageFactory(); + factory.AddMessageTypes(GetMessageDescriptions(this.messageTypes, this.versions, value)); + this.MessageFactory = factory; + } + } + + /// <summary> + /// Gets or sets a tool that can figure out what kind of message is being received + /// so it can be deserialized. + /// </summary> + protected sealed override IMessageFactory MessageFactory { + get { + return (StandardMessageFactory)base.MessageFactory; + } + + set { + StandardMessageFactory newValue = (StandardMessageFactory)value; + base.MessageFactory = newValue; + } + } + + /// <summary> + /// Generates all the message descriptions for a given set of message types and versions. + /// </summary> + /// <param name="messageTypes">The message types.</param> + /// <param name="versions">The message versions.</param> + /// <param name="descriptionsCache">The cache to use when obtaining the message descriptions.</param> + /// <returns>The generated/retrieved message descriptions.</returns> + private static IEnumerable<MessageDescription> GetMessageDescriptions(ICollection<Type> messageTypes, ICollection<Version> versions, MessageDescriptionCollection descriptionsCache) + { + Requires.NotNull(messageTypes, "messageTypes"); + Requires.NotNull(descriptionsCache, "descriptionsCache"); + Contract.Ensures(Contract.Result<IEnumerable<MessageDescription>>() != null); + + // Get all the MessageDescription objects through the standard cache, + // so that perhaps it will be a quick lookup, or at least it will be + // stored there for a quick lookup later. + var messageDescriptions = new List<MessageDescription>(messageTypes.Count * versions.Count); + messageDescriptions.AddRange(from version in versions + from messageType in messageTypes + select descriptionsCache.Get(messageType, version)); + + return messageDescriptions; + } + } +} diff --git a/src/DotNetOpenAuth.Core/Messaging/StandardWebRequestHandler.cs b/src/DotNetOpenAuth.Core/Messaging/StandardWebRequestHandler.cs new file mode 100644 index 0000000..6c6a7bb --- /dev/null +++ b/src/DotNetOpenAuth.Core/Messaging/StandardWebRequestHandler.cs @@ -0,0 +1,249 @@ +//----------------------------------------------------------------------- +// <copyright file="StandardWebRequestHandler.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Messaging { + using System; + using System.Diagnostics; + using System.Diagnostics.Contracts; + using System.IO; + using System.Net; + using System.Net.Sockets; + using System.Reflection; + using DotNetOpenAuth.Messaging; + + /// <summary> + /// The default handler for transmitting <see cref="HttpWebRequest"/> instances + /// and returning the responses. + /// </summary> + public class StandardWebRequestHandler : IDirectWebRequestHandler { + /// <summary> + /// The set of options this web request handler supports. + /// </summary> + private const DirectWebRequestOptions SupportedOptions = DirectWebRequestOptions.AcceptAllHttpResponses; + + /// <summary> + /// The value to use for the User-Agent HTTP header. + /// </summary> + private static string userAgentValue = Assembly.GetExecutingAssembly().GetName().Name + "/" + Assembly.GetExecutingAssembly().GetName().Version; + + #region IWebRequestHandler Members + + /// <summary> + /// Determines whether this instance can support the specified options. + /// </summary> + /// <param name="options">The set of options that might be given in a subsequent web request.</param> + /// <returns> + /// <c>true</c> if this instance can support the specified options; otherwise, <c>false</c>. + /// </returns> + [Pure] + public bool CanSupport(DirectWebRequestOptions options) { + return (options & ~SupportedOptions) == 0; + } + + /// <summary> + /// Prepares an <see cref="HttpWebRequest"/> that contains an POST entity for sending the entity. + /// </summary> + /// <param name="request">The <see cref="HttpWebRequest"/> that should contain the entity.</param> + /// <returns> + /// The writer the caller should write out the entity data to. + /// </returns> + /// <exception cref="ProtocolException">Thrown for any network error.</exception> + /// <remarks> + /// <para>The caller should have set the <see cref="HttpWebRequest.ContentLength"/> + /// and any other appropriate properties <i>before</i> calling this method.</para> + /// <para>Implementations should catch <see cref="WebException"/> and wrap it in a + /// <see cref="ProtocolException"/> to abstract away the transport and provide + /// a single exception type for hosts to catch.</para> + /// </remarks> + public Stream GetRequestStream(HttpWebRequest request) { + return this.GetRequestStream(request, DirectWebRequestOptions.None); + } + + /// <summary> + /// Prepares an <see cref="HttpWebRequest"/> that contains an POST entity for sending the entity. + /// </summary> + /// <param name="request">The <see cref="HttpWebRequest"/> that should contain the entity.</param> + /// <param name="options">The options to apply to this web request.</param> + /// <returns> + /// The writer the caller should write out the entity data to. + /// </returns> + /// <exception cref="ProtocolException">Thrown for any network error.</exception> + /// <remarks> + /// <para>The caller should have set the <see cref="HttpWebRequest.ContentLength"/> + /// and any other appropriate properties <i>before</i> calling this method.</para> + /// <para>Implementations should catch <see cref="WebException"/> and wrap it in a + /// <see cref="ProtocolException"/> to abstract away the transport and provide + /// a single exception type for hosts to catch.</para> + /// </remarks> + public Stream GetRequestStream(HttpWebRequest request, DirectWebRequestOptions options) { + return GetRequestStreamCore(request); + } + + /// <summary> + /// Processes an <see cref="HttpWebRequest"/> and converts the + /// <see cref="HttpWebResponse"/> to a <see cref="IncomingWebResponse"/> instance. + /// </summary> + /// <param name="request">The <see cref="HttpWebRequest"/> to handle.</param> + /// <returns> + /// An instance of <see cref="IncomingWebResponse"/> describing the response. + /// </returns> + /// <exception cref="ProtocolException">Thrown for any network error.</exception> + /// <remarks> + /// <para>Implementations should catch <see cref="WebException"/> and wrap it in a + /// <see cref="ProtocolException"/> to abstract away the transport and provide + /// a single exception type for hosts to catch. The <see cref="WebException.Response"/> + /// value, if set, should be Closed before throwing.</para> + /// </remarks> + public IncomingWebResponse GetResponse(HttpWebRequest request) { + return this.GetResponse(request, DirectWebRequestOptions.None); + } + + /// <summary> + /// Processes an <see cref="HttpWebRequest"/> and converts the + /// <see cref="HttpWebResponse"/> to a <see cref="IncomingWebResponse"/> instance. + /// </summary> + /// <param name="request">The <see cref="HttpWebRequest"/> to handle.</param> + /// <param name="options">The options to apply to this web request.</param> + /// <returns> + /// An instance of <see cref="IncomingWebResponse"/> describing the response. + /// </returns> + /// <exception cref="ProtocolException">Thrown for any network error.</exception> + /// <remarks> + /// <para>Implementations should catch <see cref="WebException"/> and wrap it in a + /// <see cref="ProtocolException"/> to abstract away the transport and provide + /// a single exception type for hosts to catch. The <see cref="WebException.Response"/> + /// value, if set, should be Closed before throwing.</para> + /// </remarks> + public IncomingWebResponse GetResponse(HttpWebRequest request, DirectWebRequestOptions options) { + // This request MAY have already been prepared by GetRequestStream, but + // we have no guarantee, so do it just to be safe. + PrepareRequest(request, false); + + try { + Logger.Http.DebugFormat("HTTP {0} {1}", request.Method, request.RequestUri); + HttpWebResponse response = (HttpWebResponse)request.GetResponse(); + return new NetworkDirectWebResponse(request.RequestUri, response); + } catch (WebException ex) { + HttpWebResponse response = (HttpWebResponse)ex.Response; + if (response != null && response.StatusCode == HttpStatusCode.ExpectationFailed && + request.ServicePoint.Expect100Continue) { + // Some OpenID servers doesn't understand the Expect header and send 417 error back. + // If this server just failed from that, alter the ServicePoint for this server + // so that we don't send that header again next time (whenever that is). + // "Expect: 100-Continue" HTTP header. (see Google Code Issue 72) + // We don't want to blindly set all ServicePoints to not use the Expect header + // as that would be a security hole allowing any visitor to a web site change + // the web site's global behavior when calling that host. + Logger.Http.InfoFormat("HTTP POST to {0} resulted in 417 Expectation Failed. Changing ServicePoint to not use Expect: Continue next time.", request.RequestUri); + request.ServicePoint.Expect100Continue = false; // TODO: investigate that CAS may throw here + + // An alternative to ServicePoint if we don't have permission to set that, + // but we'd have to set it BEFORE each request. + ////request.Expect = ""; + } + + if ((options & DirectWebRequestOptions.AcceptAllHttpResponses) != 0 && response != null && + response.StatusCode != HttpStatusCode.ExpectationFailed) { + Logger.Http.InfoFormat("The HTTP error code {0} {1} is being accepted because the {2} flag is set.", (int)response.StatusCode, response.StatusCode, DirectWebRequestOptions.AcceptAllHttpResponses); + return new NetworkDirectWebResponse(request.RequestUri, response); + } + + if (Logger.Http.IsErrorEnabled) { + if (response != null) { + using (var reader = new StreamReader(ex.Response.GetResponseStream())) { + Logger.Http.ErrorFormat("WebException from {0}: {1}{2}", ex.Response.ResponseUri, Environment.NewLine, reader.ReadToEnd()); + } + } else { + Logger.Http.ErrorFormat("WebException {1} from {0}, no response available.", request.RequestUri, ex.Status); + } + } + + // Be sure to close the response stream to conserve resources and avoid + // filling up all our incoming pipes and denying future requests. + // If in the future, some callers actually want to read this response + // we'll need to figure out how to reliably call Close on exception + // responses at all callers. + if (response != null) { + response.Close(); + } + + throw ErrorUtilities.Wrap(ex, MessagingStrings.ErrorInRequestReplyMessage); + } + } + + #endregion + + /// <summary> + /// Determines whether an exception was thrown because of the remote HTTP server returning HTTP 417 Expectation Failed. + /// </summary> + /// <param name="ex">The caught exception.</param> + /// <returns> + /// <c>true</c> if the failure was originally caused by a 417 Exceptation Failed error; otherwise, <c>false</c>. + /// </returns> + internal static bool IsExceptionFrom417ExpectationFailed(Exception ex) { + while (ex != null) { + WebException webEx = ex as WebException; + if (webEx != null) { + HttpWebResponse response = webEx.Response as HttpWebResponse; + if (response != null) { + if (response.StatusCode == HttpStatusCode.ExpectationFailed) { + return true; + } + } + } + + ex = ex.InnerException; + } + + return false; + } + + /// <summary> + /// Initiates a POST request and prepares for sending data. + /// </summary> + /// <param name="request">The HTTP request with information about the remote party to contact.</param> + /// <returns> + /// The stream where the POST entity can be written. + /// </returns> + private static Stream GetRequestStreamCore(HttpWebRequest request) { + PrepareRequest(request, true); + + try { + return request.GetRequestStream(); + } catch (SocketException ex) { + throw ErrorUtilities.Wrap(ex, MessagingStrings.WebRequestFailed, request.RequestUri); + } catch (WebException ex) { + throw ErrorUtilities.Wrap(ex, MessagingStrings.WebRequestFailed, request.RequestUri); + } + } + + /// <summary> + /// Prepares an HTTP request. + /// </summary> + /// <param name="request">The request.</param> + /// <param name="preparingPost"><c>true</c> if this is a POST request whose headers have not yet been sent out; <c>false</c> otherwise.</param> + private static void PrepareRequest(HttpWebRequest request, bool preparingPost) { + Requires.NotNull(request, "request"); + + // Be careful to not try to change the HTTP headers that have already gone out. + if (preparingPost || request.Method == "GET") { + // Set/override a few properties of the request to apply our policies for requests. + if (Debugger.IsAttached) { + // Since a debugger is attached, requests may be MUCH slower, + // so give ourselves huge timeouts. + request.ReadWriteTimeout = (int)TimeSpan.FromHours(1).TotalMilliseconds; + request.Timeout = (int)TimeSpan.FromHours(1).TotalMilliseconds; + } + + // Some sites, such as Technorati, return 403 Forbidden on identity + // pages unless a User-Agent header is included. + if (string.IsNullOrEmpty(request.UserAgent)) { + request.UserAgent = userAgentValue; + } + } + } + } +} diff --git a/src/DotNetOpenAuth/Messaging/TimespanSecondsEncoder.cs b/src/DotNetOpenAuth.Core/Messaging/TimespanSecondsEncoder.cs index b28e5a8..b28e5a8 100644 --- a/src/DotNetOpenAuth/Messaging/TimespanSecondsEncoder.cs +++ b/src/DotNetOpenAuth.Core/Messaging/TimespanSecondsEncoder.cs diff --git a/src/DotNetOpenAuth/Messaging/TimestampEncoder.cs b/src/DotNetOpenAuth.Core/Messaging/TimestampEncoder.cs index b83a426..b83a426 100644 --- a/src/DotNetOpenAuth/Messaging/TimestampEncoder.cs +++ b/src/DotNetOpenAuth.Core/Messaging/TimestampEncoder.cs diff --git a/src/DotNetOpenAuth/Messaging/UnprotectedMessageException.cs b/src/DotNetOpenAuth.Core/Messaging/UnprotectedMessageException.cs index 2f21184..2f21184 100644 --- a/src/DotNetOpenAuth/Messaging/UnprotectedMessageException.cs +++ b/src/DotNetOpenAuth.Core/Messaging/UnprotectedMessageException.cs diff --git a/src/DotNetOpenAuth.Core/Messaging/UntrustedWebRequestHandler.cs b/src/DotNetOpenAuth.Core/Messaging/UntrustedWebRequestHandler.cs new file mode 100644 index 0000000..2d94130 --- /dev/null +++ b/src/DotNetOpenAuth.Core/Messaging/UntrustedWebRequestHandler.cs @@ -0,0 +1,476 @@ +//----------------------------------------------------------------------- +// <copyright file="UntrustedWebRequestHandler.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Messaging { + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; + using System.Diagnostics.Contracts; + using System.Globalization; + using System.IO; + using System.Net; + using System.Net.Cache; + using System.Text.RegularExpressions; + using DotNetOpenAuth.Configuration; + using DotNetOpenAuth.Messaging; + + /// <summary> + /// A paranoid HTTP get/post request engine. It helps to protect against attacks from remote + /// server leaving dangling connections, sending too much data, causing requests against + /// internal servers, etc. + /// </summary> + /// <remarks> + /// Protections include: + /// * Conservative maximum time to receive the complete response. + /// * Only HTTP and HTTPS schemes are permitted. + /// * Internal IP address ranges are not permitted: 127.*.*.*, 1::* + /// * Internal host names are not permitted (periods must be found in the host name) + /// If a particular host would be permitted but is in the blacklist, it is not allowed. + /// If a particular host would not be permitted but is in the whitelist, it is allowed. + /// </remarks> + public class UntrustedWebRequestHandler : IDirectWebRequestHandler { + /// <summary> + /// The set of URI schemes allowed in untrusted web requests. + /// </summary> + private ICollection<string> allowableSchemes = new List<string> { "http", "https" }; + + /// <summary> + /// The collection of blacklisted hosts. + /// </summary> + private ICollection<string> blacklistHosts = new List<string>(Configuration.BlacklistHosts.KeysAsStrings); + + /// <summary> + /// The collection of regular expressions used to identify additional blacklisted hosts. + /// </summary> + private ICollection<Regex> blacklistHostsRegex = new List<Regex>(Configuration.BlacklistHostsRegex.KeysAsRegexs); + + /// <summary> + /// The collection of whitelisted hosts. + /// </summary> + private ICollection<string> whitelistHosts = new List<string>(Configuration.WhitelistHosts.KeysAsStrings); + + /// <summary> + /// The collection of regular expressions used to identify additional whitelisted hosts. + /// </summary> + private ICollection<Regex> whitelistHostsRegex = new List<Regex>(Configuration.WhitelistHostsRegex.KeysAsRegexs); + + /// <summary> + /// The maximum redirections to follow in the course of a single request. + /// </summary> + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private int maximumRedirections = Configuration.MaximumRedirections; + + /// <summary> + /// The maximum number of bytes to read from the response of an untrusted server. + /// </summary> + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private int maximumBytesToRead = Configuration.MaximumBytesToRead; + + /// <summary> + /// The handler that will actually send the HTTP request and collect + /// the response once the untrusted server gates have been satisfied. + /// </summary> + private IDirectWebRequestHandler chainedWebRequestHandler; + + /// <summary> + /// Initializes a new instance of the <see cref="UntrustedWebRequestHandler"/> class. + /// </summary> + public UntrustedWebRequestHandler() + : this(new StandardWebRequestHandler()) { + } + + /// <summary> + /// Initializes a new instance of the <see cref="UntrustedWebRequestHandler"/> class. + /// </summary> + /// <param name="chainedWebRequestHandler">The chained web request handler.</param> + public UntrustedWebRequestHandler(IDirectWebRequestHandler chainedWebRequestHandler) { + Requires.NotNull(chainedWebRequestHandler, "chainedWebRequestHandler"); + + this.chainedWebRequestHandler = chainedWebRequestHandler; + if (Debugger.IsAttached) { + // Since a debugger is attached, requests may be MUCH slower, + // so give ourselves huge timeouts. + this.ReadWriteTimeout = TimeSpan.FromHours(1); + this.Timeout = TimeSpan.FromHours(1); + } else { + this.ReadWriteTimeout = Configuration.ReadWriteTimeout; + this.Timeout = Configuration.Timeout; + } + } + + /// <summary> + /// Gets or sets the default maximum bytes to read in any given HTTP request. + /// </summary> + /// <value>Default is 1MB. Cannot be less than 2KB.</value> + public int MaximumBytesToRead { + get { + return this.maximumBytesToRead; + } + + set { + Requires.InRange(value >= 2048, "value"); + this.maximumBytesToRead = value; + } + } + + /// <summary> + /// Gets or sets the total number of redirections to allow on any one request. + /// Default is 10. + /// </summary> + public int MaximumRedirections { + get { + return this.maximumRedirections; + } + + set { + Requires.InRange(value >= 0, "value"); + this.maximumRedirections = value; + } + } + + /// <summary> + /// Gets or sets the time allowed to wait for single read or write operation to complete. + /// Default is 500 milliseconds. + /// </summary> + public TimeSpan ReadWriteTimeout { get; set; } + + /// <summary> + /// Gets or sets the time allowed for an entire HTTP request. + /// Default is 5 seconds. + /// </summary> + public TimeSpan Timeout { get; set; } + + /// <summary> + /// Gets a collection of host name literals that should be allowed even if they don't + /// pass standard security checks. + /// </summary> + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Whitelist", Justification = "Spelling as intended.")] + public ICollection<string> WhitelistHosts { get { return this.whitelistHosts; } } + + /// <summary> + /// Gets a collection of host name regular expressions that indicate hosts that should + /// be allowed even though they don't pass standard security checks. + /// </summary> + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Whitelist", Justification = "Spelling as intended.")] + public ICollection<Regex> WhitelistHostsRegex { get { return this.whitelistHostsRegex; } } + + /// <summary> + /// Gets a collection of host name literals that should be rejected even if they + /// pass standard security checks. + /// </summary> + public ICollection<string> BlacklistHosts { get { return this.blacklistHosts; } } + + /// <summary> + /// Gets a collection of host name regular expressions that indicate hosts that should + /// be rejected even if they pass standard security checks. + /// </summary> + public ICollection<Regex> BlacklistHostsRegex { get { return this.blacklistHostsRegex; } } + + /// <summary> + /// Gets the configuration for this class that is specified in the host's .config file. + /// </summary> + private static UntrustedWebRequestElement Configuration { + get { return DotNetOpenAuthSection.Messaging.UntrustedWebRequest; } + } + + #region IDirectWebRequestHandler Members + + /// <summary> + /// Determines whether this instance can support the specified options. + /// </summary> + /// <param name="options">The set of options that might be given in a subsequent web request.</param> + /// <returns> + /// <c>true</c> if this instance can support the specified options; otherwise, <c>false</c>. + /// </returns> + [Pure] + public bool CanSupport(DirectWebRequestOptions options) { + // We support whatever our chained handler supports, plus RequireSsl. + return this.chainedWebRequestHandler.CanSupport(options & ~DirectWebRequestOptions.RequireSsl); + } + + /// <summary> + /// Prepares an <see cref="HttpWebRequest"/> that contains an POST entity for sending the entity. + /// </summary> + /// <param name="request">The <see cref="HttpWebRequest"/> that should contain the entity.</param> + /// <param name="options">The options to apply to this web request.</param> + /// <returns> + /// The writer the caller should write out the entity data to. + /// </returns> + /// <exception cref="ProtocolException">Thrown for any network error.</exception> + /// <remarks> + /// <para>The caller should have set the <see cref="HttpWebRequest.ContentLength"/> + /// and any other appropriate properties <i>before</i> calling this method.</para> + /// <para>Implementations should catch <see cref="WebException"/> and wrap it in a + /// <see cref="ProtocolException"/> to abstract away the transport and provide + /// a single exception type for hosts to catch.</para> + /// </remarks> + public Stream GetRequestStream(HttpWebRequest request, DirectWebRequestOptions options) { + this.EnsureAllowableRequestUri(request.RequestUri, (options & DirectWebRequestOptions.RequireSsl) != 0); + + this.PrepareRequest(request, true); + + // Submit the request and get the request stream back. + return this.chainedWebRequestHandler.GetRequestStream(request, options & ~DirectWebRequestOptions.RequireSsl); + } + + /// <summary> + /// Processes an <see cref="HttpWebRequest"/> and converts the + /// <see cref="HttpWebResponse"/> to a <see cref="IncomingWebResponse"/> instance. + /// </summary> + /// <param name="request">The <see cref="HttpWebRequest"/> to handle.</param> + /// <param name="options">The options to apply to this web request.</param> + /// <returns> + /// An instance of <see cref="CachedDirectWebResponse"/> describing the response. + /// </returns> + /// <exception cref="ProtocolException">Thrown for any network error.</exception> + /// <remarks> + /// <para>Implementations should catch <see cref="WebException"/> and wrap it in a + /// <see cref="ProtocolException"/> to abstract away the transport and provide + /// a single exception type for hosts to catch. The <see cref="WebException.Response"/> + /// value, if set, should be Closed before throwing.</para> + /// </remarks> + [SuppressMessage("Microsoft.Usage", "CA2234:PassSystemUriObjectsInsteadOfStrings", Justification = "Uri(Uri, string) accepts second arguments that Uri(Uri, new Uri(string)) does not that we must support.")] + public IncomingWebResponse GetResponse(HttpWebRequest request, DirectWebRequestOptions options) { + // This request MAY have already been prepared by GetRequestStream, but + // we have no guarantee, so do it just to be safe. + this.PrepareRequest(request, false); + + // Since we may require SSL for every redirect, we handle each redirect manually + // in order to detect and fail if any redirect sends us to an HTTP url. + // We COULD allow automatic redirect in the cases where HTTPS is not required, + // but our mock request infrastructure can't do redirects on its own either. + Uri originalRequestUri = request.RequestUri; + int i; + for (i = 0; i < this.MaximumRedirections; i++) { + this.EnsureAllowableRequestUri(request.RequestUri, (options & DirectWebRequestOptions.RequireSsl) != 0); + CachedDirectWebResponse response = this.chainedWebRequestHandler.GetResponse(request, options & ~DirectWebRequestOptions.RequireSsl).GetSnapshot(this.MaximumBytesToRead); + if (response.Status == HttpStatusCode.MovedPermanently || + response.Status == HttpStatusCode.Redirect || + response.Status == HttpStatusCode.RedirectMethod || + response.Status == HttpStatusCode.RedirectKeepVerb) { + // We have no copy of the post entity stream to repeat on our manually + // cloned HttpWebRequest, so we have to bail. + ErrorUtilities.VerifyProtocol(request.Method != "POST", MessagingStrings.UntrustedRedirectsOnPOSTNotSupported); + Uri redirectUri = new Uri(response.FinalUri, response.Headers[HttpResponseHeader.Location]); + request = request.Clone(redirectUri); + } else { + if (response.FinalUri != request.RequestUri) { + // Since we don't automatically follow redirects, there's only one scenario where this + // can happen: when the server sends a (non-redirecting) Content-Location header in the response. + // It's imperative that we do not trust that header though, so coerce the FinalUri to be + // what we just requested. + Logger.Http.WarnFormat("The response from {0} included an HTTP header indicating it's the same as {1}, but it's not a redirect so we won't trust that.", request.RequestUri, response.FinalUri); + response.FinalUri = request.RequestUri; + } + + return response; + } + } + + throw ErrorUtilities.ThrowProtocol(MessagingStrings.TooManyRedirects, originalRequestUri); + } + + /// <summary> + /// Prepares an <see cref="HttpWebRequest"/> that contains an POST entity for sending the entity. + /// </summary> + /// <param name="request">The <see cref="HttpWebRequest"/> that should contain the entity.</param> + /// <returns> + /// The writer the caller should write out the entity data to. + /// </returns> + Stream IDirectWebRequestHandler.GetRequestStream(HttpWebRequest request) { + return this.GetRequestStream(request, DirectWebRequestOptions.None); + } + + /// <summary> + /// Processes an <see cref="HttpWebRequest"/> and converts the + /// <see cref="HttpWebResponse"/> to a <see cref="IncomingWebResponse"/> instance. + /// </summary> + /// <param name="request">The <see cref="HttpWebRequest"/> to handle.</param> + /// <returns> + /// An instance of <see cref="IncomingWebResponse"/> describing the response. + /// </returns> + /// <exception cref="ProtocolException">Thrown for any network error.</exception> + /// <remarks> + /// <para>Implementations should catch <see cref="WebException"/> and wrap it in a + /// <see cref="ProtocolException"/> to abstract away the transport and provide + /// a single exception type for hosts to catch. The <see cref="WebException.Response"/> + /// value, if set, should be Closed before throwing.</para> + /// </remarks> + IncomingWebResponse IDirectWebRequestHandler.GetResponse(HttpWebRequest request) { + return this.GetResponse(request, DirectWebRequestOptions.None); + } + + #endregion + + /// <summary> + /// Determines whether an IP address is the IPv6 equivalent of "localhost/127.0.0.1". + /// </summary> + /// <param name="ip">The ip address to check.</param> + /// <returns> + /// <c>true</c> if this is a loopback IP address; <c>false</c> otherwise. + /// </returns> + private static bool IsIPv6Loopback(IPAddress ip) { + Requires.NotNull(ip, "ip"); + byte[] addressBytes = ip.GetAddressBytes(); + for (int i = 0; i < addressBytes.Length - 1; i++) { + if (addressBytes[i] != 0) { + return false; + } + } + if (addressBytes[addressBytes.Length - 1] != 1) { + return false; + } + return true; + } + + /// <summary> + /// Determines whether the given host name is in a host list or host name regex list. + /// </summary> + /// <param name="host">The host name.</param> + /// <param name="stringList">The list of host names.</param> + /// <param name="regexList">The list of regex patterns of host names.</param> + /// <returns> + /// <c>true</c> if the specified host falls within at least one of the given lists; otherwise, <c>false</c>. + /// </returns> + private static bool IsHostInList(string host, ICollection<string> stringList, ICollection<Regex> regexList) { + Requires.NotNullOrEmpty(host, "host"); + Requires.NotNull(stringList, "stringList"); + Requires.NotNull(regexList, "regexList"); + foreach (string testHost in stringList) { + if (string.Equals(host, testHost, StringComparison.OrdinalIgnoreCase)) { + return true; + } + } + foreach (Regex regex in regexList) { + if (regex.IsMatch(host)) { + return true; + } + } + return false; + } + + /// <summary> + /// Determines whether a given host is whitelisted. + /// </summary> + /// <param name="host">The host name to test.</param> + /// <returns> + /// <c>true</c> if the host is whitelisted; otherwise, <c>false</c>. + /// </returns> + private bool IsHostWhitelisted(string host) { + return IsHostInList(host, this.WhitelistHosts, this.WhitelistHostsRegex); + } + + /// <summary> + /// Determines whether a given host is blacklisted. + /// </summary> + /// <param name="host">The host name to test.</param> + /// <returns> + /// <c>true</c> if the host is blacklisted; otherwise, <c>false</c>. + /// </returns> + private bool IsHostBlacklisted(string host) { + return IsHostInList(host, this.BlacklistHosts, this.BlacklistHostsRegex); + } + + /// <summary> + /// Verify that the request qualifies under our security policies + /// </summary> + /// <param name="requestUri">The request URI.</param> + /// <param name="requireSsl">If set to <c>true</c>, only web requests that can be made entirely over SSL will succeed.</param> + /// <exception cref="ProtocolException">Thrown when the URI is disallowed for security reasons.</exception> + private void EnsureAllowableRequestUri(Uri requestUri, bool requireSsl) { + ErrorUtilities.VerifyProtocol(this.IsUriAllowable(requestUri), MessagingStrings.UnsafeWebRequestDetected, requestUri); + ErrorUtilities.VerifyProtocol(!requireSsl || String.Equals(requestUri.Scheme, Uri.UriSchemeHttps, StringComparison.OrdinalIgnoreCase), MessagingStrings.InsecureWebRequestWithSslRequired, requestUri); + } + + /// <summary> + /// Determines whether a URI is allowed based on scheme and host name. + /// No requireSSL check is done here + /// </summary> + /// <param name="uri">The URI to test for whether it should be allowed.</param> + /// <returns> + /// <c>true</c> if [is URI allowable] [the specified URI]; otherwise, <c>false</c>. + /// </returns> + private bool IsUriAllowable(Uri uri) { + Requires.NotNull(uri, "uri"); + if (!this.allowableSchemes.Contains(uri.Scheme)) { + Logger.Http.WarnFormat("Rejecting URL {0} because it uses a disallowed scheme.", uri); + return false; + } + + // Allow for whitelist or blacklist to override our detection. + Func<string, bool> failsUnlessWhitelisted = (string reason) => { + if (IsHostWhitelisted(uri.DnsSafeHost)) { + return true; + } + Logger.Http.WarnFormat("Rejecting URL {0} because {1}.", uri, reason); + return false; + }; + + // Try to interpret the hostname as an IP address so we can test for internal + // IP address ranges. Note that IP addresses can appear in many forms + // (e.g. http://127.0.0.1, http://2130706433, http://0x0100007f, http://::1 + // So we convert them to a canonical IPAddress instance, and test for all + // non-routable IP ranges: 10.*.*.*, 127.*.*.*, ::1 + // Note that Uri.IsLoopback is very unreliable, not catching many of these variants. + IPAddress hostIPAddress; + if (IPAddress.TryParse(uri.DnsSafeHost, out hostIPAddress)) { + byte[] addressBytes = hostIPAddress.GetAddressBytes(); + + // The host is actually an IP address. + switch (hostIPAddress.AddressFamily) { + case System.Net.Sockets.AddressFamily.InterNetwork: + if (addressBytes[0] == 127 || addressBytes[0] == 10) { + return failsUnlessWhitelisted("it is a loopback address."); + } + break; + case System.Net.Sockets.AddressFamily.InterNetworkV6: + if (IsIPv6Loopback(hostIPAddress)) { + return failsUnlessWhitelisted("it is a loopback address."); + } + break; + default: + return failsUnlessWhitelisted("it does not use an IPv4 or IPv6 address."); + } + } else { + // The host is given by name. We require names to contain periods to + // help make sure it's not an internal address. + if (!uri.Host.Contains(".")) { + return failsUnlessWhitelisted("it does not contain a period in the host name."); + } + } + if (this.IsHostBlacklisted(uri.DnsSafeHost)) { + Logger.Http.WarnFormat("Rejected URL {0} because it is blacklisted.", uri); + return false; + } + return true; + } + + /// <summary> + /// Prepares the request by setting timeout and redirect policies. + /// </summary> + /// <param name="request">The request to prepare.</param> + /// <param name="preparingPost"><c>true</c> if this is a POST request whose headers have not yet been sent out; <c>false</c> otherwise.</param> + private void PrepareRequest(HttpWebRequest request, bool preparingPost) { + Requires.NotNull(request, "request"); + + // Be careful to not try to change the HTTP headers that have already gone out. + if (preparingPost || request.Method == "GET") { + // Set/override a few properties of the request to apply our policies for untrusted requests. + request.ReadWriteTimeout = (int)this.ReadWriteTimeout.TotalMilliseconds; + request.Timeout = (int)this.Timeout.TotalMilliseconds; + request.KeepAlive = false; + } + + // If SSL is required throughout, we cannot allow auto redirects because + // it may include a pass through an unprotected HTTP request. + // We have to follow redirects manually. + // It also allows us to ignore HttpWebResponse.FinalUri since that can be affected by + // the Content-Location header and open security holes. + request.AllowAutoRedirect = false; + } + } +} diff --git a/src/DotNetOpenAuth.Core/Messaging/UriStyleMessageFormatter.cs b/src/DotNetOpenAuth.Core/Messaging/UriStyleMessageFormatter.cs new file mode 100644 index 0000000..2c653d0 --- /dev/null +++ b/src/DotNetOpenAuth.Core/Messaging/UriStyleMessageFormatter.cs @@ -0,0 +1,77 @@ +//----------------------------------------------------------------------- +// <copyright file="UriStyleMessageFormatter.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Messaging { + using System; + using System.Collections.Generic; + using System.Diagnostics.Contracts; + using System.Linq; + using System.Security.Cryptography; + using System.Text; + using System.Web; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.Messaging.Bindings; + using DotNetOpenAuth.Messaging.Reflection; + + /// <summary> + /// A serializer for <see cref="DataBag"/>-derived types + /// </summary> + /// <typeparam name="T">The DataBag-derived type that is to be serialized/deserialized.</typeparam> + internal class UriStyleMessageFormatter<T> : DataBagFormatterBase<T> where T : DataBag, new() { + /// <summary> + /// Initializes a new instance of the <see cref="UriStyleMessageFormatter<T>"/> class. + /// </summary> + /// <param name="signingKey">The crypto service provider with the asymmetric key to use for signing or verifying the token.</param> + /// <param name="encryptingKey">The crypto service provider with the asymmetric key to use for encrypting or decrypting the token.</param> + /// <param name="compressed">A value indicating whether the data in this instance will be GZip'd.</param> + /// <param name="maximumAge">The maximum age of a token that can be decoded; useful only when <paramref name="decodeOnceOnly"/> is <c>true</c>.</param> + /// <param name="decodeOnceOnly">The nonce store to use to ensure that this instance is only decoded once.</param> + protected internal UriStyleMessageFormatter(RSACryptoServiceProvider signingKey = null, RSACryptoServiceProvider encryptingKey = null, bool compressed = false, TimeSpan? maximumAge = null, INonceStore decodeOnceOnly = null) + : base(signingKey, encryptingKey, compressed, maximumAge, decodeOnceOnly) { + } + + /// <summary> + /// Initializes a new instance of the <see cref="UriStyleMessageFormatter<T>"/> class. + /// </summary> + /// <param name="cryptoKeyStore">The crypto key store used when signing or encrypting.</param> + /// <param name="bucket">The bucket in which symmetric keys are stored for signing/encrypting data.</param> + /// <param name="signed">A value indicating whether the data in this instance will be protected against tampering.</param> + /// <param name="encrypted">A value indicating whether the data in this instance will be protected against eavesdropping.</param> + /// <param name="compressed">A value indicating whether the data in this instance will be GZip'd.</param> + /// <param name="minimumAge">The minimum age.</param> + /// <param name="maximumAge">The maximum age of a token that can be decoded; useful only when <paramref name="decodeOnceOnly"/> is <c>true</c>.</param> + /// <param name="decodeOnceOnly">The nonce store to use to ensure that this instance is only decoded once.</param> + protected internal UriStyleMessageFormatter(ICryptoKeyStore cryptoKeyStore = null, string bucket = null, bool signed = false, bool encrypted = false, bool compressed = false, TimeSpan? minimumAge = null, TimeSpan? maximumAge = null, INonceStore decodeOnceOnly = null) + : base(cryptoKeyStore, bucket, signed, encrypted, compressed, minimumAge, maximumAge, decodeOnceOnly) { + Requires.True((cryptoKeyStore != null && !String.IsNullOrEmpty(bucket)) || (!signed && !encrypted), null); + } + + /// <summary> + /// Serializes the <see cref="DataBag"/> instance to a buffer. + /// </summary> + /// <param name="message">The message.</param> + /// <returns>The buffer containing the serialized data.</returns> + protected override byte[] SerializeCore(T message) { + var fields = MessageSerializer.Get(message.GetType()).Serialize(MessageDescriptions.GetAccessor(message)); + string value = MessagingUtilities.CreateQueryString(fields); + return Encoding.UTF8.GetBytes(value); + } + + /// <summary> + /// Deserializes the <see cref="DataBag"/> instance from a buffer. + /// </summary> + /// <param name="message">The message instance to initialize with data from the buffer.</param> + /// <param name="data">The data buffer.</param> + protected override void DeserializeCore(T message, byte[] data) { + string value = Encoding.UTF8.GetString(data); + + // Deserialize into message newly created instance. + var serializer = MessageSerializer.Get(message.GetType()); + var fields = MessageDescriptions.GetAccessor(message); + serializer.Deserialize(HttpUtility.ParseQueryString(value).ToDictionary(), fields); + } + } +} diff --git a/src/DotNetOpenAuth/Migrated rules for DotNetOpenAuth.ruleset b/src/DotNetOpenAuth.Core/Migrated rules for DotNetOpenAuth.ruleset index 0ba4e6e..0ba4e6e 100644 --- a/src/DotNetOpenAuth/Migrated rules for DotNetOpenAuth.ruleset +++ b/src/DotNetOpenAuth.Core/Migrated rules for DotNetOpenAuth.ruleset diff --git a/src/DotNetOpenAuth.Core/Properties/AssemblyInfo.cs b/src/DotNetOpenAuth.Core/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..205d331 --- /dev/null +++ b/src/DotNetOpenAuth.Core/Properties/AssemblyInfo.cs @@ -0,0 +1,88 @@ +//----------------------------------------------------------------------- +// <copyright file="AssemblyInfo.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +// We DON'T put an AssemblyVersionAttribute in here because it is generated in the build. + +using System; +using System.Diagnostics.Contracts; +using System.Net; +using System.Reflection; +using System.Resources; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Security; +using System.Security.Permissions; +using System.Web.UI; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("DotNetOpenAuth")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("DotNetOpenAuth")] +[assembly: AssemblyCopyright("Copyright © 2008")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: NeutralResourcesLanguage("en-US")] +[assembly: CLSCompliant(true)] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("7d73990c-47c0-4256-9f20-a893add9e289")] + +[assembly: ContractVerification(true)] + +#if StrongNameSigned +// See comment at top of this file. We need this so that strong-naming doesn't +// keep this assembly from being useful to shared host (medium trust) web sites. +[assembly: AllowPartiallyTrustedCallers] + +[assembly: InternalsVisibleTo("DotNetOpenAuth.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100AD093C3765257C89A7010E853F2C7C741FF92FA8ACE06D7B8254702CAD5CF99104447F63AB05F8BB6F51CE0D81C8C93D2FCE8C20AAFF7042E721CBA16EAAE98778611DED11C0ABC8900DC5667F99B50A9DADEC24DBD8F2C91E3E8AD300EF64F1B4B9536CEB16FB440AF939F57624A9B486F867807C649AE4830EAB88C6C03998")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.Core.UI, PublicKey=0024000004800000940000000602000000240000525341310004000001000100AD093C3765257C89A7010E853F2C7C741FF92FA8ACE06D7B8254702CAD5CF99104447F63AB05F8BB6F51CE0D81C8C93D2FCE8C20AAFF7042E721CBA16EAAE98778611DED11C0ABC8900DC5667F99B50A9DADEC24DBD8F2C91E3E8AD300EF64F1B4B9536CEB16FB440AF939F57624A9B486F867807C649AE4830EAB88C6C03998")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.InfoCard, PublicKey=0024000004800000940000000602000000240000525341310004000001000100AD093C3765257C89A7010E853F2C7C741FF92FA8ACE06D7B8254702CAD5CF99104447F63AB05F8BB6F51CE0D81C8C93D2FCE8C20AAFF7042E721CBA16EAAE98778611DED11C0ABC8900DC5667F99B50A9DADEC24DBD8F2C91E3E8AD300EF64F1B4B9536CEB16FB440AF939F57624A9B486F867807C649AE4830EAB88C6C03998")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.InfoCard.UI, PublicKey=0024000004800000940000000602000000240000525341310004000001000100AD093C3765257C89A7010E853F2C7C741FF92FA8ACE06D7B8254702CAD5CF99104447F63AB05F8BB6F51CE0D81C8C93D2FCE8C20AAFF7042E721CBA16EAAE98778611DED11C0ABC8900DC5667F99B50A9DADEC24DBD8F2C91E3E8AD300EF64F1B4B9536CEB16FB440AF939F57624A9B486F867807C649AE4830EAB88C6C03998")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.OpenId, PublicKey=0024000004800000940000000602000000240000525341310004000001000100AD093C3765257C89A7010E853F2C7C741FF92FA8ACE06D7B8254702CAD5CF99104447F63AB05F8BB6F51CE0D81C8C93D2FCE8C20AAFF7042E721CBA16EAAE98778611DED11C0ABC8900DC5667F99B50A9DADEC24DBD8F2C91E3E8AD300EF64F1B4B9536CEB16FB440AF939F57624A9B486F867807C649AE4830EAB88C6C03998")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.OpenId.UI, PublicKey=0024000004800000940000000602000000240000525341310004000001000100AD093C3765257C89A7010E853F2C7C741FF92FA8ACE06D7B8254702CAD5CF99104447F63AB05F8BB6F51CE0D81C8C93D2FCE8C20AAFF7042E721CBA16EAAE98778611DED11C0ABC8900DC5667F99B50A9DADEC24DBD8F2C91E3E8AD300EF64F1B4B9536CEB16FB440AF939F57624A9B486F867807C649AE4830EAB88C6C03998")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.OpenId.RelyingParty, PublicKey=0024000004800000940000000602000000240000525341310004000001000100AD093C3765257C89A7010E853F2C7C741FF92FA8ACE06D7B8254702CAD5CF99104447F63AB05F8BB6F51CE0D81C8C93D2FCE8C20AAFF7042E721CBA16EAAE98778611DED11C0ABC8900DC5667F99B50A9DADEC24DBD8F2C91E3E8AD300EF64F1B4B9536CEB16FB440AF939F57624A9B486F867807C649AE4830EAB88C6C03998")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.OpenId.RelyingParty.UI, PublicKey=0024000004800000940000000602000000240000525341310004000001000100AD093C3765257C89A7010E853F2C7C741FF92FA8ACE06D7B8254702CAD5CF99104447F63AB05F8BB6F51CE0D81C8C93D2FCE8C20AAFF7042E721CBA16EAAE98778611DED11C0ABC8900DC5667F99B50A9DADEC24DBD8F2C91E3E8AD300EF64F1B4B9536CEB16FB440AF939F57624A9B486F867807C649AE4830EAB88C6C03998")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.OpenId.Provider, PublicKey=0024000004800000940000000602000000240000525341310004000001000100AD093C3765257C89A7010E853F2C7C741FF92FA8ACE06D7B8254702CAD5CF99104447F63AB05F8BB6F51CE0D81C8C93D2FCE8C20AAFF7042E721CBA16EAAE98778611DED11C0ABC8900DC5667F99B50A9DADEC24DBD8F2C91E3E8AD300EF64F1B4B9536CEB16FB440AF939F57624A9B486F867807C649AE4830EAB88C6C03998")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.OpenId.Provider.UI, PublicKey=0024000004800000940000000602000000240000525341310004000001000100AD093C3765257C89A7010E853F2C7C741FF92FA8ACE06D7B8254702CAD5CF99104447F63AB05F8BB6F51CE0D81C8C93D2FCE8C20AAFF7042E721CBA16EAAE98778611DED11C0ABC8900DC5667F99B50A9DADEC24DBD8F2C91E3E8AD300EF64F1B4B9536CEB16FB440AF939F57624A9B486F867807C649AE4830EAB88C6C03998")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.OpenIdInfoCard.UI, PublicKey=0024000004800000940000000602000000240000525341310004000001000100AD093C3765257C89A7010E853F2C7C741FF92FA8ACE06D7B8254702CAD5CF99104447F63AB05F8BB6F51CE0D81C8C93D2FCE8C20AAFF7042E721CBA16EAAE98778611DED11C0ABC8900DC5667F99B50A9DADEC24DBD8F2C91E3E8AD300EF64F1B4B9536CEB16FB440AF939F57624A9B486F867807C649AE4830EAB88C6C03998")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.OAuth, PublicKey=0024000004800000940000000602000000240000525341310004000001000100AD093C3765257C89A7010E853F2C7C741FF92FA8ACE06D7B8254702CAD5CF99104447F63AB05F8BB6F51CE0D81C8C93D2FCE8C20AAFF7042E721CBA16EAAE98778611DED11C0ABC8900DC5667F99B50A9DADEC24DBD8F2C91E3E8AD300EF64F1B4B9536CEB16FB440AF939F57624A9B486F867807C649AE4830EAB88C6C03998")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.OAuth.Consumer, PublicKey=0024000004800000940000000602000000240000525341310004000001000100AD093C3765257C89A7010E853F2C7C741FF92FA8ACE06D7B8254702CAD5CF99104447F63AB05F8BB6F51CE0D81C8C93D2FCE8C20AAFF7042E721CBA16EAAE98778611DED11C0ABC8900DC5667F99B50A9DADEC24DBD8F2C91E3E8AD300EF64F1B4B9536CEB16FB440AF939F57624A9B486F867807C649AE4830EAB88C6C03998")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.OAuth.ServiceProvider, PublicKey=0024000004800000940000000602000000240000525341310004000001000100AD093C3765257C89A7010E853F2C7C741FF92FA8ACE06D7B8254702CAD5CF99104447F63AB05F8BB6F51CE0D81C8C93D2FCE8C20AAFF7042E721CBA16EAAE98778611DED11C0ABC8900DC5667F99B50A9DADEC24DBD8F2C91E3E8AD300EF64F1B4B9536CEB16FB440AF939F57624A9B486F867807C649AE4830EAB88C6C03998")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.OAuth2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100AD093C3765257C89A7010E853F2C7C741FF92FA8ACE06D7B8254702CAD5CF99104447F63AB05F8BB6F51CE0D81C8C93D2FCE8C20AAFF7042E721CBA16EAAE98778611DED11C0ABC8900DC5667F99B50A9DADEC24DBD8F2C91E3E8AD300EF64F1B4B9536CEB16FB440AF939F57624A9B486F867807C649AE4830EAB88C6C03998")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.OAuth2.AuthorizationServer, PublicKey=0024000004800000940000000602000000240000525341310004000001000100AD093C3765257C89A7010E853F2C7C741FF92FA8ACE06D7B8254702CAD5CF99104447F63AB05F8BB6F51CE0D81C8C93D2FCE8C20AAFF7042E721CBA16EAAE98778611DED11C0ABC8900DC5667F99B50A9DADEC24DBD8F2C91E3E8AD300EF64F1B4B9536CEB16FB440AF939F57624A9B486F867807C649AE4830EAB88C6C03998")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.OAuth2.ResourceServer, PublicKey=0024000004800000940000000602000000240000525341310004000001000100AD093C3765257C89A7010E853F2C7C741FF92FA8ACE06D7B8254702CAD5CF99104447F63AB05F8BB6F51CE0D81C8C93D2FCE8C20AAFF7042E721CBA16EAAE98778611DED11C0ABC8900DC5667F99B50A9DADEC24DBD8F2C91E3E8AD300EF64F1B4B9536CEB16FB440AF939F57624A9B486F867807C649AE4830EAB88C6C03998")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.OAuth2.Client, PublicKey=0024000004800000940000000602000000240000525341310004000001000100AD093C3765257C89A7010E853F2C7C741FF92FA8ACE06D7B8254702CAD5CF99104447F63AB05F8BB6F51CE0D81C8C93D2FCE8C20AAFF7042E721CBA16EAAE98778611DED11C0ABC8900DC5667F99B50A9DADEC24DBD8F2C91E3E8AD300EF64F1B4B9536CEB16FB440AF939F57624A9B486F867807C649AE4830EAB88C6C03998")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.OAuth2.Client.UI, PublicKey=0024000004800000940000000602000000240000525341310004000001000100AD093C3765257C89A7010E853F2C7C741FF92FA8ACE06D7B8254702CAD5CF99104447F63AB05F8BB6F51CE0D81C8C93D2FCE8C20AAFF7042E721CBA16EAAE98778611DED11C0ABC8900DC5667F99B50A9DADEC24DBD8F2C91E3E8AD300EF64F1B4B9536CEB16FB440AF939F57624A9B486F867807C649AE4830EAB88C6C03998")] +[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")] +#else +[assembly: InternalsVisibleTo("DotNetOpenAuth.Test")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.InfoCard")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.InfoCard.UI")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.OpenId")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.OpenId.UI")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.OpenId.RelyingParty")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.OpenId.RelyingParty.UI")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.OpenId.Provider")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.OpenId.Provider.UI")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.OpenIdInfoCard.UI")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.OAuth")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.OAuth.Consumer")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.OAuth.ServiceProvider")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.OAuth2")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.OAuth2.AuthorizationServer")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.OAuth2.ResourceServer")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.OAuth2.Client")] +[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] +#endif diff --git a/src/DotNetOpenAuth.Core/Reporting.cs b/src/DotNetOpenAuth.Core/Reporting.cs new file mode 100644 index 0000000..0bbbcec --- /dev/null +++ b/src/DotNetOpenAuth.Core/Reporting.cs @@ -0,0 +1,900 @@ +//----------------------------------------------------------------------- +// <copyright file="Reporting.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth { + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; + using System.Diagnostics.Contracts; + using System.Globalization; + using System.IO; + using System.IO.IsolatedStorage; + using System.Linq; + using System.Net; + using System.Reflection; + using System.Security; + using System.Text; + using System.Threading; + using System.Web; + using DotNetOpenAuth.Configuration; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.Messaging.Bindings; + + /// <summary> + /// The statistical reporting mechanism used so this library's project authors + /// know what versions and features are in use. + /// </summary> + public class Reporting { + /// <summary> + /// A UTF8 encoder that doesn't emit the preamble. Used for mid-stream writers. + /// </summary> + private static readonly Encoding Utf8NoPreamble = new UTF8Encoding(false); + + /// <summary> + /// A value indicating whether reporting is desirable or not. Must be logical-AND'd with !<see cref="broken"/>. + /// </summary> + private static bool enabled; + + /// <summary> + /// A value indicating whether reporting experienced an error and cannot be enabled. + /// </summary> + private static bool broken; + + /// <summary> + /// A value indicating whether the reporting class has been initialized or not. + /// </summary> + private static bool initialized; + + /// <summary> + /// The object to lock during initialization. + /// </summary> + private static object initializationSync = new object(); + + /// <summary> + /// The isolated storage to use for collecting data in between published reports. + /// </summary> + private static IsolatedStorageFile file; + + /// <summary> + /// The GUID that shows up at the top of all reports from this user/machine/domain. + /// </summary> + private static Guid reportOriginIdentity; + + /// <summary> + /// The recipient of collected reports. + /// </summary> + private static Uri wellKnownPostLocation = new Uri("https://reports.dotnetopenauth.net/ReportingPost.ashx"); + + /// <summary> + /// The outgoing HTTP request handler to use for publishing reports. + /// </summary> + private static IDirectWebRequestHandler webRequestHandler; + + /// <summary> + /// A few HTTP request hosts and paths we've seen. + /// </summary> + private static PersistentHashSet observedRequests; + + /// <summary> + /// Cultures that have come in via HTTP requests. + /// </summary> + private static PersistentHashSet observedCultures; + + /// <summary> + /// Features that have been used. + /// </summary> + private static PersistentHashSet observedFeatures; + + /// <summary> + /// A collection of all the observations to include in the report. + /// </summary> + private static List<PersistentHashSet> observations = new List<PersistentHashSet>(); + + /// <summary> + /// The named events that we have counters for. + /// </summary> + private static Dictionary<string, PersistentCounter> events = new Dictionary<string, PersistentCounter>(StringComparer.OrdinalIgnoreCase); + + /// <summary> + /// The lock acquired while considering whether to publish a report. + /// </summary> + private static object publishingConsiderationLock = new object(); + + /// <summary> + /// The time that we last published reports. + /// </summary> + private static DateTime lastPublished = DateTime.Now; + + /// <summary> + /// Initializes static members of the <see cref="Reporting"/> class. + /// </summary> + [SuppressMessage("Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline", Justification = "We do more than field initialization here.")] + [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Reporting MUST NOT cause unhandled exceptions.")] + static Reporting() { + Enabled = DotNetOpenAuthSection.Reporting.Enabled; + } + + /// <summary> + /// Gets or sets a value indicating whether this reporting is enabled. + /// </summary> + /// <value><c>true</c> if enabled; otherwise, <c>false</c>.</value> + /// <remarks> + /// Setting this property to <c>true</c> <i>may</i> have no effect + /// if reporting has already experienced a failure of some kind. + /// </remarks> + public static bool Enabled { + get { + return enabled && !broken; + } + + set { + if (value) { + Initialize(); + } + + // Only set the static field here, so that other threads + // don't try to use reporting while we're initializing it. + enabled = value; + } + } + + /// <summary> + /// Gets the observed features. + /// </summary> + internal static PersistentHashSet ObservedFeatures { + get { return observedFeatures; } + } + + /// <summary> + /// Gets the configuration to use for reporting. + /// </summary> + internal static ReportingElement Configuration { + get { return DotNetOpenAuthSection.Reporting; } + } + + /// <summary> + /// Records an event occurrence. + /// </summary> + /// <param name="eventName">Name of the event.</param> + /// <param name="category">The category within the event. Null and empty strings are allowed, but considered the same.</param> + [SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "PersistentCounter instances are stored in a table for later use.")] + internal static void RecordEventOccurrence(string eventName, string category) { + Contract.Requires(!String.IsNullOrEmpty(eventName)); + + // In release builds, just quietly return. + if (string.IsNullOrEmpty(eventName)) { + return; + } + + if (Enabled && Configuration.IncludeEventStatistics) { + PersistentCounter counter; + lock (events) { + if (!events.TryGetValue(eventName, out counter)) { + events[eventName] = counter = new PersistentCounter(file, "event-" + SanitizeFileName(eventName) + ".txt"); + } + } + + counter.Increment(category); + Touch(); + } + } + + /// <summary> + /// Records an event occurence. + /// </summary> + /// <param name="eventNameByObjectType">The object whose type name is the event name to record.</param> + /// <param name="category">The category within the event. Null and empty strings are allowed, but considered the same.</param> + internal static void RecordEventOccurrence(object eventNameByObjectType, string category) { + Contract.Requires(eventNameByObjectType != null); + + // In release builds, just quietly return. + if (eventNameByObjectType == null) { + return; + } + + if (Enabled && Configuration.IncludeEventStatistics) { + RecordEventOccurrence(eventNameByObjectType.GetType().Name, category); + } + } + + /// <summary> + /// Records the use of a feature by name. + /// </summary> + /// <param name="feature">The feature.</param> + internal static void RecordFeatureUse(string feature) { + Contract.Requires(!String.IsNullOrEmpty(feature)); + + // In release builds, just quietly return. + if (string.IsNullOrEmpty(feature)) { + return; + } + + if (Enabled && Configuration.IncludeFeatureUsage) { + observedFeatures.Add(feature); + Touch(); + } + } + + /// <summary> + /// Records the use of a feature by object type. + /// </summary> + /// <param name="value">The object whose type is the feature to set as used.</param> + internal static void RecordFeatureUse(object value) { + Contract.Requires(value != null); + + // In release builds, just quietly return. + if (value == null) { + return; + } + + if (Enabled && Configuration.IncludeFeatureUsage) { + observedFeatures.Add(value.GetType().Name); + Touch(); + } + } + + /// <summary> + /// Records the use of a feature by object type. + /// </summary> + /// <param name="value">The object whose type is the feature to set as used.</param> + /// <param name="dependency1">Some dependency used by <paramref name="value"/>.</param> + internal static void RecordFeatureAndDependencyUse(object value, object dependency1) { + Contract.Requires(value != null); + + // In release builds, just quietly return. + if (value == null) { + return; + } + + if (Enabled && Configuration.IncludeFeatureUsage) { + StringBuilder builder = new StringBuilder(); + builder.Append(value.GetType().Name); + builder.Append(" "); + builder.Append(dependency1 != null ? dependency1.GetType().Name : "(null)"); + observedFeatures.Add(builder.ToString()); + Touch(); + } + } + + /// <summary> + /// Records the use of a feature by object type. + /// </summary> + /// <param name="value">The object whose type is the feature to set as used.</param> + /// <param name="dependency1">Some dependency used by <paramref name="value"/>.</param> + /// <param name="dependency2">Some dependency used by <paramref name="value"/>.</param> + internal static void RecordFeatureAndDependencyUse(object value, object dependency1, object dependency2) { + Contract.Requires(value != null); + + // In release builds, just quietly return. + if (value == null) { + return; + } + + if (Enabled && Configuration.IncludeFeatureUsage) { + StringBuilder builder = new StringBuilder(); + builder.Append(value.GetType().Name); + builder.Append(" "); + builder.Append(dependency1 != null ? dependency1.GetType().Name : "(null)"); + builder.Append(" "); + builder.Append(dependency2 != null ? dependency2.GetType().Name : "(null)"); + observedFeatures.Add(builder.ToString()); + Touch(); + } + } + + /// <summary> + /// Records statistics collected from incoming requests. + /// </summary> + /// <param name="request">The request.</param> + internal static void RecordRequestStatistics(HttpRequestInfo request) { + Contract.Requires(request != null); + + // In release builds, just quietly return. + if (request == null) { + return; + } + + if (Enabled) { + if (Configuration.IncludeCultures) { + observedCultures.Add(Thread.CurrentThread.CurrentCulture.Name); + } + + if (Configuration.IncludeLocalRequestUris && !observedRequests.IsFull) { + var requestBuilder = new UriBuilder(request.UrlBeforeRewriting); + requestBuilder.Query = null; + requestBuilder.Fragment = null; + observedRequests.Add(requestBuilder.Uri.AbsoluteUri); + } + + Touch(); + } + } + + /// <summary> + /// Called by every internal/public method on this class to give + /// periodic operations a chance to run. + /// </summary> + protected static void Touch() { + // Publish stats if it's time to do so. + lock (publishingConsiderationLock) { + if (DateTime.Now - lastPublished > Configuration.MinimumReportingInterval) { + lastPublished = DateTime.Now; + SendStatsAsync(); + } + } + } + + /// <summary> + /// Initializes Reporting if it has not been initialized yet. + /// </summary> + [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "This method must never throw.")] + private static void Initialize() { + lock (initializationSync) { + if (!broken && !initialized) { + try { + file = GetIsolatedStorage(); + reportOriginIdentity = GetOrCreateOriginIdentity(); + + webRequestHandler = new StandardWebRequestHandler(); + observations.Add(observedRequests = new PersistentHashSet(file, "requests.txt", 3)); + observations.Add(observedCultures = new PersistentHashSet(file, "cultures.txt", 20)); + observations.Add(observedFeatures = new PersistentHashSet(file, "features.txt", int.MaxValue)); + + // Record site-wide features in use. + if (HttpContext.Current != null && HttpContext.Current.ApplicationInstance != null) { + // MVC or web forms? + // front-end or back end web farm? + // url rewriting? + ////RecordFeatureUse(IsMVC ? "ASP.NET MVC" : "ASP.NET Web Forms"); + } + + initialized = true; + } catch (Exception e) { + // This is supposed to be as low-risk as possible, so if it fails, just disable reporting + // and avoid rethrowing. + broken = true; + Logger.Library.Error("Error while trying to initialize reporting.", e); + } + } + } + } + + /// <summary> + /// Assembles a report for submission. + /// </summary> + /// <returns>A stream that contains the report.</returns> + private static Stream GetReport() { + var stream = new MemoryStream(); + try { + var writer = new StreamWriter(stream, Encoding.UTF8); + writer.WriteLine(reportOriginIdentity.ToString("B")); + writer.WriteLine(Util.LibraryVersion); + writer.WriteLine(".NET Framework {0}", Environment.Version); + + foreach (var observation in observations) { + observation.Flush(); + writer.WriteLine("===================================="); + writer.WriteLine(observation.FileName); + try { + using (var fileStream = new IsolatedStorageFileStream(observation.FileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, file)) { + writer.Flush(); + fileStream.CopyTo(writer.BaseStream); + } + } catch (FileNotFoundException) { + writer.WriteLine("(missing)"); + } + } + + // Not all event counters may have even loaded in this app instance. + // We flush the ones in memory, and then read all of them off disk. + foreach (var counter in events.Values) { + counter.Flush(); + } + + foreach (string eventFile in file.GetFileNames("event-*.txt")) { + writer.WriteLine("===================================="); + writer.WriteLine(eventFile); + using (var fileStream = new IsolatedStorageFileStream(eventFile, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, file)) { + writer.Flush(); + fileStream.CopyTo(writer.BaseStream); + } + } + + // Make sure the stream is positioned at the beginning. + writer.Flush(); + stream.Position = 0; + return stream; + } catch { + stream.Dispose(); + throw; + } + } + + /// <summary> + /// Sends the usage reports to the library authors. + /// </summary> + /// <returns>A value indicating whether submitting the report was successful.</returns> + private static bool SendStats() { + try { + var request = (HttpWebRequest)WebRequest.Create(wellKnownPostLocation); + request.UserAgent = Util.LibraryVersion; + request.AllowAutoRedirect = false; + request.Method = "POST"; + request.ContentType = "text/dnoa-report1"; + Stream report = GetReport(); + request.ContentLength = report.Length; + using (var requestStream = webRequestHandler.GetRequestStream(request)) { + report.CopyTo(requestStream); + } + + using (var response = webRequestHandler.GetResponse(request)) { + Logger.Library.Info("Statistical report submitted successfully."); + + // The response stream may contain a message for the webmaster. + // Since as part of the report we submit the library version number, + // the report receiving service may have alerts such as: + // "You're using an obsolete version with exploitable security vulnerabilities." + using (var responseReader = response.GetResponseReader()) { + string line = responseReader.ReadLine(); + if (line != null) { + DemuxLogMessage(line); + } + } + } + + // Report submission was successful. Reset all counters. + lock (events) { + foreach (PersistentCounter counter in events.Values) { + counter.Reset(); + counter.Flush(); + } + + // We can just delete the files for counters that are not currently loaded. + foreach (string eventFile in file.GetFileNames("event-*.txt")) { + if (!events.Values.Any(e => string.Equals(e.FileName, eventFile, StringComparison.OrdinalIgnoreCase))) { + file.DeleteFile(eventFile); + } + } + } + + return true; + } catch (ProtocolException ex) { + Logger.Library.Error("Unable to submit statistical report due to an HTTP error.", ex); + } catch (FileNotFoundException ex) { + Logger.Library.Error("Unable to submit statistical report because the report file is missing.", ex); + } + + return false; + } + + /// <summary> + /// Interprets the reporting response as a log message if possible. + /// </summary> + /// <param name="line">The line from the HTTP response to interpret as a log message.</param> + private static void DemuxLogMessage(string line) { + if (line != null) { + string[] parts = line.Split(new char[] { ' ' }, 2); + if (parts.Length == 2) { + string level = parts[0]; + string message = parts[1]; + switch (level) { + case "INFO": + Logger.Library.Info(message); + break; + case "WARN": + Logger.Library.Warn(message); + break; + case "ERROR": + Logger.Library.Error(message); + break; + case "FATAL": + Logger.Library.Fatal(message); + break; + } + } + } + } + + /// <summary> + /// Sends the stats report asynchronously, and careful to not throw any unhandled exceptions. + /// </summary> + [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Unhandled exceptions MUST NOT be thrown from here.")] + private static void SendStatsAsync() { + // Do it on a background thread since it could take a while and we + // don't want to slow down this request we're borrowing. + ThreadPool.QueueUserWorkItem(state => { + try { + SendStats(); + } catch (Exception ex) { + // Something bad and unexpected happened. Just deactivate to avoid more trouble. + Logger.Library.Error("Error while trying to submit statistical report.", ex); + broken = true; + } + }); + } + + /// <summary> + /// Gets the isolated storage to use for reporting. + /// </summary> + /// <returns>An isolated storage location appropriate for our host.</returns> + private static IsolatedStorageFile GetIsolatedStorage() { + Contract.Ensures(Contract.Result<IsolatedStorageFile>() != null); + + IsolatedStorageFile result = null; + + // We'll try for whatever storage location we can get, + // and not catch exceptions from the last attempt so that + // the overall failure is caught by our caller. + try { + // This works on Personal Web Server + result = IsolatedStorageFile.GetUserStoreForDomain(); + } catch (SecurityException) { + } catch (IsolatedStorageException) { + } + + // This works on IIS when full trust is granted. + if (result == null) { + result = IsolatedStorageFile.GetMachineStoreForDomain(); + } + + Logger.Library.InfoFormat("Reporting will use isolated storage with scope: {0}", result.Scope); + return result; + } + + /// <summary> + /// Gets a unique, pseudonymous identifier for this particular web site or application. + /// </summary> + /// <returns>A GUID that will serve as the identifier.</returns> + /// <remarks> + /// The identifier is made persistent by storing the identifier in isolated storage. + /// If an existing identifier is not found, a new one is created, persisted, and returned. + /// </remarks> + private static Guid GetOrCreateOriginIdentity() { + Requires.ValidState(file != null); + Contract.Ensures(Contract.Result<Guid>() != Guid.Empty); + + Guid identityGuid = Guid.Empty; + const int GuidLength = 16; + using (var identityFileStream = new IsolatedStorageFileStream("identity.txt", FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.Read, file)) { + if (identityFileStream.Length == GuidLength) { + byte[] guidBytes = new byte[GuidLength]; + if (identityFileStream.Read(guidBytes, 0, GuidLength) == GuidLength) { + identityGuid = new Guid(guidBytes); + } + } + + if (identityGuid == Guid.Empty) { + identityGuid = Guid.NewGuid(); + byte[] guidBytes = identityGuid.ToByteArray(); + identityFileStream.SetLength(0); + identityFileStream.Write(guidBytes, 0, guidBytes.Length); + } + + return identityGuid; + } + } + + /// <summary> + /// Sanitizes the name of the file so it only includes valid filename characters. + /// </summary> + /// <param name="fileName">The filename to sanitize.</param> + /// <returns>The filename, with any and all invalid filename characters replaced with the hyphen (-) character.</returns> + private static string SanitizeFileName(string fileName) { + Requires.NotNullOrEmpty(fileName, "fileName"); + char[] invalidCharacters = Path.GetInvalidFileNameChars(); + if (fileName.IndexOfAny(invalidCharacters) < 0) { + return fileName; // nothing invalid about this filename. + } + + // Use a stringbuilder since we may be replacing several characters + // and we don't want to instantiate a new string buffer for each new version. + StringBuilder sanitized = new StringBuilder(fileName); + foreach (char invalidChar in invalidCharacters) { + sanitized.Replace(invalidChar, '-'); + } + + return sanitized.ToString(); + } + + /// <summary> + /// A set of values that persist the set to disk. + /// </summary> + internal class PersistentHashSet : IDisposable { + /// <summary> + /// The isolated persistent storage. + /// </summary> + private readonly FileStream fileStream; + + /// <summary> + /// The persistent reader. + /// </summary> + private readonly StreamReader reader; + + /// <summary> + /// The persistent writer. + /// </summary> + private readonly StreamWriter writer; + + /// <summary> + /// The total set of elements. + /// </summary> + private readonly HashSet<string> memorySet = new HashSet<string>(StringComparer.OrdinalIgnoreCase); + + /// <summary> + /// The maximum number of elements to track before not storing new elements. + /// </summary> + private readonly int maximumElements; + + /// <summary> + /// The set of new elements added to the <see cref="memorySet"/> since the last flush. + /// </summary> + private List<string> newElements = new List<string>(); + + /// <summary> + /// The time the last flush occurred. + /// </summary> + private DateTime lastFlushed; + + /// <summary> + /// A flag indicating whether the set has changed since it was last flushed. + /// </summary> + private bool dirty; + + /// <summary> + /// Initializes a new instance of the <see cref="PersistentHashSet"/> class. + /// </summary> + /// <param name="storage">The storage location.</param> + /// <param name="fileName">Name of the file.</param> + /// <param name="maximumElements">The maximum number of elements to track.</param> + internal PersistentHashSet(IsolatedStorageFile storage, string fileName, int maximumElements) { + Requires.NotNull(storage, "storage"); + Requires.NotNullOrEmpty(fileName, "fileName"); + this.FileName = fileName; + this.maximumElements = maximumElements; + + // Load the file into memory. + this.fileStream = new IsolatedStorageFileStream(fileName, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.Read, storage); + this.reader = new StreamReader(this.fileStream, Encoding.UTF8); + while (!this.reader.EndOfStream) { + this.memorySet.Add(this.reader.ReadLine()); + } + + this.writer = new StreamWriter(this.fileStream, Utf8NoPreamble); + this.lastFlushed = DateTime.Now; + } + + /// <summary> + /// Gets a value indicating whether the hashset has reached capacity and is not storing more elements. + /// </summary> + /// <value><c>true</c> if this instance is full; otherwise, <c>false</c>.</value> + internal bool IsFull { + get { + lock (this.memorySet) { + return this.memorySet.Count >= this.maximumElements; + } + } + } + + /// <summary> + /// Gets the name of the file. + /// </summary> + /// <value>The name of the file.</value> + internal string FileName { get; private set; } + + #region IDisposable Members + + /// <summary> + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// </summary> + public void Dispose() { + this.Dispose(true); + GC.SuppressFinalize(this); + } + + #endregion + + /// <summary> + /// Adds a value to the set. + /// </summary> + /// <param name="value">The value.</param> + internal void Add(string value) { + lock (this.memorySet) { + if (!this.IsFull) { + if (this.memorySet.Add(value)) { + this.newElements.Add(value); + this.dirty = true; + + if (this.IsFull) { + this.Flush(); + } + } + + if (this.dirty && DateTime.Now - this.lastFlushed > Configuration.MinimumFlushInterval) { + this.Flush(); + } + } + } + } + + /// <summary> + /// Flushes any newly added values to disk. + /// </summary> + internal void Flush() { + lock (this.memorySet) { + foreach (string element in this.newElements) { + this.writer.WriteLine(element); + } + this.writer.Flush(); + + // Assign a whole new list since future lists might be smaller in order to + // decrease demand on memory. + this.newElements = new List<string>(); + this.dirty = false; + this.lastFlushed = DateTime.Now; + } + } + + /// <summary> + /// Releases unmanaged and - optionally - managed resources + /// </summary> + /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param> + protected virtual void Dispose(bool disposing) { + if (disposing) { + this.writer.Dispose(); + this.reader.Dispose(); + this.fileStream.Dispose(); + } + } + } + + /// <summary> + /// A feature usage counter. + /// </summary> + private class PersistentCounter : IDisposable { + /// <summary> + /// The separator to use between category names and their individual counters. + /// </summary> + private static readonly char[] separator = new char[] { '\t' }; + + /// <summary> + /// The isolated persistent storage. + /// </summary> + private readonly FileStream fileStream; + + /// <summary> + /// The persistent reader. + /// </summary> + private readonly StreamReader reader; + + /// <summary> + /// The persistent writer. + /// </summary> + private readonly StreamWriter writer; + + /// <summary> + /// The time the last flush occurred. + /// </summary> + private DateTime lastFlushed; + + /// <summary> + /// The in-memory copy of the counter. + /// </summary> + private Dictionary<string, int> counters = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase); + + /// <summary> + /// A flag indicating whether the set has changed since it was last flushed. + /// </summary> + private bool dirty; + + /// <summary> + /// Initializes a new instance of the <see cref="PersistentCounter"/> class. + /// </summary> + /// <param name="storage">The storage location.</param> + /// <param name="fileName">Name of the file.</param> + internal PersistentCounter(IsolatedStorageFile storage, string fileName) { + Requires.NotNull(storage, "storage"); + Requires.NotNullOrEmpty(fileName, "fileName"); + this.FileName = fileName; + + // Load the file into memory. + this.fileStream = new IsolatedStorageFileStream(fileName, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.Read, storage); + this.reader = new StreamReader(this.fileStream, Encoding.UTF8); + while (!this.reader.EndOfStream) { + string line = this.reader.ReadLine(); + string[] parts = line.Split(separator, 2); + int counter; + if (int.TryParse(parts[0], out counter)) { + string category = string.Empty; + if (parts.Length > 1) { + category = parts[1]; + } + this.counters[category] = counter; + } + } + + this.writer = new StreamWriter(this.fileStream, Utf8NoPreamble); + this.lastFlushed = DateTime.Now; + } + + /// <summary> + /// Gets the name of the file. + /// </summary> + /// <value>The name of the file.</value> + internal string FileName { get; private set; } + + #region IDisposable Members + + /// <summary> + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// </summary> + public void Dispose() { + this.Dispose(true); + GC.SuppressFinalize(this); + } + + #endregion + + /// <summary> + /// Increments the counter. + /// </summary> + /// <param name="category">The category within the event. Null and empty strings are allowed, but considered the same.</param> + internal void Increment(string category) { + if (category == null) { + category = string.Empty; + } + lock (this) { + int counter; + this.counters.TryGetValue(category, out counter); + this.counters[category] = counter + 1; + this.dirty = true; + if (this.dirty && DateTime.Now - this.lastFlushed > Configuration.MinimumFlushInterval) { + this.Flush(); + } + } + } + + /// <summary> + /// Flushes any newly added values to disk. + /// </summary> + internal void Flush() { + lock (this) { + this.writer.BaseStream.Position = 0; + this.writer.BaseStream.SetLength(0); // truncate file + foreach (var pair in this.counters) { + this.writer.Write(pair.Value); + this.writer.Write(separator[0]); + this.writer.WriteLine(pair.Key); + } + this.writer.Flush(); + this.dirty = false; + this.lastFlushed = DateTime.Now; + } + } + + /// <summary> + /// Resets all counters. + /// </summary> + internal void Reset() { + lock (this) { + this.counters.Clear(); + } + } + + /// <summary> + /// Releases unmanaged and - optionally - managed resources + /// </summary> + /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param> + protected virtual void Dispose(bool disposing) { + if (disposing) { + this.writer.Dispose(); + this.reader.Dispose(); + this.fileStream.Dispose(); + } + } + } + } +} diff --git a/src/DotNetOpenAuth.Core/Requires.cs b/src/DotNetOpenAuth.Core/Requires.cs new file mode 100644 index 0000000..4be6da0 --- /dev/null +++ b/src/DotNetOpenAuth.Core/Requires.cs @@ -0,0 +1,250 @@ +//----------------------------------------------------------------------- +// <copyright file="Requires.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth { + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Diagnostics.Contracts; + using System.Globalization; + using System.Linq; + using System.Text; + using DotNetOpenAuth.Messaging; + + /// <summary> + /// Argument validation checks that throw some kind of ArgumentException when they fail (unless otherwise noted). + /// </summary> + internal static class Requires { + /// <summary> + /// Validates that a given parameter is not null. + /// </summary> + /// <typeparam name="T">The type of the parameter</typeparam> + /// <param name="value">The value.</param> + /// <param name="parameterName">Name of the parameter.</param> +#if !CLR4 + [ContractArgumentValidator] +#endif + [Pure, DebuggerStepThrough] + internal static void NotNull<T>(T value, string parameterName) where T : class { + if (value == null) { + throw new ArgumentNullException(parameterName); + } + + Contract.EndContractBlock(); + } + + /// <summary> + /// Validates that a parameter is not null or empty. + /// </summary> + /// <param name="value">The value.</param> + /// <param name="parameterName">Name of the parameter.</param> +#if !CLR4 + [ContractArgumentValidator] +#endif + [Pure, DebuggerStepThrough] + internal static void NotNullOrEmpty(string value, string parameterName) { + NotNull(value, parameterName); + True(value.Length > 0, parameterName, Strings.EmptyStringNotAllowed); + Contract.EndContractBlock(); + } + + /// <summary> + /// Validates that an array is not null or empty. + /// </summary> + /// <typeparam name="T">The type of the elements in the sequence.</typeparam> + /// <param name="value">The value.</param> + /// <param name="parameterName">Name of the parameter.</param> +#if !CLR4 + [ContractArgumentValidator] +#endif + [Pure, DebuggerStepThrough] + internal static void NotNullOrEmpty<T>(IEnumerable<T> value, string parameterName) { + NotNull(value, parameterName); + True(value.Any(), parameterName, Strings.InvalidArgument); + Contract.EndContractBlock(); + } + + /// <summary> + /// Validates that an argument is either null or is a sequence with no null elements. + /// </summary> + /// <typeparam name="T">The type of elements in the sequence.</typeparam> + /// <param name="sequence">The sequence.</param> + /// <param name="parameterName">Name of the parameter.</param> +#if !CLR4 + [ContractArgumentValidator] +#endif + [Pure, DebuggerStepThrough] + internal static void NullOrWithNoNullElements<T>(IEnumerable<T> sequence, string parameterName) where T : class { + if (sequence != null) { + if (sequence.Any(e => e == null)) { + throw new ArgumentException(MessagingStrings.SequenceContainsNullElement, parameterName); + } + } + } + + /// <summary> + /// Validates some expression describing the acceptable range for an argument evaluates to true. + /// </summary> + /// <param name="condition">The expression that must evaluate to true to avoid an <see cref="ArgumentOutOfRangeException"/>.</param> + /// <param name="parameterName">Name of the parameter.</param> + /// <param name="message">The message to include with the exception.</param> +#if !CLR4 + [ContractArgumentValidator] +#endif + [Pure, DebuggerStepThrough] + internal static void InRange(bool condition, string parameterName, string message = null) { + if (!condition) { + throw new ArgumentOutOfRangeException(parameterName, message); + } + + Contract.EndContractBlock(); + } + + /// <summary> + /// Validates some expression describing the acceptable condition for an argument evaluates to true. + /// </summary> + /// <param name="condition">The expression that must evaluate to true to avoid an <see cref="ArgumentException"/>.</param> + /// <param name="parameterName">Name of the parameter.</param> + /// <param name="message">The message to include with the exception.</param> +#if !CLR4 + [ContractArgumentValidator] +#endif + [Pure, DebuggerStepThrough] + internal static void True(bool condition, string parameterName = null, string message = null) { + if (!condition) { + throw new ArgumentException(message ?? Strings.InvalidArgument, parameterName); + } + + Contract.EndContractBlock(); + } + + /// <summary> + /// Validates some expression describing the acceptable condition for an argument evaluates to true. + /// </summary> + /// <param name="condition">The expression that must evaluate to true to avoid an <see cref="ArgumentException"/>.</param> + /// <param name="parameterName">Name of the parameter.</param> + /// <param name="unformattedMessage">The unformatted message.</param> + /// <param name="args">Formatting arguments.</param> +#if !CLR4 + [ContractArgumentValidator] +#endif + [Pure, DebuggerStepThrough] + internal static void True(bool condition, string parameterName, string unformattedMessage, params object[] args) { + if (!condition) { + throw new ArgumentException(String.Format(unformattedMessage, args), parameterName); + } + + Contract.EndContractBlock(); + } + + /// <summary> + /// Validates some expression describing the acceptable condition for an argument evaluates to true. + /// </summary> + /// <param name="condition">The expression that must evaluate to true to avoid an <see cref="InvalidOperationException"/>.</param> +#if !CLR4 + [ContractArgumentValidator] +#endif + [Pure, DebuggerStepThrough] + internal static void ValidState(bool condition) { + if (!condition) { + throw new InvalidOperationException(); + } + + Contract.EndContractBlock(); + } + + /// <summary> + /// Validates some expression describing the acceptable condition for an argument evaluates to true. + /// </summary> + /// <param name="condition">The expression that must evaluate to true to avoid an <see cref="InvalidOperationException"/>.</param> + /// <param name="message">The message to include with the exception.</param> +#if !CLR4 + [ContractArgumentValidator] +#endif + [Pure, DebuggerStepThrough] + internal static void ValidState(bool condition, string message) { + if (!condition) { + throw new InvalidOperationException(message); + } + + Contract.EndContractBlock(); + } + + /// <summary> + /// Validates some expression describing the acceptable condition for an argument evaluates to true. + /// </summary> + /// <param name="condition">The expression that must evaluate to true to avoid an <see cref="InvalidOperationException"/>.</param> + /// <param name="unformattedMessage">The unformatted message.</param> + /// <param name="args">Formatting arguments.</param> +#if !CLR4 + [ContractArgumentValidator] +#endif + [Pure, DebuggerStepThrough] + internal static void ValidState(bool condition, string unformattedMessage, params object[] args) { + if (!condition) { + throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture, unformattedMessage, args)); + } + + Contract.EndContractBlock(); + } + + /// <summary> + /// Validates that some argument describes a type that is or derives from a required type. + /// </summary> + /// <typeparam name="T">The type that the argument must be or derive from.</typeparam> + /// <param name="type">The type given in the argument.</param> + /// <param name="parameterName">Name of the parameter.</param> +#if !CLR4 + [ContractArgumentValidator] +#endif + [Pure, DebuggerStepThrough] + internal static void NotNullSubtype<T>(Type type, string parameterName) { + NotNull(type, parameterName); + True(typeof(T).IsAssignableFrom(type), parameterName, MessagingStrings.UnexpectedType, typeof(T).FullName, type.FullName); + + Contract.EndContractBlock(); + } + + /// <summary> + /// Validates some expression describing the acceptable condition for an argument evaluates to true. + /// </summary> + /// <param name="condition">The expression that must evaluate to true to avoid an <see cref="FormatException"/>.</param> + /// <param name="message">The message.</param> +#if !CLR4 + [ContractArgumentValidator] +#endif + [Pure, DebuggerStepThrough] + internal static void Format(bool condition, string message) { + if (!condition) { + throw new FormatException(message); + } + + Contract.EndContractBlock(); + } + + /// <summary> + /// Throws an <see cref="NotSupportedException"/> if a condition does not evaluate to <c>true</c>. + /// </summary> + /// <param name="condition">The expression that must evaluate to true to avoid an <see cref="NotSupportedException"/>.</param> + /// <param name="message">The message.</param> + [Pure, DebuggerStepThrough] + internal static void Support(bool condition, string message) { + if (!condition) { + throw new NotSupportedException(message); + } + } + + /// <summary> + /// Throws an <see cref="ArgumentException"/> + /// </summary> + /// <param name="parameterName">Name of the parameter.</param> + /// <param name="message">The message.</param> + [Pure, DebuggerStepThrough] + internal static void Fail(string parameterName, string message) { + throw new ArgumentException(message, parameterName); + } + } +} diff --git a/src/DotNetOpenAuth/Settings.StyleCop b/src/DotNetOpenAuth.Core/Settings.StyleCop index 017d610..017d610 100644 --- a/src/DotNetOpenAuth/Settings.StyleCop +++ b/src/DotNetOpenAuth.Core/Settings.StyleCop diff --git a/src/DotNetOpenAuth.Core/Strings.Designer.cs b/src/DotNetOpenAuth.Core/Strings.Designer.cs new file mode 100644 index 0000000..21411a1 --- /dev/null +++ b/src/DotNetOpenAuth.Core/Strings.Designer.cs @@ -0,0 +1,117 @@ +//------------------------------------------------------------------------------ +// <auto-generated> +// This code was generated by a tool. +// Runtime Version:4.0.30319.17291 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// </auto-generated> +//------------------------------------------------------------------------------ + +namespace DotNetOpenAuth { + 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 Strings { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Strings() { + } + + /// <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.Strings", typeof(Strings).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 configuration-specified type {0} must be public, and is not.. + /// </summary> + internal static string ConfigurationTypeMustBePublic { + get { + return ResourceManager.GetString("ConfigurationTypeMustBePublic", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to The configuration XAML reference to {0} requires a current HttpContext to resolve.. + /// </summary> + internal static string ConfigurationXamlReferenceRequiresHttpContext { + get { + return ResourceManager.GetString("ConfigurationXamlReferenceRequiresHttpContext", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to The current IHttpHandler is not one of types: {0}. An embedded resource URL provider must be set in your .config file.. + /// </summary> + internal static string EmbeddedResourceUrlProviderRequired { + get { + return ResourceManager.GetString("EmbeddedResourceUrlProviderRequired", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to The empty string is not allowed.. + /// </summary> + internal static string EmptyStringNotAllowed { + get { + return ResourceManager.GetString("EmptyStringNotAllowed", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to The argument has an unexpected value.. + /// </summary> + internal static string InvalidArgument { + get { + return ResourceManager.GetString("InvalidArgument", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to No current HttpContext was detected, so an {0} instance must be explicitly provided or specified in the .config file. Call the constructor overload that takes an {0}.. + /// </summary> + internal static string StoreRequiredWhenNoHttpContextAvailable { + get { + return ResourceManager.GetString("StoreRequiredWhenNoHttpContextAvailable", resourceCulture); + } + } + } +} diff --git a/src/DotNetOpenAuth.Core/Strings.resx b/src/DotNetOpenAuth.Core/Strings.resx new file mode 100644 index 0000000..1c69ef7 --- /dev/null +++ b/src/DotNetOpenAuth.Core/Strings.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="ConfigurationTypeMustBePublic" xml:space="preserve"> + <value>The configuration-specified type {0} must be public, and is not.</value> + </data> + <data name="StoreRequiredWhenNoHttpContextAvailable" xml:space="preserve"> + <value>No current HttpContext was detected, so an {0} instance must be explicitly provided or specified in the .config file. Call the constructor overload that takes an {0}.</value> + </data> + <data name="ConfigurationXamlReferenceRequiresHttpContext" xml:space="preserve"> + <value>The configuration XAML reference to {0} requires a current HttpContext to resolve.</value> + </data> + <data name="EmbeddedResourceUrlProviderRequired" xml:space="preserve"> + <value>The current IHttpHandler is not one of types: {0}. An embedded resource URL provider must be set in your .config file.</value> + </data> + <data name="EmptyStringNotAllowed" xml:space="preserve"> + <value>The empty string is not allowed.</value> + </data> + <data name="InvalidArgument" xml:space="preserve"> + <value>The argument has an unexpected value.</value> + </data> +</root>
\ No newline at end of file diff --git a/src/DotNetOpenAuth/Strings.sr.resx b/src/DotNetOpenAuth.Core/Strings.sr.resx index 5112265..5112265 100644 --- a/src/DotNetOpenAuth/Strings.sr.resx +++ b/src/DotNetOpenAuth.Core/Strings.sr.resx diff --git a/src/DotNetOpenAuth.Core/UriUtil.cs b/src/DotNetOpenAuth.Core/UriUtil.cs new file mode 100644 index 0000000..57360f5 --- /dev/null +++ b/src/DotNetOpenAuth.Core/UriUtil.cs @@ -0,0 +1,117 @@ +//----------------------------------------------------------------------- +// <copyright file="UriUtil.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth { + using System; + using System.Collections.Specialized; + using System.Diagnostics.CodeAnalysis; + using System.Diagnostics.Contracts; + using System.Linq; + using System.Text.RegularExpressions; + using System.Web; + using System.Web.UI; + using DotNetOpenAuth.Messaging; + + /// <summary> + /// Utility methods for working with URIs. + /// </summary> + [ContractVerification(true)] + internal static class UriUtil { + /// <summary> + /// Tests a URI for the presence of an OAuth payload. + /// </summary> + /// <param name="uri">The URI to test.</param> + /// <param name="prefix">The prefix.</param> + /// <returns> + /// True if the URI contains an OAuth message. + /// </returns> + [ContractVerification(false)] // bugs/limitations in CC static analysis + internal static bool QueryStringContainPrefixedParameters(this Uri uri, string prefix) { + Requires.NotNullOrEmpty(prefix, "prefix"); + if (uri == null) { + return false; + } + + NameValueCollection nvc = HttpUtility.ParseQueryString(uri.Query); + Contract.Assume(nvc != null); // BCL + return nvc.Keys.OfType<string>().Any(key => key.StartsWith(prefix, StringComparison.Ordinal)); + } + + /// <summary> + /// Determines whether some <see cref="Uri"/> is using HTTPS. + /// </summary> + /// <param name="uri">The Uri being tested for security.</param> + /// <returns> + /// <c>true</c> if the URI represents an encrypted request; otherwise, <c>false</c>. + /// </returns> + internal static bool IsTransportSecure(this Uri uri) { + Requires.NotNull(uri, "uri"); + return string.Equals(uri.Scheme, "https", StringComparison.OrdinalIgnoreCase); + } + + /// <summary> + /// Equivalent to UriBuilder.ToString() but omits port # if it may be implied. + /// Equivalent to UriBuilder.Uri.ToString(), but doesn't throw an exception if the Host has a wildcard. + /// </summary> + /// <param name="builder">The UriBuilder to render as a string.</param> + /// <returns>The string version of the Uri.</returns> + internal static string ToStringWithImpliedPorts(this UriBuilder builder) { + Requires.NotNull(builder, "builder"); + Contract.Ensures(Contract.Result<string>() != null); + + // We only check for implied ports on HTTP and HTTPS schemes since those + // are the only ones supported by OpenID anyway. + if ((builder.Port == 80 && string.Equals(builder.Scheme, "http", StringComparison.OrdinalIgnoreCase)) || + (builder.Port == 443 && string.Equals(builder.Scheme, "https", StringComparison.OrdinalIgnoreCase))) { + // An implied port may be removed. + string url = builder.ToString(); + + // Be really careful to only remove the first :80 or :443 so we are guaranteed + // we're removing only the port (and not something in the query string that + // looks like a port. + string result = Regex.Replace(url, @"^(https?://[^:]+):\d+", m => m.Groups[1].Value, RegexOptions.IgnoreCase); + Contract.Assume(result != null); // Regex.Replace never returns null + return result; + } else { + // The port must be explicitly given anyway. + return builder.ToString(); + } + } + + /// <summary> + /// Validates that a URL will be resolvable at runtime. + /// </summary> + /// <param name="page">The page hosting the control that receives this URL as a property.</param> + /// <param name="designMode">If set to <c>true</c> the page is in design-time mode rather than runtime mode.</param> + /// <param name="value">The URI to check.</param> + /// <exception cref="UriFormatException">Thrown if the given URL is not a valid, resolvable URI.</exception> + [SuppressMessage("Microsoft.Usage", "CA1806:DoNotIgnoreMethodResults", MessageId = "System.Uri", Justification = "Just to throw an exception on invalid input.")] + internal static void ValidateResolvableUrl(Page page, bool designMode, string value) { + if (string.IsNullOrEmpty(value)) { + return; + } + + if (page != null && !designMode) { + Contract.Assume(page.Request != null); + + // Validate new value by trying to construct a Realm object based on it. + string relativeUrl = page.ResolveUrl(value); + Contract.Assume(page.Request.Url != null); + Contract.Assume(relativeUrl != null); + new Uri(page.Request.Url, relativeUrl); // throws an exception on failure. + } else { + // We can't fully test it, but it should start with either ~/ or a protocol. + if (Regex.IsMatch(value, @"^https?://")) { + new Uri(value); // make sure it's fully-qualified, but ignore wildcards + } else if (value.StartsWith("~/", StringComparison.Ordinal)) { + // this is valid too + } else { + throw new UriFormatException(); + } + } + } + } +} diff --git a/src/DotNetOpenAuth.Core/Util.cs b/src/DotNetOpenAuth.Core/Util.cs new file mode 100644 index 0000000..6b63a36 --- /dev/null +++ b/src/DotNetOpenAuth.Core/Util.cs @@ -0,0 +1,229 @@ +//----------------------------------------------------------------------- +// <copyright file="Util.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- +namespace DotNetOpenAuth { + using System; + using System.Collections.Generic; + using System.Diagnostics.Contracts; + using System.Globalization; + using System.Net; + using System.Reflection; + using System.Text; + using System.Web; + using System.Web.UI; + + using DotNetOpenAuth.Configuration; + using DotNetOpenAuth.Messaging; + + /// <summary> + /// A grab-bag utility class. + /// </summary> + [ContractVerification(true)] + internal static class Util { + /// <summary> + /// The base namespace for this library from which all other namespaces derive. + /// </summary> + internal const string DefaultNamespace = "DotNetOpenAuth"; + + /// <summary> + /// The web.config file-specified provider of web resource URLs. + /// </summary> + private static IEmbeddedResourceRetrieval embeddedResourceRetrieval = MessagingElement.Configuration.EmbeddedResourceRetrievalProvider.CreateInstance(null, false); + + /// <summary> + /// Gets a human-readable description of the library name and version, including + /// whether the build is an official or private one. + /// </summary> + public static string LibraryVersion { + get { + string assemblyFullName = Assembly.GetExecutingAssembly().FullName; + bool official = assemblyFullName.Contains("PublicKeyToken=2780ccd10d57b246"); + + // We use InvariantCulture since this is used for logging. + return string.Format(CultureInfo.InvariantCulture, "{0} ({1})", assemblyFullName, official ? "official" : "private"); + } + } + + /// <summary> + /// Tests for equality between two objects. Safely handles the case where one or both are null. + /// </summary> + /// <typeparam name="T">The type of objects been checked for equality.</typeparam> + /// <param name="first">The first object.</param> + /// <param name="second">The second object.</param> + /// <returns><c>true</c> if the two objects are equal; <c>false</c> otherwise.</returns> + internal static bool EqualsNullSafe<T>(this T first, T second) where T : class { + // If one is null and the other is not... + if (object.ReferenceEquals(first, null) ^ object.ReferenceEquals(second, null)) { + return false; + } + + // If both are null... (we only check one because we already know both are either null or non-null) + if (object.ReferenceEquals(first, null)) { + return true; + } + + // Neither are null. Delegate to the Equals method. + return first.Equals(second); + } + + /// <summary> + /// Prepares a dictionary for printing as a string. + /// </summary> + /// <typeparam name="K">The type of the key.</typeparam> + /// <typeparam name="V">The type of the value.</typeparam> + /// <param name="pairs">The dictionary or sequence of name-value pairs.</param> + /// <returns>An object whose ToString method will perform the actual work of generating the string.</returns> + /// <remarks> + /// The work isn't done until (and if) the + /// <see cref="Object.ToString"/> method is actually called, which makes it great + /// for logging complex objects without being in a conditional block. + /// </remarks> + internal static object ToStringDeferred<K, V>(this IEnumerable<KeyValuePair<K, V>> pairs) { + return new DelayedToString<IEnumerable<KeyValuePair<K, V>>>( + pairs, + p => { + ////Contract.Requires(pairs != null); // CC: anonymous method can't handle it + ErrorUtilities.VerifyArgumentNotNull(pairs, "pairs"); + var dictionary = pairs as IDictionary<K, V>; + StringBuilder sb = new StringBuilder(dictionary != null ? dictionary.Count * 40 : 200); + foreach (var pair in pairs) { + sb.AppendFormat("\t{0}: {1}{2}", pair.Key, pair.Value, Environment.NewLine); + } + return sb.ToString(); + }); + } + + /// <summary> + /// Offers deferred ToString processing for a list of elements, that are assumed + /// to generate just a single-line string. + /// </summary> + /// <typeparam name="T">The type of elements contained in the list.</typeparam> + /// <param name="list">The list of elements.</param> + /// <returns>An object whose ToString method will perform the actual work of generating the string.</returns> + internal static object ToStringDeferred<T>(this IEnumerable<T> list) { + return ToStringDeferred<T>(list, false); + } + + /// <summary> + /// Offers deferred ToString processing for a list of elements. + /// </summary> + /// <typeparam name="T">The type of elements contained in the list.</typeparam> + /// <param name="list">The list of elements.</param> + /// <param name="multiLineElements">if set to <c>true</c>, special formatting will be applied to the output to make it clear where one element ends and the next begins.</param> + /// <returns>An object whose ToString method will perform the actual work of generating the string.</returns> + [ContractVerification(false)] + internal static object ToStringDeferred<T>(this IEnumerable<T> list, bool multiLineElements) { + return new DelayedToString<IEnumerable<T>>( + list, + l => { + // Code contracts not allowed in generator methods. + ErrorUtilities.VerifyArgumentNotNull(l, "l"); + + string newLine = Environment.NewLine; + ////Contract.Assume(newLine != null && newLine.Length > 0); + StringBuilder sb = new StringBuilder(); + if (multiLineElements) { + sb.AppendLine("[{"); + foreach (T obj in l) { + // Prepare the string repersentation of the object + string objString = obj != null ? obj.ToString() : "<NULL>"; + + // Indent every line printed + objString = objString.Replace(newLine, Environment.NewLine + "\t"); + sb.Append("\t"); + sb.Append(objString); + + if (!objString.EndsWith(Environment.NewLine, StringComparison.Ordinal)) { + sb.AppendLine(); + } + sb.AppendLine("}, {"); + } + if (sb.Length > 2 + Environment.NewLine.Length) { // if anything was in the enumeration + sb.Length -= 2 + Environment.NewLine.Length; // trim off the last ", {\r\n" + } else { + sb.Length -= 1 + Environment.NewLine.Length; // trim off the opening { + } + sb.Append("]"); + return sb.ToString(); + } else { + sb.Append("{"); + foreach (T obj in l) { + sb.Append(obj != null ? obj.ToString() : "<NULL>"); + sb.AppendLine(","); + } + if (sb.Length > 1) { + sb.Length -= 1; + } + sb.Append("}"); + return sb.ToString(); + } + }); + } + + /// <summary> + /// Gets the web resource URL from a Page or <see cref="IEmbeddedResourceRetrieval"/> object. + /// </summary> + /// <param name="someTypeInResourceAssembly">Some type in resource assembly.</param> + /// <param name="manifestResourceName">Name of the manifest resource.</param> + /// <returns>An absolute URL</returns> + internal static string GetWebResourceUrl(Type someTypeInResourceAssembly, string manifestResourceName) { + Page page; + IEmbeddedResourceRetrieval retrieval; + + if (embeddedResourceRetrieval != null) { + Uri url = embeddedResourceRetrieval.GetWebResourceUrl(someTypeInResourceAssembly, manifestResourceName); + return url != null ? url.AbsoluteUri : null; + } else if ((page = HttpContext.Current.CurrentHandler as Page) != null) { + return page.ClientScript.GetWebResourceUrl(someTypeInResourceAssembly, manifestResourceName); + } else if ((retrieval = HttpContext.Current.CurrentHandler as IEmbeddedResourceRetrieval) != null) { + return retrieval.GetWebResourceUrl(someTypeInResourceAssembly, manifestResourceName).AbsoluteUri; + } else { + throw new InvalidOperationException( + string.Format( + CultureInfo.CurrentCulture, + Strings.EmbeddedResourceUrlProviderRequired, + string.Join(", ", new string[] { typeof(Page).FullName, typeof(IEmbeddedResourceRetrieval).FullName }))); + } + } + + /// <summary> + /// Manages an individual deferred ToString call. + /// </summary> + /// <typeparam name="T">The type of object to be serialized as a string.</typeparam> + private class DelayedToString<T> { + /// <summary> + /// The object that will be serialized if called upon. + /// </summary> + private readonly T obj; + + /// <summary> + /// The method used to serialize <see cref="obj"/> to string form. + /// </summary> + private readonly Func<T, string> toString; + + /// <summary> + /// Initializes a new instance of the DelayedToString class. + /// </summary> + /// <param name="obj">The object that may be serialized to string form.</param> + /// <param name="toString">The method that will serialize the object if called upon.</param> + public DelayedToString(T obj, Func<T, string> toString) { + Requires.NotNull(toString, "toString"); + + this.obj = obj; + this.toString = toString; + } + + /// <summary> + /// Returns a <see cref="T:System.String"/> that represents the current <see cref="T:System.Object"/>. + /// </summary> + /// <returns> + /// A <see cref="T:System.String"/> that represents the current <see cref="T:System.Object"/>. + /// </returns> + public override string ToString() { + return this.toString(this.obj) ?? string.Empty; + } + } + } +} diff --git a/src/DotNetOpenAuth/ComponentModel/IssuersSuggestions.cs b/src/DotNetOpenAuth.InfoCard.UI/ComponentModel/IssuersSuggestions.cs index dc41843..dc41843 100644 --- a/src/DotNetOpenAuth/ComponentModel/IssuersSuggestions.cs +++ b/src/DotNetOpenAuth.InfoCard.UI/ComponentModel/IssuersSuggestions.cs diff --git a/src/DotNetOpenAuth.InfoCard.UI/ComponentModel/UriConverter.cs b/src/DotNetOpenAuth.InfoCard.UI/ComponentModel/UriConverter.cs new file mode 100644 index 0000000..5111846 --- /dev/null +++ b/src/DotNetOpenAuth.InfoCard.UI/ComponentModel/UriConverter.cs @@ -0,0 +1,117 @@ +//----------------------------------------------------------------------- +// <copyright file="UriConverter.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.ComponentModel { + using System; + using System.Collections; + using System.ComponentModel; + using System.ComponentModel.Design.Serialization; + using System.Diagnostics.Contracts; + using System.Linq; + using System.Reflection; + + /// <summary> + /// A design-time helper to allow controls to have properties + /// of type <see cref="Uri"/>. + /// </summary> + public class UriConverter : ConverterBase<Uri> { + /// <summary> + /// Initializes a new instance of the UriConverter class. + /// </summary> + protected UriConverter() { + } + + /// <summary> + /// Gets the type to reflect over to extract the well known values. + /// </summary> + protected virtual Type WellKnownValuesType { + get { return null; } + } + + /// <summary> + /// Returns whether the given value object is valid for this type and for the specified context. + /// </summary> + /// <param name="context">An <see cref="T:System.ComponentModel.ITypeDescriptorContext"/> that provides a format context.</param> + /// <param name="value">The <see cref="T:System.Object"/> to test for validity.</param> + /// <returns> + /// true if the specified value is valid for this object; otherwise, false. + /// </returns> + public override bool IsValid(ITypeDescriptorContext context, object value) { + Uri uriValue; + string stringValue; + if ((uriValue = value as Uri) != null) { + return uriValue.IsAbsoluteUri; + } else if ((stringValue = value as string) != null) { + Uri result; + return stringValue.Length == 0 || Uri.TryCreate(stringValue, UriKind.Absolute, out result); + } else { + return false; + } + } + + /// <summary> + /// Converts a value from its string representation to its strongly-typed object. + /// </summary> + /// <param name="value">The value.</param> + /// <returns>The strongly-typed object.</returns> + [Pure] + protected override Uri ConvertFrom(string value) { + return string.IsNullOrEmpty(value) ? null : new Uri(value); + } + + /// <summary> + /// Creates the reflection instructions for recreating an instance later. + /// </summary> + /// <param name="value">The value to recreate later.</param> + /// <returns> + /// The description of how to recreate an instance. + /// </returns> + [Pure] + protected override InstanceDescriptor CreateFrom(Uri value) { + if (value == null) { + return null; + } + + MemberInfo uriCtor = typeof(Uri).GetConstructor(new Type[] { typeof(string) }); + return CreateInstanceDescriptor(uriCtor, new object[] { value.AbsoluteUri }); + } + + /// <summary> + /// Converts the strongly-typed value to a string. + /// </summary> + /// <param name="value">The value to convert.</param> + /// <returns>The string representation of the object.</returns> + [Pure] + protected override string ConvertToString(Uri value) { + if (value == null) { + return null; + } + + return value.AbsoluteUri; + } + + /// <summary> + /// Gets the standard claim type URIs known to the library. + /// </summary> + /// <returns>An array of the standard claim types.</returns> + [Pure] + protected override ICollection GetStandardValuesForCache() { + if (this.WellKnownValuesType != null) { + var fields = from field in this.WellKnownValuesType.GetFields(BindingFlags.Static | BindingFlags.Public) + let value = (string)field.GetValue(null) + where value != null + select new Uri(value); + var properties = from prop in this.WellKnownValuesType.GetProperties(BindingFlags.Static | BindingFlags.Public) + let value = (string)prop.GetValue(null, null) + where value != null + select new Uri(value); + return fields.Concat(properties).ToArray(); + } else { + return new Uri[0]; + } + } + } +} diff --git a/src/DotNetOpenAuth.InfoCard.UI/DotNetOpenAuth.InfoCard.UI.csproj b/src/DotNetOpenAuth.InfoCard.UI/DotNetOpenAuth.InfoCard.UI.csproj new file mode 100644 index 0000000..f1aac54 --- /dev/null +++ b/src/DotNetOpenAuth.InfoCard.UI/DotNetOpenAuth.InfoCard.UI.csproj @@ -0,0 +1,68 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), EnlistmentInfo.props))\EnlistmentInfo.props" Condition=" '$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), EnlistmentInfo.props))' != '' " /> + <PropertyGroup> + <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> + <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> + </PropertyGroup> + <Import Project="$(ProjectRoot)tools\DotNetOpenAuth.props" /> + <PropertyGroup> + <SchemaVersion>2.0</SchemaVersion> + <ProjectGuid>{E040EB58-B4D2-457B-A023-AE6EF3BD34DE}</ProjectGuid> + <AppDesignerFolder>Properties</AppDesignerFolder> + <AssemblyName>DotNetOpenAuth.InfoCard.UI</AssemblyName> + </PropertyGroup> + <Import Project="$(ProjectRoot)tools\DotNetOpenAuth.Product.props" /> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> + </PropertyGroup> + <ItemGroup> + <Compile Include="ComponentModel\IssuersSuggestions.cs" /> + <Compile Include="ComponentModel\UriConverter.cs" /> + <Compile Include="InfoCard\ClaimType.cs" /> + <Compile Include="InfoCard\InfoCardImage.cs" /> + <Compile Include="Properties\AssemblyInfo.cs" /> + </ItemGroup> + <ItemGroup Condition=" '$(NoUIControls)' != 'true' "> + <Compile Include="InfoCard\ReceivingTokenEventArgs.cs" /> + <Compile Include="InfoCard\TokenProcessingErrorEventArgs.cs" /> + <Compile Include="InfoCard\InfoCardSelector.cs" /> + <Compile Include="InfoCard\ReceivedTokenEventArgs.cs" /> + </ItemGroup> + <ItemGroup> + <EmbeddedResource Include="InfoCard\infocard_114x80.png" /> + <EmbeddedResource Include="InfoCard\infocard_14x10.png" /> + <EmbeddedResource Include="InfoCard\infocard_214x150.png" /> + <EmbeddedResource Include="InfoCard\infocard_23x16.png" /> + <EmbeddedResource Include="InfoCard\infocard_300x210.png" /> + <EmbeddedResource Include="InfoCard\infocard_34x24.png" /> + <EmbeddedResource Include="InfoCard\infocard_365x256.png" /> + <EmbeddedResource Include="InfoCard\infocard_41x29.png" /> + <EmbeddedResource Include="InfoCard\infocard_50x35.png" /> + <EmbeddedResource Include="InfoCard\infocard_60x42.png" /> + <EmbeddedResource Include="InfoCard\infocard_71x50.png" /> + <EmbeddedResource Include="InfoCard\infocard_81x57.png" /> + <EmbeddedResource Include="InfoCard\infocard_92x64.png" /> + <EmbeddedResource Include="InfoCard\SupportingScript.js"> + <Copyright>$(StandardCopyright)</Copyright> + </EmbeddedResource> + </ItemGroup> + <ItemGroup> + <ProjectReference Include="..\DotNetOpenAuth.InfoCard\DotNetOpenAuth.InfoCard.csproj"> + <Project>{408D10B8-34BA-4CBD-B7AA-FEB1907ABA4C}</Project> + <Name>DotNetOpenAuth.InfoCard</Name> + </ProjectReference> + <ProjectReference Include="..\DotNetOpenAuth.Core.UI\DotNetOpenAuth.Core.UI.csproj"> + <Project>{173E7B8D-E751-46E2-A133-F72297C0D2F4}</Project> + <Name>DotNetOpenAuth.Core.UI</Name> + </ProjectReference> + <ProjectReference Include="..\DotNetOpenAuth.Core\DotNetOpenAuth.Core.csproj"> + <Project>{60426312-6AE5-4835-8667-37EDEA670222}</Project> + <Name>DotNetOpenAuth.Core</Name> + </ProjectReference> + </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))' != '' " /> +</Project>
\ No newline at end of file diff --git a/src/DotNetOpenAuth/InfoCard/ClaimType.cs b/src/DotNetOpenAuth.InfoCard.UI/InfoCard/ClaimType.cs index 9d3056a..9d3056a 100644 --- a/src/DotNetOpenAuth/InfoCard/ClaimType.cs +++ b/src/DotNetOpenAuth.InfoCard.UI/InfoCard/ClaimType.cs diff --git a/src/DotNetOpenAuth/InfoCard/InfoCardImage.cs b/src/DotNetOpenAuth.InfoCard.UI/InfoCard/InfoCardImage.cs index 247f461..247f461 100644 --- a/src/DotNetOpenAuth/InfoCard/InfoCardImage.cs +++ b/src/DotNetOpenAuth.InfoCard.UI/InfoCard/InfoCardImage.cs diff --git a/src/DotNetOpenAuth.InfoCard.UI/InfoCard/InfoCardSelector.cs b/src/DotNetOpenAuth.InfoCard.UI/InfoCard/InfoCardSelector.cs new file mode 100644 index 0000000..b1d5bfd --- /dev/null +++ b/src/DotNetOpenAuth.InfoCard.UI/InfoCard/InfoCardSelector.cs @@ -0,0 +1,772 @@ +//----------------------------------------------------------------------- +// <copyright file="InfoCardSelector.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// Certain elements are Copyright (c) 2007 Dominick Baier. +// </copyright> +//----------------------------------------------------------------------- + +[assembly: System.Web.UI.WebResource(DotNetOpenAuth.InfoCard.InfoCardSelector.ScriptResourceName, "text/javascript")] + +namespace DotNetOpenAuth.InfoCard { + using System; + using System.Collections.ObjectModel; + using System.ComponentModel; + using System.Diagnostics.CodeAnalysis; + using System.Diagnostics.Contracts; + using System.Drawing.Design; + using System.Globalization; + using System.Linq; + using System.Text; + using System.Text.RegularExpressions; + using System.Web; + using System.Web.UI; + using System.Web.UI.HtmlControls; + using System.Web.UI.WebControls; + using System.Xml; + using DotNetOpenAuth.Messaging; + + /// <summary> + /// The style to use for NOT displaying a hidden region. + /// </summary> + public enum RenderMode { + /// <summary> + /// A hidden region should be invisible while still occupying space in the page layout. + /// </summary> + Static, + + /// <summary> + /// A hidden region should collapse so that it does not occupy space in the page layout. + /// </summary> + Dynamic + } + + /// <summary> + /// An Information Card selector ASP.NET control. + /// </summary> + [ParseChildren(true, "ClaimsRequested")] + [PersistChildren(false)] + [DefaultEvent("ReceivedToken")] + [ToolboxData("<{0}:InfoCardSelector runat=\"server\"><ClaimsRequested><{0}:ClaimType Name=\"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/privatepersonalidentifier\" /></ClaimsRequested><UnsupportedTemplate><p>Your browser does not support Information Cards.</p></UnsupportedTemplate></{0}:InfoCardSelector>")] + [ContractVerification(true)] + public class InfoCardSelector : CompositeControl, IPostBackEventHandler { + /// <summary> + /// The resource name for getting at the SupportingScript.js embedded manifest stream. + /// </summary> + internal const string ScriptResourceName = "DotNetOpenAuth.InfoCard.SupportingScript.js"; + + #region Property constants + + /// <summary> + /// Default value for the <see cref="RenderMode"/> property. + /// </summary> + private const RenderMode RenderModeDefault = RenderMode.Dynamic; + + /// <summary> + /// Default value for the <see cref="AutoPostBack"/> property. + /// </summary> + private const bool AutoPostBackDefault = true; + + /// <summary> + /// Default value for the <see cref="AutoPopup"/> property. + /// </summary> + private const bool AutoPopupDefault = false; + + /// <summary> + /// Default value for the <see cref="PrivacyUrl"/> property. + /// </summary> + private const string PrivacyUrlDefault = ""; + + /// <summary> + /// Default value for the <see cref="PrivacyVersion"/> property. + /// </summary> + private const string PrivacyVersionDefault = ""; + + /// <summary> + /// Default value for the <see cref="InfoCardImage"/> property. + /// </summary> + private const InfoCardImageSize InfoCardImageDefault = InfoCardImage.DefaultImageSize; + + /// <summary> + /// Default value for the <see cref="IssuerPolicy"/> property. + /// </summary> + private const string IssuerPolicyDefault = ""; + + /// <summary> + /// Default value for the <see cref="Issuer"/> property. + /// </summary> + private const string IssuerDefault = WellKnownIssuers.SelfIssued; + + /// <summary> + /// The default value for the <see cref="TokenType"/> property. + /// </summary> + private const string TokenTypeDefault = "urn:oasis:names:tc:SAML:1.0:assertion"; + + /// <summary> + /// The viewstate key for storing the <see cref="Issuer" /> property. + /// </summary> + private const string IssuerViewStateKey = "Issuer"; + + /// <summary> + /// The viewstate key for storing the <see cref="IssuerPolicy" /> property. + /// </summary> + private const string IssuerPolicyViewStateKey = "IssuerPolicy"; + + /// <summary> + /// The viewstate key for storing the <see cref="AutoPopup" /> property. + /// </summary> + private const string AutoPopupViewStateKey = "AutoPopup"; + + /// <summary> + /// The viewstate key for storing the <see cref="ClaimsRequested" /> property. + /// </summary> + private const string ClaimsRequestedViewStateKey = "ClaimsRequested"; + + /// <summary> + /// The viewstate key for storing the <see cref="TokenType" /> property. + /// </summary> + private const string TokenTypeViewStateKey = "TokenType"; + + /// <summary> + /// The viewstate key for storing the <see cref="PrivacyUrl" /> property. + /// </summary> + private const string PrivacyUrlViewStateKey = "PrivacyUrl"; + + /// <summary> + /// The viewstate key for storing the <see cref="PrivacyVersion" /> property. + /// </summary> + private const string PrivacyVersionViewStateKey = "PrivacyVersion"; + + /// <summary> + /// The viewstate key for storing the <see cref="Audience" /> property. + /// </summary> + private const string AudienceViewStateKey = "Audience"; + + /// <summary> + /// The viewstate key for storing the <see cref="AutoPostBack" /> property. + /// </summary> + private const string AutoPostBackViewStateKey = "AutoPostBack"; + + /// <summary> + /// The viewstate key for storing the <see cref="ImageSize" /> property. + /// </summary> + private const string ImageSizeViewStateKey = "ImageSize"; + + /// <summary> + /// The viewstate key for storing the <see cref="RenderMode" /> property. + /// </summary> + private const string RenderModeViewStateKey = "RenderMode"; + + #endregion + + #region Categories + + /// <summary> + /// The "Behavior" property category. + /// </summary> + private const string BehaviorCategory = "Behavior"; + + /// <summary> + /// The "Appearance" property category. + /// </summary> + private const string AppearanceCategory = "Appearance"; + + /// <summary> + /// The "InfoCard" property category. + /// </summary> + private const string InfoCardCategory = "InfoCard"; + + #endregion + + /// <summary> + /// The panel containing the controls to display if InfoCard is supported in the user agent. + /// </summary> + private Panel infoCardSupportedPanel; + + /// <summary> + /// The panel containing the controls to display if InfoCard is NOT supported in the user agent. + /// </summary> + private Panel infoCardNotSupportedPanel; + + /// <summary> + /// Recalls whether the <see cref="Audience"/> property has been set yet, + /// so its default can be set as soon as possible without overwriting + /// an intentional value. + /// </summary> + private bool audienceSet; + + /// <summary> + /// Initializes a new instance of the <see cref="InfoCardSelector"/> class. + /// </summary> + public InfoCardSelector() { + this.ToolTip = InfoCardStrings.SelectorClickPrompt; + Reporting.RecordFeatureUse(this); + } + + /// <summary> + /// Occurs when an InfoCard has been submitted but not decoded yet. + /// </summary> + [Category(InfoCardCategory)] + public event EventHandler<ReceivingTokenEventArgs> ReceivingToken; + + /// <summary> + /// Occurs when an InfoCard has been submitted and decoded. + /// </summary> + [Category(InfoCardCategory)] + public event EventHandler<ReceivedTokenEventArgs> ReceivedToken; + + /// <summary> + /// Occurs when an InfoCard token is submitted but an error occurs in processing. + /// </summary> + [Category(InfoCardCategory)] + public event EventHandler<TokenProcessingErrorEventArgs> TokenProcessingError; + + #region Properties + + /// <summary> + /// Gets the set of claims that are requested from the Information Card. + /// </summary> + [Description("Specifies the required and optional claims.")] + [PersistenceMode(PersistenceMode.InnerProperty), Category(InfoCardCategory)] + public Collection<ClaimType> ClaimsRequested { + get { + Contract.Ensures(Contract.Result<Collection<ClaimType>>() != null); + if (this.ViewState[ClaimsRequestedViewStateKey] == null) { + var claims = new Collection<ClaimType>(); + this.ViewState[ClaimsRequestedViewStateKey] = claims; + return claims; + } else { + return (Collection<ClaimType>)this.ViewState[ClaimsRequestedViewStateKey]; + } + } + } + + /// <summary> + /// Gets or sets the issuer URI. + /// </summary> + [Description("When receiving managed cards, this is the only Issuer whose cards will be accepted.")] + [Category(InfoCardCategory), DefaultValue(IssuerDefault)] + [TypeConverter(typeof(ComponentModel.IssuersSuggestions))] + public string Issuer { + get { return (string)this.ViewState[IssuerViewStateKey] ?? IssuerDefault; } + set { this.ViewState[IssuerViewStateKey] = value; } + } + + /// <summary> + /// Gets or sets the issuer policy URI. + /// </summary> + [Description("Specifies the URI of the issuer MEX endpoint")] + [Category(InfoCardCategory), DefaultValue(IssuerPolicyDefault)] + public string IssuerPolicy { + get { return (string)this.ViewState[IssuerPolicyViewStateKey] ?? IssuerPolicyDefault; } + set { this.ViewState[IssuerPolicyViewStateKey] = value; } + } + + /// <summary> + /// Gets or sets the URL to this site's privacy policy. + /// </summary> + [Description("The URL to this site's privacy policy.")] + [Category(InfoCardCategory), DefaultValue(PrivacyUrlDefault)] + [SuppressMessage("Microsoft.Usage", "CA1806:DoNotIgnoreMethodResults", MessageId = "System.Uri", Justification = "We construct a Uri to validate the format of the string.")] + [SuppressMessage("Microsoft.Usage", "CA2234:PassSystemUriObjectsInsteadOfStrings", Justification = "That overload is NOT the same.")] + [SuppressMessage("Microsoft.Design", "CA1056:UriPropertiesShouldNotBeStrings", Justification = "This can take ~/ paths.")] + public string PrivacyUrl { + get { + return (string)this.ViewState[PrivacyUrlViewStateKey] ?? PrivacyUrlDefault; + } + + set { + ErrorUtilities.VerifyOperation(string.IsNullOrEmpty(value) || this.Page == null || this.DesignMode || (HttpContext.Current != null && HttpContext.Current.Request != null), MessagingStrings.HttpContextRequired); + if (!string.IsNullOrEmpty(value)) { + if (this.Page != null && !this.DesignMode) { + // Validate new value by trying to construct a Uri based on it. + new Uri(new HttpRequestInfo(HttpContext.Current.Request).UrlBeforeRewriting, this.Page.ResolveUrl(value)); // throws an exception on failure. + } else { + // We can't fully test it, but it should start with either ~/ or a protocol. + if (Regex.IsMatch(value, @"^https?://")) { + new Uri(value); // make sure it's fully-qualified, but ignore wildcards + } else if (value.StartsWith("~/", StringComparison.Ordinal)) { + // this is valid too + } else { + throw new UriFormatException(); + } + } + } + + this.ViewState[PrivacyUrlViewStateKey] = value; + } + } + + /// <summary> + /// Gets or sets the version of the privacy policy file. + /// </summary> + [Description("Specifies the version of the privacy policy file")] + [Category(InfoCardCategory), DefaultValue(PrivacyVersionDefault)] + public string PrivacyVersion { + get { return (string)this.ViewState[PrivacyVersionViewStateKey] ?? PrivacyVersionDefault; } + set { this.ViewState[PrivacyVersionViewStateKey] = value; } + } + + /// <summary> + /// Gets or sets the URI that must be found for the SAML token's intended audience + /// in order for the token to be processed. + /// </summary> + /// <value>Typically the URI of the page hosting the control, or <c>null</c> to disable audience verification.</value> + /// <remarks> + /// Disabling audience verification introduces a security risk + /// because tokens can be redirected to allow access to unintended resources. + /// </remarks> + [Description("Specifies the URI that must be found for the SAML token's intended audience.")] + [Bindable(true), Category(InfoCardCategory)] + [TypeConverter(typeof(ComponentModel.UriConverter))] + [UrlProperty, Editor("System.Web.UI.Design.UrlEditor, System.Design, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", typeof(UITypeEditor))] + public Uri Audience { + get { + return (Uri)this.ViewState[AudienceViewStateKey]; + } + + set { + this.ViewState[AudienceViewStateKey] = value; + this.audienceSet = true; + } + } + + /// <summary> + /// Gets or sets a value indicating whether a postback will automatically + /// be invoked when the user selects an Information Card. + /// </summary> + [Description("Specifies if the pages automatically posts back after the user has selected a card")] + [Category(BehaviorCategory), DefaultValue(AutoPostBackDefault)] + public bool AutoPostBack { + get { return (bool)(this.ViewState[AutoPostBackViewStateKey] ?? AutoPostBackDefault); } + set { this.ViewState[AutoPostBackViewStateKey] = value; } + } + + /// <summary> + /// Gets or sets the size of the standard InfoCard image to display. + /// </summary> + /// <value>The default size is 114x80.</value> + [Description("The size of the InfoCard image to use. Defaults to 114x80.")] + [DefaultValue(InfoCardImageDefault), Category(AppearanceCategory)] + public InfoCardImageSize ImageSize { + get { return (InfoCardImageSize)(this.ViewState[ImageSizeViewStateKey] ?? InfoCardImageDefault); } + set { this.ViewState[ImageSizeViewStateKey] = value; } + } + + /// <summary> + /// Gets or sets the template to display when the user agent lacks + /// an Information Card selector. + /// </summary> + [Browsable(false), DefaultValue("")] + [PersistenceMode(PersistenceMode.InnerProperty), TemplateContainer(typeof(InfoCardSelector))] + public virtual ITemplate UnsupportedTemplate { get; set; } + + /// <summary> + /// Gets or sets a value indicating whether a hidden region (either + /// the unsupported or supported InfoCard HTML) + /// collapses or merely becomes invisible when it is not to be displayed. + /// </summary> + [Description("Whether the hidden region collapses or merely becomes invisible.")] + [Category(AppearanceCategory), DefaultValue(RenderModeDefault)] + public RenderMode RenderMode { + get { return (RenderMode)(this.ViewState[RenderModeViewStateKey] ?? RenderModeDefault); } + set { this.ViewState[RenderModeViewStateKey] = value; } + } + + /// <summary> + /// Gets or sets a value indicating whether the identity selector will be triggered at page load. + /// </summary> + [Description("Controls whether the InfoCard selector automatically appears when the page is loaded.")] + [Category(BehaviorCategory), DefaultValue(AutoPopupDefault)] + public bool AutoPopup { + get { return (bool)(this.ViewState[AutoPopupViewStateKey] ?? AutoPopupDefault); } + set { this.ViewState[AutoPopupViewStateKey] = value; } + } + + #endregion + + /// <summary> + /// Gets the name of the hidden field that is used to transport the token back to the server. + /// </summary> + private string HiddenFieldName { + get { return this.ClientID + "_tokenxml"; } + } + + /// <summary> + /// Gets the id of the OBJECT tag that creates the InfoCard Selector. + /// </summary> + private string SelectorObjectId { + get { return this.ClientID + "_cs"; } + } + + /// <summary> + /// Gets the XML token, which will be encrypted if it was received over SSL. + /// </summary> + private string TokenXml { + get { return this.Page.Request.Form[this.HiddenFieldName]; } + } + + /// <summary> + /// Gets or sets the type of token the page is prepared to receive. + /// </summary> + [Description("Specifies the token type. Defaults to SAML 1.0")] + [DefaultValue(TokenTypeDefault), Category(InfoCardCategory)] + private string TokenType { + get { return (string)this.ViewState[TokenTypeViewStateKey] ?? TokenTypeDefault; } + set { this.ViewState[TokenTypeViewStateKey] = value; } + } + + /// <summary> + /// When implemented by a class, enables a server control to process an event raised when a form is posted to the server. + /// </summary> + /// <param name="eventArgument">A <see cref="T:System.String"/> that represents an optional event argument to be passed to the event handler.</param> + void IPostBackEventHandler.RaisePostBackEvent(string eventArgument) { + this.RaisePostBackEvent(eventArgument); + } + + /// <summary> + /// When implemented by a class, enables a server control to process an event raised when a form is posted to the server. + /// </summary> + /// <param name="eventArgument">A <see cref="T:System.String"/> that represents an optional event argument to be passed to the event handler.</param> + [SuppressMessage("Microsoft.Design", "CA1030:UseEventsWhereAppropriate", Justification = "Predefined signature.")] + protected virtual void RaisePostBackEvent(string eventArgument) { + if (!string.IsNullOrEmpty(this.TokenXml)) { + try { + ReceivingTokenEventArgs receivingArgs = this.OnReceivingToken(this.TokenXml); + + if (!receivingArgs.Cancel) { + try { + Token token = Token.Read(this.TokenXml, this.Audience, receivingArgs.DecryptingTokens); + this.OnReceivedToken(token); + } catch (InformationCardException ex) { + this.OnTokenProcessingError(this.TokenXml, ex); + } + } + } catch (XmlException ex) { + this.OnTokenProcessingError(this.TokenXml, ex); + } + } + } + + /// <summary> + /// Fires the <see cref="ReceivingToken"/> event. + /// </summary> + /// <param name="tokenXml">The token XML, prior to any processing.</param> + /// <returns>The event arguments sent to the event handlers.</returns> + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "decryptor", Justification = "By design")] + protected virtual ReceivingTokenEventArgs OnReceivingToken(string tokenXml) { + Requires.NotNull(tokenXml, "tokenXml"); + + var args = new ReceivingTokenEventArgs(tokenXml); + var receivingToken = this.ReceivingToken; + if (receivingToken != null) { + receivingToken(this, args); + } + + return args; + } + + /// <summary> + /// Fires the <see cref="ReceivedToken"/> event. + /// </summary> + /// <param name="token">The token, if it was decrypted.</param> + protected virtual void OnReceivedToken(Token token) { + Requires.NotNull(token, "token"); + + var receivedInfoCard = this.ReceivedToken; + if (receivedInfoCard != null) { + receivedInfoCard(this, new ReceivedTokenEventArgs(token)); + } + } + + /// <summary> + /// Fires the <see cref="TokenProcessingError"/> event. + /// </summary> + /// <param name="unprocessedToken">The unprocessed token.</param> + /// <param name="ex">The exception generated while processing the token.</param> + protected virtual void OnTokenProcessingError(string unprocessedToken, Exception ex) { + Requires.NotNull(unprocessedToken, "unprocessedToken"); + Requires.NotNull(ex, "ex"); + + var tokenProcessingError = this.TokenProcessingError; + if (tokenProcessingError != null) { + TokenProcessingErrorEventArgs args = new TokenProcessingErrorEventArgs(unprocessedToken, ex); + tokenProcessingError(this, args); + } + } + + /// <summary> + /// Raises the <see cref="E:System.Web.UI.Control.Init"/> event. + /// </summary> + /// <param name="e">An <see cref="T:System.EventArgs"/> object that contains the event data.</param> + protected override void OnInit(EventArgs e) { + // Give a default for the Audience property that allows for + // the aspx page to have preset it, and ViewState + // to initialize it (even to null) after this. + if (!this.audienceSet && !this.DesignMode) { + this.Audience = this.Page.Request.Url; + } + + base.OnInit(e); + this.Page.LoadComplete += delegate { this.EnsureChildControls(); }; + } + + /// <summary> + /// Called by the ASP.NET page framework to notify server controls that use composition-based implementation to create any child controls they contain in preparation for posting back or rendering. + /// </summary> + protected override void CreateChildControls() { + base.CreateChildControls(); + + this.Page.ClientScript.RegisterHiddenField(this.HiddenFieldName, string.Empty); + + this.Controls.Add(this.infoCardSupportedPanel = this.CreateInfoCardSupportedPanel()); + this.Controls.Add(this.infoCardNotSupportedPanel = this.CreateInfoCardUnsupportedPanel()); + + this.RenderSupportingScript(); + } + + /// <summary> + /// Raises the <see cref="E:System.Web.UI.Control.PreRender"/> event. + /// </summary> + /// <param name="e">An <see cref="T:System.EventArgs"/> object that contains the event data.</param> + protected override void OnPreRender(EventArgs e) { + base.OnPreRender(e); + + if (!this.DesignMode) { + // The Cardspace selector will display an ugly error to the user if + // the privacy URL is present but the privacy version is not. + ErrorUtilities.VerifyOperation(string.IsNullOrEmpty(this.PrivacyUrl) || !string.IsNullOrEmpty(this.PrivacyVersion), InfoCardStrings.PrivacyVersionRequiredWithPrivacyUrl); + } + + this.RegisterInfoCardSelectorObjectScript(); + } + + /// <summary> + /// Creates a control that renders to <Param Name="{0}" Value="{1}" /> + /// </summary> + /// <param name="name">The parameter name.</param> + /// <param name="value">The parameter value.</param> + /// <returns>The control that renders to the Param tag.</returns> + private static string CreateParamJs(string name, string value) { + Contract.Ensures(Contract.Result<string>() != null); + string scriptFormat = @" objp = document.createElement('param'); + objp.name = {0}; + objp.value = {1}; + obj.appendChild(objp); +"; + return string.Format( + CultureInfo.InvariantCulture, + scriptFormat, + MessagingUtilities.GetSafeJavascriptValue(name), + MessagingUtilities.GetSafeJavascriptValue(value)); + } + + /// <summary> + /// Creates the panel whose contents are displayed to the user + /// on a user agent that has an Information Card selector. + /// </summary> + /// <returns>The Panel control</returns> + [Pure] + private Panel CreateInfoCardSupportedPanel() { + Contract.Ensures(Contract.Result<Panel>() != null); + + Panel supportedPanel = new Panel(); + + try { + if (!this.DesignMode) { + // At the user agent, assume InfoCard is not supported until + // the JavaScript discovers otherwise and reveals this panel. + supportedPanel.Style[HtmlTextWriterStyle.Display] = "none"; + } + + supportedPanel.Controls.Add(this.CreateInfoCardImage()); + + // trigger the selector at page load? + if (this.AutoPopup && !this.Page.IsPostBack) { + this.Page.ClientScript.RegisterStartupScript( + typeof(InfoCardSelector), + "selector_load_trigger", + this.GetInfoCardSelectorActivationScript(true), + true); + } + return supportedPanel; + } catch { + supportedPanel.Dispose(); + throw; + } + } + + /// <summary> + /// Gets the InfoCard selector activation script. + /// </summary> + /// <param name="alwaysPostback">Whether a postback should always immediately follow the selector, even if <see cref="AutoPostBack"/> is <c>false</c>.</param> + /// <returns>The javascript to inject into the surrounding context.</returns> + private string GetInfoCardSelectorActivationScript(bool alwaysPostback) { + // generate call do __doPostback + PostBackOptions options = new PostBackOptions(this); + string postback = string.Empty; + if (alwaysPostback || this.AutoPostBack) { + postback = this.Page.ClientScript.GetPostBackEventReference(options) + ";"; + } + + // generate the onclick script for the image + string invokeScript = string.Format( + CultureInfo.InvariantCulture, + @"if (document.infoCard.activate('{0}', '{1}')) {{ {2} }}", + this.SelectorObjectId, + this.HiddenFieldName, + postback); + + return invokeScript; + } + + /// <summary> + /// Creates the panel whose contents are displayed to the user + /// on a user agent that does not have an Information Card selector. + /// </summary> + /// <returns>The Panel control.</returns> + [Pure] + private Panel CreateInfoCardUnsupportedPanel() { + Contract.Ensures(Contract.Result<Panel>() != null); + + Panel unsupportedPanel = new Panel(); + try { + if (this.UnsupportedTemplate != null) { + this.UnsupportedTemplate.InstantiateIn(unsupportedPanel); + } + return unsupportedPanel; + } catch { + unsupportedPanel.Dispose(); + throw; + } + } + + /// <summary> + /// Adds the javascript that adds the info card selector <object> HTML tag to the page. + /// </summary> + [Pure] + private void RegisterInfoCardSelectorObjectScript() { + string scriptFormat = @"{{ + var obj = document.createElement('object'); + obj.type = 'application/x-informationcard'; + obj.id = {0}; + obj.style.display = 'none'; +"; + StringBuilder script = new StringBuilder(); + script.AppendFormat( + CultureInfo.InvariantCulture, + scriptFormat, + MessagingUtilities.GetSafeJavascriptValue(this.ClientID + "_cs")); + + if (!string.IsNullOrEmpty(this.Issuer)) { + script.AppendLine(CreateParamJs("issuer", this.Issuer)); + } + + if (!string.IsNullOrEmpty(this.IssuerPolicy)) { + script.AppendLine(CreateParamJs("issuerPolicy", this.IssuerPolicy)); + } + + if (!string.IsNullOrEmpty(this.TokenType)) { + script.AppendLine(CreateParamJs("tokenType", this.TokenType)); + } + + string requiredClaims, optionalClaims; + this.GetRequestedClaims(out requiredClaims, out optionalClaims); + ErrorUtilities.VerifyArgument(!string.IsNullOrEmpty(requiredClaims) || !string.IsNullOrEmpty(optionalClaims), InfoCardStrings.EmptyClaimListNotAllowed); + if (!string.IsNullOrEmpty(requiredClaims)) { + script.AppendLine(CreateParamJs("requiredClaims", requiredClaims)); + } + if (!string.IsNullOrEmpty(optionalClaims)) { + script.AppendLine(CreateParamJs("optionalClaims", optionalClaims)); + } + + if (!string.IsNullOrEmpty(this.PrivacyUrl)) { + string privacyUrl = this.DesignMode ? this.PrivacyUrl : new Uri(Page.Request.Url, Page.ResolveUrl(this.PrivacyUrl)).AbsoluteUri; + script.AppendLine(CreateParamJs("privacyUrl", privacyUrl)); + } + + if (!string.IsNullOrEmpty(this.PrivacyVersion)) { + script.AppendLine(CreateParamJs("privacyVersion", this.PrivacyVersion)); + } + + script.AppendLine(@"if (document.infoCard.isSupported()) { document.write(obj.outerHTML); } +}"); + + this.Page.ClientScript.RegisterClientScriptBlock(typeof(InfoCardSelector), this.ClientID + "tag", script.ToString(), true); + } + + /// <summary> + /// Creates the info card clickable image. + /// </summary> + /// <returns>An Image object.</returns> + [Pure] + private Image CreateInfoCardImage() { + // add clickable image + Image image = new Image(); + try { + image.ImageUrl = this.Page.ClientScript.GetWebResourceUrl(typeof(InfoCardSelector), InfoCardImage.GetImageManifestResourceStreamName(this.ImageSize)); + image.AlternateText = InfoCardStrings.SelectorClickPrompt; + image.ToolTip = this.ToolTip; + image.Style[HtmlTextWriterStyle.Cursor] = "hand"; + + image.Attributes["onclick"] = this.GetInfoCardSelectorActivationScript(false); + return image; + } catch { + image.Dispose(); + throw; + } + } + + /// <summary> + /// Compiles lists of requested/required claims that should accompany + /// any submitted Information Card. + /// </summary> + /// <param name="required">A space-delimited list of claim type URIs for claims that must be included in a submitted Information Card.</param> + /// <param name="optional">A space-delimited list of claim type URIs for claims that may optionally be included in a submitted Information Card.</param> + [Pure] + private void GetRequestedClaims(out string required, out string optional) { + Requires.ValidState(this.ClaimsRequested != null); + Contract.Ensures(Contract.ValueAtReturn<string>(out required) != null); + Contract.Ensures(Contract.ValueAtReturn<string>(out optional) != null); + + var nonEmptyClaimTypes = this.ClaimsRequested.Where(c => c.Name != null); + + var optionalClaims = from claim in nonEmptyClaimTypes + where claim.IsOptional + select claim.Name; + var requiredClaims = from claim in nonEmptyClaimTypes + where !claim.IsOptional + select claim.Name; + + string[] requiredClaimsArray = requiredClaims.ToArray(); + string[] optionalClaimsArray = optionalClaims.ToArray(); + required = string.Join(" ", requiredClaimsArray); + optional = string.Join(" ", optionalClaimsArray); + Contract.Assume(required != null); + Contract.Assume(optional != null); + } + + /// <summary> + /// Adds Javascript snippets to the page to help the Information Card selector do its work, + /// or to downgrade gracefully if the user agent lacks an Information Card selector. + /// </summary> + private void RenderSupportingScript() { + Requires.ValidState(this.infoCardSupportedPanel != null); + + this.Page.ClientScript.RegisterClientScriptResource(typeof(InfoCardSelector), ScriptResourceName); + + if (this.RenderMode == RenderMode.Static) { + this.Page.ClientScript.RegisterStartupScript( + typeof(InfoCardSelector), + "SelectorSupportingScript_" + this.ClientID, + string.Format(CultureInfo.InvariantCulture, "document.infoCard.checkStatic('{0}', '{1}');", this.infoCardSupportedPanel.ClientID, this.infoCardNotSupportedPanel.ClientID), + true); + } else if (RenderMode == RenderMode.Dynamic) { + this.Page.ClientScript.RegisterStartupScript( + typeof(InfoCardSelector), + "SelectorSupportingScript_" + this.ClientID, + string.Format(CultureInfo.InvariantCulture, "document.infoCard.checkDynamic('{0}', '{1}');", this.infoCardSupportedPanel.ClientID, this.infoCardNotSupportedPanel.ClientID), + true); + } + } + } +} diff --git a/src/DotNetOpenAuth/InfoCard/ReceivedTokenEventArgs.cs b/src/DotNetOpenAuth.InfoCard.UI/InfoCard/ReceivedTokenEventArgs.cs index f325ff9..f325ff9 100644 --- a/src/DotNetOpenAuth/InfoCard/ReceivedTokenEventArgs.cs +++ b/src/DotNetOpenAuth.InfoCard.UI/InfoCard/ReceivedTokenEventArgs.cs diff --git a/src/DotNetOpenAuth.InfoCard.UI/InfoCard/ReceivingTokenEventArgs.cs b/src/DotNetOpenAuth.InfoCard.UI/InfoCard/ReceivingTokenEventArgs.cs new file mode 100644 index 0000000..d831a84 --- /dev/null +++ b/src/DotNetOpenAuth.InfoCard.UI/InfoCard/ReceivingTokenEventArgs.cs @@ -0,0 +1,100 @@ +//----------------------------------------------------------------------- +// <copyright file="ReceivingTokenEventArgs.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.InfoCard { + using System; + using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; + using System.Diagnostics.Contracts; + using System.IdentityModel.Tokens; + using System.Security.Cryptography.X509Certificates; + + /// <summary> + /// Arguments for the <see cref="InfoCardSelector.ReceivingToken"/> event. + /// </summary> + public class ReceivingTokenEventArgs : EventArgs { + /// <summary> + /// Initializes a new instance of the <see cref="ReceivingTokenEventArgs"/> class. + /// </summary> + /// <param name="tokenXml">The raw token XML, prior to any decryption.</param> + internal ReceivingTokenEventArgs(string tokenXml) { + Requires.NotNull(tokenXml, "tokenXml"); + + this.TokenXml = tokenXml; + this.IsEncrypted = Token.IsEncrypted(this.TokenXml); + this.DecryptingTokens = new List<SecurityToken>(); + } + + /// <summary> + /// Gets a value indicating whether the token is encrypted. + /// </summary> + /// <value> + /// <c>true</c> if the token is encrypted; otherwise, <c>false</c>. + /// </value> + public bool IsEncrypted { get; private set; } + + /// <summary> + /// Gets the raw token XML, prior to any decryption. + /// </summary> + public string TokenXml { get; private set; } + + /// <summary> + /// Gets or sets a value indicating whether processing + /// this token should be canceled. + /// </summary> + /// <value><c>true</c> if cancel; otherwise, <c>false</c>.</value> + /// <remarks> + /// If set the <c>true</c>, the <see cref="InfoCardSelector.ReceivedToken"/> + /// event will never be fired. + /// </remarks> + public bool Cancel { get; set; } + + /// <summary> + /// Gets a list where security tokens such as X.509 certificates may be + /// added to be used for token decryption. + /// </summary> + internal IList<SecurityToken> DecryptingTokens { get; private set; } + + /// <summary> + /// Adds a security token that may be used to decrypt the incoming token. + /// </summary> + /// <param name="securityToken">The security token.</param> + public void AddDecryptingToken(SecurityToken securityToken) { + Requires.NotNull(securityToken, "securityToken"); + this.DecryptingTokens.Add(securityToken); + } + + /// <summary> + /// Adds an X.509 certificate with a private key that may be used to decrypt the incoming token. + /// </summary> + /// <param name="certificate">The certificate.</param> + [SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "False positive")] + public void AddDecryptingToken(X509Certificate2 certificate) { + Requires.NotNull(certificate, "certificate"); + Requires.True(certificate.HasPrivateKey, "certificate"); + var cert = new X509SecurityToken(certificate); + try { + this.AddDecryptingToken(cert); + } catch { + cert.Dispose(); + throw; + } + } + +#if CONTRACTS_FULL + /// <summary> + /// Verifies conditions that should be true for any valid state of this object. + /// </summary> + [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Called by code contracts.")] + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called by code contracts.")] + [ContractInvariantMethod] + private void ObjectInvariant() { + Contract.Invariant(this.TokenXml != null); + Contract.Invariant(this.DecryptingTokens != null); + } +#endif + } +} diff --git a/src/DotNetOpenAuth/InfoCard/SupportingScript.js b/src/DotNetOpenAuth.InfoCard.UI/InfoCard/SupportingScript.js index a883cd7..a883cd7 100644 --- a/src/DotNetOpenAuth/InfoCard/SupportingScript.js +++ b/src/DotNetOpenAuth.InfoCard.UI/InfoCard/SupportingScript.js diff --git a/src/DotNetOpenAuth.InfoCard.UI/InfoCard/TokenProcessingErrorEventArgs.cs b/src/DotNetOpenAuth.InfoCard.UI/InfoCard/TokenProcessingErrorEventArgs.cs new file mode 100644 index 0000000..3ce9f40 --- /dev/null +++ b/src/DotNetOpenAuth.InfoCard.UI/InfoCard/TokenProcessingErrorEventArgs.cs @@ -0,0 +1,50 @@ +//----------------------------------------------------------------------- +// <copyright file="TokenProcessingErrorEventArgs.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- +namespace DotNetOpenAuth.InfoCard { + using System; + using System.Diagnostics.CodeAnalysis; + using System.Diagnostics.Contracts; + + /// <summary> + /// Arguments for the <see cref="InfoCardSelector.TokenProcessingError"/> event. + /// </summary> + public class TokenProcessingErrorEventArgs : EventArgs { + /// <summary> + /// Initializes a new instance of the <see cref="TokenProcessingErrorEventArgs"/> class. + /// </summary> + /// <param name="tokenXml">The token XML.</param> + /// <param name="exception">The exception.</param> + internal TokenProcessingErrorEventArgs(string tokenXml, Exception exception) { + Requires.NotNull(tokenXml, "tokenXml"); + Requires.NotNull(exception, "exception"); + this.TokenXml = tokenXml; + this.Exception = exception; + } + + /// <summary> + /// Gets the raw token XML. + /// </summary> + public string TokenXml { get; private set; } + + /// <summary> + /// Gets the exception that was generated while processing the token. + /// </summary> + public Exception Exception { get; private set; } + +#if CONTRACTS_FULL + /// <summary> + /// Verifies conditions that should be true for any valid state of this object. + /// </summary> + [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Called by code contracts.")] + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called by code contracts.")] + [ContractInvariantMethod] + private void ObjectInvariant() { + Contract.Invariant(this.TokenXml != null); + Contract.Invariant(this.Exception != null); + } +#endif + } +} diff --git a/src/DotNetOpenAuth/InfoCard/infocard_114x80.png b/src/DotNetOpenAuth.InfoCard.UI/InfoCard/infocard_114x80.png Binary files differindex 6dba25f..6dba25f 100644 --- a/src/DotNetOpenAuth/InfoCard/infocard_114x80.png +++ b/src/DotNetOpenAuth.InfoCard.UI/InfoCard/infocard_114x80.png diff --git a/src/DotNetOpenAuth/InfoCard/infocard_14x10.png b/src/DotNetOpenAuth.InfoCard.UI/InfoCard/infocard_14x10.png Binary files differindex d63575d..d63575d 100644 --- a/src/DotNetOpenAuth/InfoCard/infocard_14x10.png +++ b/src/DotNetOpenAuth.InfoCard.UI/InfoCard/infocard_14x10.png diff --git a/src/DotNetOpenAuth/InfoCard/infocard_214x150.png b/src/DotNetOpenAuth.InfoCard.UI/InfoCard/infocard_214x150.png Binary files differindex 71ebc7e..71ebc7e 100644 --- a/src/DotNetOpenAuth/InfoCard/infocard_214x150.png +++ b/src/DotNetOpenAuth.InfoCard.UI/InfoCard/infocard_214x150.png diff --git a/src/DotNetOpenAuth/InfoCard/infocard_23x16.png b/src/DotNetOpenAuth.InfoCard.UI/InfoCard/infocard_23x16.png Binary files differindex 9dbea9f..9dbea9f 100644 --- a/src/DotNetOpenAuth/InfoCard/infocard_23x16.png +++ b/src/DotNetOpenAuth.InfoCard.UI/InfoCard/infocard_23x16.png diff --git a/src/DotNetOpenAuth/InfoCard/infocard_300x210.png b/src/DotNetOpenAuth.InfoCard.UI/InfoCard/infocard_300x210.png Binary files differindex e805b9d..e805b9d 100644 --- a/src/DotNetOpenAuth/InfoCard/infocard_300x210.png +++ b/src/DotNetOpenAuth.InfoCard.UI/InfoCard/infocard_300x210.png diff --git a/src/DotNetOpenAuth/InfoCard/infocard_34x24.png b/src/DotNetOpenAuth.InfoCard.UI/InfoCard/infocard_34x24.png Binary files differindex b863f64..b863f64 100644 --- a/src/DotNetOpenAuth/InfoCard/infocard_34x24.png +++ b/src/DotNetOpenAuth.InfoCard.UI/InfoCard/infocard_34x24.png diff --git a/src/DotNetOpenAuth/InfoCard/infocard_365x256.png b/src/DotNetOpenAuth.InfoCard.UI/InfoCard/infocard_365x256.png Binary files differindex 30092c5..30092c5 100644 --- a/src/DotNetOpenAuth/InfoCard/infocard_365x256.png +++ b/src/DotNetOpenAuth.InfoCard.UI/InfoCard/infocard_365x256.png diff --git a/src/DotNetOpenAuth/InfoCard/infocard_41x29.png b/src/DotNetOpenAuth.InfoCard.UI/InfoCard/infocard_41x29.png Binary files differindex d3c71ae..d3c71ae 100644 --- a/src/DotNetOpenAuth/InfoCard/infocard_41x29.png +++ b/src/DotNetOpenAuth.InfoCard.UI/InfoCard/infocard_41x29.png diff --git a/src/DotNetOpenAuth/InfoCard/infocard_50x35.png b/src/DotNetOpenAuth.InfoCard.UI/InfoCard/infocard_50x35.png Binary files differindex 62ff78b..62ff78b 100644 --- a/src/DotNetOpenAuth/InfoCard/infocard_50x35.png +++ b/src/DotNetOpenAuth.InfoCard.UI/InfoCard/infocard_50x35.png diff --git a/src/DotNetOpenAuth/InfoCard/infocard_60x42.png b/src/DotNetOpenAuth.InfoCard.UI/InfoCard/infocard_60x42.png Binary files differindex 8e920c5..8e920c5 100644 --- a/src/DotNetOpenAuth/InfoCard/infocard_60x42.png +++ b/src/DotNetOpenAuth.InfoCard.UI/InfoCard/infocard_60x42.png diff --git a/src/DotNetOpenAuth/InfoCard/infocard_71x50.png b/src/DotNetOpenAuth.InfoCard.UI/InfoCard/infocard_71x50.png Binary files differindex 9e8f7fb..9e8f7fb 100644 --- a/src/DotNetOpenAuth/InfoCard/infocard_71x50.png +++ b/src/DotNetOpenAuth.InfoCard.UI/InfoCard/infocard_71x50.png diff --git a/src/DotNetOpenAuth/InfoCard/infocard_81x57.png b/src/DotNetOpenAuth.InfoCard.UI/InfoCard/infocard_81x57.png Binary files differindex 48d62b2..48d62b2 100644 --- a/src/DotNetOpenAuth/InfoCard/infocard_81x57.png +++ b/src/DotNetOpenAuth.InfoCard.UI/InfoCard/infocard_81x57.png diff --git a/src/DotNetOpenAuth/InfoCard/infocard_92x64.png b/src/DotNetOpenAuth.InfoCard.UI/InfoCard/infocard_92x64.png Binary files differindex 388e497..388e497 100644 --- a/src/DotNetOpenAuth/InfoCard/infocard_92x64.png +++ b/src/DotNetOpenAuth.InfoCard.UI/InfoCard/infocard_92x64.png diff --git a/src/DotNetOpenAuth.InfoCard.UI/Properties/AssemblyInfo.cs b/src/DotNetOpenAuth.InfoCard.UI/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..63faf9a --- /dev/null +++ b/src/DotNetOpenAuth.InfoCard.UI/Properties/AssemblyInfo.cs @@ -0,0 +1,56 @@ +//----------------------------------------------------------------------- +// <copyright file="AssemblyInfo.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +// We DON'T put an AssemblyVersionAttribute in here because it is generated in the build. + +using System; +using System.Diagnostics.Contracts; +using System.Net; +using System.Reflection; +using System.Resources; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Security; +using System.Security.Permissions; +using System.Web.UI; + +[assembly: TagPrefix("DotNetOpenAuth.InfoCard", "ic")] + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("DotNetOpenAuth InfoCard")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("DotNetOpenAuth")] +[assembly: AssemblyCopyright("Copyright © 2008")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: NeutralResourcesLanguage("en-US")] +[assembly: CLSCompliant(true)] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("7d73990c-47c0-4256-9f20-a893add9e289")] + +[assembly: ContractVerification(true)] + +#if StrongNameSigned +// See comment at top of this file. We need this so that strong-naming doesn't +// keep this assembly from being useful to shared host (medium trust) web sites. +[assembly: AllowPartiallyTrustedCallers] + +[assembly: InternalsVisibleTo("DotNetOpenAuth.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100AD093C3765257C89A7010E853F2C7C741FF92FA8ACE06D7B8254702CAD5CF99104447F63AB05F8BB6F51CE0D81C8C93D2FCE8C20AAFF7042E721CBA16EAAE98778611DED11C0ABC8900DC5667F99B50A9DADEC24DBD8F2C91E3E8AD300EF64F1B4B9536CEB16FB440AF939F57624A9B486F867807C649AE4830EAB88C6C03998")] +[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")] +#else +[assembly: InternalsVisibleTo("DotNetOpenAuth.Test")] +[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] +#endif diff --git a/src/DotNetOpenAuth.InfoCard/DotNetOpenAuth.InfoCard.csproj b/src/DotNetOpenAuth.InfoCard/DotNetOpenAuth.InfoCard.csproj new file mode 100644 index 0000000..09371f1 --- /dev/null +++ b/src/DotNetOpenAuth.InfoCard/DotNetOpenAuth.InfoCard.csproj @@ -0,0 +1,52 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), EnlistmentInfo.props))\EnlistmentInfo.props" Condition=" '$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), EnlistmentInfo.props))' != '' " /> + <PropertyGroup> + <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> + <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> + </PropertyGroup> + <Import Project="$(ProjectRoot)tools\DotNetOpenAuth.props" /> + <PropertyGroup> + <SchemaVersion>2.0</SchemaVersion> + <ProjectGuid>{408D10B8-34BA-4CBD-B7AA-FEB1907ABA4C}</ProjectGuid> + <AppDesignerFolder>Properties</AppDesignerFolder> + <AssemblyName>DotNetOpenAuth.InfoCard</AssemblyName> + </PropertyGroup> + <Import Project="$(ProjectRoot)tools\DotNetOpenAuth.Product.props" /> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> + </PropertyGroup> + <ItemGroup> + <Compile Include="InfoCardErrorUtilities.cs" /> + <Compile Include="InfoCard\InfoCardStrings.Designer.cs"> + <AutoGen>True</AutoGen> + <DesignTime>True</DesignTime> + <DependentUpon>InfoCardStrings.resx</DependentUpon> + </Compile> + <Compile Include="InfoCard\Token\InformationCardException.cs" /> + <Compile Include="InfoCard\Token\Token.cs" /> + <Compile Include="InfoCard\Token\TokenUtility.cs" /> + <Compile Include="InfoCard\Token\TokenDecryptor.cs" /> + <Compile Include="InfoCard\WellKnownIssuers.cs" /> + <Compile Include="Properties\AssemblyInfo.cs" /> + </ItemGroup> + <ItemGroup> + <EmbeddedResource Include="InfoCard\InfoCardStrings.resx"> + <Generator>ResXFileCodeGenerator</Generator> + <LastGenOutput>InfoCardStrings.Designer.cs</LastGenOutput> + </EmbeddedResource> + </ItemGroup> + <ItemGroup> + <EmbeddedResource Include="InfoCard\InfoCardStrings.sr.resx" /> + </ItemGroup> + <ItemGroup> + <ProjectReference Include="..\DotNetOpenAuth.Core\DotNetOpenAuth.Core.csproj"> + <Project>{60426312-6AE5-4835-8667-37EDEA670222}</Project> + <Name>DotNetOpenAuth.Core</Name> + </ProjectReference> + </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))' != '' " /> +</Project>
\ No newline at end of file diff --git a/src/DotNetOpenAuth/InfoCard/InfoCardStrings.Designer.cs b/src/DotNetOpenAuth.InfoCard/InfoCard/InfoCardStrings.Designer.cs index a6d3dcf..a6d3dcf 100644 --- a/src/DotNetOpenAuth/InfoCard/InfoCardStrings.Designer.cs +++ b/src/DotNetOpenAuth.InfoCard/InfoCard/InfoCardStrings.Designer.cs diff --git a/src/DotNetOpenAuth/InfoCard/InfoCardStrings.resx b/src/DotNetOpenAuth.InfoCard/InfoCard/InfoCardStrings.resx index 956b321..956b321 100644 --- a/src/DotNetOpenAuth/InfoCard/InfoCardStrings.resx +++ b/src/DotNetOpenAuth.InfoCard/InfoCard/InfoCardStrings.resx diff --git a/src/DotNetOpenAuth/InfoCard/InfoCardStrings.sr.resx b/src/DotNetOpenAuth.InfoCard/InfoCard/InfoCardStrings.sr.resx index 9df0429..9df0429 100644 --- a/src/DotNetOpenAuth/InfoCard/InfoCardStrings.sr.resx +++ b/src/DotNetOpenAuth.InfoCard/InfoCard/InfoCardStrings.sr.resx diff --git a/src/DotNetOpenAuth/InfoCard/Token/InformationCardException.cs b/src/DotNetOpenAuth.InfoCard/InfoCard/Token/InformationCardException.cs index ff08be8..ff08be8 100644 --- a/src/DotNetOpenAuth/InfoCard/Token/InformationCardException.cs +++ b/src/DotNetOpenAuth.InfoCard/InfoCard/Token/InformationCardException.cs diff --git a/src/DotNetOpenAuth.InfoCard/InfoCard/Token/Token.cs b/src/DotNetOpenAuth.InfoCard/InfoCard/Token/Token.cs new file mode 100644 index 0000000..fc7683d --- /dev/null +++ b/src/DotNetOpenAuth.InfoCard/InfoCard/Token/Token.cs @@ -0,0 +1,269 @@ +//----------------------------------------------------------------------- +// <copyright file="Token.cs" company="Andrew Arnott, Microsoft Corporation"> +// Copyright (c) Andrew Arnott, Microsoft Corporation. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.InfoCard { + using System; + using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; + using System.Diagnostics.Contracts; + using System.IdentityModel.Claims; + using System.IdentityModel.Policy; + using System.IdentityModel.Tokens; + using System.IO; + using System.Linq; + using System.Text; + using System.Xml; + using System.Xml.XPath; + using DotNetOpenAuth.Messaging; + + /// <summary> + /// The decrypted token that was submitted as an Information Card. + /// </summary> + [ContractVerification(true)] + public class Token { + /// <summary> + /// Backing field for the <see cref="Claims"/> property. + /// </summary> + private IDictionary<string, string> claims; + + /// <summary> + /// Backing field for the <see cref="UniqueId"/> property. + /// </summary> + private string uniqueId; + + /// <summary> + /// Initializes a new instance of the <see cref="Token"/> class. + /// </summary> + /// <param name="tokenXml">Xml token, which may be encrypted.</param> + /// <param name="audience">The audience. May be <c>null</c> to avoid audience checking.</param> + /// <param name="decryptor">The decryptor to use to decrypt the token, if necessary..</param> + /// <exception cref="InformationCardException">Thrown for any problem decoding or decrypting the token.</exception> + [SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times", Justification = "Not a problem for this type."), SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "False positive")] + private Token(string tokenXml, Uri audience, TokenDecryptor decryptor) { + Requires.NotNullOrEmpty(tokenXml, "tokenXml"); + Requires.True(decryptor != null || !IsEncrypted(tokenXml), null); + Contract.Ensures(this.AuthorizationContext != null); + + byte[] decryptedBytes; + string decryptedString; + + using (StringReader xmlReader = new StringReader(tokenXml)) { + using (XmlReader tokenReader = XmlReader.Create(xmlReader)) { + Contract.Assume(tokenReader != null); // BCL contract should say XmlReader.Create result != null + if (IsEncrypted(tokenReader)) { + Logger.InfoCard.DebugFormat("Incoming SAML token, before decryption: {0}", tokenXml); + decryptedBytes = decryptor.DecryptToken(tokenReader); + decryptedString = Encoding.UTF8.GetString(decryptedBytes); + Contract.Assume(decryptedString != null); // BCL contracts should be enhanced here + } else { + decryptedBytes = Encoding.UTF8.GetBytes(tokenXml); + decryptedString = tokenXml; + } + } + } + + var stringReader = new StringReader(decryptedString); + try { + this.Xml = new XPathDocument(stringReader).CreateNavigator(); + } catch { + stringReader.Dispose(); + throw; + } + + Logger.InfoCard.DebugFormat("Incoming SAML token, after any decryption: {0}", this.Xml.InnerXml); + this.AuthorizationContext = TokenUtility.AuthenticateToken(this.Xml.ReadSubtree(), audience); + } + + /// <summary> + /// Gets the AuthorizationContext behind this token. + /// </summary> + public AuthorizationContext AuthorizationContext { get; private set; } + + /// <summary> + /// Gets the the decrypted token XML. + /// </summary> + public XPathNavigator Xml { get; private set; } + + /// <summary> + /// Gets the UniqueID of this token, usable as a stable username that the user + /// has already verified belongs to him/her. + /// </summary> + /// <remarks> + /// By default, this uses the PPID and the Issuer's Public Key and hashes them + /// together to generate a UniqueID. + /// </remarks> + public string UniqueId { + get { + if (string.IsNullOrEmpty(this.uniqueId)) { + this.uniqueId = TokenUtility.GetUniqueName(this.AuthorizationContext); + } + + return this.uniqueId; + } + } + + /// <summary> + /// Gets the hash of the card issuer's public key. + /// </summary> + public string IssuerPubKeyHash { + get { return TokenUtility.GetIssuerPubKeyHash(this.AuthorizationContext); } + } + + /// <summary> + /// Gets the Site Specific ID that the user sees in the Identity Selector. + /// </summary> + public string SiteSpecificId { + get { + Requires.ValidState(this.Claims.ContainsKey(ClaimTypes.PPID) && !string.IsNullOrEmpty(this.Claims[ClaimTypes.PPID])); + string ppidValue; + ErrorUtilities.VerifyOperation(this.Claims.TryGetValue(ClaimTypes.PPID, out ppidValue) && ppidValue != null, InfoCardStrings.PpidClaimRequired); + return TokenUtility.CalculateSiteSpecificID(ppidValue); + } + } + + /// <summary> + /// Gets the claims in all the claimsets as a dictionary of strings. + /// </summary> + public IDictionary<string, string> Claims { + get { + if (this.claims == null) { + this.claims = this.GetFlattenedClaims(); + } + + return this.claims; + } + } + + /// <summary> + /// Deserializes an XML document into a token. + /// </summary> + /// <param name="tokenXml">The token XML.</param> + /// <returns>The deserialized token.</returns> + public static Token Read(string tokenXml) { + Requires.NotNullOrEmpty(tokenXml, "tokenXml"); + return Read(tokenXml, (Uri)null); + } + + /// <summary> + /// Deserializes an XML document into a token. + /// </summary> + /// <param name="tokenXml">The token XML.</param> + /// <param name="audience">The URI that this token must have been crafted to be sent to. Use <c>null</c> to accept any intended audience.</param> + /// <returns>The deserialized token.</returns> + public static Token Read(string tokenXml, Uri audience) { + Requires.NotNullOrEmpty(tokenXml, "tokenXml"); + return Read(tokenXml, audience, Enumerable.Empty<SecurityToken>()); + } + + /// <summary> + /// Deserializes an XML document into a token. + /// </summary> + /// <param name="tokenXml">The token XML.</param> + /// <param name="decryptionTokens">Any X.509 certificates that may be used to decrypt the token, if necessary.</param> + /// <returns>The deserialized token.</returns> + public static Token Read(string tokenXml, IEnumerable<SecurityToken> decryptionTokens) { + Requires.NotNullOrEmpty(tokenXml, "tokenXml"); + Requires.NotNull(decryptionTokens, "decryptionTokens"); + return Read(tokenXml, null, decryptionTokens); + } + + /// <summary> + /// Deserializes an XML document into a token. + /// </summary> + /// <param name="tokenXml">The token XML.</param> + /// <param name="audience">The URI that this token must have been crafted to be sent to. Use <c>null</c> to accept any intended audience.</param> + /// <param name="decryptionTokens">Any X.509 certificates that may be used to decrypt the token, if necessary.</param> + /// <returns>The deserialized token.</returns> + public static Token Read(string tokenXml, Uri audience, IEnumerable<SecurityToken> decryptionTokens) { + Requires.NotNullOrEmpty(tokenXml, "tokenXml"); + Requires.NotNull(decryptionTokens, "decryptionTokens"); + Contract.Ensures(Contract.Result<Token>() != null); + + TokenDecryptor decryptor = null; + + if (IsEncrypted(tokenXml)) { + decryptor = new TokenDecryptor(); + decryptor.Tokens.AddRange(decryptionTokens); + } + + return new Token(tokenXml, audience, decryptor); + } + + /// <summary> + /// Determines whether the specified token XML is encrypted. + /// </summary> + /// <param name="tokenXml">The token XML.</param> + /// <returns> + /// <c>true</c> if the specified token XML is encrypted; otherwise, <c>false</c>. + /// </returns> + [SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "False positive"), Pure] + internal static bool IsEncrypted(string tokenXml) { + Requires.NotNull(tokenXml, "tokenXml"); + + var stringReader = new StringReader(tokenXml); + XmlReader tokenReader; + try { + tokenReader = XmlReader.Create(stringReader); + } catch { + stringReader.Dispose(); + throw; + } + + try { + Contract.Assume(tokenReader != null); // CC missing for XmlReader.Create + return IsEncrypted(tokenReader); + } catch { + IDisposable disposableReader = tokenReader; + disposableReader.Dispose(); + throw; + } + } + + /// <summary> + /// Determines whether the specified token XML is encrypted. + /// </summary> + /// <param name="tokenXmlReader">The token XML.</param> + /// <returns> + /// <c>true</c> if the specified token XML is encrypted; otherwise, <c>false</c>. + /// </returns> + private static bool IsEncrypted(XmlReader tokenXmlReader) { + Requires.NotNull(tokenXmlReader, "tokenXmlReader"); + return tokenXmlReader.IsStartElement(TokenDecryptor.XmlEncryptionStrings.EncryptedData, TokenDecryptor.XmlEncryptionStrings.Namespace); + } + +#if CONTRACTS_FULL + /// <summary> + /// Verifies conditions that should be true for any valid state of this object. + /// </summary> + [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Called by code contracts.")] + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called by code contracts.")] + [ContractInvariantMethod] + private void ObjectInvariant() { + Contract.Invariant(this.AuthorizationContext != null); + } +#endif + + /// <summary> + /// Flattens the claims into a dictionary + /// </summary> + /// <returns>A dictionary of claim type URIs and claim values.</returns> + [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "Expensive call.")] + [Pure] + private IDictionary<string, string> GetFlattenedClaims() { + var flattenedClaims = new Dictionary<string, string>(); + + foreach (ClaimSet set in this.AuthorizationContext.ClaimSets) { + foreach (Claim claim in set) { + if (claim.Right == Rights.PossessProperty) { + flattenedClaims.Add(claim.ClaimType, TokenUtility.GetResourceValue(claim)); + } + } + } + + return flattenedClaims; + } + } +} diff --git a/src/DotNetOpenAuth.InfoCard/InfoCard/Token/TokenDecryptor.cs b/src/DotNetOpenAuth.InfoCard/InfoCard/Token/TokenDecryptor.cs new file mode 100644 index 0000000..e9199c7 --- /dev/null +++ b/src/DotNetOpenAuth.InfoCard/InfoCard/Token/TokenDecryptor.cs @@ -0,0 +1,210 @@ +//----------------------------------------------------------------------- +// <copyright file="TokenDecryptor.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. All rights reserved. +// </copyright> +// <license> +// Microsoft Public License (Ms-PL). +// See http://www.microsoft.com/opensource/licenses.mspx#Ms-PL +// </license> +// <author>This file was subsequently modified by Andrew Arnott.</author> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.InfoCard { + using System; + using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; + using System.Diagnostics.Contracts; + using System.IdentityModel.Selectors; + using System.IdentityModel.Tokens; + using System.Linq; + using System.Security.Cryptography; + using System.Security.Cryptography.X509Certificates; + using System.ServiceModel.Security; + using System.Xml; + using DotNetOpenAuth.Messaging; + + /// <summary> + /// A utility class for decrypting InfoCard tokens. + /// </summary> + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Decryptor", Justification = "By design")] + internal class TokenDecryptor { + /// <summary> + /// Backing field for the <see cref="Tokens"/> property. + /// </summary> + private List<SecurityToken> tokens; + + /// <summary> + /// Initializes a new instance of the <see cref="TokenDecryptor"/> class. + /// </summary> + internal TokenDecryptor() { + this.tokens = new List<SecurityToken>(); + StoreName storeName = StoreName.My; + StoreLocation storeLocation = StoreLocation.LocalMachine; + this.AddDecryptionCertificates(storeName, storeLocation); + } + + /// <summary> + /// Gets a list of possible decryption certificates, from the store/location set + /// </summary> + /// <remarks> + /// Defaults to localmachine:my (same place SSL certs are) + /// </remarks> + internal List<SecurityToken> Tokens { + get { return this.tokens; } + } + + /// <summary> + /// Adds a certificate to the list of certificates to decrypt with. + /// </summary> + /// <param name="certificate">The x509 cert to use for decryption</param> + internal void AddDecryptionCertificate(X509Certificate2 certificate) { + this.Tokens.Add(new X509SecurityToken(certificate)); + } + + /// <summary> + /// Adds a certificate to the list of certificates to decrypt with. + /// </summary> + /// <param name="storeName">store name of the certificate</param> + /// <param name="storeLocation">store location</param> + /// <param name="thumbprint">thumbprint of the cert to use</param> + internal void AddDecryptionCertificate(StoreName storeName, StoreLocation storeLocation, string thumbprint) { + this.AddDecryptionCertificates( + storeName, + storeLocation, + store => store.Find(X509FindType.FindByThumbprint, thumbprint, true)); + } + + /// <summary> + /// Adds a store of certificates to the list of certificates to decrypt with. + /// </summary> + /// <param name="storeName">store name of the certificates</param> + /// <param name="storeLocation">store location</param> + internal void AddDecryptionCertificates(StoreName storeName, StoreLocation storeLocation) { + this.AddDecryptionCertificates(storeName, storeLocation, store => store); + } + + /// <summary> + /// Decrpyts a security token from an XML EncryptedData + /// </summary> + /// <param name="reader">The encrypted token XML reader.</param> + /// <returns>A byte array of the contents of the encrypted token</returns> + internal byte[] DecryptToken(XmlReader reader) { + Requires.NotNull(reader, "reader"); + Contract.Ensures(Contract.Result<byte[]>() != null); + + byte[] securityTokenData; + string encryptionAlgorithm; + SecurityKeyIdentifier keyIdentifier; + bool isEmptyElement; + + ErrorUtilities.VerifyInternal(reader.IsStartElement(XmlEncryptionStrings.EncryptedData, XmlEncryptionStrings.Namespace), "Expected encrypted token starting XML element was not found."); + reader.Read(); // get started + + // if it's not an encryption method, something is dreadfully wrong. + InfoCardErrorUtilities.VerifyInfoCard(reader.IsStartElement(XmlEncryptionStrings.EncryptionMethod, XmlEncryptionStrings.Namespace), InfoCardStrings.EncryptionAlgorithmNotFound); + + // Looks good, let's grab the alg. + isEmptyElement = reader.IsEmptyElement; + encryptionAlgorithm = reader.GetAttribute(XmlEncryptionStrings.Algorithm); + reader.Read(); + + if (!isEmptyElement) { + while (reader.IsStartElement()) { + reader.Skip(); + } + reader.ReadEndElement(); + } + + // get the key identifier + keyIdentifier = WSSecurityTokenSerializer.DefaultInstance.ReadKeyIdentifier(reader); + + // resolve the symmetric key + SymmetricSecurityKey decryptingKey = (SymmetricSecurityKey)SecurityTokenResolver.CreateDefaultSecurityTokenResolver(this.tokens.AsReadOnly(), false).ResolveSecurityKey(keyIdentifier[0]); + SymmetricAlgorithm algorithm = decryptingKey.GetSymmetricAlgorithm(encryptionAlgorithm); + + // dig for the security token data itself. + reader.ReadStartElement(XmlEncryptionStrings.CipherData, XmlEncryptionStrings.Namespace); + reader.ReadStartElement(XmlEncryptionStrings.CipherValue, XmlEncryptionStrings.Namespace); + securityTokenData = Convert.FromBase64String(reader.ReadString()); + reader.ReadEndElement(); // CipherValue + reader.ReadEndElement(); // CipherData + reader.ReadEndElement(); // EncryptedData + + // decrypto-magic! + int blockSizeBytes = algorithm.BlockSize / 8; + byte[] iv = new byte[blockSizeBytes]; + Buffer.BlockCopy(securityTokenData, 0, iv, 0, iv.Length); + algorithm.Padding = PaddingMode.ISO10126; + algorithm.Mode = CipherMode.CBC; + ICryptoTransform decrTransform = algorithm.CreateDecryptor(algorithm.Key, iv); + byte[] plainText = decrTransform.TransformFinalBlock(securityTokenData, iv.Length, securityTokenData.Length - iv.Length); + decrTransform.Dispose(); + + return plainText; + } + +#if CONTRACTS_FULL + /// <summary> + /// Verifies conditions that should be true for any valid state of this object. + /// </summary> + [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Called by code contracts.")] + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called by code contracts.")] + [ContractInvariantMethod] + private void ObjectInvariant() { + Contract.Invariant(this.Tokens != null); + } +#endif + + /// <summary> + /// Adds a store of certificates to the list of certificates to decrypt with. + /// </summary> + /// <param name="storeName">store name of the certificates</param> + /// <param name="storeLocation">store location</param> + /// <param name="filter">A filter to on the certificates to add.</param> + private void AddDecryptionCertificates(StoreName storeName, StoreLocation storeLocation, Func<X509Certificate2Collection, X509Certificate2Collection> filter) { + X509Store store = new X509Store(storeName, storeLocation); + store.Open(OpenFlags.ReadOnly); + + this.tokens.AddRange((from cert in filter(store.Certificates).Cast<X509Certificate2>() + where cert.HasPrivateKey + select new X509SecurityToken(cert)).Cast<SecurityToken>()); + + store.Close(); + } + + /// <summary> + /// A set of strings used in parsing the XML token. + /// </summary> + internal static class XmlEncryptionStrings { + /// <summary> + /// The "http://www.w3.org/2001/04/xmlenc#" value. + /// </summary> + internal const string Namespace = "http://www.w3.org/2001/04/xmlenc#"; + + /// <summary> + /// The "EncryptionMethod" value. + /// </summary> + internal const string EncryptionMethod = "EncryptionMethod"; + + /// <summary> + /// The "CipherValue" value. + /// </summary> + internal const string CipherValue = "CipherValue"; + + /// <summary> + /// The "Algorithm" value. + /// </summary> + internal const string Algorithm = "Algorithm"; + + /// <summary> + /// The "EncryptedData" value. + /// </summary> + internal const string EncryptedData = "EncryptedData"; + + /// <summary> + /// The "CipherData" value. + /// </summary> + internal const string CipherData = "CipherData"; + } + } +}
\ No newline at end of file diff --git a/src/DotNetOpenAuth.InfoCard/InfoCard/Token/TokenUtility.cs b/src/DotNetOpenAuth.InfoCard/InfoCard/Token/TokenUtility.cs new file mode 100644 index 0000000..5daffa8 --- /dev/null +++ b/src/DotNetOpenAuth.InfoCard/InfoCard/Token/TokenUtility.cs @@ -0,0 +1,298 @@ +//----------------------------------------------------------------------- +// <copyright file="TokenUtility.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. All rights reserved. +// </copyright> +// <license> +// Microsoft Public License (Ms-PL). +// See http://www.microsoft.com/opensource/licenses.mspx#Ms-PL +// </license> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.InfoCard { + using System; + using System.Collections.Generic; + using System.Configuration; + using System.Diagnostics.Contracts; + using System.IdentityModel.Claims; + using System.IdentityModel.Policy; + using System.IdentityModel.Selectors; + using System.IdentityModel.Tokens; + using System.IO; + using System.Linq; + using System.Net.Mail; + using System.Security.Cryptography; + using System.Security.Principal; + using System.ServiceModel.Security; + using System.Text; + using System.Xml; + using DotNetOpenAuth.Messaging; + + /// <summary> + /// Tools for reading InfoCard tokens. + /// </summary> + internal static class TokenUtility { + /// <summary> + /// Gets the maximum amount the token can be out of sync with time. + /// </summary> + internal static TimeSpan MaximumClockSkew { + get { return DotNetOpenAuth.Configuration.DotNetOpenAuthSection.Messaging.MaximumClockSkew; } + } + + /// <summary> + /// Token Authentication. Translates the decrypted data into a AuthContext. + /// </summary> + /// <param name="reader">The token XML reader.</param> + /// <param name="audience">The audience that the token must be scoped for. + /// Use <c>null</c> to indicate any audience is acceptable.</param> + /// <returns> + /// The authorization context carried by the token. + /// </returns> + internal static AuthorizationContext AuthenticateToken(XmlReader reader, Uri audience) { + Contract.Ensures(Contract.Result<AuthorizationContext>() != null); + + // Extensibility Point: + // in order to accept different token types, you would need to add additional + // code to create an authenticationcontext from the security token. + // This code only supports SamlSecurityToken objects. + SamlSecurityToken token = WSSecurityTokenSerializer.DefaultInstance.ReadToken(reader, null) as SamlSecurityToken; + + if (null == token) { + throw new InformationCardException("Unable to read security token"); + } + + if (null != token.SecurityKeys && token.SecurityKeys.Count > 0) { + throw new InformationCardException("Token Security Keys Exist"); + } + + if (audience == null) { + Logger.InfoCard.Warn("SAML token Audience checking will be skipped."); + } else { + if (token.Assertion.Conditions != null && + token.Assertion.Conditions.Conditions != null) { + foreach (SamlCondition condition in token.Assertion.Conditions.Conditions) { + SamlAudienceRestrictionCondition audienceCondition = condition as SamlAudienceRestrictionCondition; + + if (audienceCondition != null) { + Logger.InfoCard.DebugFormat("SAML token audience(s): {0}", audienceCondition.Audiences.ToStringDeferred()); + bool match = audienceCondition.Audiences.Contains(audience); + + if (!match && Logger.InfoCard.IsErrorEnabled) { + Logger.InfoCard.ErrorFormat("Expected SAML token audience of {0} but found {1}.", audience.AbsoluteUri, audienceCondition.Audiences.Select(aud => aud.AbsoluteUri).ToStringDeferred()); + } + + // The token is invalid if any condition is not valid. + // An audience restriction condition is valid if any audience + // matches the Relying Party. + InfoCardErrorUtilities.VerifyInfoCard(match, InfoCardStrings.AudienceMismatch); + } + } + } + } + var samlAuthenticator = new SamlSecurityTokenAuthenticator( + new List<SecurityTokenAuthenticator>( + new SecurityTokenAuthenticator[] { + new RsaSecurityTokenAuthenticator(), + new X509SecurityTokenAuthenticator(), + }), + MaximumClockSkew); + + return AuthorizationContext.CreateDefaultAuthorizationContext(samlAuthenticator.ValidateToken(token)); + } + + /// <summary> + /// Translates claims to strings + /// </summary> + /// <param name="claim">Claim to translate to a string</param> + /// <returns>The string representation of a claim's value.</returns> + internal static string GetResourceValue(Claim claim) { + string strClaim = claim.Resource as string; + if (!string.IsNullOrEmpty(strClaim)) { + return strClaim; + } + + IdentityReference reference = claim.Resource as IdentityReference; + if (null != reference) { + return reference.Value; + } + + ICspAsymmetricAlgorithm rsa = claim.Resource as ICspAsymmetricAlgorithm; + if (null != rsa) { + using (SHA256 sha = new SHA256Managed()) { + return Convert.ToBase64String(sha.ComputeHash(rsa.ExportCspBlob(false))); + } + } + + MailAddress mail = claim.Resource as MailAddress; + if (null != mail) { + return mail.ToString(); + } + + byte[] bufferValue = claim.Resource as byte[]; + if (null != bufferValue) { + return Convert.ToBase64String(bufferValue); + } + + return claim.Resource.ToString(); + } + + /// <summary> + /// Generates a UniqueID based off the Issuer's key + /// </summary> + /// <param name="authzContext">the Authorization Context</param> + /// <returns>the hash of the internal key of the issuer</returns> + internal static string GetIssuerPubKeyHash(AuthorizationContext authzContext) { + foreach (ClaimSet cs in authzContext.ClaimSets) { + Claim currentIssuerClaim = GetUniqueRsaClaim(cs.Issuer); + + if (currentIssuerClaim != null) { + RSA rsa = currentIssuerClaim.Resource as RSA; + if (null == rsa) { + return null; + } + + return ComputeCombinedId(rsa, string.Empty); + } + } + + return null; + } + + /// <summary> + /// Generates a UniqueID based off the Issuer's key and the PPID. + /// </summary> + /// <param name="authzContext">The Authorization Context</param> + /// <returns>A unique ID for this user at this web site.</returns> + internal static string GetUniqueName(AuthorizationContext authzContext) { + Requires.NotNull(authzContext, "authzContext"); + + Claim uniqueIssuerClaim = null; + Claim uniqueUserClaim = null; + + foreach (ClaimSet cs in authzContext.ClaimSets) { + Claim currentIssuerClaim = GetUniqueRsaClaim(cs.Issuer); + + foreach (Claim c in cs.FindClaims(ClaimTypes.PPID, Rights.PossessProperty)) { + if (null == currentIssuerClaim) { + // Found a claim in a ClaimSet with no RSA issuer. + return null; + } + + if (null == uniqueUserClaim) { + uniqueUserClaim = c; + uniqueIssuerClaim = currentIssuerClaim; + } else if (!uniqueIssuerClaim.Equals(currentIssuerClaim)) { + // Found two of the desired claims with different + // issuers. No unique name. + return null; + } else if (!uniqueUserClaim.Equals(c)) { + // Found two of the desired claims with different + // values. No unique name. + return null; + } + } + } + + // No claim of the desired type was found + if (null == uniqueUserClaim) { + return null; + } + + // Unexpected resource type + string claimValue = uniqueUserClaim.Resource as string; + if (null == claimValue) { + return null; + } + + // Unexpected resource type for RSA + RSA rsa = uniqueIssuerClaim.Resource as RSA; + if (null == rsa) { + return null; + } + + return ComputeCombinedId(rsa, claimValue); + } + + /// <summary> + /// Generates the Site Specific ID to match the one in the Identity Selector. + /// </summary> + /// <value>The ID displayed by the Identity Selector.</value> + /// <param name="ppid">The personal private identifier.</param> + /// <returns>A string containing the XXX-XXXX-XXX cosmetic value.</returns> + internal static string CalculateSiteSpecificID(string ppid) { + Requires.NotNull(ppid, "ppid"); + Contract.Ensures(!string.IsNullOrEmpty(Contract.Result<string>())); + + int callSignChars = 10; + char[] charMap = "QL23456789ABCDEFGHJKMNPRSTUVWXYZ".ToCharArray(); + int charMapLength = charMap.Length; + + byte[] raw = Convert.FromBase64String(ppid); + using (HashAlgorithm hasher = SHA1.Create()) { + raw = hasher.ComputeHash(raw); + } + + StringBuilder callSign = new StringBuilder(); + + for (int i = 0; i < callSignChars; i++) { + // after char 3 and char 7, place a dash + if (i == 3 || i == 7) { + callSign.Append('-'); + } + callSign.Append(charMap[raw[i] % charMapLength]); + } + return callSign.ToString(); + } + + /// <summary> + /// Gets the Unique RSA Claim from the SAML token. + /// </summary> + /// <param name="cs">the claimset which contains the claim</param> + /// <returns>a RSA claim</returns> + private static Claim GetUniqueRsaClaim(ClaimSet cs) { + Requires.NotNull(cs, "cs"); + + Claim rsa = null; + + foreach (Claim c in cs.FindClaims(ClaimTypes.Rsa, Rights.PossessProperty)) { + if (null == rsa) { + rsa = c; + } else if (!rsa.Equals(c)) { + // Found two non-equal RSA claims + return null; + } + } + return rsa; + } + + /// <summary> + /// Does the actual calculation of a combined ID from a value and an RSA key. + /// </summary> + /// <param name="issuerKey">The key of the issuer of the token</param> + /// <param name="claimValue">the claim value to hash with.</param> + /// <returns>A base64 representation of the combined ID.</returns> + private static string ComputeCombinedId(RSA issuerKey, string claimValue) { + Requires.NotNull(issuerKey, "issuerKey"); + Requires.NotNull(claimValue, "claimValue"); + Contract.Ensures(Contract.Result<string>() != null); + + int nameLength = Encoding.UTF8.GetByteCount(claimValue); + RSAParameters rsaParams = issuerKey.ExportParameters(false); + byte[] shaInput; + byte[] shaOutput; + + int i = 0; + shaInput = new byte[rsaParams.Modulus.Length + rsaParams.Exponent.Length + nameLength]; + rsaParams.Modulus.CopyTo(shaInput, i); + i += rsaParams.Modulus.Length; + rsaParams.Exponent.CopyTo(shaInput, i); + i += rsaParams.Exponent.Length; + i += Encoding.UTF8.GetBytes(claimValue, 0, claimValue.Length, shaInput, i); + + using (SHA256 sha = SHA256.Create()) { + shaOutput = sha.ComputeHash(shaInput); + } + + return Convert.ToBase64String(shaOutput); + } + } +} diff --git a/src/DotNetOpenAuth/InfoCard/WellKnownClaimTypes.cs b/src/DotNetOpenAuth.InfoCard/InfoCard/WellKnownClaimTypes.cs index 94ebae8..94ebae8 100644 --- a/src/DotNetOpenAuth/InfoCard/WellKnownClaimTypes.cs +++ b/src/DotNetOpenAuth.InfoCard/InfoCard/WellKnownClaimTypes.cs diff --git a/src/DotNetOpenAuth/InfoCard/WellKnownIssuers.cs b/src/DotNetOpenAuth.InfoCard/InfoCard/WellKnownIssuers.cs index 8c63287..8c63287 100644 --- a/src/DotNetOpenAuth/InfoCard/WellKnownIssuers.cs +++ b/src/DotNetOpenAuth.InfoCard/InfoCard/WellKnownIssuers.cs diff --git a/src/DotNetOpenAuth.InfoCard/InfoCardErrorUtilities.cs b/src/DotNetOpenAuth.InfoCard/InfoCardErrorUtilities.cs new file mode 100644 index 0000000..da1c7ff --- /dev/null +++ b/src/DotNetOpenAuth.InfoCard/InfoCardErrorUtilities.cs @@ -0,0 +1,39 @@ +//----------------------------------------------------------------------- +// <copyright file="InfoCardErrorUtilities.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth { + using System; + using System.Collections.Generic; + using System.Diagnostics.Contracts; + using System.Globalization; + using System.Linq; + using System.Text; + + /// <summary> + /// Error reporting methods specific to InfoCard validation. + /// </summary> + internal static class InfoCardErrorUtilities { + /// <summary> + /// Checks a condition and throws an <see cref="InfoCard.InformationCardException"/> + /// if it evaluates to false. + /// </summary> + /// <param name="condition">The condition to check.</param> + /// <param name="errorMessage">The message to include in the exception, if created.</param> + /// <param name="args">The formatting arguments.</param> + /// <exception cref="InfoCard.InformationCardException">Thrown if <paramref name="condition"/> evaluates to <c>false</c>.</exception> + [Pure] + internal static void VerifyInfoCard(bool condition, string errorMessage, params object[] args) { + Requires.NotNull(args, "args"); + Contract.Ensures(condition); + Contract.EnsuresOnThrow<InfoCard.InformationCardException>(!condition); + Contract.Assume(errorMessage != null); + if (!condition) { + errorMessage = string.Format(CultureInfo.CurrentCulture, errorMessage, args); + throw new InfoCard.InformationCardException(errorMessage); + } + } + } +} diff --git a/src/DotNetOpenAuth.InfoCard/Properties/AssemblyInfo.cs b/src/DotNetOpenAuth.InfoCard/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..7260f4b --- /dev/null +++ b/src/DotNetOpenAuth.InfoCard/Properties/AssemblyInfo.cs @@ -0,0 +1,56 @@ +//----------------------------------------------------------------------- +// <copyright file="AssemblyInfo.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +// We DON'T put an AssemblyVersionAttribute in here because it is generated in the build. + +using System; +using System.Diagnostics.Contracts; +using System.Net; +using System.Reflection; +using System.Resources; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Security; +using System.Security.Permissions; +using System.Web.UI; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("DotNetOpenAuth InfoCard")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("DotNetOpenAuth")] +[assembly: AssemblyCopyright("Copyright © 2008")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: NeutralResourcesLanguage("en-US")] +[assembly: CLSCompliant(true)] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("7d73990c-47c0-4256-9f20-a893add9e289")] + +[assembly: ContractVerification(true)] + +#if StrongNameSigned +// See comment at top of this file. We need this so that strong-naming doesn't +// keep this assembly from being useful to shared host (medium trust) web sites. +[assembly: AllowPartiallyTrustedCallers] + +[assembly: InternalsVisibleTo("DotNetOpenAuth.InfoCard.UI, PublicKey=0024000004800000940000000602000000240000525341310004000001000100AD093C3765257C89A7010E853F2C7C741FF92FA8ACE06D7B8254702CAD5CF99104447F63AB05F8BB6F51CE0D81C8C93D2FCE8C20AAFF7042E721CBA16EAAE98778611DED11C0ABC8900DC5667F99B50A9DADEC24DBD8F2C91E3E8AD300EF64F1B4B9536CEB16FB440AF939F57624A9B486F867807C649AE4830EAB88C6C03998")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100AD093C3765257C89A7010E853F2C7C741FF92FA8ACE06D7B8254702CAD5CF99104447F63AB05F8BB6F51CE0D81C8C93D2FCE8C20AAFF7042E721CBA16EAAE98778611DED11C0ABC8900DC5667F99B50A9DADEC24DBD8F2C91E3E8AD300EF64F1B4B9536CEB16FB440AF939F57624A9B486F867807C649AE4830EAB88C6C03998")] +[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")] +#else +[assembly: InternalsVisibleTo("DotNetOpenAuth.InfoCard.UI")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.Test")] +[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] +#endif diff --git a/src/DotNetOpenAuth.OAuth.Consumer/DotNetOpenAuth.OAuth.Consumer.csproj b/src/DotNetOpenAuth.OAuth.Consumer/DotNetOpenAuth.OAuth.Consumer.csproj new file mode 100644 index 0000000..2478187 --- /dev/null +++ b/src/DotNetOpenAuth.OAuth.Consumer/DotNetOpenAuth.OAuth.Consumer.csproj @@ -0,0 +1,50 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), EnlistmentInfo.props))\EnlistmentInfo.props" Condition=" '$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), EnlistmentInfo.props))' != '' " /> + <PropertyGroup> + <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> + <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> + </PropertyGroup> + <Import Project="$(ProjectRoot)tools\DotNetOpenAuth.props" /> + <PropertyGroup> + <SchemaVersion>2.0</SchemaVersion> + <ProjectGuid>{B202E40D-4663-4A2B-ACDA-865F88FF7CAA}</ProjectGuid> + <AppDesignerFolder>Properties</AppDesignerFolder> + <AssemblyName>DotNetOpenAuth.OAuth.Consumer</AssemblyName> + </PropertyGroup> + <Import Project="$(ProjectRoot)tools\DotNetOpenAuth.Product.props" /> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> + </PropertyGroup> + <ItemGroup> + <Compile Include="OAuth\ChannelElements\IConsumerTokenManager.cs" /> + <Compile Include="OAuth\ChannelElements\OAuthConsumerChannel.cs" /> + <Compile Include="OAuth\ChannelElements\OAuthConsumerMessageFactory.cs" /> + <Compile Include="OAuth\ChannelElements\RsaSha1ConsumerSigningBindingElement.cs" /> + <Compile Include="OAuth\ConsumerBase.cs" /> + <Compile Include="OAuth\DesktopConsumer.cs" /> + <Compile Include="OAuth\WebConsumer.cs" /> + <Compile Include="Properties\AssemblyInfo.cs"> + <SubType> + </SubType> + </Compile> + </ItemGroup> + <ItemGroup> + <ProjectReference Include="..\DotNetOpenAuth.Core\DotNetOpenAuth.Core.csproj"> + <Project>{60426312-6AE5-4835-8667-37EDEA670222}</Project> + <Name>DotNetOpenAuth.Core</Name> + </ProjectReference> + <ProjectReference Include="..\DotNetOpenAuth.OAuth\DotNetOpenAuth.OAuth.csproj"> + <Project>{A288FCC8-6FCF-46DA-A45E-5F9281556361}</Project> + <Name>DotNetOpenAuth.OAuth</Name> + </ProjectReference> + <ProjectReference Include="..\DotNetOpenAuth.OpenId\DotNetOpenAuth.OpenId.csproj"> + <Project>{3896A32A-E876-4C23-B9B8-78E17D134CD3}</Project> + <Name>DotNetOpenAuth.OpenId</Name> + </ProjectReference> + </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))' != '' " /> +</Project>
\ No newline at end of file diff --git a/src/DotNetOpenAuth/OAuth/ChannelElements/IConsumerTokenManager.cs b/src/DotNetOpenAuth.OAuth.Consumer/OAuth/ChannelElements/IConsumerTokenManager.cs index f16be64..f16be64 100644 --- a/src/DotNetOpenAuth/OAuth/ChannelElements/IConsumerTokenManager.cs +++ b/src/DotNetOpenAuth.OAuth.Consumer/OAuth/ChannelElements/IConsumerTokenManager.cs diff --git a/src/DotNetOpenAuth.OAuth.Consumer/OAuth/ChannelElements/OAuthConsumerChannel.cs b/src/DotNetOpenAuth.OAuth.Consumer/OAuth/ChannelElements/OAuthConsumerChannel.cs new file mode 100644 index 0000000..553d6c6 --- /dev/null +++ b/src/DotNetOpenAuth.OAuth.Consumer/OAuth/ChannelElements/OAuthConsumerChannel.cs @@ -0,0 +1,70 @@ +//----------------------------------------------------------------------- +// <copyright file="OAuthConsumerChannel.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OAuth.ChannelElements { + using System; + using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; + using System.Diagnostics.Contracts; + using System.Linq; + using System.Text; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.Messaging.Bindings; + + /// <summary> + /// The messaging channel for OAuth 1.0(a) Consumers. + /// </summary> + internal class OAuthConsumerChannel : OAuthChannel { + /// <summary> + /// Initializes a new instance of the <see cref="OAuthConsumerChannel"/> class. + /// </summary> + /// <param name="signingBindingElement">The binding element to use for signing.</param> + /// <param name="store">The web application store to use for nonces.</param> + /// <param name="tokenManager">The token manager instance to use.</param> + /// <param name="securitySettings">The security settings.</param> + /// <param name="messageFactory">The message factory.</param> + [SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", MessageId = "System.Diagnostics.Contracts.__ContractsRuntime.Requires<System.ArgumentNullException>(System.Boolean,System.String,System.String)", Justification = "Code contracts"), SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "securitySettings", Justification = "Code contracts")] + internal OAuthConsumerChannel(ITamperProtectionChannelBindingElement signingBindingElement, INonceStore store, IConsumerTokenManager tokenManager, ConsumerSecuritySettings securitySettings, IMessageFactory messageFactory = null) + : base( + signingBindingElement, + store, + tokenManager, + securitySettings, + messageFactory ?? new OAuthConsumerMessageFactory(), + InitializeBindingElements(signingBindingElement, store, tokenManager, securitySettings)) { + Requires.NotNull(tokenManager, "tokenManager"); + Requires.NotNull(securitySettings, "securitySettings"); + Requires.NotNull(signingBindingElement, "signingBindingElement"); + } + + /// <summary> + /// Gets the consumer secret for a given consumer key. + /// </summary> + /// <param name="consumerKey">The consumer key.</param> + /// <returns>The consumer secret.</returns> + protected override string GetConsumerSecret(string consumerKey) { + var consumerTokenManager = (IConsumerTokenManager)this.TokenManager; + ErrorUtilities.VerifyInternal(consumerKey == consumerTokenManager.ConsumerKey, "The token manager consumer key and the consumer key set earlier do not match!"); + return consumerTokenManager.ConsumerSecret; + } + + /// <summary> + /// Initializes the binding elements for the OAuth channel. + /// </summary> + /// <param name="signingBindingElement">The signing binding element.</param> + /// <param name="store">The nonce store.</param> + /// <param name="tokenManager">The token manager.</param> + /// <param name="securitySettings">The security settings.</param> + /// <returns> + /// An array of binding elements used to initialize the channel. + /// </returns> + private static new IChannelBindingElement[] InitializeBindingElements(ITamperProtectionChannelBindingElement signingBindingElement, INonceStore store, ITokenManager tokenManager, SecuritySettings securitySettings) { + Contract.Requires(securitySettings != null); + + return OAuthChannel.InitializeBindingElements(signingBindingElement, store, tokenManager, securitySettings).ToArray(); + } + } +} diff --git a/src/DotNetOpenAuth/OAuth/ChannelElements/OAuthConsumerMessageFactory.cs b/src/DotNetOpenAuth.OAuth.Consumer/OAuth/ChannelElements/OAuthConsumerMessageFactory.cs index 327b923..327b923 100644 --- a/src/DotNetOpenAuth/OAuth/ChannelElements/OAuthConsumerMessageFactory.cs +++ b/src/DotNetOpenAuth.OAuth.Consumer/OAuth/ChannelElements/OAuthConsumerMessageFactory.cs diff --git a/src/DotNetOpenAuth.OAuth.Consumer/OAuth/ChannelElements/RsaSha1ConsumerSigningBindingElement.cs b/src/DotNetOpenAuth.OAuth.Consumer/OAuth/ChannelElements/RsaSha1ConsumerSigningBindingElement.cs new file mode 100644 index 0000000..7a7998e --- /dev/null +++ b/src/DotNetOpenAuth.OAuth.Consumer/OAuth/ChannelElements/RsaSha1ConsumerSigningBindingElement.cs @@ -0,0 +1,74 @@ +//----------------------------------------------------------------------- +// <copyright file="RsaSha1ConsumerSigningBindingElement.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OAuth.ChannelElements { + using System; + using System.Diagnostics.Contracts; + using System.Security.Cryptography; + using System.Security.Cryptography.X509Certificates; + using System.Text; + using DotNetOpenAuth.Messaging; + + /// <summary> + /// A binding element that signs outgoing messages and verifies the signature on incoming messages. + /// </summary> + public class RsaSha1ConsumerSigningBindingElement : RsaSha1SigningBindingElement { + /// <summary> + /// Initializes a new instance of the <see cref="RsaSha1ConsumerSigningBindingElement"/> class. + /// </summary> + /// <param name="signingCertificate">The certificate used to sign outgoing messages.</param> + public RsaSha1ConsumerSigningBindingElement(X509Certificate2 signingCertificate) { + Requires.NotNull(signingCertificate, "signingCertificate"); + + this.SigningCertificate = signingCertificate; + } + + /// <summary> + /// Gets or sets the certificate used to sign outgoing messages. Used only by Consumers. + /// </summary> + public X509Certificate2 SigningCertificate { get; set; } + + /// <summary> + /// Determines whether the signature on some message is valid. + /// </summary> + /// <param name="message">The message to check the signature on.</param> + /// <returns> + /// <c>true</c> if the signature on the message is valid; otherwise, <c>false</c>. + /// </returns> + protected override bool IsSignatureValid(ITamperResistantOAuthMessage message) { + throw new NotImplementedException(); + } + + /// <summary> + /// Calculates a signature for a given message. + /// </summary> + /// <param name="message">The message to sign.</param> + /// <returns>The signature for the message.</returns> + /// <remarks> + /// This method signs the message per OAuth 1.0 section 9.3. + /// </remarks> + protected override string GetSignature(ITamperResistantOAuthMessage message) { + ErrorUtilities.VerifyOperation(this.SigningCertificate != null, OAuthStrings.X509CertificateNotProvidedForSigning); + + string signatureBaseString = ConstructSignatureBaseString(message, this.Channel.MessageDescriptions.GetAccessor(message)); + byte[] data = Encoding.ASCII.GetBytes(signatureBaseString); + var provider = (RSACryptoServiceProvider)this.SigningCertificate.PrivateKey; + byte[] binarySignature = provider.SignData(data, "SHA1"); + string base64Signature = Convert.ToBase64String(binarySignature); + return base64Signature; + } + + /// <summary> + /// Creates a new object that is a copy of the current instance. + /// </summary> + /// <returns> + /// A new object that is a copy of this instance. + /// </returns> + protected override ITamperProtectionChannelBindingElement Clone() { + return new RsaSha1ConsumerSigningBindingElement(this.SigningCertificate); + } + } +} diff --git a/src/DotNetOpenAuth.OAuth.Consumer/OAuth/ConsumerBase.cs b/src/DotNetOpenAuth.OAuth.Consumer/OAuth/ConsumerBase.cs new file mode 100644 index 0000000..89f5a5f --- /dev/null +++ b/src/DotNetOpenAuth.OAuth.Consumer/OAuth/ConsumerBase.cs @@ -0,0 +1,302 @@ +//----------------------------------------------------------------------- +// <copyright file="ConsumerBase.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OAuth { + using System; + using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; + using System.Diagnostics.Contracts; + using System.Linq; + using System.Net; + using DotNetOpenAuth.Configuration; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.Messaging.Bindings; + using DotNetOpenAuth.OAuth.ChannelElements; + using DotNetOpenAuth.OAuth.Messages; + + /// <summary> + /// Base class for <see cref="WebConsumer"/> and <see cref="DesktopConsumer"/> types. + /// </summary> + public class ConsumerBase : IDisposable { + /// <summary> + /// Initializes a new instance of the <see cref="ConsumerBase"/> class. + /// </summary> + /// <param name="serviceDescription">The endpoints and behavior of the Service Provider.</param> + /// <param name="tokenManager">The host's method of storing and recalling tokens and secrets.</param> + protected ConsumerBase(ServiceProviderDescription serviceDescription, IConsumerTokenManager tokenManager) { + Requires.NotNull(serviceDescription, "serviceDescription"); + Requires.NotNull(tokenManager, "tokenManager"); + + ITamperProtectionChannelBindingElement signingElement = serviceDescription.CreateTamperProtectionElement(); + INonceStore store = new NonceMemoryStore(StandardExpirationBindingElement.MaximumMessageAge); + this.SecuritySettings = OAuthElement.Configuration.Consumer.SecuritySettings.CreateSecuritySettings(); + this.OAuthChannel = new OAuthConsumerChannel(signingElement, store, tokenManager, this.SecuritySettings); + this.ServiceProvider = serviceDescription; + + OAuthReporting.RecordFeatureAndDependencyUse(this, serviceDescription, tokenManager, null); + } + + /// <summary> + /// Gets the Consumer Key used to communicate with the Service Provider. + /// </summary> + public string ConsumerKey { + get { return this.TokenManager.ConsumerKey; } + } + + /// <summary> + /// Gets the Service Provider that will be accessed. + /// </summary> + public ServiceProviderDescription ServiceProvider { get; private set; } + + /// <summary> + /// Gets the persistence store for tokens and secrets. + /// </summary> + public IConsumerTokenManager TokenManager { + get { return (IConsumerTokenManager)this.OAuthChannel.TokenManager; } + } + + /// <summary> + /// Gets the channel to use for sending/receiving messages. + /// </summary> + public Channel Channel { + get { return this.OAuthChannel; } + } + + /// <summary> + /// Gets the security settings for this consumer. + /// </summary> + internal ConsumerSecuritySettings SecuritySettings { get; private set; } + + /// <summary> + /// Gets or sets the channel to use for sending/receiving messages. + /// </summary> + internal OAuthChannel OAuthChannel { get; set; } + + /// <summary> + /// Obtains an access token for a new account at the Service Provider via 2-legged OAuth. + /// </summary> + /// <param name="requestParameters">Any applicable parameters to include in the query string of the token request.</param> + /// <returns>The access token.</returns> + /// <remarks> + /// The token secret is stored in the <see cref="TokenManager"/>. + /// </remarks> + public string RequestNewClientAccount(IDictionary<string, string> requestParameters = null) { + // Obtain an unauthorized request token. Assume the OAuth version given in the service description. + var token = new UnauthorizedTokenRequest(this.ServiceProvider.RequestTokenEndpoint, this.ServiceProvider.Version) { + ConsumerKey = this.ConsumerKey, + }; + var tokenAccessor = this.Channel.MessageDescriptions.GetAccessor(token); + tokenAccessor.AddExtraParameters(requestParameters); + var requestTokenResponse = this.Channel.Request<UnauthorizedTokenResponse>(token); + this.TokenManager.StoreNewRequestToken(token, requestTokenResponse); + + var requestAccess = new AuthorizedTokenRequest(this.ServiceProvider.AccessTokenEndpoint, this.ServiceProvider.Version) { + RequestToken = requestTokenResponse.RequestToken, + ConsumerKey = this.ConsumerKey, + }; + var grantAccess = this.Channel.Request<AuthorizedTokenResponse>(requestAccess); + this.TokenManager.ExpireRequestTokenAndStoreNewAccessToken(this.ConsumerKey, requestTokenResponse.RequestToken, grantAccess.AccessToken, grantAccess.TokenSecret); + return grantAccess.AccessToken; + } + + /// <summary> + /// Creates a web request prepared with OAuth authorization + /// that may be further tailored by adding parameters by the caller. + /// </summary> + /// <param name="endpoint">The URL and method on the Service Provider to send the request to.</param> + /// <param name="accessToken">The access token that permits access to the protected resource.</param> + /// <returns>The initialized WebRequest object.</returns> + public HttpWebRequest PrepareAuthorizedRequest(MessageReceivingEndpoint endpoint, string accessToken) { + Requires.NotNull(endpoint, "endpoint"); + Requires.NotNullOrEmpty(accessToken, "accessToken"); + + return this.PrepareAuthorizedRequest(endpoint, accessToken, EmptyDictionary<string, string>.Instance); + } + + /// <summary> + /// Creates a web request prepared with OAuth authorization + /// that may be further tailored by adding parameters by the caller. + /// </summary> + /// <param name="endpoint">The URL and method on the Service Provider to send the request to.</param> + /// <param name="accessToken">The access token that permits access to the protected resource.</param> + /// <param name="extraData">Extra parameters to include in the message. Must not be null, but may be empty.</param> + /// <returns>The initialized WebRequest object.</returns> + public HttpWebRequest PrepareAuthorizedRequest(MessageReceivingEndpoint endpoint, string accessToken, IDictionary<string, string> extraData) { + Requires.NotNull(endpoint, "endpoint"); + Requires.NotNullOrEmpty(accessToken, "accessToken"); + Requires.NotNull(extraData, "extraData"); + + IDirectedProtocolMessage message = this.CreateAuthorizingMessage(endpoint, accessToken); + foreach (var pair in extraData) { + message.ExtraData.Add(pair); + } + + HttpWebRequest wr = this.OAuthChannel.InitializeRequest(message); + return wr; + } + + /// <summary> + /// Prepares an authorized request that carries an HTTP multi-part POST, allowing for binary data. + /// </summary> + /// <param name="endpoint">The URL and method on the Service Provider to send the request to.</param> + /// <param name="accessToken">The access token that permits access to the protected resource.</param> + /// <param name="binaryData">Extra parameters to include in the message. Must not be null, but may be empty.</param> + /// <returns>The initialized WebRequest object.</returns> + public HttpWebRequest PrepareAuthorizedRequest(MessageReceivingEndpoint endpoint, string accessToken, IEnumerable<MultipartPostPart> binaryData) { + Requires.NotNull(endpoint, "endpoint"); + Requires.NotNullOrEmpty(accessToken, "accessToken"); + Requires.NotNull(binaryData, "binaryData"); + + AccessProtectedResourceRequest message = this.CreateAuthorizingMessage(endpoint, accessToken); + foreach (MultipartPostPart part in binaryData) { + message.BinaryData.Add(part); + } + + HttpWebRequest wr = this.OAuthChannel.InitializeRequest(message); + return wr; + } + + /// <summary> + /// Prepares an HTTP request that has OAuth authorization already attached to it. + /// </summary> + /// <param name="message">The OAuth authorization message to attach to the HTTP request.</param> + /// <returns> + /// The HttpWebRequest that can be used to send the HTTP request to the remote service provider. + /// </returns> + /// <remarks> + /// If <see cref="IDirectedProtocolMessage.HttpMethods"/> property on the + /// <paramref name="message"/> has the + /// <see cref="HttpDeliveryMethods.AuthorizationHeaderRequest"/> flag set and + /// <see cref="ITamperResistantOAuthMessage.HttpMethod"/> is set to an HTTP method + /// that includes an entity body, the request stream is automatically sent + /// if and only if the <see cref="IMessage.ExtraData"/> dictionary is non-empty. + /// </remarks> + [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "Type of parameter forces the method to apply only to specific scenario.")] + public HttpWebRequest PrepareAuthorizedRequest(AccessProtectedResourceRequest message) { + Requires.NotNull(message, "message"); + return this.OAuthChannel.InitializeRequest(message); + } + + /// <summary> + /// Creates a web request prepared with OAuth authorization + /// that may be further tailored by adding parameters by the caller. + /// </summary> + /// <param name="endpoint">The URL and method on the Service Provider to send the request to.</param> + /// <param name="accessToken">The access token that permits access to the protected resource.</param> + /// <returns>The initialized WebRequest object.</returns> + /// <exception cref="WebException">Thrown if the request fails for any reason after it is sent to the Service Provider.</exception> + public IncomingWebResponse PrepareAuthorizedRequestAndSend(MessageReceivingEndpoint endpoint, string accessToken) { + IDirectedProtocolMessage message = this.CreateAuthorizingMessage(endpoint, accessToken); + HttpWebRequest wr = this.OAuthChannel.InitializeRequest(message); + return this.Channel.WebRequestHandler.GetResponse(wr); + } + + #region IDisposable Members + + /// <summary> + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// </summary> + public void Dispose() { + this.Dispose(true); + GC.SuppressFinalize(this); + } + + #endregion + + /// <summary> + /// Creates a web request prepared with OAuth authorization + /// that may be further tailored by adding parameters by the caller. + /// </summary> + /// <param name="endpoint">The URL and method on the Service Provider to send the request to.</param> + /// <param name="accessToken">The access token that permits access to the protected resource.</param> + /// <returns>The initialized WebRequest object.</returns> + protected internal AccessProtectedResourceRequest CreateAuthorizingMessage(MessageReceivingEndpoint endpoint, string accessToken) { + Requires.NotNull(endpoint, "endpoint"); + Requires.NotNullOrEmpty(accessToken, "accessToken"); + + AccessProtectedResourceRequest message = new AccessProtectedResourceRequest(endpoint, this.ServiceProvider.Version) { + AccessToken = accessToken, + ConsumerKey = this.ConsumerKey, + }; + + return message; + } + + /// <summary> + /// Prepares an OAuth message that begins an authorization request that will + /// redirect the user to the Service Provider to provide that authorization. + /// </summary> + /// <param name="callback"> + /// An optional Consumer URL that the Service Provider should redirect the + /// User Agent to upon successful authorization. + /// </param> + /// <param name="requestParameters">Extra parameters to add to the request token message. Optional.</param> + /// <param name="redirectParameters">Extra parameters to add to the redirect to Service Provider message. Optional.</param> + /// <param name="requestToken">The request token that must be exchanged for an access token after the user has provided authorization.</param> + /// <returns>The pending user agent redirect based message to be sent as an HttpResponse.</returns> + [SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "3#", Justification = "Two results")] + protected internal UserAuthorizationRequest PrepareRequestUserAuthorization(Uri callback, IDictionary<string, string> requestParameters, IDictionary<string, string> redirectParameters, out string requestToken) { + // Obtain an unauthorized request token. Assume the OAuth version given in the service description. + var token = new UnauthorizedTokenRequest(this.ServiceProvider.RequestTokenEndpoint, this.ServiceProvider.Version) { + ConsumerKey = this.ConsumerKey, + Callback = callback, + }; + var tokenAccessor = this.Channel.MessageDescriptions.GetAccessor(token); + tokenAccessor.AddExtraParameters(requestParameters); + var requestTokenResponse = this.Channel.Request<UnauthorizedTokenResponse>(token); + this.TokenManager.StoreNewRequestToken(token, requestTokenResponse); + + // Fine-tune our understanding of the SP's supported OAuth version if it's wrong. + if (this.ServiceProvider.Version != requestTokenResponse.Version) { + Logger.OAuth.WarnFormat("Expected OAuth service provider at endpoint {0} to use OAuth {1} but {2} was detected. Adjusting service description to new version.", this.ServiceProvider.RequestTokenEndpoint.Location, this.ServiceProvider.Version, requestTokenResponse.Version); + this.ServiceProvider.ProtocolVersion = Protocol.Lookup(requestTokenResponse.Version).ProtocolVersion; + } + + // Request user authorization. The OAuth version will automatically include + // or drop the callback that we're setting here. + ITokenContainingMessage assignedRequestToken = requestTokenResponse; + var requestAuthorization = new UserAuthorizationRequest(this.ServiceProvider.UserAuthorizationEndpoint, assignedRequestToken.Token, requestTokenResponse.Version) { + Callback = callback, + }; + var requestAuthorizationAccessor = this.Channel.MessageDescriptions.GetAccessor(requestAuthorization); + requestAuthorizationAccessor.AddExtraParameters(redirectParameters); + requestToken = requestAuthorization.RequestToken; + return requestAuthorization; + } + + /// <summary> + /// Exchanges a given request token for access token. + /// </summary> + /// <param name="requestToken">The request token that the user has authorized.</param> + /// <param name="verifier">The verifier code.</param> + /// <returns> + /// The access token assigned by the Service Provider. + /// </returns> + protected AuthorizedTokenResponse ProcessUserAuthorization(string requestToken, string verifier) { + Requires.NotNullOrEmpty(requestToken, "requestToken"); + Contract.Ensures(Contract.Result<AuthorizedTokenResponse>() != null); + + var requestAccess = new AuthorizedTokenRequest(this.ServiceProvider.AccessTokenEndpoint, this.ServiceProvider.Version) { + RequestToken = requestToken, + VerificationCode = verifier, + ConsumerKey = this.ConsumerKey, + }; + var grantAccess = this.Channel.Request<AuthorizedTokenResponse>(requestAccess); + this.TokenManager.ExpireRequestTokenAndStoreNewAccessToken(this.ConsumerKey, requestToken, grantAccess.AccessToken, grantAccess.TokenSecret); + return grantAccess; + } + + /// <summary> + /// Releases unmanaged and - optionally - managed resources + /// </summary> + /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param> + protected virtual void Dispose(bool disposing) { + if (disposing) { + this.Channel.Dispose(); + } + } + } +} diff --git a/src/DotNetOpenAuth/OAuth/DesktopConsumer.cs b/src/DotNetOpenAuth.OAuth.Consumer/OAuth/DesktopConsumer.cs index f9c1a94..f9c1a94 100644 --- a/src/DotNetOpenAuth/OAuth/DesktopConsumer.cs +++ b/src/DotNetOpenAuth.OAuth.Consumer/OAuth/DesktopConsumer.cs diff --git a/src/DotNetOpenAuth.OAuth.Consumer/OAuth/WebConsumer.cs b/src/DotNetOpenAuth.OAuth.Consumer/OAuth/WebConsumer.cs new file mode 100644 index 0000000..e7d7f4f --- /dev/null +++ b/src/DotNetOpenAuth.OAuth.Consumer/OAuth/WebConsumer.cs @@ -0,0 +1,155 @@ +//----------------------------------------------------------------------- +// <copyright file="WebConsumer.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OAuth { + using System; + using System.Collections.Generic; + using System.Diagnostics.Contracts; + using System.Web; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OAuth.ChannelElements; + using DotNetOpenAuth.OAuth.Messages; + using DotNetOpenAuth.OpenId.Extensions.OAuth; + using DotNetOpenAuth.OpenId.RelyingParty; + + /// <summary> + /// A website or application that uses OAuth to access the Service Provider on behalf of the User. + /// </summary> + /// <remarks> + /// The methods on this class are thread-safe. Provided the properties are set and not changed + /// afterward, a single instance of this class may be used by an entire web application safely. + /// </remarks> + public class WebConsumer : ConsumerBase { + /// <summary> + /// Initializes a new instance of the <see cref="WebConsumer"/> class. + /// </summary> + /// <param name="serviceDescription">The endpoints and behavior of the Service Provider.</param> + /// <param name="tokenManager">The host's method of storing and recalling tokens and secrets.</param> + public WebConsumer(ServiceProviderDescription serviceDescription, IConsumerTokenManager tokenManager) + : base(serviceDescription, tokenManager) { + } + + /// <summary> + /// Begins an OAuth authorization request and redirects the user to the Service Provider + /// to provide that authorization. Upon successful authorization, the user is redirected + /// back to the current page. + /// </summary> + /// <returns>The pending user agent redirect based message to be sent as an HttpResponse.</returns> + /// <remarks> + /// Requires HttpContext.Current. + /// </remarks> + public UserAuthorizationRequest PrepareRequestUserAuthorization() { + Uri callback = this.Channel.GetRequestFromContext().UrlBeforeRewriting.StripQueryArgumentsWithPrefix(Protocol.ParameterPrefix); + return this.PrepareRequestUserAuthorization(callback, null, null); + } + + /// <summary> + /// Prepares an OAuth message that begins an authorization request that will + /// redirect the user to the Service Provider to provide that authorization. + /// </summary> + /// <param name="callback"> + /// An optional Consumer URL that the Service Provider should redirect the + /// User Agent to upon successful authorization. + /// </param> + /// <param name="requestParameters">Extra parameters to add to the request token message. Optional.</param> + /// <param name="redirectParameters">Extra parameters to add to the redirect to Service Provider message. Optional.</param> + /// <returns>The pending user agent redirect based message to be sent as an HttpResponse.</returns> + public UserAuthorizationRequest PrepareRequestUserAuthorization(Uri callback, IDictionary<string, string> requestParameters, IDictionary<string, string> redirectParameters) { + string token; + return this.PrepareRequestUserAuthorization(callback, requestParameters, redirectParameters, out token); + } + + /// <summary> + /// Processes an incoming authorization-granted message from an SP and obtains an access token. + /// </summary> + /// <returns>The access token, or null if no incoming authorization message was recognized.</returns> + /// <remarks> + /// Requires HttpContext.Current. + /// </remarks> + public AuthorizedTokenResponse ProcessUserAuthorization() { + return this.ProcessUserAuthorization(this.Channel.GetRequestFromContext()); + } + + /// <summary> + /// Attaches an OAuth authorization request to an outgoing OpenID authentication request. + /// </summary> + /// <param name="openIdAuthenticationRequest">The OpenID authentication request.</param> + /// <param name="scope">The scope of access that is requested of the service provider.</param> + public void AttachAuthorizationRequest(IAuthenticationRequest openIdAuthenticationRequest, string scope) { + Requires.NotNull(openIdAuthenticationRequest, "openIdAuthenticationRequest"); + + var authorizationRequest = new AuthorizationRequest { + Consumer = this.ConsumerKey, + Scope = scope, + }; + + openIdAuthenticationRequest.AddExtension(authorizationRequest); + } + + /// <summary> + /// Processes an incoming authorization-granted message from an SP and obtains an access token. + /// </summary> + /// <param name="openIdAuthenticationResponse">The OpenID authentication response that may be carrying an authorized request token.</param> + /// <returns> + /// The access token, or null if OAuth authorization was denied by the user or service provider. + /// </returns> + /// <remarks> + /// The access token, if granted, is automatically stored in the <see cref="ConsumerBase.TokenManager"/>. + /// The token manager instance must implement <see cref="IOpenIdOAuthTokenManager"/>. + /// </remarks> + public AuthorizedTokenResponse ProcessUserAuthorization(IAuthenticationResponse openIdAuthenticationResponse) { + Requires.NotNull(openIdAuthenticationResponse, "openIdAuthenticationResponse"); + Requires.ValidState(this.TokenManager is IOpenIdOAuthTokenManager); + var openidTokenManager = this.TokenManager as IOpenIdOAuthTokenManager; + ErrorUtilities.VerifyOperation(openidTokenManager != null, OAuthStrings.OpenIdOAuthExtensionRequiresSpecialTokenManagerInterface, typeof(IOpenIdOAuthTokenManager).FullName); + + // The OAuth extension is only expected in positive assertion responses. + if (openIdAuthenticationResponse.Status != AuthenticationStatus.Authenticated) { + return null; + } + + // Retrieve the OAuth extension + var positiveAuthorization = openIdAuthenticationResponse.GetExtension<AuthorizationApprovedResponse>(); + if (positiveAuthorization == null) { + return null; + } + + // Prepare a message to exchange the request token for an access token. + // We are careful to use a v1.0 message version so that the oauth_verifier is not required. + var requestAccess = new AuthorizedTokenRequest(this.ServiceProvider.AccessTokenEndpoint, Protocol.V10.Version) { + RequestToken = positiveAuthorization.RequestToken, + ConsumerKey = this.ConsumerKey, + }; + + // Retrieve the access token and store it in the token manager. + openidTokenManager.StoreOpenIdAuthorizedRequestToken(this.ConsumerKey, positiveAuthorization); + var grantAccess = this.Channel.Request<AuthorizedTokenResponse>(requestAccess); + this.TokenManager.ExpireRequestTokenAndStoreNewAccessToken(this.ConsumerKey, positiveAuthorization.RequestToken, grantAccess.AccessToken, grantAccess.TokenSecret); + + // Provide the caller with the access token so it may be associated with the user + // that is logging in. + return grantAccess; + } + + /// <summary> + /// Processes an incoming authorization-granted message from an SP and obtains an access token. + /// </summary> + /// <param name="request">The incoming HTTP request.</param> + /// <returns>The access token, or null if no incoming authorization message was recognized.</returns> + public AuthorizedTokenResponse ProcessUserAuthorization(HttpRequestInfo request) { + Requires.NotNull(request, "request"); + + UserAuthorizationResponse authorizationMessage; + if (this.Channel.TryReadFromRequest<UserAuthorizationResponse>(request, out authorizationMessage)) { + string requestToken = authorizationMessage.RequestToken; + string verifier = authorizationMessage.VerificationCode; + return this.ProcessUserAuthorization(requestToken, verifier); + } else { + return null; + } + } + } +} diff --git a/src/DotNetOpenAuth.OAuth.Consumer/Properties/AssemblyInfo.cs b/src/DotNetOpenAuth.OAuth.Consumer/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..fd29585 --- /dev/null +++ b/src/DotNetOpenAuth.OAuth.Consumer/Properties/AssemblyInfo.cs @@ -0,0 +1,58 @@ +//----------------------------------------------------------------------- +// <copyright file="AssemblyInfo.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +// We DON'T put an AssemblyVersionAttribute in here because it is generated in the build. + +using System; +using System.Diagnostics.Contracts; +using System.Net; +using System.Reflection; +using System.Resources; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Security; +using System.Security.Permissions; +using System.Web.UI; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("DotNetOpenAuth OAuth")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("DotNetOpenAuth")] +[assembly: AssemblyCopyright("Copyright © 2008")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: NeutralResourcesLanguage("en-US")] +[assembly: CLSCompliant(true)] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("7d73990c-47c0-4256-9f20-a893add9e289")] + +[assembly: ContractVerification(true)] + +#if StrongNameSigned +// See comment at top of this file. We need this so that strong-naming doesn't +// keep this assembly from being useful to shared host (medium trust) web sites. +[assembly: AllowPartiallyTrustedCallers] + +[assembly: InternalsVisibleTo("DotNetOpenAuth.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100AD093C3765257C89A7010E853F2C7C741FF92FA8ACE06D7B8254702CAD5CF99104447F63AB05F8BB6F51CE0D81C8C93D2FCE8C20AAFF7042E721CBA16EAAE98778611DED11C0ABC8900DC5667F99B50A9DADEC24DBD8F2C91E3E8AD300EF64F1B4B9536CEB16FB440AF939F57624A9B486F867807C649AE4830EAB88C6C03998")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.OAuth2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100AD093C3765257C89A7010E853F2C7C741FF92FA8ACE06D7B8254702CAD5CF99104447F63AB05F8BB6F51CE0D81C8C93D2FCE8C20AAFF7042E721CBA16EAAE98778611DED11C0ABC8900DC5667F99B50A9DADEC24DBD8F2C91E3E8AD300EF64F1B4B9536CEB16FB440AF939F57624A9B486F867807C649AE4830EAB88C6C03998")] +[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")] +#else +[assembly: InternalsVisibleTo("DotNetOpenAuth.Test")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.OAuth.Consumer")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.OAuth.ServiceProvider")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.OAuth2")] +[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] +#endif diff --git a/src/DotNetOpenAuth.OAuth.Consumer/Properties/Properties/AssemblyInfo.cs b/src/DotNetOpenAuth.OAuth.Consumer/Properties/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..d402b02 --- /dev/null +++ b/src/DotNetOpenAuth.OAuth.Consumer/Properties/Properties/AssemblyInfo.cs @@ -0,0 +1,58 @@ +//----------------------------------------------------------------------- +// <copyright file="AssemblyInfo.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +// We DON'T put an AssemblyVersionAttribute in here because it is generated in the build. + +using System; +using System.Diagnostics.Contracts; +using System.Net; +using System.Reflection; +using System.Resources; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Security; +using System.Security.Permissions; +using System.Web.UI; + +[assembly: TagPrefix("DotNetOpenAuth.OAuth", "oauth")] + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("DotNetOpenAuth OAuth")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("DotNetOpenAuth")] +[assembly: AssemblyCopyright("Copyright © 2008")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: NeutralResourcesLanguage("en-US")] +[assembly: CLSCompliant(true)] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("7d73990c-47c0-4256-9f20-a893add9e289")] + +[assembly: ContractVerification(true)] + +#if StrongNameSigned +// See comment at top of this file. We need this so that strong-naming doesn't +// keep this assembly from being useful to shared host (medium trust) web sites. +[assembly: AllowPartiallyTrustedCallers] + +[assembly: InternalsVisibleTo("DotNetOpenAuth.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100AD093C3765257C89A7010E853F2C7C741FF92FA8ACE06D7B8254702CAD5CF99104447F63AB05F8BB6F51CE0D81C8C93D2FCE8C20AAFF7042E721CBA16EAAE98778611DED11C0ABC8900DC5667F99B50A9DADEC24DBD8F2C91E3E8AD300EF64F1B4B9536CEB16FB440AF939F57624A9B486F867807C649AE4830EAB88C6C03998")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.OAuth2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100AD093C3765257C89A7010E853F2C7C741FF92FA8ACE06D7B8254702CAD5CF99104447F63AB05F8BB6F51CE0D81C8C93D2FCE8C20AAFF7042E721CBA16EAAE98778611DED11C0ABC8900DC5667F99B50A9DADEC24DBD8F2C91E3E8AD300EF64F1B4B9536CEB16FB440AF939F57624A9B486F867807C649AE4830EAB88C6C03998")] +[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")] +#else +[assembly: InternalsVisibleTo("DotNetOpenAuth.Test")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.OAuth2")] +[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] +#endif diff --git a/src/DotNetOpenAuth.OAuth.ServiceProvider/DotNetOpenAuth.OAuth.ServiceProvider.csproj b/src/DotNetOpenAuth.OAuth.ServiceProvider/DotNetOpenAuth.OAuth.ServiceProvider.csproj new file mode 100644 index 0000000..4e99b4a --- /dev/null +++ b/src/DotNetOpenAuth.OAuth.ServiceProvider/DotNetOpenAuth.OAuth.ServiceProvider.csproj @@ -0,0 +1,57 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), EnlistmentInfo.props))\EnlistmentInfo.props" Condition=" '$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), EnlistmentInfo.props))' != '' " /> + <PropertyGroup> + <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> + <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> + </PropertyGroup> + <Import Project="$(ProjectRoot)tools\DotNetOpenAuth.props" /> + <PropertyGroup> + <SchemaVersion>2.0</SchemaVersion> + <ProjectGuid>{FED1923A-6D70-49B5-A37A-FB744FEC1C86}</ProjectGuid> + <AppDesignerFolder>Properties</AppDesignerFolder> + <AssemblyName>DotNetOpenAuth.OAuth.ServiceProvider</AssemblyName> + </PropertyGroup> + <Import Project="$(ProjectRoot)tools\DotNetOpenAuth.Product.props" /> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> + </PropertyGroup> + <ItemGroup> + <Compile Include="OAuth\ChannelElements\IConsumerDescription.cs" /> + <Compile Include="OAuth\ChannelElements\IServiceProviderAccessToken.cs" /> + <Compile Include="OAuth\ChannelElements\IServiceProviderRequestToken.cs" /> + <Compile Include="OAuth\ChannelElements\IServiceProviderTokenManager.cs" /> + <Compile Include="OAuth\ChannelElements\ITokenGenerator.cs" /> + <Compile Include="OAuth\ChannelElements\OAuthServiceProviderChannel.cs" /> + <Compile Include="OAuth\ChannelElements\OAuthServiceProviderMessageFactory.cs" /> + <Compile Include="OAuth\ChannelElements\OAuthIdentity.cs" /> + <Compile Include="OAuth\ChannelElements\OAuthPrincipal.cs" /> + <Compile Include="OAuth\ChannelElements\RsaSha1ServiceProviderSigningBindingElement.cs" /> + <Compile Include="OAuth\ChannelElements\StandardTokenGenerator.cs" /> + <Compile Include="OAuth\ChannelElements\TokenHandlingBindingElement.cs" /> + <Compile Include="OAuth\ServiceProvider.cs" /> + <Compile Include="OAuth\VerificationCodeFormat.cs" /> + <Compile Include="Properties\AssemblyInfo.cs"> + <SubType> + </SubType> + </Compile> + </ItemGroup> + <ItemGroup> + <ProjectReference Include="..\DotNetOpenAuth.Core\DotNetOpenAuth.Core.csproj"> + <Project>{60426312-6AE5-4835-8667-37EDEA670222}</Project> + <Name>DotNetOpenAuth.Core</Name> + </ProjectReference> + <ProjectReference Include="..\DotNetOpenAuth.OAuth\DotNetOpenAuth.OAuth.csproj"> + <Project>{A288FCC8-6FCF-46DA-A45E-5F9281556361}</Project> + <Name>DotNetOpenAuth.OAuth</Name> + </ProjectReference> + <ProjectReference Include="..\DotNetOpenAuth.OpenId\DotNetOpenAuth.OpenId.csproj"> + <Project>{3896A32A-E876-4C23-B9B8-78E17D134CD3}</Project> + <Name>DotNetOpenAuth.OpenId</Name> + </ProjectReference> + </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))' != '' " /> +</Project>
\ No newline at end of file diff --git a/src/DotNetOpenAuth/OAuth/ChannelElements/IConsumerDescription.cs b/src/DotNetOpenAuth.OAuth.ServiceProvider/OAuth/ChannelElements/IConsumerDescription.cs index db505d5..db505d5 100644 --- a/src/DotNetOpenAuth/OAuth/ChannelElements/IConsumerDescription.cs +++ b/src/DotNetOpenAuth.OAuth.ServiceProvider/OAuth/ChannelElements/IConsumerDescription.cs diff --git a/src/DotNetOpenAuth/OAuth/ChannelElements/IServiceProviderAccessToken.cs b/src/DotNetOpenAuth.OAuth.ServiceProvider/OAuth/ChannelElements/IServiceProviderAccessToken.cs index 35ba52d..35ba52d 100644 --- a/src/DotNetOpenAuth/OAuth/ChannelElements/IServiceProviderAccessToken.cs +++ b/src/DotNetOpenAuth.OAuth.ServiceProvider/OAuth/ChannelElements/IServiceProviderAccessToken.cs diff --git a/src/DotNetOpenAuth/OAuth/ChannelElements/IServiceProviderRequestToken.cs b/src/DotNetOpenAuth.OAuth.ServiceProvider/OAuth/ChannelElements/IServiceProviderRequestToken.cs index 6dfa416..6dfa416 100644 --- a/src/DotNetOpenAuth/OAuth/ChannelElements/IServiceProviderRequestToken.cs +++ b/src/DotNetOpenAuth.OAuth.ServiceProvider/OAuth/ChannelElements/IServiceProviderRequestToken.cs diff --git a/src/DotNetOpenAuth.OAuth.ServiceProvider/OAuth/ChannelElements/IServiceProviderTokenManager.cs b/src/DotNetOpenAuth.OAuth.ServiceProvider/OAuth/ChannelElements/IServiceProviderTokenManager.cs new file mode 100644 index 0000000..13320b3 --- /dev/null +++ b/src/DotNetOpenAuth.OAuth.ServiceProvider/OAuth/ChannelElements/IServiceProviderTokenManager.cs @@ -0,0 +1,251 @@ +//----------------------------------------------------------------------- +// <copyright file="IServiceProviderTokenManager.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OAuth.ChannelElements { + using System; + using System.Collections.Generic; + using System.Diagnostics.Contracts; + using System.Linq; + using System.Text; + + /// <summary> + /// A token manager for use by a web site in its role as a + /// service provider. + /// </summary> + [ContractClass(typeof(IServiceProviderTokenManagerContract))] + public interface IServiceProviderTokenManager : ITokenManager { + /// <summary> + /// Gets the Consumer description for a given a Consumer Key. + /// </summary> + /// <param name="consumerKey">The Consumer Key.</param> + /// <returns>A description of the consumer. Never null.</returns> + /// <exception cref="KeyNotFoundException">Thrown if the consumer key cannot be found.</exception> + IConsumerDescription GetConsumer(string consumerKey); + + /// <summary> + /// Checks whether a given request token has already been authorized + /// by some user for use by the Consumer that requested it. + /// </summary> + /// <param name="requestToken">The Consumer's request token.</param> + /// <returns> + /// True if the request token has already been fully authorized by the user + /// who owns the relevant protected resources. False if the token has not yet + /// been authorized, has expired or does not exist. + /// </returns> + bool IsRequestTokenAuthorized(string requestToken); + + /// <summary> + /// Gets details on the named request token. + /// </summary> + /// <param name="token">The request token.</param> + /// <returns>A description of the token. Never null.</returns> + /// <exception cref="KeyNotFoundException">Thrown if the token cannot be found.</exception> + /// <remarks> + /// It is acceptable for implementations to find the token, see that it has expired, + /// delete it from the database and then throw <see cref="KeyNotFoundException"/>, + /// or alternatively it can return the expired token anyway and the OAuth channel will + /// log and throw the appropriate error. + /// </remarks> + IServiceProviderRequestToken GetRequestToken(string token); + + /// <summary> + /// Gets details on the named access token. + /// </summary> + /// <param name="token">The access token.</param> + /// <returns>A description of the token. Never null.</returns> + /// <exception cref="KeyNotFoundException">Thrown if the token cannot be found.</exception> + /// <remarks> + /// It is acceptable for implementations to find the token, see that it has expired, + /// delete it from the database and then throw <see cref="KeyNotFoundException"/>, + /// or alternatively it can return the expired token anyway and the OAuth channel will + /// log and throw the appropriate error. + /// </remarks> + IServiceProviderAccessToken GetAccessToken(string token); + + /// <summary> + /// Persists any changes made to the token. + /// </summary> + /// <param name="token">The token whose properties have been changed.</param> + /// <remarks> + /// This library will invoke this method after making a set + /// of changes to the token as part of a web request to give the host + /// the opportunity to persist those changes to a database. + /// Depending on the object persistence framework the host site uses, + /// this method MAY not need to do anything (if changes made to the token + /// will automatically be saved without any extra handling). + /// </remarks> + void UpdateToken(IServiceProviderRequestToken token); + } + + /// <summary> + /// Code contract class for the <see cref="IServiceProviderTokenManager"/> interface. + /// </summary> + [ContractClassFor(typeof(IServiceProviderTokenManager))] + internal abstract class IServiceProviderTokenManagerContract : IServiceProviderTokenManager { + /// <summary> + /// Prevents a default instance of the <see cref="IServiceProviderTokenManagerContract"/> class from being created. + /// </summary> + private IServiceProviderTokenManagerContract() { + } + + #region IServiceProviderTokenManager Members + + /// <summary> + /// Gets the Consumer description for a given a Consumer Key. + /// </summary> + /// <param name="consumerKey">The Consumer Key.</param> + /// <returns> + /// A description of the consumer. Never null. + /// </returns> + /// <exception cref="KeyNotFoundException">Thrown if the consumer key cannot be found.</exception> + IConsumerDescription IServiceProviderTokenManager.GetConsumer(string consumerKey) { + Requires.NotNullOrEmpty(consumerKey, "consumerKey"); + Contract.Ensures(Contract.Result<IConsumerDescription>() != null); + throw new NotImplementedException(); + } + + /// <summary> + /// Checks whether a given request token has already been authorized + /// by some user for use by the Consumer that requested it. + /// </summary> + /// <param name="requestToken">The Consumer's request token.</param> + /// <returns> + /// True if the request token has already been fully authorized by the user + /// who owns the relevant protected resources. False if the token has not yet + /// been authorized, has expired or does not exist. + /// </returns> + bool IServiceProviderTokenManager.IsRequestTokenAuthorized(string requestToken) { + Requires.NotNullOrEmpty(requestToken, "requestToken"); + throw new NotImplementedException(); + } + + /// <summary> + /// Gets details on the named request token. + /// </summary> + /// <param name="token">The request token.</param> + /// <returns>A description of the token. Never null.</returns> + /// <exception cref="KeyNotFoundException">Thrown if the token cannot be found.</exception> + /// <remarks> + /// It is acceptable for implementations to find the token, see that it has expired, + /// delete it from the database and then throw <see cref="KeyNotFoundException"/>, + /// or alternatively it can return the expired token anyway and the OAuth channel will + /// log and throw the appropriate error. + /// </remarks> + IServiceProviderRequestToken IServiceProviderTokenManager.GetRequestToken(string token) { + Requires.NotNullOrEmpty(token, "token"); + Contract.Ensures(Contract.Result<IServiceProviderRequestToken>() != null); + throw new NotImplementedException(); + } + + /// <summary> + /// Gets details on the named access token. + /// </summary> + /// <param name="token">The access token.</param> + /// <returns>A description of the token. Never null.</returns> + /// <exception cref="KeyNotFoundException">Thrown if the token cannot be found.</exception> + /// <remarks> + /// It is acceptable for implementations to find the token, see that it has expired, + /// delete it from the database and then throw <see cref="KeyNotFoundException"/>, + /// or alternatively it can return the expired token anyway and the OAuth channel will + /// log and throw the appropriate error. + /// </remarks> + IServiceProviderAccessToken IServiceProviderTokenManager.GetAccessToken(string token) { + Requires.NotNullOrEmpty(token, "token"); + Contract.Ensures(Contract.Result<IServiceProviderAccessToken>() != null); + throw new NotImplementedException(); + } + + /// <summary> + /// Persists any changes made to the token. + /// </summary> + /// <param name="token">The token whose properties have been changed.</param> + /// <remarks> + /// This library will invoke this method after making a set + /// of changes to the token as part of a web request to give the host + /// the opportunity to persist those changes to a database. + /// Depending on the object persistence framework the host site uses, + /// this method MAY not need to do anything (if changes made to the token + /// will automatically be saved without any extra handling). + /// </remarks> + void IServiceProviderTokenManager.UpdateToken(IServiceProviderRequestToken token) { + Requires.NotNull(token, "token"); + throw new NotImplementedException(); + } + + #endregion + + #region ITokenManager Members + + /// <summary> + /// Gets the Token Secret given a request or access token. + /// </summary> + /// <param name="token">The request or access token.</param> + /// <returns> + /// The secret associated with the given token. + /// </returns> + /// <exception cref="ArgumentException">Thrown if the secret cannot be found for the given token.</exception> + string ITokenManager.GetTokenSecret(string token) { + throw new NotImplementedException(); + } + + /// <summary> + /// Stores a newly generated unauthorized request token, secret, and optional + /// application-specific parameters for later recall. + /// </summary> + /// <param name="request">The request message that resulted in the generation of a new unauthorized request token.</param> + /// <param name="response">The response message that includes the unauthorized request token.</param> + /// <exception cref="ArgumentException">Thrown if the consumer key is not registered, or a required parameter was not found in the parameters collection.</exception> + /// <remarks> + /// Request tokens stored by this method SHOULD NOT associate any user account with this token. + /// It usually opens up security holes in your application to do so. Instead, you associate a user + /// account with access tokens (not request tokens) in the <see cref="ITokenManager.ExpireRequestTokenAndStoreNewAccessToken"/> + /// method. + /// </remarks> + void ITokenManager.StoreNewRequestToken(DotNetOpenAuth.OAuth.Messages.UnauthorizedTokenRequest request, DotNetOpenAuth.OAuth.Messages.ITokenSecretContainingMessage response) { + throw new NotImplementedException(); + } + + /// <summary> + /// Deletes a request token and its associated secret and stores a new access token and secret. + /// </summary> + /// <param name="consumerKey">The Consumer that is exchanging its request token for an access token.</param> + /// <param name="requestToken">The Consumer's request token that should be deleted/expired.</param> + /// <param name="accessToken">The new access token that is being issued to the Consumer.</param> + /// <param name="accessTokenSecret">The secret associated with the newly issued access token.</param> + /// <remarks> + /// <para> + /// Any scope of granted privileges associated with the request token from the + /// original call to <see cref="ITokenManager.StoreNewRequestToken"/> should be carried over + /// to the new Access Token. + /// </para> + /// <para> + /// To associate a user account with the new access token, + /// <see cref="System.Web.HttpContext.User">HttpContext.Current.User</see> may be + /// useful in an ASP.NET web application within the implementation of this method. + /// Alternatively you may store the access token here without associating with a user account, + /// and wait until WebConsumer.ProcessUserAuthorization or + /// DesktopConsumer.ProcessUserAuthorization return the access + /// token to associate the access token with a user account at that point. + /// </para> + /// </remarks> + void ITokenManager.ExpireRequestTokenAndStoreNewAccessToken(string consumerKey, string requestToken, string accessToken, string accessTokenSecret) { + throw new NotImplementedException(); + } + + /// <summary> + /// Classifies a token as a request token or an access token. + /// </summary> + /// <param name="token">The token to classify.</param> + /// <returns> + /// Request or Access token, or invalid if the token is not recognized. + /// </returns> + TokenType ITokenManager.GetTokenType(string token) { + throw new NotImplementedException(); + } + + #endregion + } +} diff --git a/src/DotNetOpenAuth/OAuth/ChannelElements/ITokenGenerator.cs b/src/DotNetOpenAuth.OAuth.ServiceProvider/OAuth/ChannelElements/ITokenGenerator.cs index ce22479..ce22479 100644 --- a/src/DotNetOpenAuth/OAuth/ChannelElements/ITokenGenerator.cs +++ b/src/DotNetOpenAuth.OAuth.ServiceProvider/OAuth/ChannelElements/ITokenGenerator.cs diff --git a/src/DotNetOpenAuth.OAuth.ServiceProvider/OAuth/ChannelElements/OAuthIdentity.cs b/src/DotNetOpenAuth.OAuth.ServiceProvider/OAuth/ChannelElements/OAuthIdentity.cs new file mode 100644 index 0000000..6865f79 --- /dev/null +++ b/src/DotNetOpenAuth.OAuth.ServiceProvider/OAuth/ChannelElements/OAuthIdentity.cs @@ -0,0 +1,64 @@ +//----------------------------------------------------------------------- +// <copyright file="OAuthIdentity.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OAuth.ChannelElements { + using System; + using System.Diagnostics.CodeAnalysis; + using System.Diagnostics.Contracts; + using System.Runtime.InteropServices; + using System.Security.Principal; + using DotNetOpenAuth.Messaging; + + /// <summary> + /// Represents an OAuth consumer that is impersonating a known user on the system. + /// </summary> + [SuppressMessage("Microsoft.Interoperability", "CA1409:ComVisibleTypesShouldBeCreatable", Justification = "Not cocreatable.")] + [Serializable] + [ComVisible(true)] + public class OAuthIdentity : IIdentity { + /// <summary> + /// Initializes a new instance of the <see cref="OAuthIdentity"/> class. + /// </summary> + /// <param name="username">The username.</param> + internal OAuthIdentity(string username) { + Requires.NotNullOrEmpty(username, "username"); + this.Name = username; + } + + #region IIdentity Members + + /// <summary> + /// Gets the type of authentication used. + /// </summary> + /// <value>The constant "OAuth"</value> + /// <returns> + /// The type of authentication used to identify the user. + /// </returns> + public string AuthenticationType { + get { return "OAuth"; } + } + + /// <summary> + /// Gets a value indicating whether the user has been authenticated. + /// </summary> + /// <value>The value <c>true</c></value> + /// <returns>true if the user was authenticated; otherwise, false. + /// </returns> + public bool IsAuthenticated { + get { return true; } + } + + /// <summary> + /// Gets the name of the user who authorized the OAuth token the consumer is using for authorization. + /// </summary> + /// <returns> + /// The name of the user on whose behalf the code is running. + /// </returns> + public string Name { get; private set; } + + #endregion + } +} diff --git a/src/DotNetOpenAuth.OAuth.ServiceProvider/OAuth/ChannelElements/OAuthPrincipal.cs b/src/DotNetOpenAuth.OAuth.ServiceProvider/OAuth/ChannelElements/OAuthPrincipal.cs new file mode 100644 index 0000000..f4d167d --- /dev/null +++ b/src/DotNetOpenAuth.OAuth.ServiceProvider/OAuth/ChannelElements/OAuthPrincipal.cs @@ -0,0 +1,109 @@ +//----------------------------------------------------------------------- +// <copyright file="OAuthPrincipal.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OAuth.ChannelElements { + using System; + using System.Collections.Generic; + using System.Collections.ObjectModel; + using System.Diagnostics.CodeAnalysis; + using System.Diagnostics.Contracts; + using System.Linq; + using System.Runtime.InteropServices; + using System.Security.Principal; + + /// <summary> + /// Represents an OAuth consumer that is impersonating a known user on the system. + /// </summary> + [SuppressMessage("Microsoft.Interoperability", "CA1409:ComVisibleTypesShouldBeCreatable", Justification = "Not cocreatable.")] + [Serializable] + [ComVisible(true)] + public class OAuthPrincipal : IPrincipal { + /// <summary> + /// The roles this user belongs to. + /// </summary> + private ICollection<string> roles; + + /// <summary> + /// Initializes a new instance of the <see cref="OAuthPrincipal"/> class. + /// </summary> + /// <param name="userName">The username.</param> + /// <param name="roles">The roles this user belongs to.</param> + public OAuthPrincipal(string userName, string[] roles) + : this(new OAuthIdentity(userName), roles) { + } + + /// <summary> + /// Initializes a new instance of the <see cref="OAuthPrincipal"/> class. + /// </summary> + /// <param name="token">The access token.</param> + internal OAuthPrincipal(IServiceProviderAccessToken token) + : this(token.Username, token.Roles) { + Requires.NotNull(token, "token"); + + this.AccessToken = token.Token; + } + + /// <summary> + /// Initializes a new instance of the <see cref="OAuthPrincipal"/> class. + /// </summary> + /// <param name="identity">The identity.</param> + /// <param name="roles">The roles this user belongs to.</param> + internal OAuthPrincipal(OAuthIdentity identity, string[] roles) { + this.Identity = identity; + this.roles = roles; + } + + /// <summary> + /// Gets the access token used to create this principal. + /// </summary> + /// <value>A non-empty string.</value> + public string AccessToken { get; private set; } + + /// <summary> + /// Gets the roles that this principal has as a ReadOnlyCollection. + /// </summary> + public ReadOnlyCollection<string> Roles + { + get { return new ReadOnlyCollection<string>(this.roles.ToList()); } + } + + #region IPrincipal Members + + /// <summary> + /// Gets the identity of the current principal. + /// </summary> + /// <value></value> + /// <returns> + /// The <see cref="T:System.Security.Principal.IIdentity"/> object associated with the current principal. + /// </returns> + public IIdentity Identity { get; private set; } + + /// <summary> + /// Determines whether the current principal belongs to the specified role. + /// </summary> + /// <param name="role">The name of the role for which to check membership.</param> + /// <returns> + /// true if the current principal is a member of the specified role; otherwise, false. + /// </returns> + /// <remarks> + /// The role membership check uses <see cref="StringComparer.OrdinalIgnoreCase"/>. + /// </remarks> + public bool IsInRole(string role) { + return this.roles.Contains(role, StringComparer.OrdinalIgnoreCase); + } + + #endregion + + /// <summary> + /// Creates a new instance of GenericPrincipal based on this OAuthPrincipal. + /// </summary> + /// <returns>A new instance of GenericPrincipal with a GenericIdentity, having the same username and roles as this OAuthPrincipal and OAuthIdentity</returns> + public GenericPrincipal CreateGenericPrincipal() + { + return new GenericPrincipal(new GenericIdentity(this.Identity.Name), this.roles.ToArray()); + } + } +} diff --git a/src/DotNetOpenAuth.OAuth.ServiceProvider/OAuth/ChannelElements/OAuthServiceProviderChannel.cs b/src/DotNetOpenAuth.OAuth.ServiceProvider/OAuth/ChannelElements/OAuthServiceProviderChannel.cs new file mode 100644 index 0000000..43e7902 --- /dev/null +++ b/src/DotNetOpenAuth.OAuth.ServiceProvider/OAuth/ChannelElements/OAuthServiceProviderChannel.cs @@ -0,0 +1,74 @@ +//----------------------------------------------------------------------- +// <copyright file="OAuthServiceProviderChannel.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OAuth.ChannelElements { + using System; + using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; + using System.Diagnostics.Contracts; + using System.Linq; + using System.Text; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.Messaging.Bindings; + + /// <summary> + /// The messaging channel for OAuth 1.0(a) Service Providers. + /// </summary> + internal class OAuthServiceProviderChannel : OAuthChannel { + /// <summary> + /// Initializes a new instance of the <see cref="OAuthServiceProviderChannel"/> class. + /// </summary> + /// <param name="signingBindingElement">The binding element to use for signing.</param> + /// <param name="store">The web application store to use for nonces.</param> + /// <param name="tokenManager">The token manager instance to use.</param> + /// <param name="securitySettings">The security settings.</param> + /// <param name="messageTypeProvider">The message type provider.</param> + [SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", MessageId = "System.Diagnostics.Contracts.__ContractsRuntime.Requires<System.ArgumentNullException>(System.Boolean,System.String,System.String)", Justification = "Code contracts"), SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "securitySettings", Justification = "Code contracts")] + internal OAuthServiceProviderChannel(ITamperProtectionChannelBindingElement signingBindingElement, INonceStore store, IServiceProviderTokenManager tokenManager, ServiceProviderSecuritySettings securitySettings, IMessageFactory messageTypeProvider = null) + : base( + signingBindingElement, + store, + tokenManager, + securitySettings, + messageTypeProvider ?? new OAuthServiceProviderMessageFactory(tokenManager), + InitializeBindingElements(signingBindingElement, store, tokenManager, securitySettings)) { + Requires.NotNull(tokenManager, "tokenManager"); + Requires.NotNull(securitySettings, "securitySettings"); + Requires.NotNull(signingBindingElement, "signingBindingElement"); + } + + /// <summary> + /// Gets the consumer secret for a given consumer key. + /// </summary> + /// <param name="consumerKey">The consumer key.</param> + /// <returns>The consumer secret.</returns> + protected override string GetConsumerSecret(string consumerKey) { + return ((IServiceProviderTokenManager)this.TokenManager).GetConsumer(consumerKey).Secret; + } + + /// <summary> + /// Initializes the binding elements for the OAuth channel. + /// </summary> + /// <param name="signingBindingElement">The signing binding element.</param> + /// <param name="store">The nonce store.</param> + /// <param name="tokenManager">The token manager.</param> + /// <param name="securitySettings">The security settings.</param> + /// <returns> + /// An array of binding elements used to initialize the channel. + /// </returns> + private static new IChannelBindingElement[] InitializeBindingElements(ITamperProtectionChannelBindingElement signingBindingElement, INonceStore store, ITokenManager tokenManager, SecuritySettings securitySettings) { + Contract.Requires(securitySettings != null); + + var bindingElements = OAuthChannel.InitializeBindingElements(signingBindingElement, store, tokenManager, securitySettings); + + var spTokenManager = tokenManager as IServiceProviderTokenManager; + var serviceProviderSecuritySettings = securitySettings as ServiceProviderSecuritySettings; + bindingElements.Insert(0, new TokenHandlingBindingElement(spTokenManager, serviceProviderSecuritySettings)); + + return bindingElements.ToArray(); + } + } +} diff --git a/src/DotNetOpenAuth.OAuth.ServiceProvider/OAuth/ChannelElements/OAuthServiceProviderMessageFactory.cs b/src/DotNetOpenAuth.OAuth.ServiceProvider/OAuth/ChannelElements/OAuthServiceProviderMessageFactory.cs new file mode 100644 index 0000000..565756b --- /dev/null +++ b/src/DotNetOpenAuth.OAuth.ServiceProvider/OAuth/ChannelElements/OAuthServiceProviderMessageFactory.cs @@ -0,0 +1,126 @@ +//----------------------------------------------------------------------- +// <copyright file="OAuthServiceProviderMessageFactory.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OAuth.ChannelElements { + using System; + using System.Collections.Generic; + using System.Diagnostics.Contracts; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OAuth.Messages; + + /// <summary> + /// An OAuth-protocol specific implementation of the <see cref="IMessageFactory"/> + /// interface. + /// </summary> + public class OAuthServiceProviderMessageFactory : IMessageFactory { + /// <summary> + /// The token manager to use for discerning between request and access tokens. + /// </summary> + private IServiceProviderTokenManager tokenManager; + + /// <summary> + /// Initializes a new instance of the <see cref="OAuthServiceProviderMessageFactory"/> class. + /// </summary> + /// <param name="tokenManager">The token manager instance to use.</param> + public OAuthServiceProviderMessageFactory(IServiceProviderTokenManager tokenManager) { + Requires.NotNull(tokenManager, "tokenManager"); + + this.tokenManager = tokenManager; + } + + #region IMessageFactory Members + + /// <summary> + /// Analyzes an incoming request message payload to discover what kind of + /// message is embedded in it and returns the type, or null if no match is found. + /// </summary> + /// <param name="recipient">The intended or actual recipient of the request message.</param> + /// <param name="fields">The name/value pairs that make up the message payload.</param> + /// <returns> + /// A newly instantiated <see cref="IProtocolMessage"/>-derived object that this message can + /// deserialize to. Null if the request isn't recognized as a valid protocol message. + /// </returns> + /// <remarks> + /// The request messages are: + /// UnauthorizedTokenRequest + /// AuthorizedTokenRequest + /// UserAuthorizationRequest + /// AccessProtectedResourceRequest + /// </remarks> + public virtual IDirectedProtocolMessage GetNewRequestMessage(MessageReceivingEndpoint recipient, IDictionary<string, string> fields) { + MessageBase message = null; + Protocol protocol = Protocol.V10; // default to assuming the less-secure 1.0 instead of 1.0a until we prove otherwise. + string token; + fields.TryGetValue("oauth_token", out token); + + try { + if (fields.ContainsKey("oauth_consumer_key") && !fields.ContainsKey("oauth_token")) { + protocol = fields.ContainsKey("oauth_callback") ? Protocol.V10a : Protocol.V10; + message = new UnauthorizedTokenRequest(recipient, protocol.Version); + } else if (fields.ContainsKey("oauth_consumer_key") && fields.ContainsKey("oauth_token")) { + // Discern between RequestAccessToken and AccessProtectedResources, + // which have all the same parameters, by figuring out what type of token + // is in the token parameter. + bool tokenTypeIsAccessToken = this.tokenManager.GetTokenType(token) == TokenType.AccessToken; + + if (tokenTypeIsAccessToken) { + message = (MessageBase)new AccessProtectedResourceRequest(recipient, protocol.Version); + } else { + // Discern between 1.0 and 1.0a requests by checking on the consumer version we stored + // when the consumer first requested an unauthorized token. + protocol = Protocol.Lookup(this.tokenManager.GetRequestToken(token).ConsumerVersion); + message = new AuthorizedTokenRequest(recipient, protocol.Version); + } + } else { + // fail over to the message with no required fields at all. + if (token != null) { + protocol = Protocol.Lookup(this.tokenManager.GetRequestToken(token).ConsumerVersion); + } + + // If a callback parameter is included, that suggests either the consumer + // is following OAuth 1.0 instead of 1.0a, or that a hijacker is trying + // to attack. Either way, if the consumer started out as a 1.0a, keep it + // that way, and we'll just ignore the oauth_callback included in this message + // by virtue of the UserAuthorizationRequest message not including it in its + // 1.0a payload. + message = new UserAuthorizationRequest(recipient, protocol.Version); + } + + if (message != null) { + message.SetAsIncoming(); + } + + return message; + } catch (KeyNotFoundException ex) { + throw ErrorUtilities.Wrap(ex, OAuthStrings.TokenNotFound); + } + } + + /// <summary> + /// Analyzes an incoming request message payload to discover what kind of + /// message is embedded in it and returns the type, or null if no match is found. + /// </summary> + /// <param name="request"> + /// The message that was sent as a request that resulted in the response. + /// Null on a Consumer site that is receiving an indirect message from the Service Provider. + /// </param> + /// <param name="fields">The name/value pairs that make up the message payload.</param> + /// <returns> + /// The <see cref="IProtocolMessage"/>-derived concrete class that this message can + /// deserialize to. Null if the request isn't recognized as a valid protocol message. + /// </returns> + /// <remarks> + /// The response messages are: + /// None. + /// </remarks> + public virtual IDirectResponseProtocolMessage GetNewResponseMessage(IDirectedProtocolMessage request, IDictionary<string, string> fields) { + Logger.OAuth.Error("Service Providers are not expected to ever receive responses."); + return null; + } + + #endregion + } +} diff --git a/src/DotNetOpenAuth.OAuth.ServiceProvider/OAuth/ChannelElements/RsaSha1ServiceProviderSigningBindingElement.cs b/src/DotNetOpenAuth.OAuth.ServiceProvider/OAuth/ChannelElements/RsaSha1ServiceProviderSigningBindingElement.cs new file mode 100644 index 0000000..06a005b --- /dev/null +++ b/src/DotNetOpenAuth.OAuth.ServiceProvider/OAuth/ChannelElements/RsaSha1ServiceProviderSigningBindingElement.cs @@ -0,0 +1,81 @@ +//----------------------------------------------------------------------- +// <copyright file="RsaSha1ServiceProviderSigningBindingElement.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OAuth.ChannelElements { + using System; + using System.Diagnostics.Contracts; + using System.Security.Cryptography; + using System.Security.Cryptography.X509Certificates; + using System.Text; + using DotNetOpenAuth.Messaging; + + /// <summary> + /// A binding element that signs outgoing messages and verifies the signature on incoming messages. + /// </summary> + public class RsaSha1ServiceProviderSigningBindingElement : RsaSha1SigningBindingElement { + /// <summary> + /// The token manager for the service provider. + /// </summary> + private IServiceProviderTokenManager tokenManager; + + /// <summary> + /// Initializes a new instance of the <see cref="RsaSha1ServiceProviderSigningBindingElement"/> class. + /// </summary> + /// <param name="tokenManager">The token manager.</param> + public RsaSha1ServiceProviderSigningBindingElement(IServiceProviderTokenManager tokenManager) { + Requires.NotNull(tokenManager, "tokenManager"); + + this.tokenManager = tokenManager; + } + + /// <summary> + /// Determines whether the signature on some message is valid. + /// </summary> + /// <param name="message">The message to check the signature on.</param> + /// <returns> + /// <c>true</c> if the signature on the message is valid; otherwise, <c>false</c>. + /// </returns> + protected override bool IsSignatureValid(ITamperResistantOAuthMessage message) { + ErrorUtilities.VerifyInternal(this.tokenManager != null, "No token manager available for fetching Consumer public certificates."); + + string signatureBaseString = ConstructSignatureBaseString(message, this.Channel.MessageDescriptions.GetAccessor(message)); + byte[] data = Encoding.ASCII.GetBytes(signatureBaseString); + + byte[] carriedSignature = Convert.FromBase64String(message.Signature); + + X509Certificate2 cert = this.tokenManager.GetConsumer(message.ConsumerKey).Certificate; + if (cert == null) { + Logger.Signatures.WarnFormat("Incoming message from consumer '{0}' could not be matched with an appropriate X.509 certificate for signature verification.", message.ConsumerKey); + return false; + } + + var provider = (RSACryptoServiceProvider)cert.PublicKey.Key; + bool valid = provider.VerifyData(data, "SHA1", carriedSignature); + return valid; + } + + /// <summary> + /// Calculates a signature for a given message. + /// </summary> + /// <param name="message">The message to sign.</param> + /// <returns> + /// The signature for the message. + /// </returns> + protected override string GetSignature(ITamperResistantOAuthMessage message) { + throw new NotImplementedException(); + } + + /// <summary> + /// Clones this instance. + /// </summary> + /// <returns> + /// A new instance of the binding element. + /// </returns> + protected override ITamperProtectionChannelBindingElement Clone() { + return new RsaSha1ServiceProviderSigningBindingElement(this.tokenManager); + } + } +} diff --git a/src/DotNetOpenAuth/OAuth/ChannelElements/StandardTokenGenerator.cs b/src/DotNetOpenAuth.OAuth.ServiceProvider/OAuth/ChannelElements/StandardTokenGenerator.cs index d18f5fe..d18f5fe 100644 --- a/src/DotNetOpenAuth/OAuth/ChannelElements/StandardTokenGenerator.cs +++ b/src/DotNetOpenAuth.OAuth.ServiceProvider/OAuth/ChannelElements/StandardTokenGenerator.cs diff --git a/src/DotNetOpenAuth.OAuth.ServiceProvider/OAuth/ChannelElements/TokenHandlingBindingElement.cs b/src/DotNetOpenAuth.OAuth.ServiceProvider/OAuth/ChannelElements/TokenHandlingBindingElement.cs new file mode 100644 index 0000000..596336a --- /dev/null +++ b/src/DotNetOpenAuth.OAuth.ServiceProvider/OAuth/ChannelElements/TokenHandlingBindingElement.cs @@ -0,0 +1,201 @@ +//----------------------------------------------------------------------- +// <copyright file="TokenHandlingBindingElement.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OAuth.ChannelElements { + using System; + using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; + using System.Diagnostics.Contracts; + using System.Linq; + using System.Text; + using DotNetOpenAuth.Configuration; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OAuth.Messages; + + /// <summary> + /// A binding element for Service Providers to manage the + /// callbacks and verification codes on applicable messages. + /// </summary> + internal class TokenHandlingBindingElement : IChannelBindingElement { + /// <summary> + /// The token manager offered by the service provider. + /// </summary> + private IServiceProviderTokenManager tokenManager; + + /// <summary> + /// The security settings for this service provider. + /// </summary> + private ServiceProviderSecuritySettings securitySettings; + + /// <summary> + /// Initializes a new instance of the <see cref="TokenHandlingBindingElement"/> class. + /// </summary> + /// <param name="tokenManager">The token manager.</param> + /// <param name="securitySettings">The security settings.</param> + [SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", MessageId = "System.Diagnostics.Contracts.__ContractsRuntime.Requires<System.ArgumentNullException>(System.Boolean,System.String,System.String)", Justification = "Code contract"), SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "securitySettings", Justification = "Code contracts")] + internal TokenHandlingBindingElement(IServiceProviderTokenManager tokenManager, ServiceProviderSecuritySettings securitySettings) { + Requires.NotNull(tokenManager, "tokenManager"); + Requires.NotNull(securitySettings, "securitySettings"); + + this.tokenManager = tokenManager; + this.securitySettings = securitySettings; + } + + #region IChannelBindingElement Members + + /// <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 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 MessageProtections? ProcessOutgoingMessage(IProtocolMessage message) { + var userAuthResponse = message as UserAuthorizationResponse; + if (userAuthResponse != null && userAuthResponse.Version >= Protocol.V10a.Version) { + var requestToken = this.tokenManager.GetRequestToken(userAuthResponse.RequestToken); + requestToken.VerificationCode = userAuthResponse.VerificationCode; + this.tokenManager.UpdateToken(requestToken); + return MessageProtections.None; + } + + // Hook to store the token and secret on its way down to the Consumer. + var grantRequestTokenResponse = message as UnauthorizedTokenResponse; + if (grantRequestTokenResponse != null) { + this.tokenManager.StoreNewRequestToken(grantRequestTokenResponse.RequestMessage, grantRequestTokenResponse); + + // The host may have already set these properties, but just to make sure... + var requestToken = this.tokenManager.GetRequestToken(grantRequestTokenResponse.RequestToken); + requestToken.ConsumerVersion = grantRequestTokenResponse.Version; + if (grantRequestTokenResponse.RequestMessage.Callback != null) { + requestToken.Callback = grantRequestTokenResponse.RequestMessage.Callback; + } + this.tokenManager.UpdateToken(requestToken); + + return MessageProtections.None; + } + + 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 MessageProtections? ProcessIncomingMessage(IProtocolMessage message) { + var authorizedTokenRequest = message as AuthorizedTokenRequest; + if (authorizedTokenRequest != null) { + if (authorizedTokenRequest.Version >= Protocol.V10a.Version) { + string expectedVerifier = this.tokenManager.GetRequestToken(authorizedTokenRequest.RequestToken).VerificationCode; + ErrorUtilities.VerifyProtocol(string.Equals(authorizedTokenRequest.VerificationCode, expectedVerifier, StringComparison.Ordinal), OAuthStrings.IncorrectVerifier); + return MessageProtections.None; + } + + this.VerifyThrowTokenTimeToLive(authorizedTokenRequest); + } + + var userAuthorizationRequest = message as UserAuthorizationRequest; + if (userAuthorizationRequest != null) { + this.VerifyThrowTokenTimeToLive(userAuthorizationRequest); + } + + var accessResourceRequest = message as AccessProtectedResourceRequest; + if (accessResourceRequest != null) { + this.VerifyThrowTokenNotExpired(accessResourceRequest); + } + + return null; + } + + #endregion + + /// <summary> + /// Ensures that access tokens have not yet expired. + /// </summary> + /// <param name="message">The incoming message carrying the access token.</param> + private void VerifyThrowTokenNotExpired(AccessProtectedResourceRequest message) { + Requires.NotNull(message, "message"); + + try { + IServiceProviderAccessToken token = this.tokenManager.GetAccessToken(message.AccessToken); + if (token.ExpirationDate.HasValue && DateTime.Now >= token.ExpirationDate.Value.ToLocalTimeSafe()) { + Logger.OAuth.ErrorFormat( + "OAuth access token {0} rejected because it expired at {1}, and it is now {2}.", + token.Token, + token.ExpirationDate.Value, + DateTime.Now); + ErrorUtilities.ThrowProtocol(OAuthStrings.TokenNotFound); + } + } catch (KeyNotFoundException ex) { + throw ErrorUtilities.Wrap(ex, OAuthStrings.TokenNotFound); + } + } + + /// <summary> + /// Ensures that short-lived request tokens included in incoming messages have not expired. + /// </summary> + /// <param name="message">The incoming message.</param> + /// <exception cref="ProtocolException">Thrown when the token in the message has expired.</exception> + private void VerifyThrowTokenTimeToLive(ITokenContainingMessage message) { + ErrorUtilities.VerifyInternal(!(message is AccessProtectedResourceRequest), "We shouldn't be verifying TTL on access tokens."); + if (message == null || string.IsNullOrEmpty(message.Token)) { + return; + } + + try { + IServiceProviderRequestToken token = this.tokenManager.GetRequestToken(message.Token); + TimeSpan ttl = this.securitySettings.MaximumRequestTokenTimeToLive; + if (DateTime.Now >= token.CreatedOn.ToLocalTimeSafe() + ttl) { + Logger.OAuth.ErrorFormat( + "OAuth request token {0} rejected because it was originally issued at {1}, expired at {2}, and it is now {3}.", + token.Token, + token.CreatedOn, + token.CreatedOn + ttl, + DateTime.Now); + ErrorUtilities.ThrowProtocol(OAuthStrings.TokenNotFound); + } + } catch (KeyNotFoundException ex) { + throw ErrorUtilities.Wrap(ex, OAuthStrings.TokenNotFound); + } + } + } +} diff --git a/src/DotNetOpenAuth.OAuth.ServiceProvider/OAuth/ServiceProvider.cs b/src/DotNetOpenAuth.OAuth.ServiceProvider/OAuth/ServiceProvider.cs new file mode 100644 index 0000000..719e84e --- /dev/null +++ b/src/DotNetOpenAuth.OAuth.ServiceProvider/OAuth/ServiceProvider.cs @@ -0,0 +1,576 @@ +//----------------------------------------------------------------------- +// <copyright file="ServiceProvider.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OAuth { + using System; + using System.Collections.Generic; + using System.ComponentModel; + using System.Diagnostics.CodeAnalysis; + using System.Diagnostics.Contracts; + using System.Globalization; + using System.Security.Principal; + using System.ServiceModel.Channels; + using System.Web; + using DotNetOpenAuth.Configuration; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.Messaging.Bindings; + using DotNetOpenAuth.OAuth.ChannelElements; + using DotNetOpenAuth.OAuth.Messages; + using DotNetOpenAuth.OpenId; + using DotNetOpenAuth.OpenId.Extensions.OAuth; + using DotNetOpenAuth.OpenId.Messages; + using DotNetOpenAuth.OpenId.Provider; + + /// <summary> + /// A web application that allows access via OAuth. + /// </summary> + /// <remarks> + /// <para>The Service Provider’s documentation should include:</para> + /// <list> + /// <item>The URLs (Request URLs) the Consumer will use when making OAuth requests, and the HTTP methods (i.e. GET, POST, etc.) used in the Request Token URL and Access Token URL.</item> + /// <item>Signature methods supported by the Service Provider.</item> + /// <item>Any additional request parameters that the Service Provider requires in order to obtain a Token. Service Provider specific parameters MUST NOT begin with oauth_.</item> + /// </list> + /// </remarks> + public class ServiceProvider : IDisposable { + /// <summary> + /// The name of the key to use in the HttpApplication cache to store the + /// instance of <see cref="NonceMemoryStore"/> to use. + /// </summary> + private const string ApplicationStoreKey = "DotNetOpenAuth.OAuth.ServiceProvider.HttpApplicationStore"; + + /// <summary> + /// The length of the verifier code (in raw bytes before base64 encoding) to generate. + /// </summary> + private const int VerifierCodeLength = 5; + + /// <summary> + /// The field behind the <see cref="OAuthChannel"/> property. + /// </summary> + private OAuthChannel channel; + + /// <summary> + /// Initializes a new instance of the <see cref="ServiceProvider"/> class. + /// </summary> + /// <param name="serviceDescription">The endpoints and behavior on the Service Provider.</param> + /// <param name="tokenManager">The host's method of storing and recalling tokens and secrets.</param> + public ServiceProvider(ServiceProviderDescription serviceDescription, IServiceProviderTokenManager tokenManager) + : this(serviceDescription, tokenManager, new OAuthServiceProviderMessageFactory(tokenManager)) { + } + + /// <summary> + /// Initializes a new instance of the <see cref="ServiceProvider"/> class. + /// </summary> + /// <param name="serviceDescription">The endpoints and behavior on the Service Provider.</param> + /// <param name="tokenManager">The host's method of storing and recalling tokens and secrets.</param> + /// <param name="messageTypeProvider">An object that can figure out what type of message is being received for deserialization.</param> + public ServiceProvider(ServiceProviderDescription serviceDescription, IServiceProviderTokenManager tokenManager, OAuthServiceProviderMessageFactory messageTypeProvider) + : this(serviceDescription, tokenManager, OAuthElement.Configuration.ServiceProvider.ApplicationStore.CreateInstance(HttpApplicationStore), messageTypeProvider) { + Requires.NotNull(serviceDescription, "serviceDescription"); + Requires.NotNull(tokenManager, "tokenManager"); + Requires.NotNull(messageTypeProvider, "messageTypeProvider"); + } + + /// <summary> + /// Initializes a new instance of the <see cref="ServiceProvider"/> class. + /// </summary> + /// <param name="serviceDescription">The endpoints and behavior on the Service Provider.</param> + /// <param name="tokenManager">The host's method of storing and recalling tokens and secrets.</param> + /// <param name="nonceStore">The nonce store.</param> + public ServiceProvider(ServiceProviderDescription serviceDescription, IServiceProviderTokenManager tokenManager, INonceStore nonceStore) + : this(serviceDescription, tokenManager, nonceStore, new OAuthServiceProviderMessageFactory(tokenManager)) { + } + + /// <summary> + /// Initializes a new instance of the <see cref="ServiceProvider"/> class. + /// </summary> + /// <param name="serviceDescription">The endpoints and behavior on the Service Provider.</param> + /// <param name="tokenManager">The host's method of storing and recalling tokens and secrets.</param> + /// <param name="nonceStore">The nonce store.</param> + /// <param name="messageTypeProvider">An object that can figure out what type of message is being received for deserialization.</param> + public ServiceProvider(ServiceProviderDescription serviceDescription, IServiceProviderTokenManager tokenManager, INonceStore nonceStore, OAuthServiceProviderMessageFactory messageTypeProvider) { + Requires.NotNull(serviceDescription, "serviceDescription"); + Requires.NotNull(tokenManager, "tokenManager"); + Requires.NotNull(nonceStore, "nonceStore"); + Requires.NotNull(messageTypeProvider, "messageTypeProvider"); + + var signingElement = serviceDescription.CreateTamperProtectionElement(); + this.ServiceDescription = serviceDescription; + this.SecuritySettings = OAuthElement.Configuration.ServiceProvider.SecuritySettings.CreateSecuritySettings(); + this.OAuthChannel = new OAuthServiceProviderChannel(signingElement, nonceStore, tokenManager, this.SecuritySettings, messageTypeProvider); + this.TokenGenerator = new StandardTokenGenerator(); + + OAuthReporting.RecordFeatureAndDependencyUse(this, serviceDescription, tokenManager, nonceStore); + } + + /// <summary> + /// Gets the standard state storage mechanism that uses ASP.NET's + /// HttpApplication state dictionary to store associations and nonces. + /// </summary> + [EditorBrowsable(EditorBrowsableState.Advanced)] + public static INonceStore HttpApplicationStore { + get { + Contract.Ensures(Contract.Result<INonceStore>() != null); + + HttpContext context = HttpContext.Current; + ErrorUtilities.VerifyOperation(context != null, Strings.StoreRequiredWhenNoHttpContextAvailable, typeof(INonceStore).Name); + var store = (INonceStore)context.Application[ApplicationStoreKey]; + if (store == null) { + context.Application.Lock(); + try { + if ((store = (INonceStore)context.Application[ApplicationStoreKey]) == null) { + context.Application[ApplicationStoreKey] = store = new NonceMemoryStore(StandardExpirationBindingElement.MaximumMessageAge); + } + } finally { + context.Application.UnLock(); + } + } + + return store; + } + } + + /// <summary> + /// Gets the description of this Service Provider. + /// </summary> + public ServiceProviderDescription ServiceDescription { get; private set; } + + /// <summary> + /// Gets or sets the generator responsible for generating new tokens and secrets. + /// </summary> + public ITokenGenerator TokenGenerator { get; set; } + + /// <summary> + /// Gets the persistence store for tokens and secrets. + /// </summary> + public IServiceProviderTokenManager TokenManager { + get { return (IServiceProviderTokenManager)this.OAuthChannel.TokenManager; } + } + + /// <summary> + /// Gets the channel to use for sending/receiving messages. + /// </summary> + public Channel Channel { + get { return this.OAuthChannel; } + } + + /// <summary> + /// Gets the security settings for this service provider. + /// </summary> + public ServiceProviderSecuritySettings SecuritySettings { get; private set; } + + /// <summary> + /// Gets or sets the channel to use for sending/receiving messages. + /// </summary> + internal OAuthChannel OAuthChannel { + get { + return this.channel; + } + + set { + Requires.NotNull(value, "value"); + this.channel = value; + } + } + + /// <summary> + /// Creates a cryptographically strong random verification code. + /// </summary> + /// <param name="format">The desired format of the verification code.</param> + /// <param name="length">The length of the code. + /// When <paramref name="format"/> is <see cref="VerificationCodeFormat.IncludedInCallback"/>, + /// this is the length of the original byte array before base64 encoding rather than the actual + /// length of the final string.</param> + /// <returns>The verification code.</returns> + public static string CreateVerificationCode(VerificationCodeFormat format, int length) { + Requires.InRange(length >= 0, "length"); + + switch (format) { + case VerificationCodeFormat.IncludedInCallback: + return MessagingUtilities.GetCryptoRandomDataAsBase64(length); + case VerificationCodeFormat.AlphaNumericNoLookAlikes: + return MessagingUtilities.GetRandomString(length, MessagingUtilities.AlphaNumericNoLookAlikes); + case VerificationCodeFormat.AlphaUpper: + return MessagingUtilities.GetRandomString(length, MessagingUtilities.UppercaseLetters); + case VerificationCodeFormat.AlphaLower: + return MessagingUtilities.GetRandomString(length, MessagingUtilities.LowercaseLetters); + case VerificationCodeFormat.Numeric: + return MessagingUtilities.GetRandomString(length, MessagingUtilities.Digits); + default: + throw new ArgumentOutOfRangeException("format"); + } + } + + /// <summary> + /// Reads any incoming OAuth message. + /// </summary> + /// <returns>The deserialized message.</returns> + /// <remarks> + /// Requires HttpContext.Current. + /// </remarks> + public IDirectedProtocolMessage ReadRequest() { + return this.Channel.ReadFromRequest(); + } + + /// <summary> + /// Reads any incoming OAuth message. + /// </summary> + /// <param name="request">The HTTP request to read the message from.</param> + /// <returns>The deserialized message.</returns> + public IDirectedProtocolMessage ReadRequest(HttpRequestInfo request) { + return this.Channel.ReadFromRequest(request); + } + + /// <summary> + /// Gets the incoming request for an unauthorized token, if any. + /// </summary> + /// <returns>The incoming request, or null if no OAuth message was attached.</returns> + /// <exception cref="ProtocolException">Thrown if an unexpected OAuth message is attached to the incoming request.</exception> + /// <remarks> + /// Requires HttpContext.Current. + /// </remarks> + public UnauthorizedTokenRequest ReadTokenRequest() { + return this.ReadTokenRequest(this.Channel.GetRequestFromContext()); + } + + /// <summary> + /// Reads a request for an unauthorized token from the incoming HTTP request. + /// </summary> + /// <param name="request">The HTTP request to read from.</param> + /// <returns>The incoming request, or null if no OAuth message was attached.</returns> + /// <exception cref="ProtocolException">Thrown if an unexpected OAuth message is attached to the incoming request.</exception> + public UnauthorizedTokenRequest ReadTokenRequest(HttpRequestInfo request) { + UnauthorizedTokenRequest message; + if (this.Channel.TryReadFromRequest(request, out message)) { + ErrorUtilities.VerifyProtocol(message.Version >= Protocol.Lookup(this.SecuritySettings.MinimumRequiredOAuthVersion).Version, OAuthStrings.MinimumConsumerVersionRequirementNotMet, this.SecuritySettings.MinimumRequiredOAuthVersion, message.Version); + } + return message; + } + + /// <summary> + /// Prepares a message containing an unauthorized token for the Consumer to use in a + /// user agent redirect for subsequent authorization. + /// </summary> + /// <param name="request">The token request message the Consumer sent that the Service Provider is now responding to.</param> + /// <returns>The response message to send using the <see cref="Channel"/>, after optionally adding extra data to it.</returns> + public UnauthorizedTokenResponse PrepareUnauthorizedTokenMessage(UnauthorizedTokenRequest request) { + Requires.NotNull(request, "request"); + + string token = this.TokenGenerator.GenerateRequestToken(request.ConsumerKey); + string secret = this.TokenGenerator.GenerateSecret(); + UnauthorizedTokenResponse response = new UnauthorizedTokenResponse(request, token, secret); + + return response; + } + + /// <summary> + /// Gets the incoming request for the Service Provider to authorize a Consumer's + /// access to some protected resources. + /// </summary> + /// <returns>The incoming request, or null if no OAuth message was attached.</returns> + /// <exception cref="ProtocolException">Thrown if an unexpected OAuth message is attached to the incoming request.</exception> + /// <remarks> + /// Requires HttpContext.Current. + /// </remarks> + public UserAuthorizationRequest ReadAuthorizationRequest() { + return this.ReadAuthorizationRequest(this.Channel.GetRequestFromContext()); + } + + /// <summary> + /// Reads in a Consumer's request for the Service Provider to obtain permission from + /// the user to authorize the Consumer's access of some protected resource(s). + /// </summary> + /// <param name="request">The HTTP request to read from.</param> + /// <returns>The incoming request, or null if no OAuth message was attached.</returns> + /// <exception cref="ProtocolException">Thrown if an unexpected OAuth message is attached to the incoming request.</exception> + public UserAuthorizationRequest ReadAuthorizationRequest(HttpRequestInfo request) { + UserAuthorizationRequest message; + this.Channel.TryReadFromRequest(request, out message); + return message; + } + + /// <summary> + /// Gets the OAuth authorization request included with an OpenID authentication + /// request, if there is one. + /// </summary> + /// <param name="openIdRequest">The OpenID authentication request.</param> + /// <returns> + /// The scope of access the relying party is requesting, or null if no OAuth request + /// is present. + /// </returns> + /// <remarks> + /// <para>Call this method rather than simply extracting the OAuth extension + /// out from the authentication request directly to ensure that the additional + /// security measures that are required are taken.</para> + /// </remarks> + public AuthorizationRequest ReadAuthorizationRequest(IHostProcessedRequest openIdRequest) { + Requires.NotNull(openIdRequest, "openIdRequest"); + Requires.ValidState(this.TokenManager is ICombinedOpenIdProviderTokenManager); + var openidTokenManager = this.TokenManager as ICombinedOpenIdProviderTokenManager; + ErrorUtilities.VerifyOperation(openidTokenManager != null, OAuthStrings.OpenIdOAuthExtensionRequiresSpecialTokenManagerInterface, typeof(IOpenIdOAuthTokenManager).FullName); + + var authzRequest = openIdRequest.GetExtension<AuthorizationRequest>(); + if (authzRequest == null) { + return null; + } + + // OpenID+OAuth spec section 9: + // The Combined Provider SHOULD verify that the consumer key passed in the + // request is authorized to be used for the realm passed in the request. + string expectedConsumerKey = openidTokenManager.GetConsumerKey(openIdRequest.Realm); + ErrorUtilities.VerifyProtocol( + string.Equals(expectedConsumerKey, authzRequest.Consumer, StringComparison.Ordinal), + OAuthStrings.OpenIdOAuthRealmConsumerKeyDoNotMatch); + + return authzRequest; + } + + /// <summary> + /// Attaches the authorization response to an OpenID authentication response. + /// </summary> + /// <param name="openIdAuthenticationRequest">The OpenID authentication request.</param> + /// <param name="consumerKey">The consumer key. Must be <c>null</c> if and only if <paramref name="scope"/> is null.</param> + /// <param name="scope">The approved access scope. Use <c>null</c> to indicate no access was granted. The empty string will be interpreted as some default level of access is granted.</param> + [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "We want to take IAuthenticationRequest because that's the only supported use case.")] + [Obsolete("Call the overload that doesn't take a consumerKey instead.")] + public void AttachAuthorizationResponse(IHostProcessedRequest openIdAuthenticationRequest, string consumerKey, string scope) { + Requires.NotNull(openIdAuthenticationRequest, "openIdAuthenticationRequest"); + Requires.True((consumerKey == null) == (scope == null), null); + Requires.ValidState(this.TokenManager is ICombinedOpenIdProviderTokenManager); + var openidTokenManager = (ICombinedOpenIdProviderTokenManager)this.TokenManager; + ErrorUtilities.VerifyArgument(consumerKey == null || consumerKey == openidTokenManager.GetConsumerKey(openIdAuthenticationRequest.Realm), OAuthStrings.OpenIdOAuthRealmConsumerKeyDoNotMatch); + + this.AttachAuthorizationResponse(openIdAuthenticationRequest, scope); + } + + /// <summary> + /// Attaches the authorization response to an OpenID authentication response. + /// </summary> + /// <param name="openIdAuthenticationRequest">The OpenID authentication request.</param> + /// <param name="scope">The approved access scope. Use <c>null</c> to indicate no access was granted. The empty string will be interpreted as some default level of access is granted.</param> + [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "We want to take IAuthenticationRequest because that's the only supported use case.")] + public void AttachAuthorizationResponse(IHostProcessedRequest openIdAuthenticationRequest, string scope) { + Requires.NotNull(openIdAuthenticationRequest, "openIdAuthenticationRequest"); + Requires.ValidState(this.TokenManager is ICombinedOpenIdProviderTokenManager); + + var openidTokenManager = this.TokenManager as ICombinedOpenIdProviderTokenManager; + IOpenIdMessageExtension response; + if (scope != null) { + // Generate an authorized request token to return to the relying party. + string consumerKey = openidTokenManager.GetConsumerKey(openIdAuthenticationRequest.Realm); + var approvedResponse = new AuthorizationApprovedResponse { + RequestToken = this.TokenGenerator.GenerateRequestToken(consumerKey), + Scope = scope, + }; + openidTokenManager.StoreOpenIdAuthorizedRequestToken(consumerKey, approvedResponse); + response = approvedResponse; + } else { + response = new AuthorizationDeclinedResponse(); + } + + openIdAuthenticationRequest.AddResponseExtension(response); + } + + /// <summary> + /// Prepares the message to send back to the consumer following proper authorization of + /// a token by an interactive user at the Service Provider's web site. + /// </summary> + /// <param name="request">The Consumer's original authorization request.</param> + /// <returns> + /// The message to send to the Consumer using <see cref="Channel"/> if one is necessary. + /// Null if the Consumer did not request a callback as part of the authorization request. + /// </returns> + [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Consistent user experience with instance.")] + public UserAuthorizationResponse PrepareAuthorizationResponse(UserAuthorizationRequest request) { + Requires.NotNull(request, "request"); + + // It is very important for us to ignore the oauth_callback argument in the + // UserAuthorizationRequest if the Consumer is a 1.0a consumer or else we + // open up a security exploit. + IServiceProviderRequestToken token = this.TokenManager.GetRequestToken(request.RequestToken); + Uri callback; + if (request.Version >= Protocol.V10a.Version) { + // In OAuth 1.0a, we'll prefer the token-specific callback to the pre-registered one. + if (token.Callback != null) { + callback = token.Callback; + } else { + IConsumerDescription consumer = this.TokenManager.GetConsumer(token.ConsumerKey); + callback = consumer.Callback; + } + } else { + // In OAuth 1.0, we'll prefer the pre-registered callback over the token-specific one + // since 1.0 has a security weakness for user-modified callback URIs. + IConsumerDescription consumer = this.TokenManager.GetConsumer(token.ConsumerKey); + callback = consumer.Callback ?? request.Callback; + } + + return callback != null ? this.PrepareAuthorizationResponse(request, callback) : null; + } + + /// <summary> + /// Prepares the message to send back to the consumer following proper authorization of + /// a token by an interactive user at the Service Provider's web site. + /// </summary> + /// <param name="request">The Consumer's original authorization request.</param> + /// <param name="callback">The callback URI the consumer has previously registered + /// with this service provider or that came in the <see cref="UnauthorizedTokenRequest"/>.</param> + /// <returns> + /// The message to send to the Consumer using <see cref="Channel"/>. + /// </returns> + [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Consistent user experience with instance.")] + public UserAuthorizationResponse PrepareAuthorizationResponse(UserAuthorizationRequest request, Uri callback) { + Requires.NotNull(request, "request"); + Requires.NotNull(callback, "callback"); + + var authorization = new UserAuthorizationResponse(callback, request.Version) { + RequestToken = request.RequestToken, + }; + + if (authorization.Version >= Protocol.V10a.Version) { + authorization.VerificationCode = CreateVerificationCode(VerificationCodeFormat.IncludedInCallback, VerifierCodeLength); + } + + return authorization; + } + + /// <summary> + /// Gets the incoming request to exchange an authorized token for an access token. + /// </summary> + /// <returns>The incoming request, or null if no OAuth message was attached.</returns> + /// <exception cref="ProtocolException">Thrown if an unexpected OAuth message is attached to the incoming request.</exception> + /// <remarks> + /// Requires HttpContext.Current. + /// </remarks> + public AuthorizedTokenRequest ReadAccessTokenRequest() { + return this.ReadAccessTokenRequest(this.Channel.GetRequestFromContext()); + } + + /// <summary> + /// Reads in a Consumer's request to exchange an authorized request token for an access token. + /// </summary> + /// <param name="request">The HTTP request to read from.</param> + /// <returns>The incoming request, or null if no OAuth message was attached.</returns> + /// <exception cref="ProtocolException">Thrown if an unexpected OAuth message is attached to the incoming request.</exception> + public AuthorizedTokenRequest ReadAccessTokenRequest(HttpRequestInfo request) { + AuthorizedTokenRequest message; + this.Channel.TryReadFromRequest(request, out message); + return message; + } + + /// <summary> + /// Prepares and sends an access token to a Consumer, and invalidates the request token. + /// </summary> + /// <param name="request">The Consumer's message requesting an access token.</param> + /// <returns>The HTTP response to actually send to the Consumer.</returns> + public AuthorizedTokenResponse PrepareAccessTokenMessage(AuthorizedTokenRequest request) { + Requires.NotNull(request, "request"); + + ErrorUtilities.VerifyProtocol(this.TokenManager.IsRequestTokenAuthorized(request.RequestToken), OAuthStrings.AccessTokenNotAuthorized, request.RequestToken); + + string accessToken = this.TokenGenerator.GenerateAccessToken(request.ConsumerKey); + string tokenSecret = this.TokenGenerator.GenerateSecret(); + this.TokenManager.ExpireRequestTokenAndStoreNewAccessToken(request.ConsumerKey, request.RequestToken, accessToken, tokenSecret); + var grantAccess = new AuthorizedTokenResponse(request) { + AccessToken = accessToken, + TokenSecret = tokenSecret, + }; + + return grantAccess; + } + + /// <summary> + /// Gets the authorization (access token) for accessing some protected resource. + /// </summary> + /// <returns>The authorization message sent by the Consumer, or null if no authorization message is attached.</returns> + /// <remarks> + /// This method verifies that the access token and token secret are valid. + /// It falls on the caller to verify that the access token is actually authorized + /// to access the resources being requested. + /// </remarks> + /// <exception cref="ProtocolException">Thrown if an unexpected message is attached to the request.</exception> + public AccessProtectedResourceRequest ReadProtectedResourceAuthorization() { + return this.ReadProtectedResourceAuthorization(this.Channel.GetRequestFromContext()); + } + + /// <summary> + /// Gets the authorization (access token) for accessing some protected resource. + /// </summary> + /// <param name="request">HTTP details from an incoming WCF message.</param> + /// <param name="requestUri">The URI of the WCF service endpoint.</param> + /// <returns>The authorization message sent by the Consumer, or null if no authorization message is attached.</returns> + /// <remarks> + /// This method verifies that the access token and token secret are valid. + /// It falls on the caller to verify that the access token is actually authorized + /// to access the resources being requested. + /// </remarks> + /// <exception cref="ProtocolException">Thrown if an unexpected message is attached to the request.</exception> + public AccessProtectedResourceRequest ReadProtectedResourceAuthorization(HttpRequestMessageProperty request, Uri requestUri) { + return this.ReadProtectedResourceAuthorization(new HttpRequestInfo(request, requestUri)); + } + + /// <summary> + /// Gets the authorization (access token) for accessing some protected resource. + /// </summary> + /// <param name="request">The incoming HTTP request.</param> + /// <returns>The authorization message sent by the Consumer, or null if no authorization message is attached.</returns> + /// <remarks> + /// This method verifies that the access token and token secret are valid. + /// It falls on the caller to verify that the access token is actually authorized + /// to access the resources being requested. + /// </remarks> + /// <exception cref="ProtocolException">Thrown if an unexpected message is attached to the request.</exception> + public AccessProtectedResourceRequest ReadProtectedResourceAuthorization(HttpRequestInfo request) { + Requires.NotNull(request, "request"); + + AccessProtectedResourceRequest accessMessage; + if (this.Channel.TryReadFromRequest<AccessProtectedResourceRequest>(request, out accessMessage)) { + if (this.TokenManager.GetTokenType(accessMessage.AccessToken) != TokenType.AccessToken) { + throw new ProtocolException( + string.Format( + CultureInfo.CurrentCulture, + OAuthStrings.BadAccessTokenInProtectedResourceRequest, + accessMessage.AccessToken)); + } + } + + return accessMessage; + } + + /// <summary> + /// Creates a security principal that may be used. + /// </summary> + /// <param name="request">The request.</param> + /// <returns>The <see cref="IPrincipal"/> instance that can be used for access control of resources.</returns> + public OAuthPrincipal CreatePrincipal(AccessProtectedResourceRequest request) { + Requires.NotNull(request, "request"); + + IServiceProviderAccessToken accessToken = this.TokenManager.GetAccessToken(request.AccessToken); + return new OAuthPrincipal(accessToken); + } + + #region IDisposable Members + + /// <summary> + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// </summary> + public void Dispose() { + this.Dispose(true); + GC.SuppressFinalize(this); + } + + /// <summary> + /// Releases unmanaged and - optionally - managed resources + /// </summary> + /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param> + protected virtual void Dispose(bool disposing) { + if (disposing) { + this.Channel.Dispose(); + } + } + + #endregion + } +} diff --git a/src/DotNetOpenAuth/OAuth/VerificationCodeFormat.cs b/src/DotNetOpenAuth.OAuth.ServiceProvider/OAuth/VerificationCodeFormat.cs index a6560d8..a6560d8 100644 --- a/src/DotNetOpenAuth/OAuth/VerificationCodeFormat.cs +++ b/src/DotNetOpenAuth.OAuth.ServiceProvider/OAuth/VerificationCodeFormat.cs diff --git a/src/DotNetOpenAuth.OAuth.ServiceProvider/Properties/AssemblyInfo.cs b/src/DotNetOpenAuth.OAuth.ServiceProvider/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..d038290 --- /dev/null +++ b/src/DotNetOpenAuth.OAuth.ServiceProvider/Properties/AssemblyInfo.cs @@ -0,0 +1,56 @@ +//----------------------------------------------------------------------- +// <copyright file="AssemblyInfo.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +// We DON'T put an AssemblyVersionAttribute in here because it is generated in the build. + +using System; +using System.Diagnostics.Contracts; +using System.Net; +using System.Reflection; +using System.Resources; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Security; +using System.Security.Permissions; +using System.Web.UI; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("DotNetOpenAuth OAuth")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("DotNetOpenAuth")] +[assembly: AssemblyCopyright("Copyright © 2008")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: NeutralResourcesLanguage("en-US")] +[assembly: CLSCompliant(true)] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("7d73990c-47c0-4256-9f20-a893add9e289")] + +[assembly: ContractVerification(true)] + +#if StrongNameSigned +// See comment at top of this file. We need this so that strong-naming doesn't +// keep this assembly from being useful to shared host (medium trust) web sites. +[assembly: AllowPartiallyTrustedCallers] + +[assembly: InternalsVisibleTo("DotNetOpenAuth.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100AD093C3765257C89A7010E853F2C7C741FF92FA8ACE06D7B8254702CAD5CF99104447F63AB05F8BB6F51CE0D81C8C93D2FCE8C20AAFF7042E721CBA16EAAE98778611DED11C0ABC8900DC5667F99B50A9DADEC24DBD8F2C91E3E8AD300EF64F1B4B9536CEB16FB440AF939F57624A9B486F867807C649AE4830EAB88C6C03998")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.OAuth2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100AD093C3765257C89A7010E853F2C7C741FF92FA8ACE06D7B8254702CAD5CF99104447F63AB05F8BB6F51CE0D81C8C93D2FCE8C20AAFF7042E721CBA16EAAE98778611DED11C0ABC8900DC5667F99B50A9DADEC24DBD8F2C91E3E8AD300EF64F1B4B9536CEB16FB440AF939F57624A9B486F867807C649AE4830EAB88C6C03998")] +[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")] +#else +[assembly: InternalsVisibleTo("DotNetOpenAuth.Test")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.OAuth2")] +[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] +#endif diff --git a/src/DotNetOpenAuth/Configuration/OAuthConsumerElement.cs b/src/DotNetOpenAuth.OAuth/Configuration/OAuthConsumerElement.cs index b15c3e3..b15c3e3 100644 --- a/src/DotNetOpenAuth/Configuration/OAuthConsumerElement.cs +++ b/src/DotNetOpenAuth.OAuth/Configuration/OAuthConsumerElement.cs diff --git a/src/DotNetOpenAuth/Configuration/OAuthConsumerSecuritySettingsElement.cs b/src/DotNetOpenAuth.OAuth/Configuration/OAuthConsumerSecuritySettingsElement.cs index 38a183a..38a183a 100644 --- a/src/DotNetOpenAuth/Configuration/OAuthConsumerSecuritySettingsElement.cs +++ b/src/DotNetOpenAuth.OAuth/Configuration/OAuthConsumerSecuritySettingsElement.cs diff --git a/src/DotNetOpenAuth.OAuth/Configuration/OAuthElement.cs b/src/DotNetOpenAuth.OAuth/Configuration/OAuthElement.cs new file mode 100644 index 0000000..59c5851 --- /dev/null +++ b/src/DotNetOpenAuth.OAuth/Configuration/OAuthElement.cs @@ -0,0 +1,64 @@ +//----------------------------------------------------------------------- +// <copyright file="OAuthElement.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Configuration { + using System.Configuration; + using System.Diagnostics.Contracts; + + /// <summary> + /// Represents the <oauth> element in the host's .config file. + /// </summary> + internal class OAuthElement : ConfigurationSection { + /// <summary> + /// The name of the oauth section. + /// </summary> + private const string SectionName = DotNetOpenAuthSection.SectionName + "/oauth"; + + /// <summary> + /// The name of the <consumer> sub-element. + /// </summary> + private const string ConsumerElementName = "consumer"; + + /// <summary> + /// The name of the <serviceProvider> sub-element. + /// </summary> + private const string ServiceProviderElementName = "serviceProvider"; + + /// <summary> + /// Initializes a new instance of the <see cref="OAuthElement"/> class. + /// </summary> + internal OAuthElement() { + } + + /// <summary> + /// Gets the configuration section from the .config file. + /// </summary> + public static OAuthElement Configuration { + get { + Contract.Ensures(Contract.Result<OAuthElement>() != null); + return (OAuthElement)ConfigurationManager.GetSection(SectionName) ?? new OAuthElement(); + } + } + + /// <summary> + /// Gets or sets the configuration specific for Consumers. + /// </summary> + [ConfigurationProperty(ConsumerElementName)] + internal OAuthConsumerElement Consumer { + get { return (OAuthConsumerElement)this[ConsumerElementName] ?? new OAuthConsumerElement(); } + set { this[ConsumerElementName] = value; } + } + + /// <summary> + /// Gets or sets the configuration specific for Service Providers. + /// </summary> + [ConfigurationProperty(ServiceProviderElementName)] + internal OAuthServiceProviderElement ServiceProvider { + get { return (OAuthServiceProviderElement)this[ServiceProviderElementName] ?? new OAuthServiceProviderElement(); } + set { this[ServiceProviderElementName] = value; } + } + } +} diff --git a/src/DotNetOpenAuth/Configuration/OAuthServiceProviderElement.cs b/src/DotNetOpenAuth.OAuth/Configuration/OAuthServiceProviderElement.cs index 8e910a0..8e910a0 100644 --- a/src/DotNetOpenAuth/Configuration/OAuthServiceProviderElement.cs +++ b/src/DotNetOpenAuth.OAuth/Configuration/OAuthServiceProviderElement.cs diff --git a/src/DotNetOpenAuth/Configuration/OAuthServiceProviderSecuritySettingsElement.cs b/src/DotNetOpenAuth.OAuth/Configuration/OAuthServiceProviderSecuritySettingsElement.cs index 723b607..723b607 100644 --- a/src/DotNetOpenAuth/Configuration/OAuthServiceProviderSecuritySettingsElement.cs +++ b/src/DotNetOpenAuth.OAuth/Configuration/OAuthServiceProviderSecuritySettingsElement.cs diff --git a/src/DotNetOpenAuth.OAuth/DotNetOpenAuth.OAuth.csproj b/src/DotNetOpenAuth.OAuth/DotNetOpenAuth.OAuth.csproj new file mode 100644 index 0000000..20d6643 --- /dev/null +++ b/src/DotNetOpenAuth.OAuth/DotNetOpenAuth.OAuth.csproj @@ -0,0 +1,94 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), EnlistmentInfo.props))\EnlistmentInfo.props" Condition=" '$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), EnlistmentInfo.props))' != '' " /> + <PropertyGroup> + <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> + <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> + </PropertyGroup> + <Import Project="$(ProjectRoot)tools\DotNetOpenAuth.props" /> + <PropertyGroup> + <SchemaVersion>2.0</SchemaVersion> + <ProjectGuid>{A288FCC8-6FCF-46DA-A45E-5F9281556361}</ProjectGuid> + <AppDesignerFolder>Properties</AppDesignerFolder> + <AssemblyName>DotNetOpenAuth.OAuth</AssemblyName> + </PropertyGroup> + <Import Project="$(ProjectRoot)tools\DotNetOpenAuth.Product.props" /> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> + </PropertyGroup> + <ItemGroup> + <Compile Include="Configuration\OAuthConsumerElement.cs" /> + <Compile Include="Configuration\OAuthConsumerSecuritySettingsElement.cs" /> + <Compile Include="Configuration\OAuthElement.cs" /> + <Compile Include="Configuration\OAuthServiceProviderElement.cs" /> + <Compile Include="Configuration\OAuthServiceProviderSecuritySettingsElement.cs" /> + <Compile Include="Messaging\ITamperProtectionChannelBindingElement.cs" /> + <Compile Include="OAuthReporting.cs" /> + <Compile Include="OAuth\ChannelElements\ICombinedOpenIdProviderTokenManager.cs" /> + <Compile Include="OAuth\ChannelElements\IOpenIdOAuthTokenManager.cs" /> + <Compile Include="OAuth\ChannelElements\ITokenManager.cs" /> + <Compile Include="OAuth\ChannelElements\OAuthHttpMethodBindingElement.cs" /> + <Compile Include="OAuth\ChannelElements\PlaintextSigningBindingElement.cs" /> + <Compile Include="OAuth\ChannelElements\HmacSha1SigningBindingElement.cs" /> + <Compile Include="OAuth\ChannelElements\SigningBindingElementBaseContract.cs" /> + <Compile Include="OAuth\ChannelElements\SigningBindingElementChain.cs" /> + <Compile Include="OAuth\ChannelElements\TokenType.cs" /> + <Compile Include="OAuth\ChannelElements\UriOrOobEncoding.cs" /> + <Compile Include="OAuth\ConsumerSecuritySettings.cs" /> + <Compile Include="OAuth\Messages\ITokenSecretContainingMessage.cs" /> + <Compile Include="OAuth\OAuthStrings.Designer.cs"> + <AutoGen>True</AutoGen> + <DesignTime>True</DesignTime> + <DependentUpon>OAuthStrings.resx</DependentUpon> + </Compile> + <Compile Include="OAuth\SecuritySettings.cs" /> + <Compile Include="OAuth\ServiceProviderDescription.cs" /> + <Compile Include="OAuth\Messages\ITokenContainingMessage.cs" /> + <Compile Include="OAuth\Messages\SignedMessageBase.cs" /> + <Compile Include="OAuth\ChannelElements\SigningBindingElementBase.cs" /> + <Compile Include="OAuth\ServiceProviderSecuritySettings.cs" /> + <Compile Include="OAuth\ChannelElements\ITamperResistantOAuthMessage.cs" /> + <Compile Include="OAuth\Messages\MessageBase.cs" /> + <Compile Include="OAuth\Messages\AuthorizedTokenRequest.cs" /> + <Compile Include="OAuth\Messages\AccessProtectedResourceRequest.cs" /> + <Compile Include="OAuth\Messages\AuthorizedTokenResponse.cs" /> + <Compile Include="OAuth\Messages\UserAuthorizationResponse.cs" /> + <Compile Include="OAuth\Messages\UserAuthorizationRequest.cs" /> + <Compile Include="OAuth\Messages\UnauthorizedTokenResponse.cs" /> + <Compile Include="OAuth\ChannelElements\OAuthChannel.cs" /> + <Compile Include="Properties\AssemblyInfo.cs"> + <SubType> + </SubType> + </Compile> + <Compile Include="OAuth\Messages\UnauthorizedTokenRequest.cs" /> + <Compile Include="OAuth\ChannelElements\RsaSha1SigningBindingElement.cs" /> + <Compile Include="OAuth\Protocol.cs" /> + </ItemGroup> + <ItemGroup> + <None Include="OAuth\ClassDiagram.cd" /> + <None Include="OAuth\Messages\OAuth Messages.cd" /> + </ItemGroup> + <ItemGroup> + <EmbeddedResource Include="OAuth\OAuthStrings.resx"> + <Generator>ResXFileCodeGenerator</Generator> + <LastGenOutput>OAuthStrings.Designer.cs</LastGenOutput> + </EmbeddedResource> + </ItemGroup> + <ItemGroup> + <EmbeddedResource Include="OAuth\OAuthStrings.sr.resx" /> + </ItemGroup> + <ItemGroup> + <ProjectReference Include="..\DotNetOpenAuth.Core\DotNetOpenAuth.Core.csproj"> + <Project>{60426312-6AE5-4835-8667-37EDEA670222}</Project> + <Name>DotNetOpenAuth.Core</Name> + </ProjectReference> + <ProjectReference Include="..\DotNetOpenAuth.OpenId\DotNetOpenAuth.OpenId.csproj"> + <Project>{3896A32A-E876-4C23-B9B8-78E17D134CD3}</Project> + <Name>DotNetOpenAuth.OpenId</Name> + </ProjectReference> + </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))' != '' " /> +</Project>
\ No newline at end of file diff --git a/src/DotNetOpenAuth/Messaging/ITamperProtectionChannelBindingElement.cs b/src/DotNetOpenAuth.OAuth/Messaging/ITamperProtectionChannelBindingElement.cs index e177dd9..e177dd9 100644 --- a/src/DotNetOpenAuth/Messaging/ITamperProtectionChannelBindingElement.cs +++ b/src/DotNetOpenAuth.OAuth/Messaging/ITamperProtectionChannelBindingElement.cs diff --git a/src/DotNetOpenAuth/OAuth/ChannelElements/HmacSha1SigningBindingElement.cs b/src/DotNetOpenAuth.OAuth/OAuth/ChannelElements/HmacSha1SigningBindingElement.cs index 5828428..5828428 100644 --- a/src/DotNetOpenAuth/OAuth/ChannelElements/HmacSha1SigningBindingElement.cs +++ b/src/DotNetOpenAuth.OAuth/OAuth/ChannelElements/HmacSha1SigningBindingElement.cs diff --git a/src/DotNetOpenAuth/OAuth/ChannelElements/ICombinedOpenIdProviderTokenManager.cs b/src/DotNetOpenAuth.OAuth/OAuth/ChannelElements/ICombinedOpenIdProviderTokenManager.cs index dd28e71..dd28e71 100644 --- a/src/DotNetOpenAuth/OAuth/ChannelElements/ICombinedOpenIdProviderTokenManager.cs +++ b/src/DotNetOpenAuth.OAuth/OAuth/ChannelElements/ICombinedOpenIdProviderTokenManager.cs diff --git a/src/DotNetOpenAuth/OAuth/ChannelElements/IOpenIdOAuthTokenManager.cs b/src/DotNetOpenAuth.OAuth/OAuth/ChannelElements/IOpenIdOAuthTokenManager.cs index b3ee320..b3ee320 100644 --- a/src/DotNetOpenAuth/OAuth/ChannelElements/IOpenIdOAuthTokenManager.cs +++ b/src/DotNetOpenAuth.OAuth/OAuth/ChannelElements/IOpenIdOAuthTokenManager.cs diff --git a/src/DotNetOpenAuth/OAuth/ChannelElements/ITamperResistantOAuthMessage.cs b/src/DotNetOpenAuth.OAuth/OAuth/ChannelElements/ITamperResistantOAuthMessage.cs index a95001d..a95001d 100644 --- a/src/DotNetOpenAuth/OAuth/ChannelElements/ITamperResistantOAuthMessage.cs +++ b/src/DotNetOpenAuth.OAuth/OAuth/ChannelElements/ITamperResistantOAuthMessage.cs diff --git a/src/DotNetOpenAuth.OAuth/OAuth/ChannelElements/ITokenManager.cs b/src/DotNetOpenAuth.OAuth/OAuth/ChannelElements/ITokenManager.cs new file mode 100644 index 0000000..7d68b63 --- /dev/null +++ b/src/DotNetOpenAuth.OAuth/OAuth/ChannelElements/ITokenManager.cs @@ -0,0 +1,169 @@ +//----------------------------------------------------------------------- +// <copyright file="ITokenManager.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OAuth.ChannelElements { + using System; + using System.Collections.Generic; + using System.Diagnostics.Contracts; + using System.Linq; + using System.Text; + using DotNetOpenAuth.OAuth.Messages; + + /// <summary> + /// An interface OAuth hosts must implement for persistent storage + /// and recall of tokens and secrets for an individual OAuth consumer + /// or service provider. + /// </summary> + [ContractClass(typeof(ITokenManagerContract))] + public interface ITokenManager { + /// <summary> + /// Gets the Token Secret given a request or access token. + /// </summary> + /// <param name="token">The request or access token.</param> + /// <returns>The secret associated with the given token.</returns> + /// <exception cref="ArgumentException">Thrown if the secret cannot be found for the given token.</exception> + string GetTokenSecret(string token); + + /// <summary> + /// Stores a newly generated unauthorized request token, secret, and optional + /// application-specific parameters for later recall. + /// </summary> + /// <param name="request">The request message that resulted in the generation of a new unauthorized request token.</param> + /// <param name="response">The response message that includes the unauthorized request token.</param> + /// <exception cref="ArgumentException">Thrown if the consumer key is not registered, or a required parameter was not found in the parameters collection.</exception> + /// <remarks> + /// Request tokens stored by this method SHOULD NOT associate any user account with this token. + /// It usually opens up security holes in your application to do so. Instead, you associate a user + /// account with access tokens (not request tokens) in the <see cref="ExpireRequestTokenAndStoreNewAccessToken"/> + /// method. + /// </remarks> + void StoreNewRequestToken(UnauthorizedTokenRequest request, ITokenSecretContainingMessage response); + + /// <summary> + /// Deletes a request token and its associated secret and stores a new access token and secret. + /// </summary> + /// <param name="consumerKey">The Consumer that is exchanging its request token for an access token.</param> + /// <param name="requestToken">The Consumer's request token that should be deleted/expired.</param> + /// <param name="accessToken">The new access token that is being issued to the Consumer.</param> + /// <param name="accessTokenSecret">The secret associated with the newly issued access token.</param> + /// <remarks> + /// <para> + /// Any scope of granted privileges associated with the request token from the + /// original call to <see cref="StoreNewRequestToken"/> should be carried over + /// to the new Access Token. + /// </para> + /// <para> + /// To associate a user account with the new access token, + /// <see cref="System.Web.HttpContext.User">HttpContext.Current.User</see> may be + /// useful in an ASP.NET web application within the implementation of this method. + /// Alternatively you may store the access token here without associating with a user account, + /// and wait until WebConsumer.ProcessUserAuthorization or + /// DesktopConsumer.ProcessUserAuthorization return the access + /// token to associate the access token with a user account at that point. + /// </para> + /// </remarks> + void ExpireRequestTokenAndStoreNewAccessToken(string consumerKey, string requestToken, string accessToken, string accessTokenSecret); + + /// <summary> + /// Classifies a token as a request token or an access token. + /// </summary> + /// <param name="token">The token to classify.</param> + /// <returns>Request or Access token, or invalid if the token is not recognized.</returns> + TokenType GetTokenType(string token); + } + + /// <summary> + /// The code contract class for the <see cref="ITokenManager"/> interface. + /// </summary> + [ContractClassFor(typeof(ITokenManager))] + internal abstract class ITokenManagerContract : ITokenManager { + /// <summary> + /// Prevents a default instance of the <see cref="ITokenManagerContract"/> class from being created. + /// </summary> + private ITokenManagerContract() { + } + + #region ITokenManager Members + + /// <summary> + /// Gets the Token Secret given a request or access token. + /// </summary> + /// <param name="token">The request or access token.</param> + /// <returns> + /// The secret associated with the given token. + /// </returns> + /// <exception cref="ArgumentException">Thrown if the secret cannot be found for the given token.</exception> + string ITokenManager.GetTokenSecret(string token) { + Requires.NotNullOrEmpty(token, "token"); + Contract.Ensures(Contract.Result<string>() != null); + throw new NotImplementedException(); + } + + /// <summary> + /// Stores a newly generated unauthorized request token, secret, and optional + /// application-specific parameters for later recall. + /// </summary> + /// <param name="request">The request message that resulted in the generation of a new unauthorized request token.</param> + /// <param name="response">The response message that includes the unauthorized request token.</param> + /// <exception cref="ArgumentException">Thrown if the consumer key is not registered, or a required parameter was not found in the parameters collection.</exception> + /// <remarks> + /// Request tokens stored by this method SHOULD NOT associate any user account with this token. + /// It usually opens up security holes in your application to do so. Instead, you associate a user + /// account with access tokens (not request tokens) in the <see cref="ITokenManager.ExpireRequestTokenAndStoreNewAccessToken"/> + /// method. + /// </remarks> + void ITokenManager.StoreNewRequestToken(UnauthorizedTokenRequest request, ITokenSecretContainingMessage response) { + Requires.NotNull(request, "request"); + Requires.NotNull(response, "response"); + throw new NotImplementedException(); + } + + /// <summary> + /// Deletes a request token and its associated secret and stores a new access token and secret. + /// </summary> + /// <param name="consumerKey">The Consumer that is exchanging its request token for an access token.</param> + /// <param name="requestToken">The Consumer's request token that should be deleted/expired.</param> + /// <param name="accessToken">The new access token that is being issued to the Consumer.</param> + /// <param name="accessTokenSecret">The secret associated with the newly issued access token.</param> + /// <remarks> + /// <para> + /// Any scope of granted privileges associated with the request token from the + /// original call to <see cref="ITokenManager.StoreNewRequestToken"/> should be carried over + /// to the new Access Token. + /// </para> + /// <para> + /// To associate a user account with the new access token, + /// <see cref="System.Web.HttpContext.User">HttpContext.Current.User</see> may be + /// useful in an ASP.NET web application within the implementation of this method. + /// Alternatively you may store the access token here without associating with a user account, + /// and wait until WebConsumer.ProcessUserAuthorization or + /// DesktopConsumer.ProcessUserAuthorization return the access + /// token to associate the access token with a user account at that point. + /// </para> + /// </remarks> + void ITokenManager.ExpireRequestTokenAndStoreNewAccessToken(string consumerKey, string requestToken, string accessToken, string accessTokenSecret) { + Requires.NotNullOrEmpty(consumerKey, "consumerKey"); + Requires.NotNullOrEmpty(requestToken, "requestToken"); + Requires.NotNullOrEmpty(accessToken, "accessToken"); + Requires.NotNull(accessTokenSecret, "accessTokenSecret"); + throw new NotImplementedException(); + } + + /// <summary> + /// Classifies a token as a request token or an access token. + /// </summary> + /// <param name="token">The token to classify.</param> + /// <returns> + /// Request or Access token, or invalid if the token is not recognized. + /// </returns> + TokenType ITokenManager.GetTokenType(string token) { + Requires.NotNullOrEmpty(token, "token"); + throw new NotImplementedException(); + } + + #endregion + } +} diff --git a/src/DotNetOpenAuth.OAuth/OAuth/ChannelElements/OAuthChannel.cs b/src/DotNetOpenAuth.OAuth/OAuth/ChannelElements/OAuthChannel.cs new file mode 100644 index 0000000..32b57d0 --- /dev/null +++ b/src/DotNetOpenAuth.OAuth/OAuth/ChannelElements/OAuthChannel.cs @@ -0,0 +1,356 @@ +//----------------------------------------------------------------------- +// <copyright file="OAuthChannel.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OAuth.ChannelElements { + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; + using System.Diagnostics.Contracts; + using System.Globalization; + using System.IO; + using System.Linq; + using System.Net; + using System.Net.Mime; + using System.Text; + using System.Web; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.Messaging.Bindings; + using DotNetOpenAuth.Messaging.Reflection; + using DotNetOpenAuth.OAuth.Messages; + + /// <summary> + /// An OAuth-specific implementation of the <see cref="Channel"/> class. + /// </summary> + internal abstract class OAuthChannel : Channel { + /// <summary> + /// Initializes a new instance of the <see cref="OAuthChannel"/> class. + /// </summary> + /// <param name="signingBindingElement">The binding element to use for signing.</param> + /// <param name="store">The web application store to use for nonces.</param> + /// <param name="tokenManager">The ITokenManager instance to use.</param> + /// <param name="securitySettings">The security settings.</param> + /// <param name="messageTypeProvider">An injected message type provider instance. + /// Except for mock testing, this should always be one of + /// OAuthConsumerMessageFactory or OAuthServiceProviderMessageFactory.</param> + /// <param name="bindingElements">The binding elements.</param> + [SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", MessageId = "System.Diagnostics.Contracts.__ContractsRuntime.Requires<System.ArgumentNullException>(System.Boolean,System.String,System.String)", Justification = "Code contracts"), SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "securitySettings", Justification = "Code contracts")] + protected OAuthChannel(ITamperProtectionChannelBindingElement signingBindingElement, INonceStore store, ITokenManager tokenManager, SecuritySettings securitySettings, IMessageFactory messageTypeProvider, IChannelBindingElement[] bindingElements) + : base(messageTypeProvider, bindingElements) { + Requires.NotNull(tokenManager, "tokenManager"); + Requires.NotNull(securitySettings, "securitySettings"); + Requires.NotNull(signingBindingElement, "signingBindingElement"); + Requires.True(signingBindingElement.SignatureCallback == null, "signingBindingElement", OAuthStrings.SigningElementAlreadyAssociatedWithChannel); + Requires.NotNull(bindingElements, "bindingElements"); + + this.TokenManager = tokenManager; + signingBindingElement.SignatureCallback = this.SignatureCallback; + } + + /// <summary> + /// Gets or sets the Consumer web application path. + /// </summary> + internal Uri Realm { get; set; } + + /// <summary> + /// Gets the token manager being used. + /// </summary> + protected internal ITokenManager TokenManager { get; private set; } + + /// <summary> + /// Uri-escapes the names and values in a dictionary per OAuth 1.0 section 5.1. + /// </summary> + /// <param name="message">The message with data to encode.</param> + /// <returns>A dictionary of name-value pairs with their strings encoded.</returns> + internal static IDictionary<string, string> GetUriEscapedParameters(IEnumerable<KeyValuePair<string, string>> message) { + var encodedDictionary = new Dictionary<string, string>(); + UriEscapeParameters(message, encodedDictionary); + return encodedDictionary; + } + + /// <summary> + /// Initializes a web request for sending by attaching a message to it. + /// Use this method to prepare a protected resource request that you do NOT + /// expect an OAuth message response to. + /// </summary> + /// <param name="request">The message to attach.</param> + /// <returns>The initialized web request.</returns> + internal HttpWebRequest InitializeRequest(IDirectedProtocolMessage request) { + Requires.NotNull(request, "request"); + + ProcessOutgoingMessage(request); + return this.CreateHttpRequest(request); + } + + /// <summary> + /// Initializes the binding elements for the OAuth channel. + /// </summary> + /// <param name="signingBindingElement">The signing binding element.</param> + /// <param name="store">The nonce store.</param> + /// <param name="tokenManager">The token manager.</param> + /// <param name="securitySettings">The security settings.</param> + /// <returns> + /// An array of binding elements used to initialize the channel. + /// </returns> + protected static List<IChannelBindingElement> InitializeBindingElements(ITamperProtectionChannelBindingElement signingBindingElement, INonceStore store, ITokenManager tokenManager, SecuritySettings securitySettings) { + Contract.Requires(securitySettings != null); + + var bindingElements = new List<IChannelBindingElement> { + new OAuthHttpMethodBindingElement(), + signingBindingElement, + new StandardExpirationBindingElement(), + new StandardReplayProtectionBindingElement(store), + }; + + return bindingElements; + } + + /// <summary> + /// Searches an incoming HTTP request for data that could be used to assemble + /// a protocol request message. + /// </summary> + /// <param name="request">The HTTP request to search.</param> + /// <returns>The deserialized message, if one is found. Null otherwise.</returns> + protected override IDirectedProtocolMessage ReadFromRequestCore(HttpRequestInfo request) { + // First search the Authorization header. + string authorization = request.Headers[HttpRequestHeader.Authorization]; + var fields = MessagingUtilities.ParseAuthorizationHeader(Protocol.AuthorizationHeaderScheme, authorization).ToDictionary(); + fields.Remove("realm"); // ignore the realm parameter, since we don't use it, and it must be omitted from signature base string. + + // Scrape the entity + if (!string.IsNullOrEmpty(request.Headers[HttpRequestHeader.ContentType])) { + var contentType = new ContentType(request.Headers[HttpRequestHeader.ContentType]); + if (string.Equals(contentType.MediaType, HttpFormUrlEncoded, StringComparison.Ordinal)) { + foreach (string key in request.Form) { + if (key != null) { + fields.Add(key, request.Form[key]); + } else { + Logger.OAuth.WarnFormat("Ignoring query string parameter '{0}' since it isn't a standard name=value parameter.", request.Form[key]); + } + } + } + } + + // Scrape the query string + foreach (string key in request.QueryStringBeforeRewriting) { + if (key != null) { + fields.Add(key, request.QueryStringBeforeRewriting[key]); + } else { + Logger.OAuth.WarnFormat("Ignoring query string parameter '{0}' since it isn't a standard name=value parameter.", request.QueryStringBeforeRewriting[key]); + } + } + + MessageReceivingEndpoint recipient; + try { + recipient = request.GetRecipient(); + } catch (ArgumentException ex) { + Logger.OAuth.WarnFormat("Unrecognized HTTP request: " + ex.ToString()); + return null; + } + + // Deserialize the message using all the data we've collected. + var message = (IDirectedProtocolMessage)this.Receive(fields, recipient); + + // Add receiving HTTP transport information required for signature generation. + var signedMessage = message as ITamperResistantOAuthMessage; + if (signedMessage != null) { + signedMessage.Recipient = request.UrlBeforeRewriting; + signedMessage.HttpMethod = request.HttpMethod; + } + + return message; + } + + /// <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> + protected override IDictionary<string, string> ReadFromResponseCore(IncomingWebResponse response) { + string body = response.GetResponseReader().ReadToEnd(); + return HttpUtility.ParseQueryString(body).ToDictionary(); + } + + /// <summary> + /// Prepares an HTTP request that carries a given message. + /// </summary> + /// <param name="request">The message to send.</param> + /// <returns> + /// The <see cref="HttpRequest"/> prepared to send the request. + /// </returns> + protected override HttpWebRequest CreateHttpRequest(IDirectedProtocolMessage request) { + HttpWebRequest httpRequest; + + HttpDeliveryMethods transmissionMethod = request.HttpMethods; + if ((transmissionMethod & HttpDeliveryMethods.AuthorizationHeaderRequest) != 0) { + httpRequest = this.InitializeRequestAsAuthHeader(request); + } else if ((transmissionMethod & HttpDeliveryMethods.PostRequest) != 0) { + var requestMessageWithBinaryData = request as IMessageWithBinaryData; + ErrorUtilities.VerifyProtocol(requestMessageWithBinaryData == null || !requestMessageWithBinaryData.SendAsMultipart, OAuthStrings.MultipartPostMustBeUsedWithAuthHeader); + httpRequest = this.InitializeRequestAsPost(request); + } else if ((transmissionMethod & HttpDeliveryMethods.GetRequest) != 0) { + httpRequest = InitializeRequestAsGet(request); + } else if ((transmissionMethod & HttpDeliveryMethods.HeadRequest) != 0) { + httpRequest = InitializeRequestAsHead(request); + } else if ((transmissionMethod & HttpDeliveryMethods.PutRequest) != 0) { + httpRequest = this.InitializeRequestAsPut(request); + } else if ((transmissionMethod & HttpDeliveryMethods.DeleteRequest) != 0) { + httpRequest = InitializeRequestAsDelete(request); + } else { + throw new NotSupportedException(); + } + return httpRequest; + } + + /// <summary> + /// Queues a message for sending in the response stream where the fields + /// are sent in the response stream in querystring style. + /// </summary> + /// <param name="response">The message to send as a response.</param> + /// <returns>The pending user agent redirect based message to be sent as an HttpResponse.</returns> + /// <remarks> + /// This method implements spec V1.0 section 5.3. + /// </remarks> + protected override OutgoingWebResponse PrepareDirectResponse(IProtocolMessage response) { + var messageAccessor = this.MessageDescriptions.GetAccessor(response); + var fields = messageAccessor.Serialize(); + string responseBody = MessagingUtilities.CreateQueryString(fields); + + OutgoingWebResponse encodedResponse = new OutgoingWebResponse { + Body = responseBody, + OriginalMessage = response, + Status = HttpStatusCode.OK, + Headers = new System.Net.WebHeaderCollection(), + }; + + IHttpDirectResponse httpMessage = response as IHttpDirectResponse; + if (httpMessage != null) { + encodedResponse.Status = httpMessage.HttpStatusCode; + } + + return encodedResponse; + } + + /// <summary> + /// Gets the consumer secret for a given consumer key. + /// </summary> + /// <param name="consumerKey">The consumer key.</param> + /// <returns>A consumer secret.</returns> + protected abstract string GetConsumerSecret(string consumerKey); + + /// <summary> + /// Uri-escapes the names and values in a dictionary per OAuth 1.0 section 5.1. + /// </summary> + /// <param name="source">The dictionary with names and values to encode.</param> + /// <param name="destination">The dictionary to add the encoded pairs to.</param> + private static void UriEscapeParameters(IEnumerable<KeyValuePair<string, string>> source, IDictionary<string, string> destination) { + Requires.NotNull(source, "source"); + Requires.NotNull(destination, "destination"); + + foreach (var pair in source) { + var key = MessagingUtilities.EscapeUriDataStringRfc3986(pair.Key); + var value = MessagingUtilities.EscapeUriDataStringRfc3986(pair.Value); + destination.Add(key, value); + } + } + + /// <summary> + /// Gets the HTTP method to use for a message. + /// </summary> + /// <param name="message">The message.</param> + /// <returns>"POST", "GET" or some other similar http verb.</returns> + private static string GetHttpMethod(IDirectedProtocolMessage message) { + Requires.NotNull(message, "message"); + + var signedMessage = message as ITamperResistantOAuthMessage; + if (signedMessage != null) { + return signedMessage.HttpMethod; + } else { + return MessagingUtilities.GetHttpVerb(message.HttpMethods); + } + } + + /// <summary> + /// Prepares to send a request to the Service Provider via the Authorization header. + /// </summary> + /// <param name="requestMessage">The message to be transmitted to the ServiceProvider.</param> + /// <returns>The web request ready to send.</returns> + /// <remarks> + /// <para>If the message has non-empty ExtraData in it, the request stream is sent to + /// the server automatically. If it is empty, the request stream must be sent by the caller.</para> + /// <para>This method implements OAuth 1.0 section 5.2, item #1 (described in section 5.4).</para> + /// </remarks> + private HttpWebRequest InitializeRequestAsAuthHeader(IDirectedProtocolMessage requestMessage) { + var dictionary = this.MessageDescriptions.GetAccessor(requestMessage); + + // copy so as to not modify original + var fields = new Dictionary<string, string>(); + foreach (string key in dictionary.DeclaredKeys) { + fields.Add(key, dictionary[key]); + } + if (this.Realm != null) { + fields.Add("realm", this.Realm.AbsoluteUri); + } + + HttpWebRequest httpRequest; + UriBuilder recipientBuilder = new UriBuilder(requestMessage.Recipient); + bool hasEntity = HttpMethodHasEntity(GetHttpMethod(requestMessage)); + + if (!hasEntity) { + MessagingUtilities.AppendQueryArgs(recipientBuilder, requestMessage.ExtraData); + } + httpRequest = (HttpWebRequest)WebRequest.Create(recipientBuilder.Uri); + httpRequest.Method = GetHttpMethod(requestMessage); + + httpRequest.Headers.Add(HttpRequestHeader.Authorization, MessagingUtilities.AssembleAuthorizationHeader(Protocol.AuthorizationHeaderScheme, fields)); + + if (hasEntity) { + // WARNING: We only set up the request stream for the caller if there is + // extra data. If there isn't any extra data, the caller must do this themselves. + var requestMessageWithBinaryData = requestMessage as IMessageWithBinaryData; + if (requestMessageWithBinaryData != null && requestMessageWithBinaryData.SendAsMultipart) { + // Include the binary data in the multipart entity, and any standard text extra message data. + // The standard declared message parts are included in the authorization header. + var multiPartFields = new List<MultipartPostPart>(requestMessageWithBinaryData.BinaryData); + multiPartFields.AddRange(requestMessage.ExtraData.Select(field => MultipartPostPart.CreateFormPart(field.Key, field.Value))); + this.SendParametersInEntityAsMultipart(httpRequest, multiPartFields); + } else { + ErrorUtilities.VerifyProtocol(requestMessageWithBinaryData == null || requestMessageWithBinaryData.BinaryData.Count == 0, MessagingStrings.BinaryDataRequiresMultipart); + if (requestMessage.ExtraData.Count > 0) { + this.SendParametersInEntity(httpRequest, requestMessage.ExtraData); + } else { + // We'll assume the content length is zero since the caller may not have + // anything. They're responsible to change it when the add the payload if they have one. + httpRequest.ContentLength = 0; + } + } + } + + return httpRequest; + } + + /// <summary> + /// Fills out the secrets in a message so that signing/verification can be performed. + /// </summary> + /// <param name="message">The message about to be signed or whose signature is about to be verified.</param> + private void SignatureCallback(ITamperResistantProtocolMessage message) { + var oauthMessage = message as ITamperResistantOAuthMessage; + try { + Logger.Channel.Debug("Applying secrets to message to prepare for signing or signature verification."); + oauthMessage.ConsumerSecret = this.GetConsumerSecret(oauthMessage.ConsumerKey); + + var tokenMessage = message as ITokenContainingMessage; + if (tokenMessage != null) { + oauthMessage.TokenSecret = this.TokenManager.GetTokenSecret(tokenMessage.Token); + } + } catch (KeyNotFoundException ex) { + throw new ProtocolException(OAuthStrings.ConsumerOrTokenSecretNotFound, ex); + } + } + } +} diff --git a/src/DotNetOpenAuth/OAuth/ChannelElements/OAuthHttpMethodBindingElement.cs b/src/DotNetOpenAuth.OAuth/OAuth/ChannelElements/OAuthHttpMethodBindingElement.cs index 37fb80b..37fb80b 100644 --- a/src/DotNetOpenAuth/OAuth/ChannelElements/OAuthHttpMethodBindingElement.cs +++ b/src/DotNetOpenAuth.OAuth/OAuth/ChannelElements/OAuthHttpMethodBindingElement.cs diff --git a/src/DotNetOpenAuth/OAuth/ChannelElements/PlaintextSigningBindingElement.cs b/src/DotNetOpenAuth.OAuth/OAuth/ChannelElements/PlaintextSigningBindingElement.cs index 22e5f20..22e5f20 100644 --- a/src/DotNetOpenAuth/OAuth/ChannelElements/PlaintextSigningBindingElement.cs +++ b/src/DotNetOpenAuth.OAuth/OAuth/ChannelElements/PlaintextSigningBindingElement.cs diff --git a/src/DotNetOpenAuth.OAuth/OAuth/ChannelElements/RsaSha1SigningBindingElement.cs b/src/DotNetOpenAuth.OAuth/OAuth/ChannelElements/RsaSha1SigningBindingElement.cs new file mode 100644 index 0000000..83be094 --- /dev/null +++ b/src/DotNetOpenAuth.OAuth/OAuth/ChannelElements/RsaSha1SigningBindingElement.cs @@ -0,0 +1,31 @@ +//----------------------------------------------------------------------- +// <copyright file="RsaSha1SigningBindingElement.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OAuth.ChannelElements { + using System; + using System.Diagnostics.Contracts; + using System.Security.Cryptography; + using System.Security.Cryptography.X509Certificates; + using System.Text; + using DotNetOpenAuth.Messaging; + + /// <summary> + /// A binding element that signs outgoing messages and verifies the signature on incoming messages. + /// </summary> + public abstract class RsaSha1SigningBindingElement : SigningBindingElementBase { + /// <summary> + /// The name of the hash algorithm to use. + /// </summary> + protected const string HashAlgorithmName = "RSA-SHA1"; + + /// <summary> + /// Initializes a new instance of the <see cref="RsaSha1SigningBindingElement"/> class. + /// </summary> + protected RsaSha1SigningBindingElement() + : base(HashAlgorithmName) { + } + } +} diff --git a/src/DotNetOpenAuth.OAuth/OAuth/ChannelElements/SigningBindingElementBase.cs b/src/DotNetOpenAuth.OAuth/OAuth/ChannelElements/SigningBindingElementBase.cs new file mode 100644 index 0000000..2c47453 --- /dev/null +++ b/src/DotNetOpenAuth.OAuth/OAuth/ChannelElements/SigningBindingElementBase.cs @@ -0,0 +1,329 @@ +//----------------------------------------------------------------------- +// <copyright file="SigningBindingElementBase.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OAuth.ChannelElements { + using System; + using System.Collections.Generic; + using System.Collections.Specialized; + using System.Diagnostics.CodeAnalysis; + using System.Diagnostics.Contracts; + using System.Globalization; + using System.Linq; + using System.Text; + using System.Web; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.Messaging.Bindings; + using DotNetOpenAuth.Messaging.Reflection; + + /// <summary> + /// A binding element that signs outgoing messages and verifies the signature on incoming messages. + /// </summary> + [ContractClass(typeof(SigningBindingElementBaseContract))] + public abstract class SigningBindingElementBase : ITamperProtectionChannelBindingElement { + /// <summary> + /// The signature method this binding element uses. + /// </summary> + private string signatureMethod; + + /// <summary> + /// Initializes a new instance of the <see cref="SigningBindingElementBase"/> class. + /// </summary> + /// <param name="signatureMethod">The OAuth signature method that the binding element uses.</param> + internal SigningBindingElementBase(string signatureMethod) { + this.signatureMethod = signatureMethod; + } + + #region IChannelBindingElement Properties + + /// <summary> + /// Gets the message protection provided by this binding element. + /// </summary> + public MessageProtections Protection { + get { return MessageProtections.TamperProtection; } + } + + /// <summary> + /// Gets or sets the channel that this binding element belongs to. + /// </summary> + public Channel Channel { get; set; } + + #endregion + + #region ITamperProtectionChannelBindingElement members + + /// <summary> + /// Gets or sets the delegate that will initialize the non-serialized properties necessary on a signed + /// message so that its signature can be correctly calculated for verification. + /// </summary> + public Action<ITamperResistantOAuthMessage> SignatureCallback { get; set; } + + /// <summary> + /// Creates a new object that is a copy of the current instance. + /// </summary> + /// <returns> + /// A new object that is a copy of this instance. + /// </returns> + ITamperProtectionChannelBindingElement ITamperProtectionChannelBindingElement.Clone() { + ITamperProtectionChannelBindingElement clone = this.Clone(); + clone.SignatureCallback = this.SignatureCallback; + return clone; + } + + #endregion + + #region IChannelBindingElement Methods + + /// <summary> + /// Signs the outgoing message. + /// </summary> + /// <param name="message">The message to sign.</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> + public MessageProtections? ProcessOutgoingMessage(IProtocolMessage message) { + var signedMessage = message as ITamperResistantOAuthMessage; + if (signedMessage != null && this.IsMessageApplicable(signedMessage)) { + if (this.SignatureCallback != null) { + this.SignatureCallback(signedMessage); + } else { + Logger.Bindings.Warn("Signing required, but callback delegate was not provided to provide additional data for signing."); + } + + signedMessage.SignatureMethod = this.signatureMethod; + Logger.Bindings.DebugFormat("Signing {0} message using {1}.", message.GetType().Name, this.signatureMethod); + signedMessage.Signature = this.GetSignature(signedMessage); + return MessageProtections.TamperProtection; + } + + return null; + } + + /// <summary> + /// Verifies the signature on an incoming message. + /// </summary> + /// <param name="message">The message whose signature should be verified.</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="InvalidSignatureException">Thrown if the signature is invalid.</exception> + public MessageProtections? ProcessIncomingMessage(IProtocolMessage message) { + var signedMessage = message as ITamperResistantOAuthMessage; + if (signedMessage != null && this.IsMessageApplicable(signedMessage)) { + Logger.Bindings.DebugFormat("Verifying incoming {0} message signature of: {1}", message.GetType().Name, signedMessage.Signature); + + if (!string.Equals(signedMessage.SignatureMethod, this.signatureMethod, StringComparison.Ordinal)) { + Logger.Bindings.WarnFormat("Expected signature method '{0}' but received message with a signature method of '{1}'.", this.signatureMethod, signedMessage.SignatureMethod); + return MessageProtections.None; + } + + if (this.SignatureCallback != null) { + this.SignatureCallback(signedMessage); + } else { + Logger.Bindings.Warn("Signature verification required, but callback delegate was not provided to provide additional data for signature verification."); + } + + if (!this.IsSignatureValid(signedMessage)) { + Logger.Bindings.Error("Signature verification failed."); + throw new InvalidSignatureException(message); + } + + return MessageProtections.TamperProtection; + } + + return null; + } + + #endregion + + /// <summary> + /// Constructs the OAuth Signature Base String and returns the result. + /// </summary> + /// <param name="message">The message.</param> + /// <param name="messageDictionary">The message to derive the signature base string from.</param> + /// <returns>The signature base string.</returns> + /// <remarks> + /// This method implements OAuth 1.0 section 9.1. + /// </remarks> + [SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Justification = "Unavoidable")] + internal static string ConstructSignatureBaseString(ITamperResistantOAuthMessage message, MessageDictionary messageDictionary) { + Requires.NotNull(message, "message"); + Requires.NotNullOrEmpty(message.HttpMethod, "message.HttpMethod"); + Requires.NotNull(messageDictionary, "messageDictionary"); + ErrorUtilities.VerifyArgument(messageDictionary.Message == message, "Message references are not equal."); + + List<string> signatureBaseStringElements = new List<string>(3); + + signatureBaseStringElements.Add(message.HttpMethod.ToUpperInvariant()); + + // For multipart POST messages, only include the message parts that are NOT + // in the POST entity (those parts that may appear in an OAuth authorization header). + var encodedDictionary = new Dictionary<string, string>(); + IEnumerable<KeyValuePair<string, string>> partsToInclude = Enumerable.Empty<KeyValuePair<string, string>>(); + var binaryMessage = message as IMessageWithBinaryData; + if (binaryMessage != null && binaryMessage.SendAsMultipart) { + HttpDeliveryMethods authHeaderInUseFlags = HttpDeliveryMethods.PostRequest | HttpDeliveryMethods.AuthorizationHeaderRequest; + ErrorUtilities.VerifyProtocol((binaryMessage.HttpMethods & authHeaderInUseFlags) == authHeaderInUseFlags, OAuthStrings.MultipartPostMustBeUsedWithAuthHeader); + + // Include the declared keys in the signature as those will be signable. + // Cache in local variable to avoid recalculating DeclaredKeys in the delegate. + ICollection<string> declaredKeys = messageDictionary.DeclaredKeys; + partsToInclude = messageDictionary.Where(pair => declaredKeys.Contains(pair.Key)); + } else { + partsToInclude = messageDictionary; + } + + // If this message was deserialized, include only those explicitly included message parts (excludes defaulted values) + // in the signature. + var originalPayloadMessage = (IMessageOriginalPayload)message; + if (originalPayloadMessage.OriginalPayload != null) { + partsToInclude = partsToInclude.Where(pair => originalPayloadMessage.OriginalPayload.ContainsKey(pair.Key)); + } + + foreach (var pair in OAuthChannel.GetUriEscapedParameters(partsToInclude)) { + encodedDictionary[pair.Key] = pair.Value; + } + + // An incoming message will already have included the query and form parameters + // in the message dictionary, but an outgoing message COULD have SOME parameters + // in the query that are not in the message dictionary because they were included + // in the receiving endpoint (the original URL). + // In an outgoing message, the POST entity can only contain parameters if they were + // in the message dictionary, so no need to pull out any parameters from there. + if (message.Recipient.Query != null) { + NameValueCollection nvc = HttpUtility.ParseQueryString(message.Recipient.Query); + foreach (string key in nvc) { + string escapedKey = MessagingUtilities.EscapeUriDataStringRfc3986(key); + string escapedValue = MessagingUtilities.EscapeUriDataStringRfc3986(nvc[key]); + string existingValue; + if (!encodedDictionary.TryGetValue(escapedKey, out existingValue)) { + encodedDictionary.Add(escapedKey, escapedValue); + } else { + ErrorUtilities.VerifyInternal(escapedValue == existingValue, "Somehow we have conflicting values for the '{0}' parameter.", escapedKey); + } + } + } + encodedDictionary.Remove("oauth_signature"); + + UriBuilder endpoint = new UriBuilder(message.Recipient); + endpoint.Query = null; + endpoint.Fragment = null; + signatureBaseStringElements.Add(endpoint.Uri.AbsoluteUri); + + var sortedKeyValueList = new List<KeyValuePair<string, string>>(encodedDictionary); + sortedKeyValueList.Sort(SignatureBaseStringParameterComparer); + StringBuilder paramBuilder = new StringBuilder(); + foreach (var pair in sortedKeyValueList) { + if (paramBuilder.Length > 0) { + paramBuilder.Append("&"); + } + + paramBuilder.Append(pair.Key); + paramBuilder.Append('='); + paramBuilder.Append(pair.Value); + } + + signatureBaseStringElements.Add(paramBuilder.ToString()); + + StringBuilder signatureBaseString = new StringBuilder(); + foreach (string element in signatureBaseStringElements) { + if (signatureBaseString.Length > 0) { + signatureBaseString.Append("&"); + } + + signatureBaseString.Append(MessagingUtilities.EscapeUriDataStringRfc3986(element)); + } + + Logger.Bindings.DebugFormat("Constructed signature base string: {0}", signatureBaseString); + return signatureBaseString.ToString(); + } + + /// <summary> + /// Calculates a signature for a given message. + /// </summary> + /// <param name="message">The message to sign.</param> + /// <returns>The signature for the message.</returns> + /// <remarks> + /// This method signs the message per OAuth 1.0 section 9.2. + /// </remarks> + internal string GetSignatureTestHook(ITamperResistantOAuthMessage message) { + return this.GetSignature(message); + } + + /// <summary> + /// Gets the "ConsumerSecret&TokenSecret" string, allowing either property to be empty or null. + /// </summary> + /// <param name="message">The message to extract the secrets from.</param> + /// <returns>The concatenated string.</returns> + protected static string GetConsumerAndTokenSecretString(ITamperResistantOAuthMessage message) { + StringBuilder builder = new StringBuilder(); + if (!string.IsNullOrEmpty(message.ConsumerSecret)) { + builder.Append(MessagingUtilities.EscapeUriDataStringRfc3986(message.ConsumerSecret)); + } + builder.Append("&"); + if (!string.IsNullOrEmpty(message.TokenSecret)) { + builder.Append(MessagingUtilities.EscapeUriDataStringRfc3986(message.TokenSecret)); + } + return builder.ToString(); + } + + /// <summary> + /// Determines whether the signature on some message is valid. + /// </summary> + /// <param name="message">The message to check the signature on.</param> + /// <returns> + /// <c>true</c> if the signature on the message is valid; otherwise, <c>false</c>. + /// </returns> + protected virtual bool IsSignatureValid(ITamperResistantOAuthMessage message) { + Requires.NotNull(message, "message"); + + string signature = this.GetSignature(message); + return MessagingUtilities.EqualsConstantTime(message.Signature, signature); + } + + /// <summary> + /// Clones this instance. + /// </summary> + /// <returns>A new instance of the binding element.</returns> + /// <remarks> + /// Implementations of this method need not clone the SignatureVerificationCallback member, as the + /// <see cref="SigningBindingElementBase"/> class does this. + /// </remarks> + protected abstract ITamperProtectionChannelBindingElement Clone(); + + /// <summary> + /// Calculates a signature for a given message. + /// </summary> + /// <param name="message">The message to sign.</param> + /// <returns>The signature for the message.</returns> + protected abstract string GetSignature(ITamperResistantOAuthMessage message); + + /// <summary> + /// Checks whether this binding element applies to this message. + /// </summary> + /// <param name="message">The message that needs to be signed.</param> + /// <returns>True if this binding element can be used to sign the message. False otherwise.</returns> + protected virtual bool IsMessageApplicable(ITamperResistantOAuthMessage message) { + return string.IsNullOrEmpty(message.SignatureMethod) || message.SignatureMethod == this.signatureMethod; + } + + /// <summary> + /// Sorts parameters according to OAuth signature base string rules. + /// </summary> + /// <param name="left">The first parameter to compare.</param> + /// <param name="right">The second parameter to compare.</param> + /// <returns>Negative, zero or positive.</returns> + private static int SignatureBaseStringParameterComparer(KeyValuePair<string, string> left, KeyValuePair<string, string> right) { + int result = string.CompareOrdinal(left.Key, right.Key); + if (result != 0) { + return result; + } + + return string.CompareOrdinal(left.Value, right.Value); + } + } +} diff --git a/src/DotNetOpenAuth.OAuth/OAuth/ChannelElements/SigningBindingElementBaseContract.cs b/src/DotNetOpenAuth.OAuth/OAuth/ChannelElements/SigningBindingElementBaseContract.cs new file mode 100644 index 0000000..4450fb5 --- /dev/null +++ b/src/DotNetOpenAuth.OAuth/OAuth/ChannelElements/SigningBindingElementBaseContract.cs @@ -0,0 +1,47 @@ +//----------------------------------------------------------------------- +// <copyright file="SigningBindingElementBaseContract.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OAuth.ChannelElements { + using System; + using System.Diagnostics.Contracts; + using DotNetOpenAuth.Messaging; + + /// <summary> + /// Code Contract for the <see cref="SigningBindingElementBase"/> class. + /// </summary> + [ContractClassFor(typeof(SigningBindingElementBase))] + internal abstract class SigningBindingElementBaseContract : SigningBindingElementBase { + /// <summary> + /// Prevents a default instance of the SigningBindingElementBaseContract class from being created. + /// </summary> + private SigningBindingElementBaseContract() + : base(string.Empty) { + } + + /// <summary> + /// Clones this instance. + /// </summary> + /// <returns>A new instance of the binding element.</returns> + /// <remarks> + /// Implementations of this method need not clone the SignatureVerificationCallback member, as the + /// <see cref="SigningBindingElementBase"/> class does this. + /// </remarks> + protected override ITamperProtectionChannelBindingElement Clone() { + throw new NotImplementedException(); + } + + /// <summary> + /// Calculates a signature for a given message. + /// </summary> + /// <param name="message">The message to sign.</param> + /// <returns>The signature for the message.</returns> + protected override string GetSignature(ITamperResistantOAuthMessage message) { + Requires.NotNull(message, "message"); + Requires.ValidState(this.Channel != null); + throw new NotImplementedException(); + } + } +} diff --git a/src/DotNetOpenAuth.OAuth/OAuth/ChannelElements/SigningBindingElementChain.cs b/src/DotNetOpenAuth.OAuth/OAuth/ChannelElements/SigningBindingElementChain.cs new file mode 100644 index 0000000..849ad5e --- /dev/null +++ b/src/DotNetOpenAuth.OAuth/OAuth/ChannelElements/SigningBindingElementChain.cs @@ -0,0 +1,142 @@ +//----------------------------------------------------------------------- +// <copyright file="SigningBindingElementChain.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OAuth.ChannelElements { + using System; + using System.Collections.Generic; + using System.Diagnostics.Contracts; + using System.Linq; + using System.Text; + using DotNetOpenAuth.Messaging; + + /// <summary> + /// A tamper protection applying binding element that can use any of several given + /// binding elements to apply the protection. + /// </summary> + internal class SigningBindingElementChain : ITamperProtectionChannelBindingElement { + /// <summary> + /// The various signing binding elements that may be applicable to a message in preferred use order. + /// </summary> + private readonly ITamperProtectionChannelBindingElement[] signers; + + /// <summary> + /// Initializes a new instance of the <see cref="SigningBindingElementChain"/> class. + /// </summary> + /// <param name="signers"> + /// The signing binding elements that may be used for some outgoing message, + /// in preferred use order. + /// </param> + internal SigningBindingElementChain(ITamperProtectionChannelBindingElement[] signers) { + Requires.NotNullOrEmpty(signers, "signers"); + Requires.NullOrWithNoNullElements(signers, "signers"); + Requires.True(signers.Select(s => s.Protection).Distinct().Count() == 1, "signers", OAuthStrings.SigningElementsMustShareSameProtection); + + this.signers = signers; + } + + #region ITamperProtectionChannelBindingElement Properties + + /// <summary> + /// Gets or sets the delegate that will initialize the non-serialized properties necessary on a signed + /// message so that its signature can be correctly calculated for verification. + /// May be null for Consumers (who never have to verify signatures). + /// </summary> + public Action<ITamperResistantOAuthMessage> SignatureCallback { + get { + return this.signers[0].SignatureCallback; + } + + set { + foreach (ITamperProtectionChannelBindingElement signer in this.signers) { + signer.SignatureCallback = value; + } + } + } + + #endregion + + #region IChannelBindingElement Members + + /// <summary> + /// Gets the protection offered (if any) by this binding element. + /// </summary> + public MessageProtections Protection { + get { return this.signers[0].Protection; } + } + + /// <summary> + /// Gets or sets the channel that this binding element belongs to. + /// </summary> + public Channel Channel { + get { + return this.signers[0].Channel; + } + + set { + foreach (var signer in this.signers) { + signer.Channel = value; + } + } + } + + /// <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> + public MessageProtections? ProcessOutgoingMessage(IProtocolMessage message) { + foreach (IChannelBindingElement signer in this.signers) { + ErrorUtilities.VerifyInternal(signer.Channel != null, "A binding element's Channel property is unexpectedly null."); + MessageProtections? result = signer.ProcessOutgoingMessage(message); + if (result.HasValue) { + return result; + } + } + + 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> + public MessageProtections? ProcessIncomingMessage(IProtocolMessage message) { + foreach (IChannelBindingElement signer in this.signers) { + ErrorUtilities.VerifyInternal(signer.Channel != null, "A binding element's Channel property is unexpectedly null."); + MessageProtections? result = signer.ProcessIncomingMessage(message); + if (result.HasValue) { + return result; + } + } + + return null; + } + + #endregion + + #region ITamperProtectionChannelBindingElement Methods + + /// <summary> + /// Creates a new object that is a copy of the current instance. + /// </summary> + /// <returns> + /// A new object that is a copy of this instance. + /// </returns> + ITamperProtectionChannelBindingElement ITamperProtectionChannelBindingElement.Clone() { + return new SigningBindingElementChain(this.signers.Select(el => (ITamperProtectionChannelBindingElement)el.Clone()).ToArray()); + } + + #endregion + } +} diff --git a/src/DotNetOpenAuth/OAuth/ChannelElements/TokenType.cs b/src/DotNetOpenAuth.OAuth/OAuth/ChannelElements/TokenType.cs index 46c2bd9..46c2bd9 100644 --- a/src/DotNetOpenAuth/OAuth/ChannelElements/TokenType.cs +++ b/src/DotNetOpenAuth.OAuth/OAuth/ChannelElements/TokenType.cs diff --git a/src/DotNetOpenAuth/OAuth/ChannelElements/UriOrOobEncoding.cs b/src/DotNetOpenAuth.OAuth/OAuth/ChannelElements/UriOrOobEncoding.cs index 287ef01..287ef01 100644 --- a/src/DotNetOpenAuth/OAuth/ChannelElements/UriOrOobEncoding.cs +++ b/src/DotNetOpenAuth.OAuth/OAuth/ChannelElements/UriOrOobEncoding.cs diff --git a/src/DotNetOpenAuth/OAuth/ClassDiagram.cd b/src/DotNetOpenAuth.OAuth/OAuth/ClassDiagram.cd index f56fd83..f56fd83 100644 --- a/src/DotNetOpenAuth/OAuth/ClassDiagram.cd +++ b/src/DotNetOpenAuth.OAuth/OAuth/ClassDiagram.cd diff --git a/src/DotNetOpenAuth/OAuth/ConsumerSecuritySettings.cs b/src/DotNetOpenAuth.OAuth/OAuth/ConsumerSecuritySettings.cs index bb2fbaa..bb2fbaa 100644 --- a/src/DotNetOpenAuth/OAuth/ConsumerSecuritySettings.cs +++ b/src/DotNetOpenAuth.OAuth/OAuth/ConsumerSecuritySettings.cs diff --git a/src/DotNetOpenAuth/OAuth/Messages/AccessProtectedResourceRequest.cs b/src/DotNetOpenAuth.OAuth/OAuth/Messages/AccessProtectedResourceRequest.cs index f3231f0..f3231f0 100644 --- a/src/DotNetOpenAuth/OAuth/Messages/AccessProtectedResourceRequest.cs +++ b/src/DotNetOpenAuth.OAuth/OAuth/Messages/AccessProtectedResourceRequest.cs diff --git a/src/DotNetOpenAuth/OAuth/Messages/AuthorizedTokenRequest.cs b/src/DotNetOpenAuth.OAuth/OAuth/Messages/AuthorizedTokenRequest.cs index 02c6c1d..02c6c1d 100644 --- a/src/DotNetOpenAuth/OAuth/Messages/AuthorizedTokenRequest.cs +++ b/src/DotNetOpenAuth.OAuth/OAuth/Messages/AuthorizedTokenRequest.cs diff --git a/src/DotNetOpenAuth/OAuth/Messages/AuthorizedTokenResponse.cs b/src/DotNetOpenAuth.OAuth/OAuth/Messages/AuthorizedTokenResponse.cs index 0b14819..0b14819 100644 --- a/src/DotNetOpenAuth/OAuth/Messages/AuthorizedTokenResponse.cs +++ b/src/DotNetOpenAuth.OAuth/OAuth/Messages/AuthorizedTokenResponse.cs diff --git a/src/DotNetOpenAuth/OAuth/Messages/ITokenContainingMessage.cs b/src/DotNetOpenAuth.OAuth/OAuth/Messages/ITokenContainingMessage.cs index 832b754..832b754 100644 --- a/src/DotNetOpenAuth/OAuth/Messages/ITokenContainingMessage.cs +++ b/src/DotNetOpenAuth.OAuth/OAuth/Messages/ITokenContainingMessage.cs diff --git a/src/DotNetOpenAuth/OAuth/Messages/ITokenSecretContainingMessage.cs b/src/DotNetOpenAuth.OAuth/OAuth/Messages/ITokenSecretContainingMessage.cs index 95809ec..95809ec 100644 --- a/src/DotNetOpenAuth/OAuth/Messages/ITokenSecretContainingMessage.cs +++ b/src/DotNetOpenAuth.OAuth/OAuth/Messages/ITokenSecretContainingMessage.cs diff --git a/src/DotNetOpenAuth.OAuth/OAuth/Messages/MessageBase.cs b/src/DotNetOpenAuth.OAuth/OAuth/Messages/MessageBase.cs new file mode 100644 index 0000000..faa4345 --- /dev/null +++ b/src/DotNetOpenAuth.OAuth/OAuth/Messages/MessageBase.cs @@ -0,0 +1,281 @@ +//----------------------------------------------------------------------- +// <copyright file="MessageBase.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OAuth.Messages { + using System; + using System.Collections.Generic; + using System.Diagnostics.Contracts; + using System.Globalization; + using System.Text; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.Messaging.Reflection; + using DotNetOpenAuth.OAuth.ChannelElements; + + /// <summary> + /// A base class for all OAuth messages. + /// </summary> + [Serializable] + public abstract class MessageBase : IDirectedProtocolMessage, IDirectResponseProtocolMessage { + /// <summary> + /// A store for extra name/value data pairs that are attached to this message. + /// </summary> + private Dictionary<string, string> extraData = new Dictionary<string, string>(); + + /// <summary> + /// Gets a value indicating whether signing this message is required. + /// </summary> + private MessageProtections protectionRequired; + + /// <summary> + /// Gets a value indicating whether this is a direct or indirect message. + /// </summary> + private MessageTransport transport; + + /// <summary> + /// The URI to the remote endpoint to send this message to. + /// </summary> + private MessageReceivingEndpoint recipient; + + /// <summary> + /// Backing store for the <see cref="OriginatingRequest"/> properties. + /// </summary> + private IDirectedProtocolMessage originatingRequest; + + /// <summary> + /// Backing store for the <see cref="Incoming"/> properties. + /// </summary> + private bool incoming; + +#if DEBUG + /// <summary> + /// Initializes static members of the <see cref="MessageBase"/> class. + /// </summary> + static MessageBase() { + LowSecurityMode = true; + } +#endif + + /// <summary> + /// Initializes a new instance of the <see cref="MessageBase"/> class for direct response messages. + /// </summary> + /// <param name="protectionRequired">The level of protection the message requires.</param> + /// <param name="originatingRequest">The request that asked for this direct response.</param> + /// <param name="version">The OAuth version.</param> + protected MessageBase(MessageProtections protectionRequired, IDirectedProtocolMessage originatingRequest, Version version) { + Requires.NotNull(originatingRequest, "originatingRequest"); + Requires.NotNull(version, "version"); + + this.protectionRequired = protectionRequired; + this.transport = MessageTransport.Direct; + this.originatingRequest = originatingRequest; + this.Version = version; + } + + /// <summary> + /// Initializes a new instance of the <see cref="MessageBase"/> class for direct requests or indirect messages. + /// </summary> + /// <param name="protectionRequired">The level of protection the message requires.</param> + /// <param name="transport">A value indicating whether this message requires a direct or indirect transport.</param> + /// <param name="recipient">The URI that a directed message will be delivered to.</param> + /// <param name="version">The OAuth version.</param> + protected MessageBase(MessageProtections protectionRequired, MessageTransport transport, MessageReceivingEndpoint recipient, Version version) { + Requires.NotNull(recipient, "recipient"); + Requires.NotNull(version, "version"); + + this.protectionRequired = protectionRequired; + this.transport = transport; + this.recipient = recipient; + this.Version = version; + } + + #region IProtocolMessage Properties + + /// <summary> + /// Gets the version of the protocol this message is prepared to implement. + /// </summary> + Version IMessage.Version { + get { return this.Version; } + } + + /// <summary> + /// Gets the level of protection this message requires. + /// </summary> + MessageProtections IProtocolMessage.RequiredProtection { + get { return this.RequiredProtection; } + } + + /// <summary> + /// Gets a value indicating whether this is a direct or indirect message. + /// </summary> + MessageTransport IProtocolMessage.Transport { + get { return this.Transport; } + } + + /// <summary> + /// Gets the dictionary of additional name/value fields tacked on to this message. + /// </summary> + IDictionary<string, string> IMessage.ExtraData { + get { return this.ExtraData; } + } + + #endregion + + #region IDirectedProtocolMessage Members + + /// <summary> + /// Gets the URI to the Service Provider endpoint to send this message to. + /// </summary> + Uri IDirectedProtocolMessage.Recipient { + get { return this.recipient != null ? this.recipient.Location : null; } + } + + /// <summary> + /// Gets the preferred method of transport for the message. + /// </summary> + HttpDeliveryMethods IDirectedProtocolMessage.HttpMethods { + get { return this.HttpMethods; } + } + + #endregion + + #region IDirectResponseProtocolMessage Members + + /// <summary> + /// Gets the originating request message that caused this response to be formed. + /// </summary> + IDirectedProtocolMessage IDirectResponseProtocolMessage.OriginatingRequest { + get { return this.originatingRequest; } + } + + #endregion + + /// <summary> + /// Gets or sets a value indicating whether security sensitive strings are + /// emitted from the ToString() method. + /// </summary> + internal static bool LowSecurityMode { get; set; } + + /// <summary> + /// Gets a value indicating whether this message was deserialized as an incoming message. + /// </summary> + protected internal bool Incoming { + get { return this.incoming; } + } + + /// <summary> + /// Gets the version of the protocol this message is prepared to implement. + /// </summary> + protected internal Version Version { get; private set; } + + /// <summary> + /// Gets the level of protection this message requires. + /// </summary> + protected MessageProtections RequiredProtection { + get { return this.protectionRequired; } + } + + /// <summary> + /// Gets a value indicating whether this is a direct or indirect message. + /// </summary> + protected MessageTransport Transport { + get { return this.transport; } + } + + /// <summary> + /// Gets the dictionary of additional name/value fields tacked on to this message. + /// </summary> + protected IDictionary<string, string> ExtraData { + get { return this.extraData; } + } + + /// <summary> + /// Gets the preferred method of transport for the message. + /// </summary> + protected HttpDeliveryMethods HttpMethods { + get { return this.recipient != null ? this.recipient.AllowedMethods : HttpDeliveryMethods.None; } + } + + /// <summary> + /// Gets or sets the URI to the Service Provider endpoint to send this message to. + /// </summary> + protected Uri Recipient { + get { + return this.recipient != null ? this.recipient.Location : null; + } + + set { + if (this.recipient != null) { + this.recipient = new MessageReceivingEndpoint(value, this.recipient.AllowedMethods); + } else if (value != null) { + throw new InvalidOperationException(); + } + } + } + + /// <summary> + /// Gets the originating request message that caused this response to be formed. + /// </summary> + protected IDirectedProtocolMessage OriginatingRequest { + get { return this.originatingRequest; } + } + + #region IProtocolMessage Methods + + /// <summary> + /// Checks the message state for conformity to the protocol specification + /// and throws an exception if the message is invalid. + /// </summary> + void IMessage.EnsureValidMessage() { + this.EnsureValidMessage(); + } + + #endregion + + /// <summary> + /// Returns a human-friendly string describing the message and all serializable properties. + /// </summary> + /// <param name="channel">The channel that will carry this message.</param> + /// <returns> + /// The string representation of this object. + /// </returns> + internal virtual string ToString(Channel channel) { + Requires.NotNull(channel, "channel"); + + StringBuilder builder = new StringBuilder(); + builder.AppendFormat(CultureInfo.InvariantCulture, "{0} message", GetType().Name); + if (this.recipient != null) { + builder.AppendFormat(CultureInfo.InvariantCulture, " as {0} to {1}", this.recipient.AllowedMethods, this.recipient.Location); + } + builder.AppendLine(); + MessageDictionary dictionary = channel.MessageDescriptions.GetAccessor(this); + foreach (var pair in dictionary) { + string value = pair.Value; + if (pair.Key == "oauth_signature" && !LowSecurityMode) { + value = "xxxxxxxxxxxxx (not shown)"; + } + builder.Append('\t'); + builder.Append(pair.Key); + builder.Append(": "); + builder.AppendLine(value); + } + + return builder.ToString(); + } + + /// <summary> + /// Sets a flag indicating that this message is received (as opposed to sent). + /// </summary> + internal void SetAsIncoming() { + this.incoming = true; + } + + /// <summary> + /// Checks the message state for conformity to the protocol specification + /// and throws an exception if the message is invalid. + /// </summary> + protected virtual void EnsureValidMessage() { } + } +} diff --git a/src/DotNetOpenAuth/OAuth/Messages/OAuth Messages.cd b/src/DotNetOpenAuth.OAuth/OAuth/Messages/OAuth Messages.cd index f5526d2..f5526d2 100644 --- a/src/DotNetOpenAuth/OAuth/Messages/OAuth Messages.cd +++ b/src/DotNetOpenAuth.OAuth/OAuth/Messages/OAuth Messages.cd diff --git a/src/DotNetOpenAuth/OAuth/Messages/SignedMessageBase.cs b/src/DotNetOpenAuth.OAuth/OAuth/Messages/SignedMessageBase.cs index 6084fc0..6084fc0 100644 --- a/src/DotNetOpenAuth/OAuth/Messages/SignedMessageBase.cs +++ b/src/DotNetOpenAuth.OAuth/OAuth/Messages/SignedMessageBase.cs diff --git a/src/DotNetOpenAuth/OAuth/Messages/UnauthorizedTokenRequest.cs b/src/DotNetOpenAuth.OAuth/OAuth/Messages/UnauthorizedTokenRequest.cs index 9214d91..9214d91 100644 --- a/src/DotNetOpenAuth/OAuth/Messages/UnauthorizedTokenRequest.cs +++ b/src/DotNetOpenAuth.OAuth/OAuth/Messages/UnauthorizedTokenRequest.cs diff --git a/src/DotNetOpenAuth.OAuth/OAuth/Messages/UnauthorizedTokenResponse.cs b/src/DotNetOpenAuth.OAuth/OAuth/Messages/UnauthorizedTokenResponse.cs new file mode 100644 index 0000000..04dee88 --- /dev/null +++ b/src/DotNetOpenAuth.OAuth/OAuth/Messages/UnauthorizedTokenResponse.cs @@ -0,0 +1,100 @@ +//----------------------------------------------------------------------- +// <copyright file="UnauthorizedTokenResponse.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OAuth.Messages { + using System; + using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; + using System.Diagnostics.Contracts; + using DotNetOpenAuth.Messaging; + + /// <summary> + /// A direct message sent from Service Provider to Consumer in response to + /// a Consumer's <see cref="UnauthorizedTokenRequest"/> request. + /// </summary> + public class UnauthorizedTokenResponse : MessageBase, ITokenSecretContainingMessage { + /// <summary> + /// Initializes a new instance of the <see cref="UnauthorizedTokenResponse"/> class. + /// </summary> + /// <param name="requestMessage">The unauthorized request token message that this message is being generated in response to.</param> + /// <param name="requestToken">The request token.</param> + /// <param name="tokenSecret">The token secret.</param> + /// <remarks> + /// This constructor is used by the Service Provider to send the message. + /// </remarks> + protected internal UnauthorizedTokenResponse(UnauthorizedTokenRequest requestMessage, string requestToken, string tokenSecret) + : this(requestMessage, requestMessage.Version) { + Requires.NotNull(requestToken, "requestToken"); + Requires.NotNull(tokenSecret, "tokenSecret"); + + this.RequestToken = requestToken; + this.TokenSecret = tokenSecret; + } + + /// <summary> + /// Initializes a new instance of the <see cref="UnauthorizedTokenResponse"/> class. + /// </summary> + /// <param name="originatingRequest">The originating request.</param> + /// <param name="version">The OAuth version.</param> + /// <remarks>This constructor is used by the consumer to deserialize the message.</remarks> + protected internal UnauthorizedTokenResponse(UnauthorizedTokenRequest originatingRequest, Version version) + : base(MessageProtections.None, originatingRequest, version) { + } + + /// <summary> + /// Gets or sets the Request or Access Token. + /// </summary> + [SuppressMessage("Microsoft.Design", "CA1033:InterfaceMethodsShouldBeCallableByChildTypes", Justification = "This property IS accessible by a different name.")] + string ITokenContainingMessage.Token { + get { return this.RequestToken; } + set { this.RequestToken = value; } + } + + /// <summary> + /// Gets or sets the Request or Access Token secret. + /// </summary> + string ITokenSecretContainingMessage.TokenSecret { + get { return this.TokenSecret; } + set { this.TokenSecret = value; } + } + + /// <summary> + /// Gets the extra, non-OAuth parameters that will be included in the message. + /// </summary> + public new IDictionary<string, string> ExtraData { + get { return base.ExtraData; } + } + + /// <summary> + /// Gets or sets the Request Token. + /// </summary> + [MessagePart("oauth_token", IsRequired = true)] + internal string RequestToken { get; set; } + + /// <summary> + /// Gets the original request for an unauthorized token. + /// </summary> + internal UnauthorizedTokenRequest RequestMessage { + get { return (UnauthorizedTokenRequest)this.OriginatingRequest; } + } + + /// <summary> + /// Gets or sets the Token Secret. + /// </summary> + [MessagePart("oauth_token_secret", IsRequired = true)] + protected internal string TokenSecret { get; set; } + + /// <summary> + /// Gets a value indicating whether the Service Provider recognized the callback parameter in the request. + /// </summary> + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Message serialization invoked.")] + [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Message parts must be instance members.")] + [MessagePart("oauth_callback_confirmed", IsRequired = true, MinVersion = Protocol.V10aVersion)] + private bool CallbackConfirmed { + get { return true; } + } + } +} diff --git a/src/DotNetOpenAuth/OAuth/Messages/UserAuthorizationRequest.cs b/src/DotNetOpenAuth.OAuth/OAuth/Messages/UserAuthorizationRequest.cs index a5823bb..a5823bb 100644 --- a/src/DotNetOpenAuth/OAuth/Messages/UserAuthorizationRequest.cs +++ b/src/DotNetOpenAuth.OAuth/OAuth/Messages/UserAuthorizationRequest.cs diff --git a/src/DotNetOpenAuth/OAuth/Messages/UserAuthorizationResponse.cs b/src/DotNetOpenAuth.OAuth/OAuth/Messages/UserAuthorizationResponse.cs index 73fddc7..73fddc7 100644 --- a/src/DotNetOpenAuth/OAuth/Messages/UserAuthorizationResponse.cs +++ b/src/DotNetOpenAuth.OAuth/OAuth/Messages/UserAuthorizationResponse.cs diff --git a/src/DotNetOpenAuth/OAuth/OAuthStrings.Designer.cs b/src/DotNetOpenAuth.OAuth/OAuth/OAuthStrings.Designer.cs index 723839d..723839d 100644 --- a/src/DotNetOpenAuth/OAuth/OAuthStrings.Designer.cs +++ b/src/DotNetOpenAuth.OAuth/OAuth/OAuthStrings.Designer.cs diff --git a/src/DotNetOpenAuth/OAuth/OAuthStrings.resx b/src/DotNetOpenAuth.OAuth/OAuth/OAuthStrings.resx index 34b314b..34b314b 100644 --- a/src/DotNetOpenAuth/OAuth/OAuthStrings.resx +++ b/src/DotNetOpenAuth.OAuth/OAuth/OAuthStrings.resx diff --git a/src/DotNetOpenAuth/OAuth/OAuthStrings.sr.resx b/src/DotNetOpenAuth.OAuth/OAuth/OAuthStrings.sr.resx index ef9ce60..ef9ce60 100644 --- a/src/DotNetOpenAuth/OAuth/OAuthStrings.sr.resx +++ b/src/DotNetOpenAuth.OAuth/OAuth/OAuthStrings.sr.resx diff --git a/src/DotNetOpenAuth.OAuth/OAuth/Protocol.cs b/src/DotNetOpenAuth.OAuth/OAuth/Protocol.cs new file mode 100644 index 0000000..0b7aaa9 --- /dev/null +++ b/src/DotNetOpenAuth.OAuth/OAuth/Protocol.cs @@ -0,0 +1,148 @@ +//----------------------------------------------------------------------- +// <copyright file="Protocol.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OAuth { + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; + using System.Diagnostics.Contracts; + using System.Linq; + using System.Text; + using DotNetOpenAuth.Messaging; + + /// <summary> + /// An enumeration of the OAuth protocol versions supported by this library. + /// </summary> + public enum ProtocolVersion { + /// <summary> + /// OAuth 1.0 specification + /// </summary> + V10, + + /// <summary> + /// OAuth 1.0a specification + /// </summary> + [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "a", Justification = "By design.")] + V10a, + } + + /// <summary> + /// Constants used in the OAuth protocol. + /// </summary> + /// <remarks> + /// OAuth Protocol Parameter names and values are case sensitive. Each OAuth Protocol Parameters MUST NOT appear more than once per request, and are REQUIRED unless otherwise noted, + /// per OAuth 1.0 section 5. + /// </remarks> + [DebuggerDisplay("OAuth {Version}")] + internal class Protocol { + /// <summary> + /// The namespace to use for V1.0 of the protocol. + /// </summary> + internal const string DataContractNamespaceV10 = "http://oauth.net/core/1.0/"; + + /// <summary> + /// The prefix used for all key names in the protocol. + /// </summary> + internal const string ParameterPrefix = "oauth_"; + + /// <summary> + /// The string representation of a <see cref="Version"/> instance to be used to represent OAuth 1.0a. + /// </summary> + internal const string V10aVersion = "1.0.1"; + + /// <summary> + /// The scheme to use in Authorization header message requests. + /// </summary> + internal const string AuthorizationHeaderScheme = "OAuth"; + + /// <summary> + /// Gets the <see cref="Protocol"/> instance with values initialized for V1.0 of the protocol. + /// </summary> + internal static readonly Protocol V10 = new Protocol { + dataContractNamespace = DataContractNamespaceV10, + Version = new Version(1, 0), + ProtocolVersion = ProtocolVersion.V10, + }; + + /// <summary> + /// Gets the <see cref="Protocol"/> instance with values initialized for V1.0a of the protocol. + /// </summary> + internal static readonly Protocol V10a = new Protocol { + dataContractNamespace = DataContractNamespaceV10, + Version = new Version(V10aVersion), + ProtocolVersion = ProtocolVersion.V10a, + }; + + /// <summary> + /// A list of all supported OAuth versions, in order starting from newest version. + /// </summary> + internal static readonly List<Protocol> AllVersions = new List<Protocol>() { V10a, V10 }; + + /// <summary> + /// The default (or most recent) supported version of the OAuth protocol. + /// </summary> + internal static readonly Protocol Default = AllVersions[0]; + + /// <summary> + /// The namespace to use for this version of the protocol. + /// </summary> + private string dataContractNamespace; + + /// <summary> + /// Initializes a new instance of the <see cref="Protocol"/> class. + /// </summary> + internal Protocol() { + this.PublishedVersion = "1.0"; + } + + /// <summary> + /// Gets the OAuth version this instance represents. + /// </summary> + internal Version Version { get; private set; } + + /// <summary> + /// Gets the version to declare on the wire. + /// </summary> + internal string PublishedVersion { get; private set; } + + /// <summary> + /// Gets the <see cref="ProtocolVersion"/> enum value for the <see cref="Protocol"/> instance. + /// </summary> + internal ProtocolVersion ProtocolVersion { get; private set; } + + /// <summary> + /// Gets the namespace to use for this version of the protocol. + /// </summary> + internal string DataContractNamespace { + get { return this.dataContractNamespace; } + } + + /// <summary> + /// Gets the OAuth Protocol instance to use for the given version. + /// </summary> + /// <param name="version">The OAuth version to get.</param> + /// <returns>A matching <see cref="Protocol"/> instance.</returns> + public static Protocol Lookup(ProtocolVersion version) { + switch (version) { + case ProtocolVersion.V10: return Protocol.V10; + case ProtocolVersion.V10a: return Protocol.V10a; + default: throw new ArgumentOutOfRangeException("version"); + } + } + + /// <summary> + /// Gets the OAuth Protocol instance to use for the given version. + /// </summary> + /// <param name="version">The OAuth version to get.</param> + /// <returns>A matching <see cref="Protocol"/> instance.</returns> + internal static Protocol Lookup(Version version) { + Requires.NotNull(version, "version"); + Requires.InRange(AllVersions.Any(p => p.Version == version), "version"); + return AllVersions.First(p => p.Version == version); + } + } +} diff --git a/src/DotNetOpenAuth/OAuth/SecuritySettings.cs b/src/DotNetOpenAuth.OAuth/OAuth/SecuritySettings.cs index 3329f09..3329f09 100644 --- a/src/DotNetOpenAuth/OAuth/SecuritySettings.cs +++ b/src/DotNetOpenAuth.OAuth/OAuth/SecuritySettings.cs diff --git a/src/DotNetOpenAuth/OAuth/ServiceProviderDescription.cs b/src/DotNetOpenAuth.OAuth/OAuth/ServiceProviderDescription.cs index 6205f55..6205f55 100644 --- a/src/DotNetOpenAuth/OAuth/ServiceProviderDescription.cs +++ b/src/DotNetOpenAuth.OAuth/OAuth/ServiceProviderDescription.cs diff --git a/src/DotNetOpenAuth/OAuth/ServiceProviderSecuritySettings.cs b/src/DotNetOpenAuth.OAuth/OAuth/ServiceProviderSecuritySettings.cs index 701e36c..701e36c 100644 --- a/src/DotNetOpenAuth/OAuth/ServiceProviderSecuritySettings.cs +++ b/src/DotNetOpenAuth.OAuth/OAuth/ServiceProviderSecuritySettings.cs diff --git a/src/DotNetOpenAuth.OAuth/OAuthReporting.cs b/src/DotNetOpenAuth.OAuth/OAuthReporting.cs new file mode 100644 index 0000000..c6f8841 --- /dev/null +++ b/src/DotNetOpenAuth.OAuth/OAuthReporting.cs @@ -0,0 +1,56 @@ +//----------------------------------------------------------------------- +// <copyright file="OAuthReporting.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth { + using System; + using System.Collections.Generic; + using System.Diagnostics.Contracts; + using System.Linq; + using System.Text; + using DotNetOpenAuth.Messaging.Bindings; + using DotNetOpenAuth.OAuth; + using DotNetOpenAuth.OAuth.ChannelElements; + + /// <summary> + /// Utility methods specific to OAuth feature reporting. + /// </summary> + internal class OAuthReporting : Reporting { + /// <summary> + /// Records the feature and dependency use. + /// </summary> + /// <param name="value">The consumer or service provider.</param> + /// <param name="service">The service.</param> + /// <param name="tokenManager">The token manager.</param> + /// <param name="nonceStore">The nonce store.</param> + internal static void RecordFeatureAndDependencyUse(object value, ServiceProviderDescription service, ITokenManager tokenManager, INonceStore nonceStore) { + Contract.Requires(value != null); + Contract.Requires(service != null); + Contract.Requires(tokenManager != null); + + // In release builds, just quietly return. + if (value == null || service == null || tokenManager == null) { + return; + } + + if (Reporting.Enabled && Reporting.Configuration.IncludeFeatureUsage) { + StringBuilder builder = new StringBuilder(); + builder.Append(value.GetType().Name); + builder.Append(" "); + builder.Append(tokenManager.GetType().Name); + if (nonceStore != null) { + builder.Append(" "); + builder.Append(nonceStore.GetType().Name); + } + builder.Append(" "); + builder.Append(service.Version); + builder.Append(" "); + builder.Append(service.UserAuthorizationEndpoint); + Reporting.ObservedFeatures.Add(builder.ToString()); + Reporting.Touch(); + } + } + } +} diff --git a/src/DotNetOpenAuth.OAuth/Properties/AssemblyInfo.cs b/src/DotNetOpenAuth.OAuth/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..49ef570 --- /dev/null +++ b/src/DotNetOpenAuth.OAuth/Properties/AssemblyInfo.cs @@ -0,0 +1,62 @@ +//----------------------------------------------------------------------- +// <copyright file="AssemblyInfo.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +// We DON'T put an AssemblyVersionAttribute in here because it is generated in the build. + +using System; +using System.Diagnostics.Contracts; +using System.Net; +using System.Reflection; +using System.Resources; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Security; +using System.Security.Permissions; +using System.Web.UI; + +[assembly: TagPrefix("DotNetOpenAuth.OAuth", "oauth")] + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("DotNetOpenAuth OAuth")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("DotNetOpenAuth")] +[assembly: AssemblyCopyright("Copyright © 2008")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: NeutralResourcesLanguage("en-US")] +[assembly: CLSCompliant(true)] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("7d73990c-47c0-4256-9f20-a893add9e289")] + +[assembly: ContractVerification(true)] + +#if StrongNameSigned +// See comment at top of this file. We need this so that strong-naming doesn't +// keep this assembly from being useful to shared host (medium trust) web sites. +[assembly: AllowPartiallyTrustedCallers] + +[assembly: InternalsVisibleTo("DotNetOpenAuth.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100AD093C3765257C89A7010E853F2C7C741FF92FA8ACE06D7B8254702CAD5CF99104447F63AB05F8BB6F51CE0D81C8C93D2FCE8C20AAFF7042E721CBA16EAAE98778611DED11C0ABC8900DC5667F99B50A9DADEC24DBD8F2C91E3E8AD300EF64F1B4B9536CEB16FB440AF939F57624A9B486F867807C649AE4830EAB88C6C03998")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.OAuth.Consumer, PublicKey=0024000004800000940000000602000000240000525341310004000001000100AD093C3765257C89A7010E853F2C7C741FF92FA8ACE06D7B8254702CAD5CF99104447F63AB05F8BB6F51CE0D81C8C93D2FCE8C20AAFF7042E721CBA16EAAE98778611DED11C0ABC8900DC5667F99B50A9DADEC24DBD8F2C91E3E8AD300EF64F1B4B9536CEB16FB440AF939F57624A9B486F867807C649AE4830EAB88C6C03998")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.OAuth.ServiceProvider, PublicKey=0024000004800000940000000602000000240000525341310004000001000100AD093C3765257C89A7010E853F2C7C741FF92FA8ACE06D7B8254702CAD5CF99104447F63AB05F8BB6F51CE0D81C8C93D2FCE8C20AAFF7042E721CBA16EAAE98778611DED11C0ABC8900DC5667F99B50A9DADEC24DBD8F2C91E3E8AD300EF64F1B4B9536CEB16FB440AF939F57624A9B486F867807C649AE4830EAB88C6C03998")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.OAuth2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100AD093C3765257C89A7010E853F2C7C741FF92FA8ACE06D7B8254702CAD5CF99104447F63AB05F8BB6F51CE0D81C8C93D2FCE8C20AAFF7042E721CBA16EAAE98778611DED11C0ABC8900DC5667F99B50A9DADEC24DBD8F2C91E3E8AD300EF64F1B4B9536CEB16FB440AF939F57624A9B486F867807C649AE4830EAB88C6C03998")] +[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")] +#else +[assembly: InternalsVisibleTo("DotNetOpenAuth.Test")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.OAuth.Consumer")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.OAuth.ServiceProvider")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.OAuth2")] +[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] +#endif diff --git a/src/DotNetOpenAuth.OAuth2.AuthorizationServer/DotNetOpenAuth.OAuth2.AuthorizationServer.csproj b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/DotNetOpenAuth.OAuth2.AuthorizationServer.csproj new file mode 100644 index 0000000..e28c158 --- /dev/null +++ b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/DotNetOpenAuth.OAuth2.AuthorizationServer.csproj @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), EnlistmentInfo.props))\EnlistmentInfo.props" Condition=" '$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), EnlistmentInfo.props))' != '' " /> + <PropertyGroup> + <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> + <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> + </PropertyGroup> + <Import Project="$(ProjectRoot)tools\DotNetOpenAuth.props" /> + <PropertyGroup> + <SchemaVersion>2.0</SchemaVersion> + <ProjectGuid>{99BB7543-EA16-43EE-A7BC-D7A25A3B22F6}</ProjectGuid> + <AppDesignerFolder>Properties</AppDesignerFolder> + <AssemblyName>DotNetOpenAuth.OAuth2.AuthorizationServer</AssemblyName> + </PropertyGroup> + <Import Project="$(ProjectRoot)tools\DotNetOpenAuth.Product.props" /> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> + </PropertyGroup> + <ItemGroup> + <Compile Include="OAuth2\AuthorizationServer.cs" /> + <Compile Include="Properties\AssemblyInfo.cs" /> + </ItemGroup> + <ItemGroup> + <ProjectReference Include="..\DotNetOpenAuth.Core\DotNetOpenAuth.Core.csproj"> + <Project>{60426312-6AE5-4835-8667-37EDEA670222}</Project> + <Name>DotNetOpenAuth.Core</Name> + </ProjectReference> + <ProjectReference Include="..\DotNetOpenAuth.OAuth2\DotNetOpenAuth.OAuth2.csproj"> + <Project>{56459A6C-6BA2-4BAC-A9C0-27E3BD961FA6}</Project> + <Name>DotNetOpenAuth.OAuth2</Name> + </ProjectReference> + <ProjectReference Include="..\DotNetOpenAuth.OAuth\DotNetOpenAuth.OAuth.csproj"> + <Project>{A288FCC8-6FCF-46DA-A45E-5F9281556361}</Project> + <Name>DotNetOpenAuth.OAuth</Name> + </ProjectReference> + </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))' != '' " /> +</Project>
\ No newline at end of file diff --git a/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/AuthorizationServer.cs b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/AuthorizationServer.cs new file mode 100644 index 0000000..73c2bd6 --- /dev/null +++ b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/AuthorizationServer.cs @@ -0,0 +1,258 @@ +//----------------------------------------------------------------------- +// <copyright file="AuthorizationServer.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; + using DotNetOpenAuth.OAuth2.Messages; + + /// <summary> + /// Authorization Server supporting the web server flow. + /// </summary> + public class AuthorizationServer { + /// <summary> + /// Initializes a new instance of the <see cref="AuthorizationServer"/> class. + /// </summary> + /// <param name="authorizationServer">The authorization server.</param> + public AuthorizationServer(IAuthorizationServer authorizationServer) { + Requires.NotNull(authorizationServer, "authorizationServer"); + this.OAuthChannel = new OAuth2AuthorizationServerChannel(authorizationServer); + } + + /// <summary> + /// Gets the channel. + /// </summary> + /// <value>The channel.</value> + public Channel Channel { + get { return this.OAuthChannel; } + } + + /// <summary> + /// Gets the authorization server. + /// </summary> + /// <value>The authorization server.</value> + public IAuthorizationServer AuthorizationServerServices { + get { return this.OAuthChannel.AuthorizationServer; } + } + + /// <summary> + /// Gets the channel. + /// </summary> + internal OAuth2AuthorizationServerChannel OAuthChannel { get; private set; } + + /// <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> + /// <param name="request">The HTTP request to read from.</param> + /// <returns>The incoming request, or null if no OAuth message was attached.</returns> + /// <exception cref="ProtocolException">Thrown if an unexpected OAuth message is attached to the incoming request.</exception> + public EndUserAuthorizationRequest ReadAuthorizationRequest(HttpRequestInfo request = null) { + if (request == null) { + request = this.Channel.GetRequestFromContext(); + } + + EndUserAuthorizationRequest message; + if (this.Channel.TryReadFromRequest(request, out message)) { + 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); + } + } + + return message; + } + + /// <summary> + /// Approves an authorization request and sends an HTTP response to the user agent to redirect the user back to the Client. + /// </summary> + /// <param name="authorizationRequest">The authorization request to approve.</param> + /// <param name="userName">The username of the account that approved the request (or whose data will be accessed by the client).</param> + /// <param name="scopes">The scope of access the client should be granted. If <c>null</c>, all scopes in the original request will be granted.</param> + /// <param name="callback">The Client callback URL to use when formulating the redirect to send the user agent back to the Client.</param> + public void ApproveAuthorizationRequest(EndUserAuthorizationRequest authorizationRequest, string userName, IEnumerable<string> scopes = null, Uri callback = null) { + Requires.NotNull(authorizationRequest, "authorizationRequest"); + + var response = this.PrepareApproveAuthorizationRequest(authorizationRequest, userName, scopes, callback); + this.Channel.Respond(response); + } + + /// <summary> + /// Rejects an authorization request and sends an HTTP response to the user agent to redirect the user back to the Client. + /// </summary> + /// <param name="authorizationRequest">The authorization request to disapprove.</param> + /// <param name="callback">The Client callback URL to use when formulating the redirect to send the user agent back to the Client.</param> + public void RejectAuthorizationRequest(EndUserAuthorizationRequest authorizationRequest, Uri callback = null) { + Requires.NotNull(authorizationRequest, "authorizationRequest"); + + var response = this.PrepareRejectAuthorizationRequest(authorizationRequest, callback); + this.Channel.Respond(response); + } + + /// <summary> + /// Checks the incoming HTTP request for an access token request and prepares a response if the request message was found. + /// </summary> + /// <param name="response">The formulated response, or <c>null</c> if the request was not found..</param> + /// <returns>A value indicating whether any access token request was found in the HTTP request.</returns> + /// <remarks> + /// This method assumes that the authorization server and the resource server are the same and that they share a single + /// asymmetric key for signing and encrypting the access token. If this is not true, use the <see cref="ReadAccessTokenRequest"/> method instead. + /// </remarks> + public bool TryPrepareAccessTokenResponse(out IDirectResponseProtocolMessage response) { + return this.TryPrepareAccessTokenResponse(this.Channel.GetRequestFromContext(), out response); + } + + /// <summary> + /// Checks the incoming HTTP request for an access token request and prepares a response if the request message was found. + /// </summary> + /// <param name="httpRequestInfo">The HTTP request info.</param> + /// <param name="response">The formulated response, or <c>null</c> if the request was not found..</param> + /// <returns>A value indicating whether any access token request was found in the HTTP request.</returns> + /// <remarks> + /// This method assumes that the authorization server and the resource server are the same and that they share a single + /// asymmetric key for signing and encrypting the access token. If this is not true, use the <see cref="ReadAccessTokenRequest"/> method instead. + /// </remarks> + public bool TryPrepareAccessTokenResponse(HttpRequestInfo httpRequestInfo, out IDirectResponseProtocolMessage response) { + Requires.NotNull(httpRequestInfo, "httpRequestInfo"); + Contract.Ensures(Contract.Result<bool>() == (Contract.ValueAtReturn<IDirectResponseProtocolMessage>(out response) != null)); + + var request = this.ReadAccessTokenRequest(httpRequestInfo); + if (request != null) { + response = this.PrepareAccessTokenResponse(request); + return true; + } + + response = null; + return false; + } + + /// <summary> + /// Reads the access token request. + /// </summary> + /// <param name="requestInfo">The request info.</param> + /// <returns>The Client's request for an access token; or <c>null</c> if no such message was found in the request.</returns> + public AccessTokenRequestBase ReadAccessTokenRequest(HttpRequestInfo requestInfo = null) { + if (requestInfo == null) { + requestInfo = this.Channel.GetRequestFromContext(); + } + + AccessTokenRequestBase request; + this.Channel.TryReadFromRequest(requestInfo, out request); + return request; + } + + /// <summary> + /// Prepares a response to inform the Client that the user has rejected the Client's authorization request. + /// </summary> + /// <param name="authorizationRequest">The authorization request.</param> + /// <param name="callback">The Client callback URL to use when formulating the redirect to send the user agent back to the Client.</param> + /// <returns>The authorization response message to send to the Client.</returns> + public EndUserAuthorizationFailedResponse PrepareRejectAuthorizationRequest(EndUserAuthorizationRequest authorizationRequest, Uri callback = null) { + Requires.NotNull(authorizationRequest, "authorizationRequest"); + Contract.Ensures(Contract.Result<EndUserAuthorizationFailedResponse>() != null); + + if (callback == null) { + callback = this.GetCallback(authorizationRequest); + } + + var response = new EndUserAuthorizationFailedResponse(callback, authorizationRequest); + return response; + } + + /// <summary> + /// Approves an authorization request. + /// </summary> + /// <param name="authorizationRequest">The authorization request to approve.</param> + /// <param name="userName">The username of the account that approved the request (or whose data will be accessed by the client).</param> + /// <param name="scopes">The scope of access the client should be granted. If <c>null</c>, all scopes in the original request will be granted.</param> + /// <param name="callback">The Client callback URL to use when formulating the redirect to send the user agent back to the Client.</param> + /// <returns>The authorization response message to send to the Client.</returns> + public EndUserAuthorizationSuccessResponseBase PrepareApproveAuthorizationRequest(EndUserAuthorizationRequest authorizationRequest, string userName, IEnumerable<string> scopes = null, Uri callback = null) { + Requires.NotNull(authorizationRequest, "authorizationRequest"); + Requires.NotNullOrEmpty(userName, "userName"); + Contract.Ensures(Contract.Result<EndUserAuthorizationSuccessResponseBase>() != null); + + if (callback == null) { + callback = this.GetCallback(authorizationRequest); + } + + var client = this.AuthorizationServerServices.GetClientOrThrow(authorizationRequest.ClientIdentifier); + EndUserAuthorizationSuccessResponseBase response; + switch (authorizationRequest.ResponseType) { + case EndUserAuthorizationResponseType.AccessToken: + var accessTokenResponse = new EndUserAuthorizationSuccessAccessTokenResponse(callback, authorizationRequest); + accessTokenResponse.Lifetime = this.AuthorizationServerServices.GetAccessTokenLifetime(authorizationRequest); + response = accessTokenResponse; + break; + case EndUserAuthorizationResponseType.AuthorizationCode: + response = new EndUserAuthorizationSuccessAuthCodeResponse(callback, authorizationRequest); + break; + default: + throw ErrorUtilities.ThrowInternal("Unexpected response type."); + } + + response.AuthorizingUsername = userName; + + // Customize the approved scope if the authorization server has decided to do so. + if (scopes != null) { + response.Scope.ResetContents(scopes); + } + + return response; + } + + /// <summary> + /// 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> + /// <returns>The response message to send to the client.</returns> + public virtual IDirectResponseProtocolMessage PrepareAccessTokenResponse(AccessTokenRequestBase request, bool includeRefreshToken = true) { + Requires.NotNull(request, "request"); + + var tokenRequest = (IAuthorizationCarryingRequest)request; + var response = new AccessTokenSuccessResponse(request) { + Lifetime = this.AuthorizationServerServices.GetAccessTokenLifetime(request), + HasRefreshToken = includeRefreshToken, + }; + response.Scope.ResetContents(tokenRequest.AuthorizationDescription.Scope); + return response; + } + + /// <summary> + /// Gets the redirect URL to use for a particular authorization request. + /// </summary> + /// <param name="authorizationRequest">The authorization request.</param> + /// <returns>The URL to redirect to. Never <c>null</c>.</returns> + /// <exception cref="ProtocolException">Thrown if no callback URL could be determined.</exception> + protected Uri GetCallback(EndUserAuthorizationRequest authorizationRequest) { + Requires.NotNull(authorizationRequest, "authorizationRequest"); + Contract.Ensures(Contract.Result<Uri>() != null); + + var client = this.AuthorizationServerServices.GetClientOrThrow(authorizationRequest.ClientIdentifier); + + // Prefer a request-specific callback to the pre-registered one (if any). + if (authorizationRequest.Callback != null) { + // The OAuth channel has already validated the callback parameter against + // the authorization server's whitelist for this client. + return authorizationRequest.Callback; + } + + // 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); + return defaultCallback; + } + } +} diff --git a/src/DotNetOpenAuth.OAuth2.AuthorizationServer/Properties/AssemblyInfo.cs b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..54db6cf --- /dev/null +++ b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/Properties/AssemblyInfo.cs @@ -0,0 +1,54 @@ +//----------------------------------------------------------------------- +// <copyright file="AssemblyInfo.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +// We DON'T put an AssemblyVersionAttribute in here because it is generated in the build. + +using System; +using System.Diagnostics.Contracts; +using System.Net; +using System.Reflection; +using System.Resources; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Security; +using System.Security.Permissions; +using System.Web.UI; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("DotNetOpenAuth OAuth 2.0")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("DotNetOpenAuth")] +[assembly: AssemblyCopyright("Copyright © 2008")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: NeutralResourcesLanguage("en-US")] +[assembly: CLSCompliant(true)] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("7d73990c-47c0-4256-9f20-a893add9e289")] + +[assembly: ContractVerification(true)] + +#if StrongNameSigned +// See comment at top of this file. We need this so that strong-naming doesn't +// keep this assembly from being useful to shared host (medium trust) web sites. +[assembly: AllowPartiallyTrustedCallers] + +[assembly: InternalsVisibleTo("DotNetOpenAuth.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100AD093C3765257C89A7010E853F2C7C741FF92FA8ACE06D7B8254702CAD5CF99104447F63AB05F8BB6F51CE0D81C8C93D2FCE8C20AAFF7042E721CBA16EAAE98778611DED11C0ABC8900DC5667F99B50A9DADEC24DBD8F2C91E3E8AD300EF64F1B4B9536CEB16FB440AF939F57624A9B486F867807C649AE4830EAB88C6C03998")] +[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")] +#else +[assembly: InternalsVisibleTo("DotNetOpenAuth.Test")] +[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] +#endif diff --git a/src/DotNetOpenAuth.OAuth2.Client.UI/DotNetOpenAuth.OAuth2.Client.UI.csproj b/src/DotNetOpenAuth.OAuth2.Client.UI/DotNetOpenAuth.OAuth2.Client.UI.csproj new file mode 100644 index 0000000..7c35a50 --- /dev/null +++ b/src/DotNetOpenAuth.OAuth2.Client.UI/DotNetOpenAuth.OAuth2.Client.UI.csproj @@ -0,0 +1,55 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), EnlistmentInfo.props))\EnlistmentInfo.props" Condition=" '$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), EnlistmentInfo.props))' != '' " /> + <PropertyGroup> + <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> + <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> + </PropertyGroup> + <Import Project="$(ProjectRoot)tools\DotNetOpenAuth.props" /> + <PropertyGroup> + <SchemaVersion>2.0</SchemaVersion> + <ProjectGuid>{ADC2CC8C-541E-4F86-ACB1-DD504A36FA4B}</ProjectGuid> + <AppDesignerFolder>Properties</AppDesignerFolder> + <AssemblyName>DotNetOpenAuth.OAuth2.Client.UI</AssemblyName> + </PropertyGroup> + <Import Project="$(ProjectRoot)tools\DotNetOpenAuth.Product.props" /> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> + </PropertyGroup> + <ItemGroup> + <Compile Include="OAuth2\ClientAuthorizationView.cs"> + <SubType>UserControl</SubType> + </Compile> + <Compile Include="OAuth2\ClientAuthorizationView.Designer.cs"> + <DependentUpon>ClientAuthorizationView.cs</DependentUpon> + </Compile> + <Compile Include="Properties\AssemblyInfo.cs" /> + </ItemGroup> + <ItemGroup> + <ProjectReference Include="..\DotNetOpenAuth.Core\DotNetOpenAuth.Core.csproj"> + <Project>{60426312-6AE5-4835-8667-37EDEA670222}</Project> + <Name>DotNetOpenAuth.Core</Name> + </ProjectReference> + <ProjectReference Include="..\DotNetOpenAuth.OAuth2.Client\DotNetOpenAuth.OAuth2.Client.csproj"> + <Project>{CDEDD439-7F35-4E6E-8605-4E70BDC4CC99}</Project> + <Name>DotNetOpenAuth.OAuth2.Client</Name> + </ProjectReference> + <ProjectReference Include="..\DotNetOpenAuth.OAuth2\DotNetOpenAuth.OAuth2.csproj"> + <Project>{56459A6C-6BA2-4BAC-A9C0-27E3BD961FA6}</Project> + <Name>DotNetOpenAuth.OAuth2</Name> + </ProjectReference> + <ProjectReference Include="..\DotNetOpenAuth.OAuth\DotNetOpenAuth.OAuth.csproj"> + <Project>{A288FCC8-6FCF-46DA-A45E-5F9281556361}</Project> + <Name>DotNetOpenAuth.OAuth</Name> + </ProjectReference> + </ItemGroup> + <ItemGroup> + <EmbeddedResource Include="OAuth2\ClientAuthorizationView.resx"> + <DependentUpon>ClientAuthorizationView.cs</DependentUpon> + </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))' != '' " /> +</Project>
\ No newline at end of file diff --git a/src/DotNetOpenAuth/OAuth2/ClientAuthorizationView.Designer.cs b/src/DotNetOpenAuth.OAuth2.Client.UI/OAuth2/ClientAuthorizationView.Designer.cs index c05a4b8..c05a4b8 100644 --- a/src/DotNetOpenAuth/OAuth2/ClientAuthorizationView.Designer.cs +++ b/src/DotNetOpenAuth.OAuth2.Client.UI/OAuth2/ClientAuthorizationView.Designer.cs diff --git a/src/DotNetOpenAuth.OAuth2.Client.UI/OAuth2/ClientAuthorizationView.cs b/src/DotNetOpenAuth.OAuth2.Client.UI/OAuth2/ClientAuthorizationView.cs new file mode 100644 index 0000000..acd8fb3 --- /dev/null +++ b/src/DotNetOpenAuth.OAuth2.Client.UI/OAuth2/ClientAuthorizationView.cs @@ -0,0 +1,192 @@ +//----------------------------------------------------------------------- +// <copyright file="ClientAuthorizationView.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OAuth2 { + using System; + using System.Collections.Generic; + using System.ComponentModel; + using System.Data; + using System.Diagnostics.Contracts; + using System.Drawing; + using System.Linq; + using System.Text; + using System.Windows.Forms; + using DotNetOpenAuth.Messaging; + + /// <summary> + /// A WinForms control that hosts a mini-browser for hosting by native applications to + /// allow the user to authorize the client without leaving the application. + /// </summary> + public partial class ClientAuthorizationView : UserControl { + /// <summary> + /// Initializes a new instance of the <see cref="ClientAuthorizationView"/> class. + /// </summary> + public ClientAuthorizationView() { + this.InitializeComponent(); + + this.Authorization = new AuthorizationState(); + } + + /// <summary> + /// Occurs when the authorization flow has completed. + /// </summary> + public event EventHandler<ClientAuthorizationCompleteEventArgs> Completed; + + /// <summary> + /// Gets the authorization tracking object. + /// </summary> + public IAuthorizationState Authorization { get; private set; } + + /// <summary> + /// Gets or sets the client used to coordinate the authorization flow. + /// </summary> + public UserAgentClient Client { get; set; } + + /// <summary> + /// Gets the set of scopes that describe the requested level of access. + /// </summary> + public HashSet<string> Scope { + get { return this.Authorization.Scope; } + } + + /// <summary> + /// Gets or sets the callback URL used to indicate the flow has completed. + /// </summary> + public Uri Callback { + get { return this.Authorization.Callback; } + set { this.Authorization.Callback = value; } + } + + /// <summary> + /// Gets a value indicating whether the authorization flow has been completed. + /// </summary> + public bool IsCompleted { + get { return this.Authorization == null || this.Authorization.AccessToken != null; } + } + + /// <summary> + /// Gets a value indicating whether authorization has been granted. + /// </summary> + /// <value>Null if <see cref="IsCompleted"/> is <c>false</c></value> + public bool? IsGranted { + get { + if (this.Authorization == null) { + return false; + } + + return this.Authorization.AccessToken != null ? (bool?)true : null; + } + } + + /// <summary> + /// Gets a value indicating whether authorization has been rejected. + /// </summary> + /// <value>Null if <see cref="IsCompleted"/> is <c>false</c></value> + public bool? IsRejected { + get { + bool? granted = this.IsGranted; + return granted.HasValue ? (bool?)(!granted.Value) : null; + } + } + + /// <summary> + /// Called when the authorization flow has been completed. + /// </summary> + protected virtual void OnCompleted() { + var completed = this.Completed; + if (completed != null) { + completed(this, new ClientAuthorizationCompleteEventArgs(this.Authorization)); + } + } + + /// <summary> + /// Raises the <see cref="E:System.Windows.Forms.UserControl.Load"/> event. + /// </summary> + /// <param name="e">An <see cref="T:System.EventArgs"/> that contains the event data.</param> + protected override void OnLoad(EventArgs e) { + base.OnLoad(e); + + Uri authorizationUrl = this.Client.RequestUserAuthorization(this.Authorization); + this.webBrowser1.Navigate(authorizationUrl.AbsoluteUri); // use AbsoluteUri to workaround bug in WebBrowser that calls Uri.ToString instead of Uri.AbsoluteUri leading to escaping errors. + } + + /// <summary> + /// Tests whether two URLs are equal for purposes of detecting the conclusion of authorization. + /// </summary> + /// <param name="location1">The first location.</param> + /// <param name="location2">The second location.</param> + /// <param name="components">The components to compare.</param> + /// <returns><c>true</c> if the given components are equal.</returns> + private static bool SignificantlyEqual(Uri location1, Uri location2, UriComponents components) { + string value1 = location1.GetComponents(components, UriFormat.Unescaped); + string value2 = location2.GetComponents(components, UriFormat.Unescaped); + return string.Equals(value1, value2, StringComparison.Ordinal); + } + + /// <summary> + /// Handles the Navigating event of the webBrowser1 control. + /// </summary> + /// <param name="sender">The source of the event.</param> + /// <param name="e">The <see cref="System.Windows.Forms.WebBrowserNavigatingEventArgs"/> instance containing the event data.</param> + private void WebBrowser1_Navigating(object sender, WebBrowserNavigatingEventArgs e) { + this.ProcessLocationChanged(e.Url); + } + + /// <summary> + /// Processes changes in the URL the browser has navigated to. + /// </summary> + /// <param name="location">The location.</param> + private void ProcessLocationChanged(Uri location) { + if (SignificantlyEqual(location, this.Authorization.Callback, UriComponents.SchemeAndServer | UriComponents.Path)) { + try { + this.Client.ProcessUserAuthorization(location, this.Authorization); + } catch (ProtocolException ex) { + MessageBox.Show(ex.ToStringDescriptive()); + } finally { + this.OnCompleted(); + } + } + } + + /// <summary> + /// Handles the Navigated event of the webBrowser1 control. + /// </summary> + /// <param name="sender">The source of the event.</param> + /// <param name="e">The <see cref="System.Windows.Forms.WebBrowserNavigatedEventArgs"/> instance containing the event data.</param> + private void WebBrowser1_Navigated(object sender, WebBrowserNavigatedEventArgs e) { + this.ProcessLocationChanged(e.Url); + } + + /// <summary> + /// Handles the LocationChanged event of the webBrowser1 control. + /// </summary> + /// <param name="sender">The source of the event.</param> + /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param> + private void WebBrowser1_LocationChanged(object sender, EventArgs e) { + this.ProcessLocationChanged(this.webBrowser1.Url); + } + + /// <summary> + /// Describes the results of a completed authorization flow. + /// </summary> + public class ClientAuthorizationCompleteEventArgs : EventArgs { + /// <summary> + /// Initializes a new instance of the <see cref="ClientAuthorizationCompleteEventArgs"/> class. + /// </summary> + /// <param name="authorization">The authorization.</param> + public ClientAuthorizationCompleteEventArgs(IAuthorizationState authorization) { + Requires.NotNull(authorization, "authorization"); + this.Authorization = authorization; + } + + /// <summary> + /// Gets the authorization tracking object. + /// </summary> + /// <value>Null if authorization was rejected by the user.</value> + public IAuthorizationState Authorization { get; private set; } + } + } +} diff --git a/src/DotNetOpenAuth/OAuth2/ClientAuthorizationView.resx b/src/DotNetOpenAuth.OAuth2.Client.UI/OAuth2/ClientAuthorizationView.resx index 7080a7d..7080a7d 100644 --- a/src/DotNetOpenAuth/OAuth2/ClientAuthorizationView.resx +++ b/src/DotNetOpenAuth.OAuth2.Client.UI/OAuth2/ClientAuthorizationView.resx diff --git a/src/DotNetOpenAuth.OAuth2.Client.UI/Properties/AssemblyInfo.cs b/src/DotNetOpenAuth.OAuth2.Client.UI/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..54db6cf --- /dev/null +++ b/src/DotNetOpenAuth.OAuth2.Client.UI/Properties/AssemblyInfo.cs @@ -0,0 +1,54 @@ +//----------------------------------------------------------------------- +// <copyright file="AssemblyInfo.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +// We DON'T put an AssemblyVersionAttribute in here because it is generated in the build. + +using System; +using System.Diagnostics.Contracts; +using System.Net; +using System.Reflection; +using System.Resources; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Security; +using System.Security.Permissions; +using System.Web.UI; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("DotNetOpenAuth OAuth 2.0")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("DotNetOpenAuth")] +[assembly: AssemblyCopyright("Copyright © 2008")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: NeutralResourcesLanguage("en-US")] +[assembly: CLSCompliant(true)] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("7d73990c-47c0-4256-9f20-a893add9e289")] + +[assembly: ContractVerification(true)] + +#if StrongNameSigned +// See comment at top of this file. We need this so that strong-naming doesn't +// keep this assembly from being useful to shared host (medium trust) web sites. +[assembly: AllowPartiallyTrustedCallers] + +[assembly: InternalsVisibleTo("DotNetOpenAuth.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100AD093C3765257C89A7010E853F2C7C741FF92FA8ACE06D7B8254702CAD5CF99104447F63AB05F8BB6F51CE0D81C8C93D2FCE8C20AAFF7042E721CBA16EAAE98778611DED11C0ABC8900DC5667F99B50A9DADEC24DBD8F2C91E3E8AD300EF64F1B4B9536CEB16FB440AF939F57624A9B486F867807C649AE4830EAB88C6C03998")] +[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")] +#else +[assembly: InternalsVisibleTo("DotNetOpenAuth.Test")] +[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] +#endif diff --git a/src/DotNetOpenAuth.OAuth2.Client/DotNetOpenAuth.OAuth2.Client.csproj b/src/DotNetOpenAuth.OAuth2.Client/DotNetOpenAuth.OAuth2.Client.csproj new file mode 100644 index 0000000..8cfec0e --- /dev/null +++ b/src/DotNetOpenAuth.OAuth2.Client/DotNetOpenAuth.OAuth2.Client.csproj @@ -0,0 +1,43 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), EnlistmentInfo.props))\EnlistmentInfo.props" Condition=" '$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), EnlistmentInfo.props))' != '' " /> + <PropertyGroup> + <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> + <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> + </PropertyGroup> + <Import Project="$(ProjectRoot)tools\DotNetOpenAuth.props" /> + <PropertyGroup> + <SchemaVersion>2.0</SchemaVersion> + <ProjectGuid>{CDEDD439-7F35-4E6E-8605-4E70BDC4CC99}</ProjectGuid> + <AppDesignerFolder>Properties</AppDesignerFolder> + <AssemblyName>DotNetOpenAuth.OAuth2.Client</AssemblyName> + </PropertyGroup> + <Import Project="$(ProjectRoot)tools\DotNetOpenAuth.Product.props" /> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> + </PropertyGroup> + <ItemGroup> + <Compile Include="OAuth2\UserAgentClient.cs" /> + <Compile Include="Properties\AssemblyInfo.cs" /> + <Compile Include="OAuth2\ClientBase.cs" /> + <Compile Include="OAuth2\WebServerClient.cs" /> + </ItemGroup> + <ItemGroup> + <ProjectReference Include="..\DotNetOpenAuth.Core\DotNetOpenAuth.Core.csproj"> + <Project>{60426312-6AE5-4835-8667-37EDEA670222}</Project> + <Name>DotNetOpenAuth.Core</Name> + </ProjectReference> + <ProjectReference Include="..\DotNetOpenAuth.OAuth2\DotNetOpenAuth.OAuth2.csproj"> + <Project>{56459A6C-6BA2-4BAC-A9C0-27E3BD961FA6}</Project> + <Name>DotNetOpenAuth.OAuth2</Name> + </ProjectReference> + <ProjectReference Include="..\DotNetOpenAuth.OAuth\DotNetOpenAuth.OAuth.csproj"> + <Project>{A288FCC8-6FCF-46DA-A45E-5F9281556361}</Project> + <Name>DotNetOpenAuth.OAuth</Name> + </ProjectReference> + </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))' != '' " /> +</Project>
\ No newline at end of file diff --git a/src/DotNetOpenAuth.OAuth2.Client/OAuth2/ClientBase.cs b/src/DotNetOpenAuth.OAuth2.Client/OAuth2/ClientBase.cs new file mode 100644 index 0000000..2465182 --- /dev/null +++ b/src/DotNetOpenAuth.OAuth2.Client/OAuth2/ClientBase.cs @@ -0,0 +1,256 @@ +//----------------------------------------------------------------------- +// <copyright file="ClientBase.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.Globalization; + using System.Linq; + using System.Net; + using System.Text; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OAuth2.ChannelElements; + using DotNetOpenAuth.OAuth2.Messages; + + /// <summary> + /// A base class for common OAuth Client behaviors. + /// </summary> + public class ClientBase { + /// <summary> + /// Initializes a new instance of the <see cref="ClientBase"/> class. + /// </summary> + /// <param name="authorizationServer">The token issuer.</param> + /// <param name="clientIdentifier">The client identifier.</param> + /// <param name="clientSecret">The client secret.</param> + protected ClientBase(AuthorizationServerDescription authorizationServer, string clientIdentifier = null, string clientSecret = null) { + Requires.NotNull(authorizationServer, "authorizationServer"); + this.AuthorizationServer = authorizationServer; + this.Channel = new OAuth2ClientChannel(); + this.ClientIdentifier = clientIdentifier; + this.ClientSecret = clientSecret; + } + + /// <summary> + /// Gets the token issuer. + /// </summary> + /// <value>The token issuer.</value> + public AuthorizationServerDescription AuthorizationServer { get; private set; } + + /// <summary> + /// Gets the OAuth channel. + /// </summary> + /// <value>The channel.</value> + public Channel Channel { get; private set; } + + /// <summary> + /// Gets or sets the identifier by which this client is known to the Authorization Server. + /// </summary> + public string ClientIdentifier { get; set; } + + /// <summary> + /// Gets or sets the client secret shared with the Authorization Server. + /// </summary> + public string ClientSecret { get; set; } + + /// <summary> + /// Adds the necessary HTTP Authorization header to an HTTP request for protected resources + /// so that the Service Provider will allow the request through. + /// </summary> + /// <param name="request">The request for protected resources from the service provider.</param> + /// <param name="accessToken">The access token previously obtained from the Authorization Server.</param> + public static void AuthorizeRequest(HttpWebRequest request, string accessToken) { + Requires.NotNull(request, "request"); + Requires.NotNullOrEmpty(accessToken, "accessToken"); + + OAuthUtilities.AuthorizeWithBearerToken(request, accessToken); + } + + /// <summary> + /// Adds the OAuth authorization token to an outgoing HTTP request, renewing a + /// (nearly) expired access token if necessary. + /// </summary> + /// <param name="request">The request for protected resources from the service provider.</param> + /// <param name="authorization">The authorization for this request previously obtained via OAuth.</param> + public void AuthorizeRequest(HttpWebRequest request, IAuthorizationState authorization) { + Requires.NotNull(request, "request"); + Requires.NotNull(authorization, "authorization"); + Requires.True(!string.IsNullOrEmpty(authorization.AccessToken), "authorization"); + ErrorUtilities.VerifyProtocol(!authorization.AccessTokenExpirationUtc.HasValue || authorization.AccessTokenExpirationUtc < DateTime.UtcNow || authorization.RefreshToken != null, "authorization has expired"); + + if (authorization.AccessTokenExpirationUtc.HasValue && authorization.AccessTokenExpirationUtc.Value < DateTime.UtcNow) { + ErrorUtilities.VerifyProtocol(authorization.RefreshToken != null, "Access token has expired and cannot be automatically refreshed."); + this.RefreshAuthorization(authorization); + } + + AuthorizeRequest(request, authorization.AccessToken); + } + + /// <summary> + /// Refreshes a short-lived access token using a longer-lived refresh token + /// with a new access token that has the same scope as the refresh token. + /// The refresh token itself may also be refreshed. + /// </summary> + /// <param name="authorization">The authorization to update.</param> + /// <param name="skipIfUsefulLifeExceeds">If given, the access token will <em>not</em> be refreshed if its remaining lifetime exceeds this value.</param> + /// <returns>A value indicating whether the access token was actually renewed; <c>true</c> if it was renewed, or <c>false</c> if it still had useful life remaining.</returns> + /// <remarks> + /// This method may modify the value of the <see cref="IAuthorizationState.RefreshToken"/> property on + /// the <paramref name="authorization"/> parameter if the authorization server has cycled out your refresh token. + /// If the parameter value was updated, this method calls <see cref="IAuthorizationState.SaveChanges"/> on that instance. + /// </remarks> + public bool RefreshAuthorization(IAuthorizationState authorization, TimeSpan? skipIfUsefulLifeExceeds = null) { + Requires.NotNull(authorization, "authorization"); + Requires.True(!string.IsNullOrEmpty(authorization.RefreshToken), "authorization"); + + if (skipIfUsefulLifeExceeds.HasValue && authorization.AccessTokenExpirationUtc.HasValue) { + TimeSpan usefulLifeRemaining = authorization.AccessTokenExpirationUtc.Value - DateTime.UtcNow; + if (usefulLifeRemaining > skipIfUsefulLifeExceeds.Value) { + // There is useful life remaining in the access token. Don't refresh. + Logger.OAuth.DebugFormat("Skipping token refresh step because access token's remaining life is {0}, which exceeds {1}.", usefulLifeRemaining, skipIfUsefulLifeExceeds.Value); + return false; + } + } + + var request = new AccessTokenRefreshRequest(this.AuthorizationServer) { + ClientIdentifier = this.ClientIdentifier, + ClientSecret = this.ClientSecret, + RefreshToken = authorization.RefreshToken, + }; + + var response = this.Channel.Request<AccessTokenSuccessResponse>(request); + UpdateAuthorizationWithResponse(authorization, response); + return true; + } + + /// <summary> + /// Gets an access token that may be used for only a subset of the scope for which a given + /// refresh token is authorized. + /// </summary> + /// <param name="refreshToken">The refresh token.</param> + /// <param name="scope">The scope subset desired in the access token.</param> + /// <returns>A description of the obtained access token, and possibly a new refresh token.</returns> + /// <remarks> + /// If the return value includes a new refresh token, the old refresh token should be discarded and + /// replaced with the new one. + /// </remarks> + public IAuthorizationState GetScopedAccessToken(string refreshToken, HashSet<string> scope) { + Requires.NotNullOrEmpty(refreshToken, "refreshToken"); + Requires.NotNull(scope, "scope"); + Contract.Ensures(Contract.Result<IAuthorizationState>() != null); + + var request = new AccessTokenRefreshRequest(this.AuthorizationServer) { + ClientIdentifier = this.ClientIdentifier, + ClientSecret = this.ClientSecret, + RefreshToken = refreshToken, + }; + + var response = this.Channel.Request<AccessTokenSuccessResponse>(request); + var authorization = new AuthorizationState(); + UpdateAuthorizationWithResponse(authorization, response); + + return authorization; + } + + /// <summary> + /// Updates the authorization state maintained by the client with the content of an outgoing response. + /// </summary> + /// <param name="authorizationState">The authorization state maintained by the client.</param> + /// <param name="accessTokenSuccess">The access token containing response message.</param> + internal static void UpdateAuthorizationWithResponse(IAuthorizationState authorizationState, AccessTokenSuccessResponse accessTokenSuccess) { + Requires.NotNull(authorizationState, "authorizationState"); + Requires.NotNull(accessTokenSuccess, "accessTokenSuccess"); + + authorizationState.AccessToken = accessTokenSuccess.AccessToken; + authorizationState.AccessTokenExpirationUtc = DateTime.UtcNow + accessTokenSuccess.Lifetime; + authorizationState.AccessTokenIssueDateUtc = DateTime.UtcNow; + + // The authorization server MAY choose to renew the refresh token itself. + if (accessTokenSuccess.RefreshToken != null) { + authorizationState.RefreshToken = accessTokenSuccess.RefreshToken; + } + + // An included scope parameter in the response only describes the access token's scope. + // Don't update the whole authorization state object with that scope because that represents + // the refresh token's original scope. + if ((authorizationState.Scope == null || authorizationState.Scope.Count == 0) && accessTokenSuccess.Scope != null) { + authorizationState.Scope.ResetContents(accessTokenSuccess.Scope); + } + + authorizationState.SaveChanges(); + } + + /// <summary> + /// Updates the authorization state maintained by the client with the content of an outgoing response. + /// </summary> + /// <param name="authorizationState">The authorization state maintained by the client.</param> + /// <param name="accessTokenSuccess">The access token containing response message.</param> + internal static void UpdateAuthorizationWithResponse(IAuthorizationState authorizationState, EndUserAuthorizationSuccessAccessTokenResponse accessTokenSuccess) { + Requires.NotNull(authorizationState, "authorizationState"); + Requires.NotNull(accessTokenSuccess, "accessTokenSuccess"); + + authorizationState.AccessToken = accessTokenSuccess.AccessToken; + authorizationState.AccessTokenExpirationUtc = DateTime.UtcNow + accessTokenSuccess.Lifetime; + authorizationState.AccessTokenIssueDateUtc = DateTime.UtcNow; + if (accessTokenSuccess.Scope != null && accessTokenSuccess.Scope != authorizationState.Scope) { + if (authorizationState.Scope != null) { + Logger.OAuth.InfoFormat( + "Requested scope of \"{0}\" changed to \"{1}\" by authorization server.", + authorizationState.Scope, + accessTokenSuccess.Scope); + } + + authorizationState.Scope.ResetContents(accessTokenSuccess.Scope); + } + + authorizationState.SaveChanges(); + } + + /// <summary> + /// Updates authorization state with a success response from the Authorization Server. + /// </summary> + /// <param name="authorizationState">The authorization state to update.</param> + /// <param name="authorizationSuccess">The authorization success message obtained from the authorization server.</param> + internal void UpdateAuthorizationWithResponse(IAuthorizationState authorizationState, EndUserAuthorizationSuccessAuthCodeResponse authorizationSuccess) { + Requires.NotNull(authorizationState, "authorizationState"); + Requires.NotNull(authorizationSuccess, "authorizationSuccess"); + + var accessTokenRequest = new AccessTokenAuthorizationCodeRequest(this.AuthorizationServer) { + ClientIdentifier = this.ClientIdentifier, + ClientSecret = this.ClientSecret, + Callback = authorizationState.Callback, + AuthorizationCode = authorizationSuccess.AuthorizationCode, + }; + IProtocolMessage accessTokenResponse = this.Channel.Request(accessTokenRequest); + var accessTokenSuccess = accessTokenResponse as AccessTokenSuccessResponse; + var failedAccessTokenResponse = accessTokenResponse as AccessTokenFailedResponse; + if (accessTokenSuccess != null) { + UpdateAuthorizationWithResponse(authorizationState, accessTokenSuccess); + } else { + authorizationState.Delete(); + string error = failedAccessTokenResponse != null ? failedAccessTokenResponse.Error : "(unknown)"; + ErrorUtilities.ThrowProtocol(OAuthStrings.CannotObtainAccessTokenWithReason, error); + } + } + + /// <summary> + /// Calculates the fraction of life remaining in an access token. + /// </summary> + /// <param name="authorization">The authorization to measure.</param> + /// <returns>A fractional number no greater than 1. Could be negative if the access token has already expired.</returns> + private static double ProportionalLifeRemaining(IAuthorizationState authorization) { + Requires.NotNull(authorization, "authorization"); + Requires.True(authorization.AccessTokenIssueDateUtc.HasValue, "authorization"); + Requires.True(authorization.AccessTokenExpirationUtc.HasValue, "authorization"); + + // Calculate what % of the total life this access token has left. + TimeSpan totalLifetime = authorization.AccessTokenExpirationUtc.Value - authorization.AccessTokenIssueDateUtc.Value; + TimeSpan elapsedLifetime = DateTime.UtcNow - authorization.AccessTokenIssueDateUtc.Value; + double proportionLifetimeRemaining = 1 - (elapsedLifetime.TotalSeconds / totalLifetime.TotalSeconds); + return proportionLifetimeRemaining; + } + } +} diff --git a/src/DotNetOpenAuth.OAuth2.Client/OAuth2/UserAgentClient.cs b/src/DotNetOpenAuth.OAuth2.Client/OAuth2/UserAgentClient.cs new file mode 100644 index 0000000..d452525 --- /dev/null +++ b/src/DotNetOpenAuth.OAuth2.Client/OAuth2/UserAgentClient.cs @@ -0,0 +1,123 @@ +//----------------------------------------------------------------------- +// <copyright file="UserAgentClient.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.Text; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OAuth2.Messages; + + /// <summary> + /// The OAuth client for the user-agent flow, providing services for installed apps + /// and in-browser Javascript widgets. + /// </summary> + public class UserAgentClient : ClientBase { + /// <summary> + /// Initializes a new instance of the <see cref="UserAgentClient"/> class. + /// </summary> + /// <param name="authorizationServer">The token issuer.</param> + /// <param name="clientIdentifier">The client identifier.</param> + /// <param name="clientSecret">The client secret.</param> + public UserAgentClient(AuthorizationServerDescription authorizationServer, string clientIdentifier = null, string clientSecret = null) + : base(authorizationServer, clientIdentifier, clientSecret) { + } + + /// <summary> + /// Initializes a new instance of the <see cref="UserAgentClient"/> class. + /// </summary> + /// <param name="authorizationEndpoint">The authorization endpoint.</param> + /// <param name="tokenEndpoint">The token endpoint.</param> + /// <param name="clientIdentifier">The client identifier.</param> + /// <param name="clientSecret">The client secret.</param> + public UserAgentClient(Uri authorizationEndpoint, Uri tokenEndpoint, string clientIdentifier = null, string clientSecret = null) + : this(new AuthorizationServerDescription { AuthorizationEndpoint = authorizationEndpoint, TokenEndpoint = tokenEndpoint }, clientIdentifier, clientSecret) { + Requires.NotNull(authorizationEndpoint, "authorizationEndpoint"); + Requires.NotNull(tokenEndpoint, "tokenEndpoint"); + } + + /// <summary> + /// Generates a URL that the user's browser can be directed to in order to authorize + /// this client to access protected data at some resource server. + /// </summary> + /// <param name="scope">The scope of authorized access requested.</param> + /// <param name="state">The client state that should be returned with the authorization response.</param> + /// <param name="returnTo">The URL that the authorization response should be sent to via a user-agent redirect.</param> + /// <returns> + /// A fully-qualified URL suitable to initiate the authorization flow. + /// </returns> + public Uri RequestUserAuthorization(IEnumerable<string> scope = null, string state = null, Uri returnTo = null) { + var authorization = new AuthorizationState(scope) { + Callback = returnTo, + }; + + return this.RequestUserAuthorization(authorization); + } + + /// <summary> + /// Generates a URL that the user's browser can be directed to in order to authorize + /// this client to access protected data at some resource server. + /// </summary> + /// <param name="authorization">The authorization state that is tracking this particular request. Optional.</param> + /// <param name="state">The client state that should be returned with the authorization response.</param> + /// <returns> + /// A fully-qualified URL suitable to initiate the authorization flow. + /// </returns> + public Uri RequestUserAuthorization(IAuthorizationState authorization, string state = null) { + Requires.NotNull(authorization, "authorization"); + Requires.ValidState(!string.IsNullOrEmpty(this.ClientIdentifier)); + + if (authorization.Callback == null) { + authorization.Callback = new Uri("http://localhost/"); + } + + var request = new EndUserAuthorizationRequest(this.AuthorizationServer) { + ClientIdentifier = this.ClientIdentifier, + Callback = authorization.Callback, + ClientState = state, + }; + request.Scope.ResetContents(authorization.Scope); + + return this.Channel.PrepareResponse(request).GetDirectUriRequest(this.Channel); + } + + /// <summary> + /// Scans the incoming request for an authorization response message. + /// </summary> + /// <param name="actualRedirectUrl">The actual URL of the incoming HTTP request.</param> + /// <param name="authorizationState">The authorization.</param> + /// <returns>The granted authorization, or <c>null</c> if the incoming HTTP request did not contain an authorization server response or authorization was rejected.</returns> + public IAuthorizationState ProcessUserAuthorization(Uri actualRedirectUrl, IAuthorizationState authorizationState = null) { + Requires.NotNull(actualRedirectUrl, "actualRedirectUrl"); + + if (authorizationState == null) { + authorizationState = new AuthorizationState(); + } + + var carrier = new HttpRequestInfo("GET", actualRedirectUrl, actualRedirectUrl.PathAndQuery, new System.Net.WebHeaderCollection(), null); + IDirectedProtocolMessage response = this.Channel.ReadFromRequest(carrier); + if (response == null) { + return null; + } + + EndUserAuthorizationSuccessAccessTokenResponse accessTokenSuccess; + EndUserAuthorizationSuccessAuthCodeResponse authCodeSuccess; + EndUserAuthorizationFailedResponse failure; + if ((accessTokenSuccess = response as EndUserAuthorizationSuccessAccessTokenResponse) != null) { + UpdateAuthorizationWithResponse(authorizationState, accessTokenSuccess); + } else if ((authCodeSuccess = response as EndUserAuthorizationSuccessAuthCodeResponse) != null) { + this.UpdateAuthorizationWithResponse(authorizationState, authCodeSuccess); + } else if ((failure = response as EndUserAuthorizationFailedResponse) != null) { + authorizationState.Delete(); + return null; + } + + return authorizationState; + } + } +} diff --git a/src/DotNetOpenAuth.OAuth2.Client/OAuth2/WebServerClient.cs b/src/DotNetOpenAuth.OAuth2.Client/OAuth2/WebServerClient.cs new file mode 100644 index 0000000..4f793a5 --- /dev/null +++ b/src/DotNetOpenAuth.OAuth2.Client/OAuth2/WebServerClient.cs @@ -0,0 +1,130 @@ +//----------------------------------------------------------------------- +// <copyright file="WebServerClient.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.Net; + using System.Text; + using System.Web; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OAuth2.Messages; + + /// <summary> + /// An OAuth 2.0 consumer designed for web applications. + /// </summary> + public class WebServerClient : ClientBase { + /// <summary> + /// Initializes a new instance of the <see cref="WebServerClient"/> class. + /// </summary> + /// <param name="authorizationServer">The authorization server.</param> + /// <param name="clientIdentifier">The client identifier.</param> + /// <param name="clientSecret">The client secret.</param> + public WebServerClient(AuthorizationServerDescription authorizationServer, string clientIdentifier = null, string clientSecret = null) + : base(authorizationServer, clientIdentifier, clientSecret) { + } + + /// <summary> + /// Gets or sets an optional component that gives you greater control to record and influence the authorization process. + /// </summary> + /// <value>The authorization tracker.</value> + public IClientAuthorizationTracker AuthorizationTracker { get; set; } + + /// <summary> + /// Prepares a request for user authorization from an authorization server. + /// </summary> + /// <param name="scope">The scope of authorized access requested.</param> + /// <param name="state">The state of the client that should be sent back with the authorization response.</param> + /// <param name="returnTo">The URL the authorization server should redirect the browser (typically on this site) to when the authorization is completed. If null, the current request's URL will be used.</param> + public void RequestUserAuthorization(IEnumerable<string> scope = null, string state = null, Uri returnTo = null) { + var authorizationState = new AuthorizationState(scope) { + Callback = returnTo, + }; + this.PrepareRequestUserAuthorization(authorizationState, state).Send(); + } + + /// <summary> + /// Prepares a request for user authorization from an authorization server. + /// </summary> + /// <param name="scopes">The scope of authorized access requested.</param> + /// <param name="state">The state of the client that should be sent back with the authorization response.</param> + /// <returns>The authorization request.</returns> + public OutgoingWebResponse PrepareRequestUserAuthorization(IEnumerable<string> scopes = null, string state = null) { + var authorizationState = new AuthorizationState(scopes); + return this.PrepareRequestUserAuthorization(authorizationState, state); + } + + /// <summary> + /// Prepares a request for user authorization from an authorization server. + /// </summary> + /// <param name="authorization">The authorization state to associate with this particular request.</param> + /// <param name="state">The state of the client that should be sent back with the authorization response.</param> + /// <returns>The authorization request.</returns> + public OutgoingWebResponse PrepareRequestUserAuthorization(IAuthorizationState authorization, string state = null) { + Requires.NotNull(authorization, "authorization"); + Requires.ValidState(authorization.Callback != null || (HttpContext.Current != null && HttpContext.Current.Request != null), MessagingStrings.HttpContextRequired); + Requires.ValidState(!string.IsNullOrEmpty(this.ClientIdentifier)); + Contract.Ensures(Contract.Result<OutgoingWebResponse>() != null); + + if (authorization.Callback == null) { + authorization.Callback = this.Channel.GetRequestFromContext().UrlBeforeRewriting + .StripMessagePartsFromQueryString(this.Channel.MessageDescriptions.Get(typeof(EndUserAuthorizationSuccessResponseBase), Protocol.Default.Version)) + .StripMessagePartsFromQueryString(this.Channel.MessageDescriptions.Get(typeof(EndUserAuthorizationFailedResponse), Protocol.Default.Version)); + authorization.SaveChanges(); + } + + var request = new EndUserAuthorizationRequest(this.AuthorizationServer) { + ClientIdentifier = this.ClientIdentifier, + Callback = authorization.Callback, + ClientState = state, + }; + request.Scope.ResetContents(authorization.Scope); + + return this.Channel.PrepareResponse(request); + } + + /// <summary> + /// Processes the authorization response from an authorization server, if available. + /// </summary> + /// <param name="request">The incoming HTTP request that may carry an authorization response.</param> + /// <returns>The authorization state that contains the details of the authorization.</returns> + public IAuthorizationState ProcessUserAuthorization(HttpRequestInfo request = null) { + Requires.ValidState(!string.IsNullOrEmpty(this.ClientIdentifier)); + Requires.ValidState(!string.IsNullOrEmpty(this.ClientSecret)); + + if (request == null) { + request = this.Channel.GetRequestFromContext(); + } + + IMessageWithClientState response; + if (this.Channel.TryReadFromRequest<IMessageWithClientState>(request, out response)) { + Uri callback = MessagingUtilities.StripMessagePartsFromQueryString(request.UrlBeforeRewriting, this.Channel.MessageDescriptions.Get(response)); + IAuthorizationState authorizationState; + if (this.AuthorizationTracker != null) { + authorizationState = this.AuthorizationTracker.GetAuthorizationState(callback, response.ClientState); + ErrorUtilities.VerifyProtocol(authorizationState != null, "Unexpected OAuth authorization response received with callback and client state that does not match an expected value."); + } else { + authorizationState = new AuthorizationState { Callback = callback }; + } + var success = response as EndUserAuthorizationSuccessAuthCodeResponse; + var failure = response as EndUserAuthorizationFailedResponse; + ErrorUtilities.VerifyProtocol(success != null || failure != null, MessagingStrings.UnexpectedMessageReceivedOfMany); + if (success != null) { + this.UpdateAuthorizationWithResponse(authorizationState, success); + } else { // failure + Logger.OAuth.Info("User refused to grant the requested authorization at the Authorization Server."); + authorizationState.Delete(); + } + + return authorizationState; + } + + return null; + } + } +} diff --git a/src/DotNetOpenAuth.OAuth2.Client/Properties/AssemblyInfo.cs b/src/DotNetOpenAuth.OAuth2.Client/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..54db6cf --- /dev/null +++ b/src/DotNetOpenAuth.OAuth2.Client/Properties/AssemblyInfo.cs @@ -0,0 +1,54 @@ +//----------------------------------------------------------------------- +// <copyright file="AssemblyInfo.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +// We DON'T put an AssemblyVersionAttribute in here because it is generated in the build. + +using System; +using System.Diagnostics.Contracts; +using System.Net; +using System.Reflection; +using System.Resources; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Security; +using System.Security.Permissions; +using System.Web.UI; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("DotNetOpenAuth OAuth 2.0")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("DotNetOpenAuth")] +[assembly: AssemblyCopyright("Copyright © 2008")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: NeutralResourcesLanguage("en-US")] +[assembly: CLSCompliant(true)] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("7d73990c-47c0-4256-9f20-a893add9e289")] + +[assembly: ContractVerification(true)] + +#if StrongNameSigned +// See comment at top of this file. We need this so that strong-naming doesn't +// keep this assembly from being useful to shared host (medium trust) web sites. +[assembly: AllowPartiallyTrustedCallers] + +[assembly: InternalsVisibleTo("DotNetOpenAuth.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100AD093C3765257C89A7010E853F2C7C741FF92FA8ACE06D7B8254702CAD5CF99104447F63AB05F8BB6F51CE0D81C8C93D2FCE8C20AAFF7042E721CBA16EAAE98778611DED11C0ABC8900DC5667F99B50A9DADEC24DBD8F2C91E3E8AD300EF64F1B4B9536CEB16FB440AF939F57624A9B486F867807C649AE4830EAB88C6C03998")] +[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")] +#else +[assembly: InternalsVisibleTo("DotNetOpenAuth.Test")] +[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] +#endif diff --git a/src/DotNetOpenAuth.OAuth2.ResourceServer/DotNetOpenAuth.OAuth2.ResourceServer.csproj b/src/DotNetOpenAuth.OAuth2.ResourceServer/DotNetOpenAuth.OAuth2.ResourceServer.csproj new file mode 100644 index 0000000..43c478b --- /dev/null +++ b/src/DotNetOpenAuth.OAuth2.ResourceServer/DotNetOpenAuth.OAuth2.ResourceServer.csproj @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), EnlistmentInfo.props))\EnlistmentInfo.props" Condition=" '$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), EnlistmentInfo.props))' != '' " /> + <PropertyGroup> + <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> + <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> + </PropertyGroup> + <Import Project="$(ProjectRoot)tools\DotNetOpenAuth.props" /> + <PropertyGroup> + <SchemaVersion>2.0</SchemaVersion> + <ProjectGuid>{A1A3150A-7B0E-4A34-8E35-045296CD3C76}</ProjectGuid> + <AppDesignerFolder>Properties</AppDesignerFolder> + <AssemblyName>DotNetOpenAuth.OAuth2.ResourceServer</AssemblyName> + </PropertyGroup> + <Import Project="$(ProjectRoot)tools\DotNetOpenAuth.Product.props" /> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> + </PropertyGroup> + <ItemGroup> + <Compile Include="OAuth2\ResourceServer.cs" /> + <Compile Include="Properties\AssemblyInfo.cs" /> + </ItemGroup> + <ItemGroup> + <ProjectReference Include="..\DotNetOpenAuth.Core\DotNetOpenAuth.Core.csproj"> + <Project>{60426312-6AE5-4835-8667-37EDEA670222}</Project> + <Name>DotNetOpenAuth.Core</Name> + </ProjectReference> + <ProjectReference Include="..\DotNetOpenAuth.OAuth.ServiceProvider\DotNetOpenAuth.OAuth.ServiceProvider.csproj"> + <Project>{FED1923A-6D70-49B5-A37A-FB744FEC1C86}</Project> + <Name>DotNetOpenAuth.OAuth.ServiceProvider</Name> + </ProjectReference> + <ProjectReference Include="..\DotNetOpenAuth.OAuth2\DotNetOpenAuth.OAuth2.csproj"> + <Project>{56459A6C-6BA2-4BAC-A9C0-27E3BD961FA6}</Project> + <Name>DotNetOpenAuth.OAuth2</Name> + </ProjectReference> + </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))' != '' " /> +</Project>
\ No newline at end of file diff --git a/src/DotNetOpenAuth.OAuth2.ResourceServer/OAuth2/ResourceServer.cs b/src/DotNetOpenAuth.OAuth2.ResourceServer/OAuth2/ResourceServer.cs new file mode 100644 index 0000000..927a6ed --- /dev/null +++ b/src/DotNetOpenAuth.OAuth2.ResourceServer/OAuth2/ResourceServer.cs @@ -0,0 +1,136 @@ +//----------------------------------------------------------------------- +// <copyright file="ResourceServer.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OAuth2 { + using System; + using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; + using System.Diagnostics.Contracts; + using System.Linq; + using System.Net; + using System.Security.Principal; + using System.ServiceModel.Channels; + using System.Text; + using System.Text.RegularExpressions; + using System.Web; + using ChannelElements; + using Messages; + using Messaging; + + /// <summary> + /// Provides services for validating OAuth access tokens. + /// </summary> + public class ResourceServer { + /// <summary> + /// Initializes a new instance of the <see cref="ResourceServer"/> class. + /// </summary> + /// <param name="accessTokenAnalyzer">The access token analyzer.</param> + public ResourceServer(IAccessTokenAnalyzer accessTokenAnalyzer) { + Requires.NotNull(accessTokenAnalyzer, "accessTokenAnalyzer"); + + this.AccessTokenAnalyzer = accessTokenAnalyzer; + this.Channel = new OAuth2ResourceServerChannel(); + } + + /// <summary> + /// Gets the access token analyzer. + /// </summary> + /// <value>The access token analyzer.</value> + public IAccessTokenAnalyzer AccessTokenAnalyzer { get; private set; } + + /// <summary> + /// Gets the channel. + /// </summary> + /// <value>The channel.</value> + internal OAuth2ResourceServerChannel Channel { get; private set; } + + /// <summary> + /// Discovers what access the client should have considering the access token in the current request. + /// </summary> + /// <param name="userName">The name on the account the client has access to.</param> + /// <param name="scope">The set of operations the client is authorized for.</param> + /// <returns>An error to return to the client if access is not authorized; <c>null</c> if access is granted.</returns> + [SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "0#", Justification = "Try pattern")] + [SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "1#", Justification = "Try pattern")] + public OutgoingWebResponse VerifyAccess(out string userName, out HashSet<string> scope) { + return this.VerifyAccess(this.Channel.GetRequestFromContext(), out userName, out scope); + } + + /// <summary> + /// Discovers what access the client should have considering the access token in the current request. + /// </summary> + /// <param name="httpRequestInfo">The HTTP request info.</param> + /// <param name="userName">The name on the account the client has access to.</param> + /// <param name="scope">The set of operations the client is authorized for.</param> + /// <returns> + /// An error to return to the client if access is not authorized; <c>null</c> if access is granted. + /// </returns> + [SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "1#", Justification = "Try pattern")] + [SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "2#", Justification = "Try pattern")] + public virtual OutgoingWebResponse VerifyAccess(HttpRequestInfo httpRequestInfo, out string userName, out HashSet<string> scope) { + Requires.NotNull(httpRequestInfo, "httpRequestInfo"); + + AccessProtectedResourceRequest request = null; + try { + if (this.Channel.TryReadFromRequest<AccessProtectedResourceRequest>(httpRequestInfo, out request)) { + if (this.AccessTokenAnalyzer.TryValidateAccessToken(request, request.AccessToken, out userName, out scope)) { + // No errors to return. + return null; + } + + throw ErrorUtilities.ThrowProtocol("Bad access token"); + } else { + var response = new UnauthorizedResponse(new ProtocolException("Missing access token")); + + userName = null; + scope = null; + return this.Channel.PrepareResponse(response); + } + } catch (ProtocolException ex) { + var response = request != null ? new UnauthorizedResponse(request, ex) : new UnauthorizedResponse(ex); + + userName = null; + scope = null; + return this.Channel.PrepareResponse(response); + } + } + + /// <summary> + /// Discovers what access the client should have considering the access token in the current request. + /// </summary> + /// <param name="httpRequestInfo">The HTTP request info.</param> + /// <param name="principal">The principal that contains the user and roles that the access token is authorized for.</param> + /// <returns> + /// An error to return to the client if access is not authorized; <c>null</c> if access is granted. + /// </returns> + [SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "1#", Justification = "Try pattern")] + public virtual OutgoingWebResponse VerifyAccess(HttpRequestInfo httpRequestInfo, out IPrincipal principal) { + string username; + HashSet<string> scope; + var result = this.VerifyAccess(httpRequestInfo, out username, out scope); + principal = result == null ? new OAuth.ChannelElements.OAuthPrincipal(username, scope != null ? scope.ToArray() : new string[0]) : null; + return result; + } + + /// <summary> + /// Discovers what access the client should have considering the access token in the current request. + /// </summary> + /// <param name="request">HTTP details from an incoming WCF message.</param> + /// <param name="requestUri">The URI of the WCF service endpoint.</param> + /// <param name="principal">The principal that contains the user and roles that the access token is authorized for.</param> + /// <returns> + /// An error to return to the client if access is not authorized; <c>null</c> if access is granted. + /// </returns> + [SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "1#", Justification = "Try pattern")] + [SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "2#", Justification = "Try pattern")] + public virtual OutgoingWebResponse VerifyAccess(HttpRequestMessageProperty request, Uri requestUri, out IPrincipal principal) { + Requires.NotNull(request, "request"); + Requires.NotNull(requestUri, "requestUri"); + + return this.VerifyAccess(new HttpRequestInfo(request, requestUri), out principal); + } + } +} diff --git a/src/DotNetOpenAuth.OAuth2.ResourceServer/Properties/AssemblyInfo.cs b/src/DotNetOpenAuth.OAuth2.ResourceServer/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..54db6cf --- /dev/null +++ b/src/DotNetOpenAuth.OAuth2.ResourceServer/Properties/AssemblyInfo.cs @@ -0,0 +1,54 @@ +//----------------------------------------------------------------------- +// <copyright file="AssemblyInfo.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +// We DON'T put an AssemblyVersionAttribute in here because it is generated in the build. + +using System; +using System.Diagnostics.Contracts; +using System.Net; +using System.Reflection; +using System.Resources; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Security; +using System.Security.Permissions; +using System.Web.UI; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("DotNetOpenAuth OAuth 2.0")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("DotNetOpenAuth")] +[assembly: AssemblyCopyright("Copyright © 2008")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: NeutralResourcesLanguage("en-US")] +[assembly: CLSCompliant(true)] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("7d73990c-47c0-4256-9f20-a893add9e289")] + +[assembly: ContractVerification(true)] + +#if StrongNameSigned +// See comment at top of this file. We need this so that strong-naming doesn't +// keep this assembly from being useful to shared host (medium trust) web sites. +[assembly: AllowPartiallyTrustedCallers] + +[assembly: InternalsVisibleTo("DotNetOpenAuth.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100AD093C3765257C89A7010E853F2C7C741FF92FA8ACE06D7B8254702CAD5CF99104447F63AB05F8BB6F51CE0D81C8C93D2FCE8C20AAFF7042E721CBA16EAAE98778611DED11C0ABC8900DC5667F99B50A9DADEC24DBD8F2C91E3E8AD300EF64F1B4B9536CEB16FB440AF939F57624A9B486F867807C649AE4830EAB88C6C03998")] +[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")] +#else +[assembly: InternalsVisibleTo("DotNetOpenAuth.Test")] +[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] +#endif diff --git a/src/DotNetOpenAuth.OAuth2/DotNetOpenAuth.OAuth2.csproj b/src/DotNetOpenAuth.OAuth2/DotNetOpenAuth.OAuth2.csproj new file mode 100644 index 0000000..aa685a2 --- /dev/null +++ b/src/DotNetOpenAuth.OAuth2/DotNetOpenAuth.OAuth2.csproj @@ -0,0 +1,100 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), EnlistmentInfo.props))\EnlistmentInfo.props" Condition=" '$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), EnlistmentInfo.props))' != '' " /> + <PropertyGroup> + <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> + <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> + </PropertyGroup> + <Import Project="$(ProjectRoot)tools\DotNetOpenAuth.props" /> + <PropertyGroup> + <SchemaVersion>2.0</SchemaVersion> + <ProjectGuid>{56459A6C-6BA2-4BAC-A9C0-27E3BD961FA6}</ProjectGuid> + <AppDesignerFolder>Properties</AppDesignerFolder> + <AssemblyName>DotNetOpenAuth.OAuth2</AssemblyName> + </PropertyGroup> + <Import Project="$(ProjectRoot)tools\DotNetOpenAuth.Product.props" /> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> + </PropertyGroup> + <ItemGroup> + <Compile Include="OAuth2\AuthorizationState.cs" /> + <Compile Include="OAuth2\ChannelElements\AccessRequestBindingElement.cs" /> + <Compile Include="OAuth2\ChannelElements\AccessToken.cs" /> + <Compile Include="OAuth2\ChannelElements\AccessTokenBindingElement.cs" /> + <Compile Include="OAuth2\ChannelElements\AuthorizationDataBag.cs" /> + <Compile Include="OAuth2\ChannelElements\AuthServerBindingElementBase.cs" /> + <Compile Include="OAuth2\ChannelElements\GrantTypeEncoder.cs" /> + <Compile Include="OAuth2\ChannelElements\EndUserAuthorizationResponseTypeEncoder.cs" /> + <Compile Include="OAuth2\ChannelElements\OAuth2ChannelBase.cs" /> + <Compile Include="OAuth2\ChannelElements\OAuth2ClientChannel.cs" /> + <Compile Include="OAuth2\ChannelElements\ScopeEncoder.cs" /> + <Compile Include="OAuth2\ChannelElements\IAuthorizationDescription.cs" /> + <Compile Include="OAuth2\ChannelElements\IAuthorizationCarryingRequest.cs" /> + <Compile Include="OAuth2\ChannelElements\OAuth2ResourceServerChannel.cs" /> + <Compile Include="OAuth2\ChannelElements\RefreshToken.cs" /> + <Compile Include="OAuth2\ChannelElements\AuthorizationCode.cs" /> + <Compile Include="OAuth2\ChannelElements\AuthorizationCodeBindingElement.cs" /> + <Compile Include="OAuth2\ChannelElements\AuthServerAllFlowsBindingElement.cs" /> + <Compile Include="OAuth2\IAccessTokenAnalyzer.cs" /> + <Compile Include="OAuth2\IAuthorizationServer.cs" /> + <Compile Include="OAuth2\IAuthorizationState.cs" /> + <Compile Include="OAuth2\IClientAuthorizationTracker.cs" /> + <Compile Include="OAuth2\IConsumerDescription.cs" /> + <Compile Include="OAuth2\Messages\AccessProtectedResourceRequest.cs" /> + <Compile Include="OAuth2\Messages\AccessTokenAuthorizationCodeRequest.cs" /> + <Compile Include="OAuth2\Messages\AccessTokenResourceOwnerPasswordCredentialsRequest.cs" /> + <Compile Include="OAuth2\Messages\AccessTokenRequestBase.cs" /> + <Compile Include="OAuth2\Messages\AccessTokenClientCredentialsRequest.cs" /> + <Compile Include="OAuth2\Messages\AuthenticatedClientRequestBase.cs" /> + <Compile Include="OAuth2\Messages\EndUserAuthorizationSuccessAccessTokenResponse.cs" /> + <Compile Include="OAuth2\Messages\EndUserAuthorizationFailedResponse.cs" /> + <Compile Include="OAuth2\Messages\EndUserAuthorizationSuccessAuthCodeResponse.cs" /> + <Compile Include="OAuth2\Messages\GrantType.cs" /> + <Compile Include="OAuth2\Messages\AccessTokenRefreshRequest.cs" /> + <Compile Include="OAuth2\Messages\EndUserAuthorizationResponseType.cs" /> + <Compile Include="OAuth2\Messages\IAccessTokenRequest.cs" /> + <Compile Include="OAuth2\Messages\IMessageWithClientState.cs" /> + <Compile Include="OAuth2\Messages\ScopedAccessTokenRequest.cs" /> + <Compile Include="OAuth2\Messages\UnauthorizedResponse.cs" /> + <Compile Include="OAuth2\Messages\AccessTokenFailedResponse.cs" /> + <Compile Include="OAuth2\Messages\AccessTokenSuccessResponse.cs" /> + <Compile Include="OAuth2\Messages\EndUserAuthorizationSuccessResponseBase.cs" /> + <Compile Include="OAuth2\StandardAccessTokenAnalyzer.cs" /> + <Compile Include="OAuth2\OAuthUtilities.cs" /> + <Compile Include="Properties\AssemblyInfo.cs" /> + <Compile Include="OAuth2\ChannelElements\OAuth2AuthorizationServerChannel.cs" /> + <Compile Include="OAuth2\Messages\MessageBase.cs" /> + <Compile Include="OAuth2\Messages\EndUserAuthorizationRequest.cs" /> + <Compile Include="OAuth2\Protocol.cs" /> + <Compile Include="OAuth2\OAuthStrings.Designer.cs"> + <AutoGen>True</AutoGen> + <DesignTime>True</DesignTime> + <DependentUpon>OAuthStrings.resx</DependentUpon> + </Compile> + <Compile Include="OAuth2\AuthorizationServerDescription.cs" /> + </ItemGroup> + <ItemGroup> + <None Include="OAuth2\Messages\OAuth 2 Messages.cd" /> + <None Include="OAuth2\OAuth 2 client facades.cd" /> + </ItemGroup> + <ItemGroup> + <EmbeddedResource Include="OAuth2\OAuthStrings.resx"> + <Generator>ResXFileCodeGenerator</Generator> + <LastGenOutput>OAuthStrings.Designer.cs</LastGenOutput> + </EmbeddedResource> + </ItemGroup> + <ItemGroup> + <ProjectReference Include="..\DotNetOpenAuth.Core\DotNetOpenAuth.Core.csproj"> + <Project>{60426312-6AE5-4835-8667-37EDEA670222}</Project> + <Name>DotNetOpenAuth.Core</Name> + </ProjectReference> + <ProjectReference Include="..\DotNetOpenAuth.OAuth\DotNetOpenAuth.OAuth.csproj"> + <Project>{A288FCC8-6FCF-46DA-A45E-5F9281556361}</Project> + <Name>DotNetOpenAuth.OAuth</Name> + </ProjectReference> + </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))' != '' " /> +</Project>
\ No newline at end of file diff --git a/src/DotNetOpenAuth/OAuth2/AuthorizationServerDescription.cs b/src/DotNetOpenAuth.OAuth2/OAuth2/AuthorizationServerDescription.cs index bbad27c..bbad27c 100644 --- a/src/DotNetOpenAuth/OAuth2/AuthorizationServerDescription.cs +++ b/src/DotNetOpenAuth.OAuth2/OAuth2/AuthorizationServerDescription.cs diff --git a/src/DotNetOpenAuth/OAuth2/AuthorizationState.cs b/src/DotNetOpenAuth.OAuth2/OAuth2/AuthorizationState.cs index bfc33aa..bfc33aa 100644 --- a/src/DotNetOpenAuth/OAuth2/AuthorizationState.cs +++ b/src/DotNetOpenAuth.OAuth2/OAuth2/AuthorizationState.cs diff --git a/src/DotNetOpenAuth/OAuth2/ChannelElements/AccessRequestBindingElement.cs b/src/DotNetOpenAuth.OAuth2/OAuth2/ChannelElements/AccessRequestBindingElement.cs index b86f5dd..b86f5dd 100644 --- a/src/DotNetOpenAuth/OAuth2/ChannelElements/AccessRequestBindingElement.cs +++ b/src/DotNetOpenAuth.OAuth2/OAuth2/ChannelElements/AccessRequestBindingElement.cs diff --git a/src/DotNetOpenAuth.OAuth2/OAuth2/ChannelElements/AccessToken.cs b/src/DotNetOpenAuth.OAuth2/OAuth2/ChannelElements/AccessToken.cs new file mode 100644 index 0000000..a502962 --- /dev/null +++ b/src/DotNetOpenAuth.OAuth2/OAuth2/ChannelElements/AccessToken.cs @@ -0,0 +1,102 @@ +//----------------------------------------------------------------------- +// <copyright file="AccessToken.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OAuth2.ChannelElements { + using System; + using System.Collections.Generic; + using System.Diagnostics.Contracts; + using System.Security.Cryptography; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.Messaging.Bindings; + + /// <summary> + /// A short-lived token that accompanies HTTP requests to protected data to authorize the request. + /// </summary> + internal class AccessToken : AuthorizationDataBag { + /// <summary> + /// Initializes a new instance of the <see cref="AccessToken"/> class. + /// </summary> + public AccessToken() { + } + + /// <summary> + /// Initializes a new instance of the <see cref="AccessToken"/> class. + /// </summary> + /// <param name="authorization">The authorization to be described by the access token.</param> + /// <param name="lifetime">The lifetime of the access token.</param> + internal AccessToken(IAuthorizationDescription authorization, TimeSpan? lifetime) { + Requires.NotNull(authorization, "authorization"); + + this.ClientIdentifier = authorization.ClientIdentifier; + this.UtcCreationDate = authorization.UtcIssued; + this.User = authorization.User; + this.Scope.ResetContents(authorization.Scope); + this.Lifetime = lifetime; + } + + /// <summary> + /// Initializes a new instance of the <see cref="AccessToken"/> class. + /// </summary> + /// <param name="clientIdentifier">The client identifier.</param> + /// <param name="scopes">The scopes.</param> + /// <param name="username">The username of the account that authorized this token.</param> + /// <param name="lifetime">The lifetime for this access token.</param> + internal AccessToken(string clientIdentifier, IEnumerable<string> scopes, string username, TimeSpan? lifetime) { + Requires.NotNullOrEmpty(clientIdentifier, "clientIdentifier"); + + this.ClientIdentifier = clientIdentifier; + this.Scope.ResetContents(scopes); + this.User = username; + this.Lifetime = lifetime; + this.UtcCreationDate = DateTime.UtcNow; + } + + /// <summary> + /// Gets or sets the lifetime of the access token. + /// </summary> + /// <value>The lifetime.</value> + [MessagePart(Encoder = typeof(TimespanSecondsEncoder))] + internal TimeSpan? Lifetime { get; set; } + + /// <summary> + /// Creates a formatter capable of serializing/deserializing an access token. + /// </summary> + /// <param name="signingKey">The crypto service provider with the authorization server's private key used to asymmetrically sign the access token.</param> + /// <param name="encryptingKey">The crypto service provider with the resource server's public key used to encrypt the access token.</param> + /// <returns>An access token serializer.</returns> + internal static IDataBagFormatter<AccessToken> CreateFormatter(RSACryptoServiceProvider signingKey, RSACryptoServiceProvider encryptingKey) { + Contract.Requires(signingKey != null || !signingKey.PublicOnly); + Contract.Requires(encryptingKey != null); + Contract.Ensures(Contract.Result<IDataBagFormatter<AccessToken>>() != null); + + return new UriStyleMessageFormatter<AccessToken>(signingKey, encryptingKey); + } + + /// <summary> + /// Checks the message state for conformity to the protocol specification + /// and throws an exception if the message is invalid. + /// </summary> + /// <remarks> + /// <para>Some messages have required fields, or combinations of fields that must relate to each other + /// in specialized ways. After deserializing a message, this method checks the state of the + /// message to see if it conforms to the protocol.</para> + /// <para>Note that this property should <i>not</i> check signatures or perform any state checks + /// outside this scope of this particular message.</para> + /// </remarks> + /// <exception cref="ProtocolException">Thrown if the message is invalid.</exception> + protected override void EnsureValidMessage() { + base.EnsureValidMessage(); + + // Has this token expired? + if (this.Lifetime.HasValue) { + DateTime expirationDate = this.UtcCreationDate + this.Lifetime.Value; + if (expirationDate < DateTime.UtcNow) { + throw new ExpiredMessageException(expirationDate, this.ContainingMessage); + } + } + } + } +} diff --git a/src/DotNetOpenAuth/OAuth2/ChannelElements/AccessTokenBindingElement.cs b/src/DotNetOpenAuth.OAuth2/OAuth2/ChannelElements/AccessTokenBindingElement.cs index 3a709b6..3a709b6 100644 --- a/src/DotNetOpenAuth/OAuth2/ChannelElements/AccessTokenBindingElement.cs +++ b/src/DotNetOpenAuth.OAuth2/OAuth2/ChannelElements/AccessTokenBindingElement.cs diff --git a/src/DotNetOpenAuth/OAuth2/ChannelElements/AuthServerAllFlowsBindingElement.cs b/src/DotNetOpenAuth.OAuth2/OAuth2/ChannelElements/AuthServerAllFlowsBindingElement.cs index bc464e2..bc464e2 100644 --- a/src/DotNetOpenAuth/OAuth2/ChannelElements/AuthServerAllFlowsBindingElement.cs +++ b/src/DotNetOpenAuth.OAuth2/OAuth2/ChannelElements/AuthServerAllFlowsBindingElement.cs diff --git a/src/DotNetOpenAuth/OAuth2/ChannelElements/AuthServerBindingElementBase.cs b/src/DotNetOpenAuth.OAuth2/OAuth2/ChannelElements/AuthServerBindingElementBase.cs index 9d3e78f..9d3e78f 100644 --- a/src/DotNetOpenAuth/OAuth2/ChannelElements/AuthServerBindingElementBase.cs +++ b/src/DotNetOpenAuth.OAuth2/OAuth2/ChannelElements/AuthServerBindingElementBase.cs diff --git a/src/DotNetOpenAuth.OAuth2/OAuth2/ChannelElements/AuthorizationCode.cs b/src/DotNetOpenAuth.OAuth2/OAuth2/ChannelElements/AuthorizationCode.cs new file mode 100644 index 0000000..6900b89 --- /dev/null +++ b/src/DotNetOpenAuth.OAuth2/OAuth2/ChannelElements/AuthorizationCode.cs @@ -0,0 +1,100 @@ +//----------------------------------------------------------------------- +// <copyright file="AuthorizationCode.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OAuth2.ChannelElements { + using System; + using System.Collections.Generic; + 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.</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"); + Requires.NotNull(callback, "callback"); + + this.ClientIdentifier = clientIdentifier; + this.CallbackHash = CalculateCallbackHash(callback); + this.Scope.ResetContents(scopes); + this.User = username; + this.UtcCreationDate = DateTime.UtcNow; + } + + /// <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(IAuthorizationServer authorizationServer) { + Requires.NotNull(authorizationServer, "authorizationServer"); + Contract.Ensures(Contract.Result<IDataBagFormatter<AuthorizationCode>>() != null); + + return new UriStyleMessageFormatter<AuthorizationCode>( + authorizationServer.CryptoKeyStore, + AuthorizationCodeKeyBucket, + signed: true, + encrypted: true, + compressed: false, + maximumAge: AuthorizationCodeBindingElement.MaximumMessageAge, + decodeOnceOnly: authorizationServer.VerificationCodeNonceStore); + } + + /// <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> + internal void VerifyCallback(Uri callback) { + ErrorUtilities.VerifyProtocol(MessagingUtilities.AreEquivalent(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> + private static byte[] CalculateCallbackHash(Uri callback) { + using (var hasher = new SHA256Managed()) { + return hasher.ComputeHash(Encoding.UTF8.GetBytes(callback.AbsoluteUri)); + } + } + } +} diff --git a/src/DotNetOpenAuth.OAuth2/OAuth2/ChannelElements/AuthorizationCodeBindingElement.cs b/src/DotNetOpenAuth.OAuth2/OAuth2/ChannelElements/AuthorizationCodeBindingElement.cs new file mode 100644 index 0000000..b0e6203 --- /dev/null +++ b/src/DotNetOpenAuth.OAuth2/OAuth2/ChannelElements/AuthorizationCodeBindingElement.cs @@ -0,0 +1,101 @@ +//----------------------------------------------------------------------- +// <copyright file="AuthorizationCodeBindingElement.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 Messages; + using Messaging; + using Messaging.Bindings; + + /// <summary> + /// A binding element for OAuth 2.0 authorization servers that create/verify + /// issued authorization codes as part of obtaining access/refresh tokens. + /// </summary> + internal class AuthorizationCodeBindingElement : AuthServerBindingElementBase { + /// <summary> + /// Initializes a new instance of the <see cref="AuthorizationCodeBindingElement"/> class. + /// </summary> + internal AuthorizationCodeBindingElement() { + } + + /// <summary> + /// Gets the protection commonly offered (if any) by this binding element. + /// </summary> + /// <value>Always <c>MessageProtections.None</c></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> + /// 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> + /// 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 response = message as EndUserAuthorizationSuccessAuthCodeResponse; + if (response != null) { + var directResponse = (IDirectResponseProtocolMessage)response; + var request = (EndUserAuthorizationRequest)directResponse.OriginatingRequest; + IAuthorizationCarryingRequest tokenCarryingResponse = response; + tokenCarryingResponse.AuthorizationDescription = new AuthorizationCode(request.ClientIdentifier, request.Callback, response.Scope, response.AuthorizingUsername); + + return MessageProtections.None; + } + + 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) { + var request = message as AccessTokenAuthorizationCodeRequest; + if (request != null) { + IAuthorizationCarryingRequest tokenRequest = request; + ((AuthorizationCode)tokenRequest.AuthorizationDescription).VerifyCallback(request.Callback); + + return MessageProtections.None; + } + + return null; + } + } +} diff --git a/src/DotNetOpenAuth/OAuth2/ChannelElements/AuthorizationDataBag.cs b/src/DotNetOpenAuth.OAuth2/OAuth2/ChannelElements/AuthorizationDataBag.cs index 8f1d983..8f1d983 100644 --- a/src/DotNetOpenAuth/OAuth2/ChannelElements/AuthorizationDataBag.cs +++ b/src/DotNetOpenAuth.OAuth2/OAuth2/ChannelElements/AuthorizationDataBag.cs diff --git a/src/DotNetOpenAuth/OAuth2/ChannelElements/EndUserAuthorizationResponseTypeEncoder.cs b/src/DotNetOpenAuth.OAuth2/OAuth2/ChannelElements/EndUserAuthorizationResponseTypeEncoder.cs index 139025d..139025d 100644 --- a/src/DotNetOpenAuth/OAuth2/ChannelElements/EndUserAuthorizationResponseTypeEncoder.cs +++ b/src/DotNetOpenAuth.OAuth2/OAuth2/ChannelElements/EndUserAuthorizationResponseTypeEncoder.cs diff --git a/src/DotNetOpenAuth/OAuth2/ChannelElements/GrantTypeEncoder.cs b/src/DotNetOpenAuth.OAuth2/OAuth2/ChannelElements/GrantTypeEncoder.cs index 78ed975..78ed975 100644 --- a/src/DotNetOpenAuth/OAuth2/ChannelElements/GrantTypeEncoder.cs +++ b/src/DotNetOpenAuth.OAuth2/OAuth2/ChannelElements/GrantTypeEncoder.cs diff --git a/src/DotNetOpenAuth/OAuth2/ChannelElements/IAuthorizationCarryingRequest.cs b/src/DotNetOpenAuth.OAuth2/OAuth2/ChannelElements/IAuthorizationCarryingRequest.cs index e450131..e450131 100644 --- a/src/DotNetOpenAuth/OAuth2/ChannelElements/IAuthorizationCarryingRequest.cs +++ b/src/DotNetOpenAuth.OAuth2/OAuth2/ChannelElements/IAuthorizationCarryingRequest.cs diff --git a/src/DotNetOpenAuth/OAuth2/ChannelElements/IAuthorizationDescription.cs b/src/DotNetOpenAuth.OAuth2/OAuth2/ChannelElements/IAuthorizationDescription.cs index 2b3a9ce..2b3a9ce 100644 --- a/src/DotNetOpenAuth/OAuth2/ChannelElements/IAuthorizationDescription.cs +++ b/src/DotNetOpenAuth.OAuth2/OAuth2/ChannelElements/IAuthorizationDescription.cs diff --git a/src/DotNetOpenAuth.OAuth2/OAuth2/ChannelElements/OAuth2AuthorizationServerChannel.cs b/src/DotNetOpenAuth.OAuth2/OAuth2/ChannelElements/OAuth2AuthorizationServerChannel.cs new file mode 100644 index 0000000..0c31023 --- /dev/null +++ b/src/DotNetOpenAuth.OAuth2/OAuth2/ChannelElements/OAuth2AuthorizationServerChannel.cs @@ -0,0 +1,108 @@ +//----------------------------------------------------------------------- +// <copyright file="OAuth2AuthorizationServerChannel.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. 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; + + /// <summary> + /// The channel for the OAuth protocol. + /// </summary> + internal class OAuth2AuthorizationServerChannel : OAuth2ChannelBase { + /// <summary> + /// Initializes a new instance of the <see cref="OAuth2AuthorizationServerChannel"/> class. + /// </summary> + /// <param name="authorizationServer">The authorization server.</param> + protected internal OAuth2AuthorizationServerChannel(IAuthorizationServer authorizationServer) + : base(InitializeBindingElements(authorizationServer)) { + Requires.NotNull(authorizationServer, "authorizationServer"); + this.AuthorizationServer = authorizationServer; + } + + /// <summary> + /// Gets the authorization server. + /// </summary> + /// <value>The authorization server.</value> + public IAuthorizationServer AuthorizationServer { get; private 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(); + 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(HttpRequestInfo 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> + /// <returns> + /// An array of binding elements used to initialize the channel. + /// </returns> + private static IChannelBindingElement[] InitializeBindingElements(IAuthorizationServer authorizationServer) { + Requires.NotNull(authorizationServer, "authorizationServer"); + var bindingElements = new List<IChannelBindingElement>(); + + bindingElements.Add(new AuthServerAllFlowsBindingElement()); + bindingElements.Add(new AuthorizationCodeBindingElement()); + bindingElements.Add(new AccessTokenBindingElement()); + bindingElements.Add(new AccessRequestBindingElement()); + + return bindingElements.ToArray(); + } + } +} diff --git a/src/DotNetOpenAuth/OAuth2/ChannelElements/OAuth2ChannelBase.cs b/src/DotNetOpenAuth.OAuth2/OAuth2/ChannelElements/OAuth2ChannelBase.cs index a646f51..a646f51 100644 --- a/src/DotNetOpenAuth/OAuth2/ChannelElements/OAuth2ChannelBase.cs +++ b/src/DotNetOpenAuth.OAuth2/OAuth2/ChannelElements/OAuth2ChannelBase.cs diff --git a/src/DotNetOpenAuth/OAuth2/ChannelElements/OAuth2ClientChannel.cs b/src/DotNetOpenAuth.OAuth2/OAuth2/ChannelElements/OAuth2ClientChannel.cs index e4a9afd..e4a9afd 100644 --- a/src/DotNetOpenAuth/OAuth2/ChannelElements/OAuth2ClientChannel.cs +++ b/src/DotNetOpenAuth.OAuth2/OAuth2/ChannelElements/OAuth2ClientChannel.cs diff --git a/src/DotNetOpenAuth.OAuth2/OAuth2/ChannelElements/OAuth2ResourceServerChannel.cs b/src/DotNetOpenAuth.OAuth2/OAuth2/ChannelElements/OAuth2ResourceServerChannel.cs new file mode 100644 index 0000000..97a70c8 --- /dev/null +++ b/src/DotNetOpenAuth.OAuth2/OAuth2/ChannelElements/OAuth2ResourceServerChannel.cs @@ -0,0 +1,153 @@ +//----------------------------------------------------------------------- +// <copyright file="OAuth2ResourceServerChannel.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OAuth2.ChannelElements { + using System; + using System.Collections.Generic; + using System.Diagnostics.Contracts; + using System.Linq; + using System.Net; + using System.Net.Mime; + using System.Text; + using System.Web; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.Messaging.Reflection; + using DotNetOpenAuth.OAuth2.Messages; + + /// <summary> + /// The channel for the OAuth protocol. + /// </summary> + internal class OAuth2ResourceServerChannel : StandardMessageFactoryChannel { + /// <summary> + /// The messages receivable by this channel. + /// </summary> + private static readonly Type[] MessageTypes = new Type[] { + typeof(Messages.AccessProtectedResourceRequest), + }; + + /// <summary> + /// The protocol versions supported by this channel. + /// </summary> + private static readonly Version[] Versions = Protocol.AllVersions.Select(v => v.Version).ToArray(); + + /// <summary> + /// Initializes a new instance of the <see cref="OAuth2ResourceServerChannel"/> class. + /// </summary> + protected internal OAuth2ResourceServerChannel() + : base(MessageTypes, Versions) { + // TODO: add signing (authenticated request) binding element. + } + + /// <summary> + /// Gets the protocol message that may be embedded in the given HTTP request. + /// </summary> + /// <param name="request">The request to search for an embedded message.</param> + /// <returns> + /// The deserialized message, if one is found. Null otherwise. + /// </returns> + protected override IDirectedProtocolMessage ReadFromRequestCore(HttpRequestInfo request) { + var fields = new Dictionary<string, string>(); + string accessToken; + if ((accessToken = SearchForBearerAccessTokenInRequest(request)) != null) { + fields["token_type"] = Protocol.AccessTokenTypes.Bearer; + fields["access_token"] = accessToken; + } + + if (fields.Count > 0) { + MessageReceivingEndpoint recipient; + try { + recipient = request.GetRecipient(); + } catch (ArgumentException ex) { + Logger.OAuth.WarnFormat("Unrecognized HTTP request: " + ex.ToString()); + return null; + } + + // Deserialize the message using all the data we've collected. + var message = (IDirectedProtocolMessage)this.Receive(fields, recipient); + return message; + } + + return null; + } + + /// <summary> + /// Gets the protocol message that may be in the given HTTP response. + /// </summary> + /// <param name="response">The response that is anticipated to contain an protocol message.</param> + /// <returns> + /// The deserialized message parts, if found. Null otherwise. + /// </returns> + /// <exception cref="ProtocolException">Thrown when the response is not valid.</exception> + protected override IDictionary<string, string> ReadFromResponseCore(IncomingWebResponse response) { + // We never expect resource servers to send out direct requests, + // and therefore won't have direct responses. + throw new NotImplementedException(); + } + + /// <summary> + /// Queues a message for sending in the response stream where the fields + /// are sent in the response stream in querystring style. + /// </summary> + /// <param name="response">The message to send as a response.</param> + /// <returns> + /// The pending user agent redirect based message to be sent as an HttpResponse. + /// </returns> + /// <remarks> + /// This method implements spec OAuth V1.0 section 5.3. + /// </remarks> + protected override OutgoingWebResponse PrepareDirectResponse(IProtocolMessage response) { + var webResponse = new OutgoingWebResponse(); + + // The only direct response from a resource server is a 401 Unauthorized error. + var unauthorizedResponse = response as UnauthorizedResponse; + ErrorUtilities.VerifyInternal(unauthorizedResponse != null, "Only unauthorized responses are expected."); + + // First initialize based on the specifics within the message. + var httpResponse = response as IHttpDirectResponse; + webResponse.Status = httpResponse != null ? httpResponse.HttpStatusCode : HttpStatusCode.Unauthorized; + foreach (string headerName in httpResponse.Headers) { + webResponse.Headers.Add(headerName, httpResponse.Headers[headerName]); + } + + // Now serialize all the message parts into the WWW-Authenticate header. + var fields = this.MessageDescriptions.GetAccessor(response); + webResponse.Headers[HttpResponseHeader.WwwAuthenticate] = MessagingUtilities.AssembleAuthorizationHeader(Protocol.BearerHttpAuthorizationScheme, fields); + return webResponse; + } + + /// <summary> + /// Searches for a bearer access token in the request. + /// </summary> + /// <param name="request">The request.</param> + /// <returns>The bearer access token, if one exists. Otherwise <c>null</c>.</returns> + private static string SearchForBearerAccessTokenInRequest(HttpRequestInfo request) { + Requires.NotNull(request, "request"); + + // First search the authorization header. + string authorizationHeader = request.Headers[HttpRequestHeader.Authorization]; + if (!string.IsNullOrEmpty(authorizationHeader) && authorizationHeader.StartsWith(Protocol.BearerHttpAuthorizationSchemeWithTrailingSpace, StringComparison.OrdinalIgnoreCase)) { + return authorizationHeader.Substring(Protocol.BearerHttpAuthorizationSchemeWithTrailingSpace.Length); + } + + // Failing that, scan the entity + if (!string.IsNullOrEmpty(request.Headers[HttpRequestHeader.ContentType])) { + var contentType = new ContentType(request.Headers[HttpRequestHeader.ContentType]); + if (string.Equals(contentType.MediaType, HttpFormUrlEncoded, StringComparison.Ordinal)) { + if (request.Form[Protocol.BearerTokenEncodedUrlParameterName] != null) { + return request.Form[Protocol.BearerTokenEncodedUrlParameterName]; + } + } + } + + // Finally, check the least desirable location: the query string + if (!String.IsNullOrEmpty(request.QueryStringBeforeRewriting[Protocol.BearerTokenEncodedUrlParameterName])) { + return request.QueryStringBeforeRewriting[Protocol.BearerTokenEncodedUrlParameterName]; + } + + return null; + } + } +} diff --git a/src/DotNetOpenAuth.OAuth2/OAuth2/ChannelElements/RefreshToken.cs b/src/DotNetOpenAuth.OAuth2/OAuth2/ChannelElements/RefreshToken.cs new file mode 100644 index 0000000..ee41957 --- /dev/null +++ b/src/DotNetOpenAuth.OAuth2/OAuth2/ChannelElements/RefreshToken.cs @@ -0,0 +1,56 @@ +//----------------------------------------------------------------------- +// <copyright file="RefreshToken.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. 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/ChannelElements/ScopeEncoder.cs b/src/DotNetOpenAuth.OAuth2/OAuth2/ChannelElements/ScopeEncoder.cs index 7ae5fbf..7ae5fbf 100644 --- a/src/DotNetOpenAuth/OAuth2/ChannelElements/ScopeEncoder.cs +++ b/src/DotNetOpenAuth.OAuth2/OAuth2/ChannelElements/ScopeEncoder.cs diff --git a/src/DotNetOpenAuth.OAuth2/OAuth2/IAccessTokenAnalyzer.cs b/src/DotNetOpenAuth.OAuth2/OAuth2/IAccessTokenAnalyzer.cs new file mode 100644 index 0000000..0897090 --- /dev/null +++ b/src/DotNetOpenAuth.OAuth2/OAuth2/IAccessTokenAnalyzer.cs @@ -0,0 +1,64 @@ +//----------------------------------------------------------------------- +// <copyright file="IAccessTokenAnalyzer.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OAuth2 { + using System; + using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; + using System.Diagnostics.Contracts; + using System.Linq; + using System.Text; + using DotNetOpenAuth.Messaging; + + /// <summary> + /// An interface that resource server hosts should implement if they accept access tokens + /// issued by non-DotNetOpenAuth authorization servers. + /// </summary> + [ContractClass((typeof(IAccessTokenAnalyzerContract)))] + public interface IAccessTokenAnalyzer { + /// <summary> + /// Reads an access token to find out what data it authorizes access to. + /// </summary> + /// <param name="message">The message carrying the access token.</param> + /// <param name="accessToken">The access token.</param> + /// <param name="user">The user whose data is accessible with this access token.</param> + /// <param name="scope">The scope of access authorized by this access token.</param> + /// <returns>A value indicating whether this access token is valid.</returns> + [SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "1#", Justification = "Try pattern")] + [SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "2#", Justification = "Try pattern")] + bool TryValidateAccessToken(IDirectedProtocolMessage message, string accessToken, out string user, out HashSet<string> scope); + } + + /// <summary> + /// Code contract for the <see cref="IAccessTokenAnalyzer"/> interface. + /// </summary> + [ContractClassFor(typeof(IAccessTokenAnalyzer))] + internal abstract class IAccessTokenAnalyzerContract : IAccessTokenAnalyzer { + /// <summary> + /// Prevents a default instance of the <see cref="IAccessTokenAnalyzerContract"/> class from being created. + /// </summary> + private IAccessTokenAnalyzerContract() { + } + + /// <summary> + /// Reads an access token to find out what data it authorizes access to. + /// </summary> + /// <param name="message">The message carrying the access token.</param> + /// <param name="accessToken">The access token.</param> + /// <param name="user">The user whose data is accessible with this access token.</param> + /// <param name="scope">The scope of access authorized by this access token.</param> + /// <returns> + /// A value indicating whether this access token is valid. + /// </returns> + bool IAccessTokenAnalyzer.TryValidateAccessToken(IDirectedProtocolMessage message, string accessToken, out string user, out HashSet<string> scope) { + Requires.NotNull(message, "message"); + Requires.NotNullOrEmpty(accessToken, "accessToken"); + Contract.Ensures(Contract.Result<bool>() == (Contract.ValueAtReturn<string>(out user) != null)); + + throw new NotImplementedException(); + } + } +} diff --git a/src/DotNetOpenAuth.OAuth2/OAuth2/IAuthorizationServer.cs b/src/DotNetOpenAuth.OAuth2/OAuth2/IAuthorizationServer.cs new file mode 100644 index 0000000..6457f36 --- /dev/null +++ b/src/DotNetOpenAuth.OAuth2/OAuth2/IAuthorizationServer.cs @@ -0,0 +1,238 @@ +//----------------------------------------------------------------------- +// <copyright file="IAuthorizationServer.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.Messaging.Bindings; + using DotNetOpenAuth.OAuth2.ChannelElements; + using DotNetOpenAuth.OAuth2.Messages; + + /// <summary> + /// Provides host-specific authorization server services needed by this library. + /// </summary> + [ContractClass(typeof(IAuthorizationServerContract))] + public interface IAuthorizationServer { + /// <summary> + /// Gets the store for storeing 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 VerificationCodeNonceStore { get; } + + /// <summary> + /// Gets 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> + RSACryptoServiceProvider AccessTokenSigningKey { get; } + + /// <summary> + /// Obtains the lifetime for a new 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> + /// Receives the lifetime for this access token. Note that within this lifetime, authorization <i>may</i> not be revokable. + /// Short lifetimes are recommended (i.e. one hour), particularly when the client is not authenticated or + /// the resources to which access is being granted are sensitive. + /// </returns> + TimeSpan GetAccessTokenLifetime(IAccessTokenRequest accessTokenRequestMessage); + + /// <summary> + /// Obtains the encryption key for an access token being created. + /// </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> + /// The crypto service provider with the asymmetric public key to use for encrypting access tokens for a specific resource server. + /// The caller is responsible to dispose of this value. + /// </returns> + /// <remarks> + /// The caller is responsible to dispose of the returned value. + /// </remarks> + RSACryptoServiceProvider GetResourceServerEncryptionKey(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> + IConsumerDescription 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> + /// Code Contract for the <see cref="IAuthorizationServer"/> interface. + /// </summary> + [ContractClassFor(typeof(IAuthorizationServer))] + internal abstract class IAuthorizationServerContract : IAuthorizationServer { + /// <summary> + /// Prevents a default instance of the <see cref="IAuthorizationServerContract"/> class from being created. + /// </summary> + private IAuthorizationServerContract() { + } + + /// <summary> + /// Gets the store for storeing crypto keys used to symmetrically encrypt and sign authorization codes and refresh tokens. + /// </summary> + ICryptoKeyStore IAuthorizationServer.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 IAuthorizationServer.VerificationCodeNonceStore { + get { + Contract.Ensures(Contract.Result<INonceStore>() != null); + throw new NotImplementedException(); + } + } + + /// <summary> + /// Gets the crypto service provider with the asymmetric private key to use for signing access tokens. + /// </summary> + /// <value> + /// Must not be null, and must contain the private key. + /// </value> + /// <returns>A crypto service provider instance that contains the private key.</returns> + RSACryptoServiceProvider IAuthorizationServer.AccessTokenSigningKey { + get { + Contract.Ensures(Contract.Result<RSACryptoServiceProvider>() != null); + Contract.Ensures(!Contract.Result<RSACryptoServiceProvider>().PublicOnly); + throw new NotImplementedException(); + } + } + + /// <summary> + /// Obtains the lifetime for a new 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> + /// Receives the lifetime for this access token. Note that within this lifetime, authorization <i>may</i> not be revokable. + /// Short lifetimes are recommended (i.e. one hour), particularly when the client is not authenticated or + /// the resources to which access is being granted are sensitive. + /// </returns> + TimeSpan IAuthorizationServer.GetAccessTokenLifetime(IAccessTokenRequest accessTokenRequestMessage) { + Requires.NotNull(accessTokenRequestMessage, "accessTokenRequestMessage"); + throw new NotImplementedException(); + } + + /// <summary> + /// Obtains the encryption key for an access token being created. + /// </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> + /// The crypto service provider with the asymmetric public key to use for encrypting access tokens for a specific resource server. + /// The caller is responsible to dispose of this value. + /// </returns> + RSACryptoServiceProvider IAuthorizationServer.GetResourceServerEncryptionKey(IAccessTokenRequest accessTokenRequestMessage) { + Requires.NotNull(accessTokenRequestMessage, "accessTokenRequestMessage"); + Contract.Ensures(Contract.Result<RSACryptoServiceProvider>() != 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> + IConsumerDescription IAuthorizationServer.GetClient(string clientIdentifier) { + Requires.NotNullOrEmpty(clientIdentifier, "clientIdentifier"); + Contract.Ensures(Contract.Result<IConsumerDescription>() != 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 IAuthorizationServer.IsAuthorizationValid(IAuthorizationDescription authorization) { + Requires.NotNull(authorization, "authorization"); + throw new NotImplementedException(); + } + } +} diff --git a/src/DotNetOpenAuth/OAuth2/IAuthorizationState.cs b/src/DotNetOpenAuth.OAuth2/OAuth2/IAuthorizationState.cs index 39437ac..39437ac 100644 --- a/src/DotNetOpenAuth/OAuth2/IAuthorizationState.cs +++ b/src/DotNetOpenAuth.OAuth2/OAuth2/IAuthorizationState.cs diff --git a/src/DotNetOpenAuth.OAuth2/OAuth2/IClientAuthorizationTracker.cs b/src/DotNetOpenAuth.OAuth2/OAuth2/IClientAuthorizationTracker.cs new file mode 100644 index 0000000..4529cef --- /dev/null +++ b/src/DotNetOpenAuth.OAuth2/OAuth2/IClientAuthorizationTracker.cs @@ -0,0 +1,53 @@ +//----------------------------------------------------------------------- +// <copyright file="IClientAuthorizationTracker.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OAuth2 { + using System; + using System.Diagnostics.Contracts; + + /// <summary> + /// A token manager implemented by some clients to assist in tracking authorization state. + /// </summary> + [ContractClass(typeof(IClientAuthorizationTrackerContract))] + public interface IClientAuthorizationTracker { + /// <summary> + /// Gets the state of the authorization for a given callback URL and client state. + /// </summary> + /// <param name="callbackUrl">The callback URL.</param> + /// <param name="clientState">State of the client stored at the beginning of an authorization request.</param> + /// <returns>The authorization state; may be <c>null</c> if no authorization state matches.</returns> + IAuthorizationState GetAuthorizationState(Uri callbackUrl, string clientState); + } + + /// <summary> + /// Contract class for the <see cref="IClientAuthorizationTracker"/> interface. + /// </summary> + [ContractClassFor(typeof(IClientAuthorizationTracker))] + internal abstract class IClientAuthorizationTrackerContract : IClientAuthorizationTracker { + /// <summary> + /// Prevents a default instance of the <see cref="IClientAuthorizationTrackerContract"/> class from being created. + /// </summary> + private IClientAuthorizationTrackerContract() { + } + + #region IClientTokenManager Members + + /// <summary> + /// Gets the state of the authorization for a given callback URL and client state. + /// </summary> + /// <param name="callbackUrl">The callback URL.</param> + /// <param name="clientState">State of the client stored at the beginning of an authorization request.</param> + /// <returns> + /// The authorization state; may be <c>null</c> if no authorization state matches. + /// </returns> + IAuthorizationState IClientAuthorizationTracker.GetAuthorizationState(Uri callbackUrl, string clientState) { + Requires.NotNull(callbackUrl, "callbackUrl"); + throw new NotImplementedException(); + } + + #endregion + } +} diff --git a/src/DotNetOpenAuth.OAuth2/OAuth2/IConsumerDescription.cs b/src/DotNetOpenAuth.OAuth2/OAuth2/IConsumerDescription.cs new file mode 100644 index 0000000..cf07dc6 --- /dev/null +++ b/src/DotNetOpenAuth.OAuth2/OAuth2/IConsumerDescription.cs @@ -0,0 +1,101 @@ +//----------------------------------------------------------------------- +// <copyright file="IConsumerDescription.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; + + /// <summary> + /// A description of a client from an Authorization Server's point of view. + /// </summary> + [ContractClass(typeof(IConsumerDescriptionContract))] + public interface IConsumerDescription { + /// <summary> + /// Gets the client secret. + /// </summary> + string Secret { get; } + + /// <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> + Uri DefaultCallback { get; } + + /// <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.</param> + /// <returns> + /// <c>true</c> if the callback URL is allowable for this client; otherwise, <c>false</c>. + /// </returns> + /// <remarks> + /// <para> + /// At the point this method is invoked, the identity of the client has <em>not</em> + /// been confirmed. To avoid open redirector attacks, the alleged client's identity + /// is used to lookup a list of allowable callback URLs to make sure that the callback URL + /// the actual client is requesting is one of the expected ones. + /// </para> + /// <para> + /// From OAuth 2.0 section 2.1: + /// The authorization server SHOULD require the client to pre-register + /// their redirection URI or at least certain components such as the + /// scheme, host, port and path. If a redirection URI was registered, + /// the authorization server MUST compare any redirection URI received at + /// the authorization endpoint with the registered URI. + /// </para> + /// </remarks> + bool IsCallbackAllowed(Uri callback); + } + + /// <summary> + /// Contract class for the <see cref="IConsumerDescription"/> interface. + /// </summary> + [ContractClassFor(typeof(IConsumerDescription))] + internal abstract class IConsumerDescriptionContract : IConsumerDescription { + #region IConsumerDescription Members + + /// <summary> + /// Gets the client secret. + /// </summary> + /// <value></value> + string IConsumerDescription.Secret { + get { throw new NotImplementedException(); } + } + + /// <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> + Uri IConsumerDescription.DefaultCallback { + get { + Contract.Ensures(Contract.Result<Uri>() == null || Contract.Result<Uri>().IsAbsoluteUri); + throw new NotImplementedException(); + } + } + + /// <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 requested callback URI.</param> + /// <returns> + /// <c>true</c> if the callback is allowed; otherwise, <c>false</c>. + /// </returns> + bool IConsumerDescription.IsCallbackAllowed(Uri callback) { + Requires.NotNull(callback, "callback"); + Requires.True(callback.IsAbsoluteUri, "callback"); + throw new NotImplementedException(); + } + + #endregion + } +} diff --git a/src/DotNetOpenAuth/OAuth2/Messages/AccessProtectedResourceRequest.cs b/src/DotNetOpenAuth.OAuth2/OAuth2/Messages/AccessProtectedResourceRequest.cs index 2e94156..2e94156 100644 --- a/src/DotNetOpenAuth/OAuth2/Messages/AccessProtectedResourceRequest.cs +++ b/src/DotNetOpenAuth.OAuth2/OAuth2/Messages/AccessProtectedResourceRequest.cs diff --git a/src/DotNetOpenAuth.OAuth2/OAuth2/Messages/AccessTokenAuthorizationCodeRequest.cs b/src/DotNetOpenAuth.OAuth2/OAuth2/Messages/AccessTokenAuthorizationCodeRequest.cs new file mode 100644 index 0000000..f3dad87 --- /dev/null +++ b/src/DotNetOpenAuth.OAuth2/OAuth2/Messages/AccessTokenAuthorizationCodeRequest.cs @@ -0,0 +1,94 @@ +//----------------------------------------------------------------------- +// <copyright file="AccessTokenAuthorizationCodeRequest.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OAuth2.Messages { + using System; + using System.Collections.Generic; + using System.Diagnostics.Contracts; + using System.Linq; + using System.Text; + + using DotNetOpenAuth.Messaging; + 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 AccessTokenAuthorizationCodeRequest : AccessTokenRequestBase, IAuthorizationCarryingRequest { + /// <summary> + /// Initializes a new instance of the <see cref="AccessTokenAuthorizationCodeRequest"/> class. + /// </summary> + /// <param name="tokenEndpoint">The Authorization Server's access token endpoint URL.</param> + /// <param name="version">The version.</param> + internal AccessTokenAuthorizationCodeRequest(Uri tokenEndpoint, Version version) + : base(tokenEndpoint, version) { + } + + /// <summary> + /// Initializes a new instance of the <see cref="AccessTokenAuthorizationCodeRequest"/> class. + /// </summary> + /// <param name="authorizationServer">The authorization server.</param> + internal AccessTokenAuthorizationCodeRequest(AuthorizationServerDescription authorizationServer) + : this(authorizationServer.TokenEndpoint, authorizationServer.Version) { + Requires.NotNull(authorizationServer, "authorizationServer"); + } + + /// <summary> + /// Gets the type of the code or token. + /// </summary> + /// <value>The type of the code or token.</value> + CodeOrTokenType IAuthorizationCarryingRequest.CodeOrTokenType { + get { return CodeOrTokenType.AuthorizationCode; } + } + + /// <summary> + /// Gets or sets the verification code or refresh/access token. + /// </summary> + /// <value>The code or token.</value> + string IAuthorizationCarryingRequest.CodeOrToken { + get { return this.AuthorizationCode; } + set { this.AuthorizationCode = value; } + } + + /// <summary> + /// Gets or sets the authorization that the token describes. + /// </summary> + IAuthorizationDescription IAuthorizationCarryingRequest.AuthorizationDescription { get; set; } + + /// <summary> + /// Gets the type of the grant. + /// </summary> + /// <value>The type of the grant.</value> + internal override GrantType GrantType { + get { return Messages.GrantType.AuthorizationCode; } + } + + /// <summary> + /// Gets or sets the verification code previously communicated to the Client + /// in <see cref="EndUserAuthorizationSuccessAuthCodeResponse.AuthorizationCode"/>. + /// </summary> + /// <value>The verification code received from the authorization server.</value> + [MessagePart(Protocol.code, IsRequired = true)] + internal string AuthorizationCode { get; set; } + + /// <summary> + /// Gets or sets the callback URL used in <see cref="EndUserAuthorizationRequest.Callback"/> + /// </summary> + /// <value> + /// The Callback URL used to obtain the Verification Code. + /// </value> + [MessagePart(Protocol.redirect_uri, IsRequired = true)] + internal Uri Callback { get; set; } + + /// <summary> + /// Gets the scope of operations the client is allowed to invoke. + /// </summary> + protected override HashSet<string> RequestedScope { + get { return ((IAuthorizationCarryingRequest)this).AuthorizationDescription.Scope; } + } + } +} diff --git a/src/DotNetOpenAuth/OAuth2/Messages/AccessTokenClientCredentialsRequest.cs b/src/DotNetOpenAuth.OAuth2/OAuth2/Messages/AccessTokenClientCredentialsRequest.cs index 01e1633..01e1633 100644 --- a/src/DotNetOpenAuth/OAuth2/Messages/AccessTokenClientCredentialsRequest.cs +++ b/src/DotNetOpenAuth.OAuth2/OAuth2/Messages/AccessTokenClientCredentialsRequest.cs diff --git a/src/DotNetOpenAuth/OAuth2/Messages/AccessTokenFailedResponse.cs b/src/DotNetOpenAuth.OAuth2/OAuth2/Messages/AccessTokenFailedResponse.cs index 82de341..82de341 100644 --- a/src/DotNetOpenAuth/OAuth2/Messages/AccessTokenFailedResponse.cs +++ b/src/DotNetOpenAuth.OAuth2/OAuth2/Messages/AccessTokenFailedResponse.cs diff --git a/src/DotNetOpenAuth/OAuth2/Messages/AccessTokenRefreshRequest.cs b/src/DotNetOpenAuth.OAuth2/OAuth2/Messages/AccessTokenRefreshRequest.cs index 22354e4..22354e4 100644 --- a/src/DotNetOpenAuth/OAuth2/Messages/AccessTokenRefreshRequest.cs +++ b/src/DotNetOpenAuth.OAuth2/OAuth2/Messages/AccessTokenRefreshRequest.cs diff --git a/src/DotNetOpenAuth.OAuth2/OAuth2/Messages/AccessTokenRequestBase.cs b/src/DotNetOpenAuth.OAuth2/OAuth2/Messages/AccessTokenRequestBase.cs new file mode 100644 index 0000000..4bf593a --- /dev/null +++ b/src/DotNetOpenAuth.OAuth2/OAuth2/Messages/AccessTokenRequestBase.cs @@ -0,0 +1,78 @@ +//----------------------------------------------------------------------- +// <copyright file="AccessTokenRequestBase.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.Configuration; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OAuth2.ChannelElements; + + /// <summary> + /// A message sent from the client to the authorization server to exchange a previously obtained grant for an access token. + /// </summary> + public abstract class AccessTokenRequestBase : AuthenticatedClientRequestBase, IAccessTokenRequest { + /// <summary> + /// Initializes a new instance of the <see cref="AccessTokenRequestBase"/> class. + /// </summary> + /// <param name="tokenEndpoint">The Authorization Server's access token endpoint URL.</param> + /// <param name="version">The version.</param> + protected AccessTokenRequestBase(Uri tokenEndpoint, Version version) + : base(tokenEndpoint, version) { + this.HttpMethods = HttpDeliveryMethods.PostRequest; + } + + /// <summary> + /// Gets the scope of operations the client is allowed to invoke. + /// </summary> + HashSet<string> IAccessTokenRequest.Scope { + get { return this.RequestedScope; } + } + + /// <summary> + /// Gets a value indicating whether the client requesting the access token has authenticated itself. + /// </summary> + /// <value> + /// Always true, because of our base class. + /// </value> + bool IAccessTokenRequest.ClientAuthenticated { + get { return true; } + } + + /// <summary> + /// Gets the type of the grant. + /// </summary> + /// <value>The type of the grant.</value> + [MessagePart(Protocol.grant_type, IsRequired = true, Encoder = typeof(GrantTypeEncoder))] + internal abstract GrantType GrantType { get; } + + /// <summary> + /// Gets the scope of operations the client is allowed to invoke. + /// </summary> + protected abstract HashSet<string> RequestedScope { get; } + + /// <summary> + /// Checks the message state for conformity to the protocol specification + /// and throws an exception if the message is invalid. + /// </summary> + /// <remarks> + /// <para>Some messages have required fields, or combinations of fields that must relate to each other + /// in specialized ways. After deserializing a message, this method checks the state of the + /// message to see if it conforms to the protocol.</para> + /// <para>Note that this property should <i>not</i> check signatures or perform any state checks + /// outside this scope of this particular message.</para> + /// </remarks> + /// <exception cref="ProtocolException">Thrown if the message is invalid.</exception> + protected override void EnsureValidMessage() { + base.EnsureValidMessage(); + ErrorUtilities.VerifyProtocol( + DotNetOpenAuthSection.Messaging.RelaxSslRequirements || this.Recipient.IsTransportSecure(), + OAuthStrings.HttpsRequired); + } + } +} diff --git a/src/DotNetOpenAuth/OAuth2/Messages/AccessTokenResourceOwnerPasswordCredentialsRequest.cs b/src/DotNetOpenAuth.OAuth2/OAuth2/Messages/AccessTokenResourceOwnerPasswordCredentialsRequest.cs index 82febe9..82febe9 100644 --- a/src/DotNetOpenAuth/OAuth2/Messages/AccessTokenResourceOwnerPasswordCredentialsRequest.cs +++ b/src/DotNetOpenAuth.OAuth2/OAuth2/Messages/AccessTokenResourceOwnerPasswordCredentialsRequest.cs diff --git a/src/DotNetOpenAuth/OAuth2/Messages/AccessTokenSuccessResponse.cs b/src/DotNetOpenAuth.OAuth2/OAuth2/Messages/AccessTokenSuccessResponse.cs index 5682bf7..5682bf7 100644 --- a/src/DotNetOpenAuth/OAuth2/Messages/AccessTokenSuccessResponse.cs +++ b/src/DotNetOpenAuth.OAuth2/OAuth2/Messages/AccessTokenSuccessResponse.cs diff --git a/src/DotNetOpenAuth/OAuth2/Messages/AuthenticatedClientRequestBase.cs b/src/DotNetOpenAuth.OAuth2/OAuth2/Messages/AuthenticatedClientRequestBase.cs index 22f7461..22f7461 100644 --- a/src/DotNetOpenAuth/OAuth2/Messages/AuthenticatedClientRequestBase.cs +++ b/src/DotNetOpenAuth.OAuth2/OAuth2/Messages/AuthenticatedClientRequestBase.cs diff --git a/src/DotNetOpenAuth.OAuth2/OAuth2/Messages/EndUserAuthorizationFailedResponse.cs b/src/DotNetOpenAuth.OAuth2/OAuth2/Messages/EndUserAuthorizationFailedResponse.cs new file mode 100644 index 0000000..ecf3254 --- /dev/null +++ b/src/DotNetOpenAuth.OAuth2/OAuth2/Messages/EndUserAuthorizationFailedResponse.cs @@ -0,0 +1,81 @@ +//----------------------------------------------------------------------- +// <copyright file="EndUserAuthorizationFailedResponse.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OAuth2.Messages { + using System; + using System.Collections.Generic; + using System.Diagnostics.Contracts; + using System.Linq; + using System.Runtime.Remoting.Messaging; + using System.Text; + using DotNetOpenAuth.Messaging; + + /// <summary> + /// The message that an Authorization Server responds to a Client with when the user denies a user authorization request. + /// </summary> + public class EndUserAuthorizationFailedResponse : MessageBase, IMessageWithClientState { + /// <summary> + /// Initializes a new instance of the <see cref="EndUserAuthorizationFailedResponse"/> 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 EndUserAuthorizationFailedResponse(Uri clientCallback, Version version) + : base(version, MessageTransport.Indirect, clientCallback) { + Requires.NotNull(version, "version"); + Requires.NotNull(clientCallback, "clientCallback"); + } + + /// <summary> + /// Initializes a new instance of the <see cref="EndUserAuthorizationFailedResponse"/> 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 EndUserAuthorizationFailedResponse(Uri clientCallback, EndUserAuthorizationRequest request) + : base(((IProtocolMessage)request).Version, MessageTransport.Indirect, clientCallback) { + Requires.NotNull(request, "request"); + ((IMessageWithClientState)this).ClientState = request.ClientState; + } + + /// <summary> + /// Gets or sets the error. + /// </summary> + /// <value> + /// One of the values given in <see cref="Protocol.EndUserAuthorizationRequestErrorCodes"/>. + /// OR a numerical HTTP status code from the 4xx or 5xx + /// range, with the exception of the 400 (Bad Request) and + /// 401 (Unauthorized) status codes. For example, if the + /// service is temporarily unavailable, the authorization + /// server MAY return an error response with "error" set to + /// "503". + /// </value> + [MessagePart(Protocol.error, IsRequired = true)] + public string Error { get; set; } + + /// <summary> + /// Gets or sets a human readable description of the error. + /// </summary> + /// <value>Human-readable text providing additional information, used to assist in the understanding and resolution of the error that occurred.</value> + [MessagePart(Protocol.error_description, IsRequired = false)] + public string ErrorDescription { get; set; } + + /// <summary> + /// Gets or sets the location of the web page that describes the error and possible resolution. + /// </summary> + /// <value>A URI identifying a human-readable web page with information about the error, used to provide the end-user with additional information about the error.</value> + [MessagePart(Protocol.error_uri, IsRequired = false)] + public Uri ErrorUri { get; set; } + + /// <summary> + /// Gets or sets some state as provided by the client in the authorization request. + /// </summary> + /// <value>An opaque value defined by the client.</value> + /// <remarks> + /// REQUIRED if the Client sent the value in the <see cref="EndUserAuthorizationRequest"/>. + /// </remarks> + [MessagePart(Protocol.state, IsRequired = false)] + string IMessageWithClientState.ClientState { get; set; } + } +} diff --git a/src/DotNetOpenAuth.OAuth2/OAuth2/Messages/EndUserAuthorizationRequest.cs b/src/DotNetOpenAuth.OAuth2/OAuth2/Messages/EndUserAuthorizationRequest.cs new file mode 100644 index 0000000..2455406 --- /dev/null +++ b/src/DotNetOpenAuth.OAuth2/OAuth2/Messages/EndUserAuthorizationRequest.cs @@ -0,0 +1,118 @@ +//----------------------------------------------------------------------- +// <copyright file="EndUserAuthorizationRequest.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OAuth2.Messages { + using System; + using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; + using System.Diagnostics.Contracts; + using DotNetOpenAuth.Configuration; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OAuth2.ChannelElements; + + /// <summary> + /// A message sent by a web application Client to the AuthorizationServer + /// via the user agent to obtain authorization from the user and prepare + /// to issue an access token to the Consumer if permission is granted. + /// </summary> + [Serializable] + public class EndUserAuthorizationRequest : MessageBase, IAccessTokenRequest { + /// <summary> + /// Initializes a new instance of the <see cref="EndUserAuthorizationRequest"/> class. + /// </summary> + /// <param name="authorizationEndpoint">The Authorization Server's user authorization URL to direct the user to.</param> + /// <param name="version">The protocol version.</param> + internal EndUserAuthorizationRequest(Uri authorizationEndpoint, Version version) + : base(version, MessageTransport.Indirect, authorizationEndpoint) { + Requires.NotNull(authorizationEndpoint, "authorizationEndpoint"); + Requires.NotNull(version, "version"); + this.HttpMethods = HttpDeliveryMethods.GetRequest | HttpDeliveryMethods.PostRequest; + this.Scope = new HashSet<string>(OAuthUtilities.ScopeStringComparer); + this.ResponseType = EndUserAuthorizationResponseType.AuthorizationCode; + } + + /// <summary> + /// Initializes a new instance of the <see cref="EndUserAuthorizationRequest"/> class. + /// </summary> + /// <param name="authorizationServer">The authorization server.</param> + internal EndUserAuthorizationRequest(AuthorizationServerDescription authorizationServer) + : this(authorizationServer.AuthorizationEndpoint, authorizationServer.Version) { + Requires.NotNull(authorizationServer, "authorizationServer"); + Requires.True(authorizationServer.Version != null, "authorizationServer"); + Requires.True(authorizationServer.AuthorizationEndpoint != null, "authorizationServer"); + } + + /// <summary> + /// Gets or sets the grant type that the client expects of the authorization server. + /// </summary> + /// <value>Always <see cref="EndUserAuthorizationResponseType.AuthorizationCode"/>. Other response types are not supported.</value> + [MessagePart(Protocol.response_type, IsRequired = true, Encoder = typeof(EndUserAuthorizationResponseTypeEncoder))] + public EndUserAuthorizationResponseType ResponseType { get; set; } + + /// <summary> + /// Gets or sets the identifier by which this client is known to the Authorization Server. + /// </summary> + [MessagePart(Protocol.client_id, IsRequired = true)] + public string ClientIdentifier { get; set; } + + /// <summary> + /// Gets a value indicating whether the client requesting the access token has authenticated itself. + /// </summary> + /// <value> + /// Always false because authorization requests only include the client_id, without a secret. + /// </value> + bool IAccessTokenRequest.ClientAuthenticated { + get { return false; } + } + + /// <summary> + /// Gets or sets the callback URL. + /// </summary> + /// <value> + /// An absolute URL to which the Authorization Server will redirect the User back after + /// the user has approved the authorization request. + /// </value> + /// <remarks> + /// REQUIRED unless a redirection URI has been established between the client and authorization server via other means. An absolute URI to which the authorization server will redirect the user-agent to when the end-user authorization step is completed. The authorization server MAY require the client to pre-register their redirection URI. The redirection URI MUST NOT include a query component as defined by [RFC3986] (Berners-Lee, T., Fielding, R., and L. Masinter, “Uniform Resource Identifier (URI): Generic Syntax,” January 2005.) section 3 if the state parameter is present. + /// </remarks> + [MessagePart(Protocol.redirect_uri, IsRequired = false)] + public Uri Callback { get; set; } + + /// <summary> + /// Gets or sets state of the client that should be sent back with the authorization response. + /// </summary> + /// <value> + /// An opaque value that Clients can use to maintain state associated with this request. + /// </value> + /// <remarks> + /// This data is proprietary to the client and should be considered an opaque string to the + /// authorization server. + /// </remarks> + [MessagePart(Protocol.state, IsRequired = false)] + public string ClientState { get; set; } + + /// <summary> + /// Gets the scope of access being requested. + /// </summary> + /// <value>The scope of the access request expressed as a list of space-delimited strings. The value of the scope parameter is defined by the authorization server. If the value contains multiple space-delimited strings, their order does not matter, and each string adds an additional access range to the requested scope.</value> + [MessagePart(Protocol.scope, IsRequired = false, Encoder = typeof(ScopeEncoder))] + public HashSet<string> Scope { get; private set; } + + /// <summary> + /// Checks the message state for conformity to the protocol specification + /// and throws an exception if the message is invalid. + /// </summary> + /// <exception cref="ProtocolException">Thrown if the message is invalid.</exception> + protected override void EnsureValidMessage() { + base.EnsureValidMessage(); + + ErrorUtilities.VerifyProtocol( + DotNetOpenAuthSection.Messaging.RelaxSslRequirements || this.Recipient.IsTransportSecure(), + OAuthStrings.HttpsRequired); + ErrorUtilities.VerifyProtocol(this.Callback == null || this.Callback.IsAbsoluteUri, this, OAuthStrings.AbsoluteUriRequired, Protocol.redirect_uri); + } + } +} diff --git a/src/DotNetOpenAuth/OAuth2/Messages/EndUserAuthorizationResponseType.cs b/src/DotNetOpenAuth.OAuth2/OAuth2/Messages/EndUserAuthorizationResponseType.cs index 815fef6..815fef6 100644 --- a/src/DotNetOpenAuth/OAuth2/Messages/EndUserAuthorizationResponseType.cs +++ b/src/DotNetOpenAuth.OAuth2/OAuth2/Messages/EndUserAuthorizationResponseType.cs diff --git a/src/DotNetOpenAuth.OAuth2/OAuth2/Messages/EndUserAuthorizationSuccessAccessTokenResponse.cs b/src/DotNetOpenAuth.OAuth2/OAuth2/Messages/EndUserAuthorizationSuccessAccessTokenResponse.cs new file mode 100644 index 0000000..b82b1da --- /dev/null +++ b/src/DotNetOpenAuth.OAuth2/OAuth2/Messages/EndUserAuthorizationSuccessAccessTokenResponse.cs @@ -0,0 +1,121 @@ +//----------------------------------------------------------------------- +// <copyright file="EndUserAuthorizationSuccessAccessTokenResponse.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OAuth2.Messages { + using System; + using System.Collections.Generic; + using System.Diagnostics.Contracts; + using System.Linq; + using System.Text; + + using DotNetOpenAuth.Messaging; + 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 only an access token, + /// and to return the user to the Client where they started their experience. + /// </summary> + internal class EndUserAuthorizationSuccessAccessTokenResponse : EndUserAuthorizationSuccessResponseBase, IAuthorizationCarryingRequest, IHttpIndirectResponse { + /// <summary> + /// Initializes a new instance of the <see cref="EndUserAuthorizationSuccessAccessTokenResponse"/> 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 EndUserAuthorizationSuccessAccessTokenResponse(Uri clientCallback, Version version) + : base(clientCallback, version) { + Requires.NotNull(version, "version"); + Requires.NotNull(clientCallback, "clientCallback"); + this.TokenType = Protocol.AccessTokenTypes.Bearer; + } + + /// <summary> + /// Initializes a new instance of the <see cref="EndUserAuthorizationSuccessAccessTokenResponse"/> 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 EndUserAuthorizationSuccessAccessTokenResponse(Uri clientCallback, EndUserAuthorizationRequest request) + : base(clientCallback, request) { + Requires.NotNull(clientCallback, "clientCallback"); + Requires.NotNull(request, "request"); + ((IMessageWithClientState)this).ClientState = request.ClientState; + this.TokenType = Protocol.AccessTokenTypes.Bearer; + } + + #region IAuthorizationCarryingRequest Members + + /// <summary> + /// Gets or sets the verification code or refresh/access token. + /// </summary> + /// <value>The code or token.</value> + string IAuthorizationCarryingRequest.CodeOrToken { + get { return this.AccessToken; } + set { this.AccessToken = value; } + } + + /// <summary> + /// Gets the type of the code or token. + /// </summary> + /// <value>The type of the code or token.</value> + CodeOrTokenType IAuthorizationCarryingRequest.CodeOrTokenType { + get { return CodeOrTokenType.AccessToken; } + } + + /// <summary> + /// Gets or sets the authorization that the token describes. + /// </summary> + /// <value></value> + IAuthorizationDescription IAuthorizationCarryingRequest.AuthorizationDescription { get; set; } + + #endregion + + #region IHttpIndirectResponse Members + + /// <summary> + /// Gets a value indicating whether the payload for the message should be included + /// in the redirect fragment instead of the query string or POST entity. + /// </summary> + bool IHttpIndirectResponse.Include301RedirectPayloadInFragment { + get { return true; } + } + + #endregion + + /// <summary> + /// Gets or sets the token type. + /// </summary> + /// <value>Usually "bearer".</value> + /// <remarks> + /// Described in OAuth 2.0 section 7.1. + /// </remarks> + [MessagePart(Protocol.token_type, IsRequired = true)] + public string TokenType { get; internal set; } + + /// <summary> + /// Gets or sets the access token. + /// </summary> + /// <value>The access token.</value> + [MessagePart(Protocol.access_token, IsRequired = true)] + public string AccessToken { get; set; } + + /// <summary> + /// Gets or sets the scope of the <see cref="AccessToken"/> if one is given; otherwise the scope of the authorization code. + /// </summary> + /// <value>The scope.</value> + [MessagePart(Protocol.scope, IsRequired = false, Encoder = typeof(ScopeEncoder))] + public new ICollection<string> Scope { + get { return base.Scope; } + protected set { base.Scope = value; } + } + + /// <summary> + /// Gets or sets the lifetime of the authorization. + /// </summary> + /// <value>The lifetime.</value> + [MessagePart(Protocol.expires_in, IsRequired = false, Encoder = typeof(TimespanSecondsEncoder))] + internal TimeSpan? Lifetime { get; set; } + } +} diff --git a/src/DotNetOpenAuth.OAuth2/OAuth2/Messages/EndUserAuthorizationSuccessAuthCodeResponse.cs b/src/DotNetOpenAuth.OAuth2/OAuth2/Messages/EndUserAuthorizationSuccessAuthCodeResponse.cs new file mode 100644 index 0000000..ac7f392 --- /dev/null +++ b/src/DotNetOpenAuth.OAuth2/OAuth2/Messages/EndUserAuthorizationSuccessAuthCodeResponse.cs @@ -0,0 +1,76 @@ +//----------------------------------------------------------------------- +// <copyright file="EndUserAuthorizationSuccessAuthCodeResponse.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OAuth2.Messages { + using System; + using System.Diagnostics.Contracts; + + using DotNetOpenAuth.Messaging; + 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 EndUserAuthorizationSuccessAuthCodeResponse : EndUserAuthorizationSuccessResponseBase, IAuthorizationCarryingRequest { + /// <summary> + /// Initializes a new instance of the <see cref="EndUserAuthorizationSuccessAuthCodeResponse"/> 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 EndUserAuthorizationSuccessAuthCodeResponse(Uri clientCallback, Version version) + : base(clientCallback, version) { + Requires.NotNull(version, "version"); + Requires.NotNull(clientCallback, "clientCallback"); + } + + /// <summary> + /// Initializes a new instance of the <see cref="EndUserAuthorizationSuccessAuthCodeResponse"/> 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 EndUserAuthorizationSuccessAuthCodeResponse(Uri clientCallback, EndUserAuthorizationRequest request) + : base(clientCallback, request) { + Requires.NotNull(clientCallback, "clientCallback"); + Requires.NotNull(request, "request"); + ((IMessageWithClientState)this).ClientState = request.ClientState; + } + + #region IAuthorizationCarryingRequest Members + + /// <summary> + /// Gets or sets the verification code or refresh/access token. + /// </summary> + /// <value>The code or token.</value> + string IAuthorizationCarryingRequest.CodeOrToken { + get { return this.AuthorizationCode; } + set { this.AuthorizationCode = value; } + } + + /// <summary> + /// Gets the type of the code or token. + /// </summary> + /// <value>The type of the code or token.</value> + CodeOrTokenType IAuthorizationCarryingRequest.CodeOrTokenType { + get { return CodeOrTokenType.AuthorizationCode; } + } + + /// <summary> + /// Gets or sets the authorization that the token describes. + /// </summary> + IAuthorizationDescription IAuthorizationCarryingRequest.AuthorizationDescription { get; set; } + + #endregion + + /// <summary> + /// Gets or sets the authorization code. + /// </summary> + /// <value>The authorization code.</value> + [MessagePart(Protocol.code, IsRequired = true)] + internal string AuthorizationCode { get; set; } + } +} diff --git a/src/DotNetOpenAuth.OAuth2/OAuth2/Messages/EndUserAuthorizationSuccessResponseBase.cs b/src/DotNetOpenAuth.OAuth2/OAuth2/Messages/EndUserAuthorizationSuccessResponseBase.cs new file mode 100644 index 0000000..ff52743 --- /dev/null +++ b/src/DotNetOpenAuth.OAuth2/OAuth2/Messages/EndUserAuthorizationSuccessResponseBase.cs @@ -0,0 +1,69 @@ +//----------------------------------------------------------------------- +// <copyright file="EndUserAuthorizationSuccessResponseBase.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OAuth2.Messages { + using System; + using System.Collections.Generic; + using System.Diagnostics.Contracts; + using System.Security.Cryptography; + + using DotNetOpenAuth.Messaging; + 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, and to return the user + /// to the Client where they started their experience. + /// </summary> + public abstract class EndUserAuthorizationSuccessResponseBase : MessageBase, IMessageWithClientState { + /// <summary> + /// Initializes a new instance of the <see cref="EndUserAuthorizationSuccessResponseBase"/> 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 EndUserAuthorizationSuccessResponseBase(Uri clientCallback, Version version) + : base(version, MessageTransport.Indirect, clientCallback) { + Requires.NotNull(version, "version"); + Requires.NotNull(clientCallback, "clientCallback"); + this.Scope = new HashSet<string>(OAuthUtilities.ScopeStringComparer); + } + + /// <summary> + /// Initializes a new instance of the <see cref="EndUserAuthorizationSuccessResponseBase"/> 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 EndUserAuthorizationSuccessResponseBase(Uri clientCallback, EndUserAuthorizationRequest request) + : base(request, clientCallback) { + Requires.NotNull(clientCallback, "clientCallback"); + Requires.NotNull(request, "request"); + ((IMessageWithClientState)this).ClientState = request.ClientState; + this.Scope = new HashSet<string>(OAuthUtilities.ScopeStringComparer); + this.Scope.ResetContents(request.Scope); + } + + /// <summary> + /// Gets or sets some state as provided by the client in the authorization request. + /// </summary> + /// <value>An opaque value defined by the client.</value> + /// <remarks> + /// REQUIRED if the Client sent the value in the <see cref="EndUserAuthorizationRequest"/>. + /// </remarks> + [MessagePart(Protocol.state, IsRequired = false)] + string IMessageWithClientState.ClientState { get; set; } + + /// <summary> + /// Gets or sets the scope of the <see cref="AccessToken"/> if one is given; otherwise the scope of the authorization code. + /// </summary> + /// <value>The scope.</value> + public ICollection<string> Scope { get; protected set; } + + /// <summary> + /// Gets or sets the authorizing user's account name. + /// </summary> + internal string AuthorizingUsername { get; set; } + } +} diff --git a/src/DotNetOpenAuth/OAuth2/Messages/GrantType.cs b/src/DotNetOpenAuth.OAuth2/OAuth2/Messages/GrantType.cs index 4580a7f..4580a7f 100644 --- a/src/DotNetOpenAuth/OAuth2/Messages/GrantType.cs +++ b/src/DotNetOpenAuth.OAuth2/OAuth2/Messages/GrantType.cs diff --git a/src/DotNetOpenAuth/OAuth2/Messages/IAccessTokenRequest.cs b/src/DotNetOpenAuth.OAuth2/OAuth2/Messages/IAccessTokenRequest.cs index 7246beb..7246beb 100644 --- a/src/DotNetOpenAuth/OAuth2/Messages/IAccessTokenRequest.cs +++ b/src/DotNetOpenAuth.OAuth2/OAuth2/Messages/IAccessTokenRequest.cs diff --git a/src/DotNetOpenAuth/OAuth2/Messages/IMessageWithClientState.cs b/src/DotNetOpenAuth.OAuth2/OAuth2/Messages/IMessageWithClientState.cs index fa371ae..fa371ae 100644 --- a/src/DotNetOpenAuth/OAuth2/Messages/IMessageWithClientState.cs +++ b/src/DotNetOpenAuth.OAuth2/OAuth2/Messages/IMessageWithClientState.cs diff --git a/src/DotNetOpenAuth.OAuth2/OAuth2/Messages/MessageBase.cs b/src/DotNetOpenAuth.OAuth2/OAuth2/Messages/MessageBase.cs new file mode 100644 index 0000000..c5e438f --- /dev/null +++ b/src/DotNetOpenAuth.OAuth2/OAuth2/Messages/MessageBase.cs @@ -0,0 +1,239 @@ +//----------------------------------------------------------------------- +// <copyright file="MessageBase.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OAuth2.Messages { + using System; + using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; + using System.Diagnostics.Contracts; + using DotNetOpenAuth.Messaging; + + /// <summary> + /// A common message base class for OAuth messages. + /// </summary> + [Serializable] + public class MessageBase : IDirectedProtocolMessage, IDirectResponseProtocolMessage { + /// <summary> + /// A dictionary to contain extra message data. + /// </summary> + private Dictionary<string, string> extraData = new Dictionary<string, string>(); + + /// <summary> + /// The originating request. + /// </summary> + private IDirectedProtocolMessage originatingRequest; + + /// <summary> + /// The backing field for the <see cref="IMessage.Version"/> property. + /// </summary> + private Version version; + + /// <summary> + /// A value indicating whether this message is a direct or indirect message. + /// </summary> + private MessageTransport messageTransport; + + /// <summary> + /// Initializes a new instance of the <see cref="MessageBase"/> class + /// that is used for direct response messages. + /// </summary> + /// <param name="version">The version.</param> + protected MessageBase(Version version) { + Requires.NotNull(version, "version"); + this.messageTransport = MessageTransport.Direct; + this.version = version; + this.HttpMethods = HttpDeliveryMethods.GetRequest; + } + + /// <summary> + /// Initializes a new instance of the <see cref="MessageBase"/> class. + /// </summary> + /// <param name="request">The originating request.</param> + /// <param name="recipient">The recipient of the directed message. Null if not applicable.</param> + protected MessageBase(IDirectedProtocolMessage request, Uri recipient = null) { + Requires.NotNull(request, "request"); + this.originatingRequest = request; + this.messageTransport = request.Transport; + this.version = request.Version; + this.Recipient = recipient; + this.HttpMethods = HttpDeliveryMethods.GetRequest; + } + + /// <summary> + /// Initializes a new instance of the <see cref="MessageBase"/> class + /// that is used for directed messages. + /// </summary> + /// <param name="version">The version.</param> + /// <param name="messageTransport">The message transport.</param> + /// <param name="recipient">The recipient.</param> + protected MessageBase(Version version, MessageTransport messageTransport, Uri recipient) { + Requires.NotNull(version, "version"); + Requires.NotNull(recipient, "recipient"); + + this.version = version; + this.messageTransport = messageTransport; + this.Recipient = recipient; + this.HttpMethods = HttpDeliveryMethods.GetRequest | HttpDeliveryMethods.PostRequest; + } + + #region IMessage Properties + + /// <summary> + /// Gets the version of the protocol or extension this message is prepared to implement. + /// </summary> + /// <remarks> + /// Implementations of this interface should ensure that this property never returns null. + /// </remarks> + Version IMessage.Version { + get { return this.Version; } + } + + /// <summary> + /// Gets the extra, non-standard Protocol parameters included in the message. + /// </summary> + /// <value></value> + /// <remarks> + /// Implementations of this interface should ensure that this property never returns null. + /// </remarks> + public IDictionary<string, string> ExtraData { + get { return this.extraData; } + } + + #endregion + + #region IProtocolMessage Members + + /// <summary> + /// Gets the level of protection this message requires. + /// </summary> + /// <value><see cref="MessageProtections.None"/></value> + MessageProtections IProtocolMessage.RequiredProtection { + get { return RequiredProtection; } + } + + /// <summary> + /// Gets a value indicating whether this is a direct or indirect message. + /// </summary> + /// <value></value> + MessageTransport IProtocolMessage.Transport { + get { return this.Transport; } + } + + #endregion + + #region IDirectedProtocolMessage Members + + /// <summary> + /// Gets the preferred method of transport for the message. + /// </summary> + /// <remarks> + /// For indirect messages this will likely be GET+POST, which both can be simulated in the user agent: + /// the GET with a simple 301 Redirect, and the POST with an HTML form in the response with javascript + /// to automate submission. + /// </remarks> + HttpDeliveryMethods IDirectedProtocolMessage.HttpMethods { + get { return this.HttpMethods; } + } + + /// <summary> + /// Gets the URL of the intended receiver of this message. + /// </summary> + Uri IDirectedProtocolMessage.Recipient { + get { return this.Recipient; } + } + + #endregion + + #region IDirectResponseProtocolMessage Members + + /// <summary> + /// Gets the originating request message that caused this response to be formed. + /// </summary> + IDirectedProtocolMessage IDirectResponseProtocolMessage.OriginatingRequest { + get { return this.OriginatingRequest; } + } + + #endregion + + /// <summary> + /// Gets the level of protection this message requires. + /// </summary> + protected static MessageProtections RequiredProtection { + get { return MessageProtections.None; } + } + + /// <summary> + /// Gets a value indicating whether this is a direct or indirect message. + /// </summary> + protected MessageTransport Transport { + get { return this.messageTransport; } + } + + /// <summary> + /// Gets the version of the protocol or extension this message is prepared to implement. + /// </summary> + protected Version Version { + get { return this.version; } + } + + /// <summary> + /// Gets or sets the preferred method of transport for the message. + /// </summary> + /// <remarks> + /// For indirect messages this will likely be GET+POST, which both can be simulated in the user agent: + /// the GET with a simple 301 Redirect, and the POST with an HTML form in the response with javascript + /// to automate submission. + /// </remarks> + protected HttpDeliveryMethods HttpMethods { get; set; } + + /// <summary> + /// Gets the originating request message that caused this response to be formed. + /// </summary> + protected IDirectedProtocolMessage OriginatingRequest { + get { return this.originatingRequest; } + } + + /// <summary> + /// Gets the URL of the intended receiver of this message. + /// </summary> + protected Uri Recipient { get; private set; } + + #region IMessage Methods + + /// <summary> + /// Checks the message state for conformity to the protocol specification + /// and throws an exception if the message is invalid. + /// </summary> + /// <remarks> + /// <para>Some messages have required fields, or combinations of fields that must relate to each other + /// in specialized ways. After deserializing a message, this method checks the state of the + /// message to see if it conforms to the protocol.</para> + /// <para>Note that this property should <i>not</i> check signatures or perform any state checks + /// outside this scope of this particular message.</para> + /// </remarks> + /// <exception cref="ProtocolException">Thrown if the message is invalid.</exception> + void IMessage.EnsureValidMessage() { + this.EnsureValidMessage(); + } + + #endregion + + /// <summary> + /// Checks the message state for conformity to the protocol specification + /// and throws an exception if the message is invalid. + /// </summary> + /// <remarks> + /// <para>Some messages have required fields, or combinations of fields that must relate to each other + /// in specialized ways. After deserializing a message, this method checks the state of the + /// message to see if it conforms to the protocol.</para> + /// <para>Note that this property should <i>not</i> check signatures or perform any state checks + /// outside this scope of this particular message.</para> + /// </remarks> + /// <exception cref="ProtocolException">Thrown if the message is invalid.</exception> + protected virtual void EnsureValidMessage() { + } + } +} diff --git a/src/DotNetOpenAuth/OAuth2/Messages/OAuth 2 Messages.cd b/src/DotNetOpenAuth.OAuth2/OAuth2/Messages/OAuth 2 Messages.cd index 05e3ad9..05e3ad9 100644 --- a/src/DotNetOpenAuth/OAuth2/Messages/OAuth 2 Messages.cd +++ b/src/DotNetOpenAuth.OAuth2/OAuth2/Messages/OAuth 2 Messages.cd diff --git a/src/DotNetOpenAuth/OAuth2/Messages/ScopedAccessTokenRequest.cs b/src/DotNetOpenAuth.OAuth2/OAuth2/Messages/ScopedAccessTokenRequest.cs index 5568b1c..5568b1c 100644 --- a/src/DotNetOpenAuth/OAuth2/Messages/ScopedAccessTokenRequest.cs +++ b/src/DotNetOpenAuth.OAuth2/OAuth2/Messages/ScopedAccessTokenRequest.cs diff --git a/src/DotNetOpenAuth.OAuth2/OAuth2/Messages/UnauthorizedResponse.cs b/src/DotNetOpenAuth.OAuth2/OAuth2/Messages/UnauthorizedResponse.cs new file mode 100644 index 0000000..529fab2 --- /dev/null +++ b/src/DotNetOpenAuth.OAuth2/OAuth2/Messages/UnauthorizedResponse.cs @@ -0,0 +1,115 @@ +//----------------------------------------------------------------------- +// <copyright file="UnauthorizedResponse.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OAuth2.Messages { + using System; + using System.Diagnostics.Contracts; + using System.Net; + using System.Text; + using DotNetOpenAuth.Messaging; + + /// <summary> + /// A direct response that is simply a 401 Unauthorized with an + /// WWW-Authenticate: OAuth header. + /// </summary> + internal class UnauthorizedResponse : MessageBase, IHttpDirectResponse { + /// <summary> + /// Initializes a new instance of the <see cref="UnauthorizedResponse"/> class. + /// </summary> + /// <param name="exception">The exception.</param> + /// <param name="version">The protocol version.</param> + internal UnauthorizedResponse(ProtocolException exception, Version version = null) + : base(version ?? Protocol.Default.Version) { + Requires.NotNull(exception, "exception"); + this.ErrorMessage = exception.Message; + } + + /// <summary> + /// Initializes a new instance of the <see cref="UnauthorizedResponse"/> class. + /// </summary> + /// <param name="request">The request.</param> + internal UnauthorizedResponse(IDirectedProtocolMessage request) + : base(request) { + this.Realm = "Service"; + } + + /// <summary> + /// Initializes a new instance of the <see cref="UnauthorizedResponse"/> class. + /// </summary> + /// <param name="request">The request.</param> + /// <param name="exception">The exception.</param> + internal UnauthorizedResponse(IDirectedProtocolMessage request, ProtocolException exception) + : this(request) { + Requires.NotNull(exception, "exception"); + this.ErrorMessage = exception.Message; + } + + #region IHttpDirectResponse Members + + /// <summary> + /// Gets the HTTP status code that the direct response should be sent with. + /// </summary> + HttpStatusCode IHttpDirectResponse.HttpStatusCode { + get { return HttpStatusCode.Unauthorized; } + } + + /// <summary> + /// Gets the HTTP headers to add to the response. + /// </summary> + /// <value>May be an empty collection, but must not be <c>null</c>.</value> + WebHeaderCollection IHttpDirectResponse.Headers { + get { + return new WebHeaderCollection() { + { HttpResponseHeader.WwwAuthenticate, Protocol.BearerHttpAuthorizationScheme }, + }; + } + } + + #endregion + + /// <summary> + /// Gets or sets the error message. + /// </summary> + /// <value>The error message.</value> + [MessagePart("error")] + internal string ErrorMessage { get; set; } + + /// <summary> + /// Gets or sets the realm. + /// </summary> + /// <value>The realm.</value> + [MessagePart("realm")] + internal string Realm { get; set; } + + /// <summary> + /// Gets or sets the scope. + /// </summary> + /// <value>The scope.</value> + [MessagePart("scope")] + internal string Scope { get; set; } + + /// <summary> + /// Gets or sets the algorithms. + /// </summary> + /// <value>The algorithms.</value> + [MessagePart("algorithms")] + internal string Algorithms { get; set; } + + /// <summary> + /// Gets or sets the user endpoint. + /// </summary> + /// <value>The user endpoint.</value> + [MessagePart("user-uri")] + internal Uri UserEndpoint { get; set; } + + /// <summary> + /// Gets or sets the token endpoint. + /// </summary> + /// <value>The token endpoint.</value> + [MessagePart("token-uri")] + internal Uri TokenEndpoint { get; set; } + } +} diff --git a/src/DotNetOpenAuth/OAuth2/OAuth 2 client facades.cd b/src/DotNetOpenAuth.OAuth2/OAuth2/OAuth 2 client facades.cd index 97bf2eb..97bf2eb 100644 --- a/src/DotNetOpenAuth/OAuth2/OAuth 2 client facades.cd +++ b/src/DotNetOpenAuth.OAuth2/OAuth2/OAuth 2 client facades.cd diff --git a/src/DotNetOpenAuth/OAuth2/OAuthStrings.Designer.cs b/src/DotNetOpenAuth.OAuth2/OAuth2/OAuthStrings.Designer.cs index 9ea12ab..9ea12ab 100644 --- a/src/DotNetOpenAuth/OAuth2/OAuthStrings.Designer.cs +++ b/src/DotNetOpenAuth.OAuth2/OAuth2/OAuthStrings.Designer.cs diff --git a/src/DotNetOpenAuth/OAuth2/OAuthStrings.resx b/src/DotNetOpenAuth.OAuth2/OAuth2/OAuthStrings.resx index fc2f2d2..fc2f2d2 100644 --- a/src/DotNetOpenAuth/OAuth2/OAuthStrings.resx +++ b/src/DotNetOpenAuth.OAuth2/OAuth2/OAuthStrings.resx diff --git a/src/DotNetOpenAuth.OAuth2/OAuth2/OAuthUtilities.cs b/src/DotNetOpenAuth.OAuth2/OAuth2/OAuthUtilities.cs new file mode 100644 index 0000000..1ae3701 --- /dev/null +++ b/src/DotNetOpenAuth.OAuth2/OAuth2/OAuthUtilities.cs @@ -0,0 +1,123 @@ +//----------------------------------------------------------------------- +// <copyright file="OAuthUtilities.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.Globalization; + using System.Linq; + using System.Net; + using System.Text; + using DotNetOpenAuth.Messaging; + + /// <summary> + /// Some common utility methods for OAuth 2.0. + /// </summary> + public static class OAuthUtilities { + /// <summary> + /// The <see cref="StringComparer"/> instance to use when comparing scope equivalence. + /// </summary> + public static readonly StringComparer ScopeStringComparer = StringComparer.Ordinal; + + /// <summary> + /// The delimiter between scope elements. + /// </summary> + private static char[] scopeDelimiter = new char[] { ' ' }; + + /// <summary> + /// The characters that may appear in an access token that is included in an HTTP Authorization header. + /// </summary> + /// <remarks> + /// This is defined in OAuth 2.0 DRAFT 10, section 5.1.1. (http://tools.ietf.org/id/draft-ietf-oauth-v2-10.html#authz-header) + /// </remarks> + private static string accessTokenAuthorizationHeaderAllowedCharacters = MessagingUtilities.UppercaseLetters + + MessagingUtilities.LowercaseLetters + + MessagingUtilities.Digits + + @"!#$%&'()*+-./:<=>?@[]^_`{|}~\,;"; + + /// <summary> + /// Determines whether one given scope is a subset of another scope. + /// </summary> + /// <param name="requestedScope">The requested scope, which may be a subset of <paramref name="grantedScope"/>.</param> + /// <param name="grantedScope">The granted scope, the suspected superset.</param> + /// <returns> + /// <c>true</c> if all the elements that appear in <paramref name="requestedScope"/> also appear in <paramref name="grantedScope"/>; + /// <c>false</c> otherwise. + /// </returns> + public static bool IsScopeSubset(string requestedScope, string grantedScope) { + if (string.IsNullOrEmpty(requestedScope)) { + return true; + } + + if (string.IsNullOrEmpty(grantedScope)) { + return false; + } + + var requestedScopes = new HashSet<string>(requestedScope.Split(scopeDelimiter, StringSplitOptions.RemoveEmptyEntries)); + var grantedScopes = new HashSet<string>(grantedScope.Split(scopeDelimiter, StringSplitOptions.RemoveEmptyEntries)); + return requestedScopes.IsSubsetOf(grantedScopes); + } + + /// <summary> + /// Identifies individual scope elements + /// </summary> + /// <param name="scope">The space-delimited list of scopes.</param> + /// <returns>A set of individual scopes, with any duplicates removed.</returns> + public static HashSet<string> SplitScopes(string scope) { + if (string.IsNullOrEmpty(scope)) { + return new HashSet<string>(); + } + + return new HashSet<string>(scope.Split(scopeDelimiter, StringSplitOptions.RemoveEmptyEntries), ScopeStringComparer); + } + + /// <summary> + /// Serializes a set of scopes as a space-delimited list. + /// </summary> + /// <param name="scopes">The scopes to serialize.</param> + /// <returns>A space-delimited list.</returns> + public static string JoinScopes(HashSet<string> scopes) { + Requires.NotNull(scopes, "scopes"); + return string.Join(" ", scopes.ToArray()); + } + + /// <summary> + /// Authorizes an HTTP request using an OAuth 2.0 access token in an HTTP Authorization header. + /// </summary> + /// <param name="request">The request to authorize.</param> + /// <param name="accessToken">The access token previously obtained from the Authorization Server.</param> + internal static void AuthorizeWithBearerToken(this HttpWebRequest request, string accessToken) { + Requires.NotNull(request, "request"); + Requires.NotNullOrEmpty(accessToken, "accessToken"); + ErrorUtilities.VerifyProtocol(accessToken.All(ch => accessTokenAuthorizationHeaderAllowedCharacters.IndexOf(ch) >= 0), "The access token contains characters that must not appear in the HTTP Authorization header."); + + request.Headers[HttpRequestHeader.Authorization] = string.Format( + CultureInfo.InvariantCulture, + Protocol.BearerHttpAuthorizationHeaderFormat, + accessToken); + } + + /// <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 IConsumerDescription GetClientOrThrow(this IAuthorizationServer authorizationServer, string clientIdentifier) { + Requires.NotNullOrEmpty(clientIdentifier, "clientIdentifier"); + Contract.Ensures(Contract.Result<IConsumerDescription>() != null); + + try { + return authorizationServer.GetClient(clientIdentifier); + } catch (KeyNotFoundException ex) { + throw ErrorUtilities.Wrap(ex, OAuth.OAuthStrings.ConsumerOrTokenSecretNotFound); + } catch (ArgumentException ex) { + throw ErrorUtilities.Wrap(ex, OAuth.OAuthStrings.ConsumerOrTokenSecretNotFound); + } + } + } +} diff --git a/src/DotNetOpenAuth/OAuth2/Protocol.cs b/src/DotNetOpenAuth.OAuth2/OAuth2/Protocol.cs index 3cb8253..3cb8253 100644 --- a/src/DotNetOpenAuth/OAuth2/Protocol.cs +++ b/src/DotNetOpenAuth.OAuth2/OAuth2/Protocol.cs diff --git a/src/DotNetOpenAuth.OAuth2/OAuth2/StandardAccessTokenAnalyzer.cs b/src/DotNetOpenAuth.OAuth2/OAuth2/StandardAccessTokenAnalyzer.cs new file mode 100644 index 0000000..f2b7d1c --- /dev/null +++ b/src/DotNetOpenAuth.OAuth2/OAuth2/StandardAccessTokenAnalyzer.cs @@ -0,0 +1,66 @@ +//----------------------------------------------------------------------- +// <copyright file="StandardAccessTokenAnalyzer.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.Security.Cryptography; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OAuth2.ChannelElements; + + /// <summary> + /// An access token reader that understands DotNetOpenAuth authorization server issued tokens. + /// </summary> + public class StandardAccessTokenAnalyzer : IAccessTokenAnalyzer { + /// <summary> + /// Initializes a new instance of the <see cref="StandardAccessTokenAnalyzer"/> class. + /// </summary> + /// <param name="authorizationServerPublicSigningKey">The crypto service provider with the authorization server public signing key.</param> + /// <param name="resourceServerPrivateEncryptionKey">The crypto service provider with the resource server private encryption key.</param> + public StandardAccessTokenAnalyzer(RSACryptoServiceProvider authorizationServerPublicSigningKey, RSACryptoServiceProvider resourceServerPrivateEncryptionKey) { + Requires.NotNull(authorizationServerPublicSigningKey, "authorizationServerPublicSigningKey"); + Requires.NotNull(resourceServerPrivateEncryptionKey, "resourceServerPrivateEncryptionKey"); + Requires.True(!resourceServerPrivateEncryptionKey.PublicOnly, "resourceServerPrivateEncryptionKey"); + this.AuthorizationServerPublicSigningKey = authorizationServerPublicSigningKey; + this.ResourceServerPrivateEncryptionKey = resourceServerPrivateEncryptionKey; + } + + /// <summary> + /// Gets the authorization server public signing key. + /// </summary> + /// <value>The authorization server public signing key.</value> + public RSACryptoServiceProvider AuthorizationServerPublicSigningKey { get; private set; } + + /// <summary> + /// Gets the resource server private encryption key. + /// </summary> + /// <value>The resource server private encryption key.</value> + public RSACryptoServiceProvider ResourceServerPrivateEncryptionKey { get; private set; } + + /// <summary> + /// Reads an access token to find out what data it authorizes access to. + /// </summary> + /// <param name="message">The message carrying the access token.</param> + /// <param name="accessToken">The access token.</param> + /// <param name="user">The user whose data is accessible with this access token.</param> + /// <param name="scope">The scope of access authorized by this access token.</param> + /// <returns> + /// A value indicating whether this access token is valid. + /// </returns> + /// <remarks> + /// This method also responsible to throw a <see cref="ProtocolException"/> or return + /// <c>false</c> when the access token is expired, invalid, or from an untrusted authorization server. + /// </remarks> + public virtual bool TryValidateAccessToken(IDirectedProtocolMessage message, string accessToken, out string user, out HashSet<string> scope) { + var accessTokenFormatter = AccessToken.CreateFormatter(this.AuthorizationServerPublicSigningKey, this.ResourceServerPrivateEncryptionKey); + var token = accessTokenFormatter.Deserialize(message, accessToken); + user = token.User; + scope = new HashSet<string>(token.Scope, OAuthUtilities.ScopeStringComparer); + return true; + } + } +} diff --git a/src/DotNetOpenAuth.OAuth2/Properties/AssemblyInfo.cs b/src/DotNetOpenAuth.OAuth2/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..5c7b883 --- /dev/null +++ b/src/DotNetOpenAuth.OAuth2/Properties/AssemblyInfo.cs @@ -0,0 +1,60 @@ +//----------------------------------------------------------------------- +// <copyright file="AssemblyInfo.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +// We DON'T put an AssemblyVersionAttribute in here because it is generated in the build. + +using System; +using System.Diagnostics.Contracts; +using System.Net; +using System.Reflection; +using System.Resources; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Security; +using System.Security.Permissions; +using System.Web.UI; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("DotNetOpenAuth OAuth 2.0")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("DotNetOpenAuth")] +[assembly: AssemblyCopyright("Copyright © 2008")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: NeutralResourcesLanguage("en-US")] +[assembly: CLSCompliant(true)] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("7d73990c-47c0-4256-9f20-a893add9e289")] + +[assembly: ContractVerification(true)] + +#if StrongNameSigned +// See comment at top of this file. We need this so that strong-naming doesn't +// keep this assembly from being useful to shared host (medium trust) web sites. +[assembly: AllowPartiallyTrustedCallers] + +[assembly: InternalsVisibleTo("DotNetOpenAuth.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100AD093C3765257C89A7010E853F2C7C741FF92FA8ACE06D7B8254702CAD5CF99104447F63AB05F8BB6F51CE0D81C8C93D2FCE8C20AAFF7042E721CBA16EAAE98778611DED11C0ABC8900DC5667F99B50A9DADEC24DBD8F2C91E3E8AD300EF64F1B4B9536CEB16FB440AF939F57624A9B486F867807C649AE4830EAB88C6C03998")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.OAuth2.AuthorizationServer, PublicKey=0024000004800000940000000602000000240000525341310004000001000100AD093C3765257C89A7010E853F2C7C741FF92FA8ACE06D7B8254702CAD5CF99104447F63AB05F8BB6F51CE0D81C8C93D2FCE8C20AAFF7042E721CBA16EAAE98778611DED11C0ABC8900DC5667F99B50A9DADEC24DBD8F2C91E3E8AD300EF64F1B4B9536CEB16FB440AF939F57624A9B486F867807C649AE4830EAB88C6C03998")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.OAuth2.ResourceServer, PublicKey=0024000004800000940000000602000000240000525341310004000001000100AD093C3765257C89A7010E853F2C7C741FF92FA8ACE06D7B8254702CAD5CF99104447F63AB05F8BB6F51CE0D81C8C93D2FCE8C20AAFF7042E721CBA16EAAE98778611DED11C0ABC8900DC5667F99B50A9DADEC24DBD8F2C91E3E8AD300EF64F1B4B9536CEB16FB440AF939F57624A9B486F867807C649AE4830EAB88C6C03998")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.OAuth2.Client, PublicKey=0024000004800000940000000602000000240000525341310004000001000100AD093C3765257C89A7010E853F2C7C741FF92FA8ACE06D7B8254702CAD5CF99104447F63AB05F8BB6F51CE0D81C8C93D2FCE8C20AAFF7042E721CBA16EAAE98778611DED11C0ABC8900DC5667F99B50A9DADEC24DBD8F2C91E3E8AD300EF64F1B4B9536CEB16FB440AF939F57624A9B486F867807C649AE4830EAB88C6C03998")] +[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")] +#else +[assembly: InternalsVisibleTo("DotNetOpenAuth.Test")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.OAuth2.AuthorizationServer")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.OAuth2.ResourceServer")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.OAuth2.Client")] +[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] +#endif diff --git a/src/DotNetOpenAuth.OpenId.Provider.UI/DotNetOpenAuth.OpenId.Provider.UI.csproj b/src/DotNetOpenAuth.OpenId.Provider.UI/DotNetOpenAuth.OpenId.Provider.UI.csproj new file mode 100644 index 0000000..aede485 --- /dev/null +++ b/src/DotNetOpenAuth.OpenId.Provider.UI/DotNetOpenAuth.OpenId.Provider.UI.csproj @@ -0,0 +1,59 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), EnlistmentInfo.props))\EnlistmentInfo.props" Condition=" '$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), EnlistmentInfo.props))' != '' " /> + <PropertyGroup> + <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> + <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> + </PropertyGroup> + <Import Project="$(ProjectRoot)tools\DotNetOpenAuth.props" /> + <PropertyGroup> + <SchemaVersion>2.0</SchemaVersion> + <ProjectGuid>{9D0F8866-2131-4C2A-BC0E-16FEA5B50828}</ProjectGuid> + <OutputType>Library</OutputType> + <AppDesignerFolder>Properties</AppDesignerFolder> + <RootNamespace>DotNetOpenAuth</RootNamespace> + <AssemblyName>DotNetOpenAuth.OpenId.Provider.UI</AssemblyName> + </PropertyGroup> + <Import Project="$(ProjectRoot)tools\DotNetOpenAuth.Product.props" /> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> + </PropertyGroup> + <ItemGroup> + <Compile Include="OpenId\Provider\AnonymousRequestEventArgs.cs" /> + <Compile Include="OpenId\Provider\AuthenticationChallengeEventArgs.cs" /> + <Compile Include="OpenId\Provider\IdentityEndpoint.cs" /> + <Compile Include="OpenId\Provider\IdentityEndpointNormalizationEventArgs.cs" /> + <Compile Include="OpenId\Provider\ProviderEndpoint.cs" /> + <Compile Include="Properties\AssemblyInfo.cs" /> + </ItemGroup> + <ItemGroup> + <ProjectReference Include="..\DotNetOpenAuth.Core\DotNetOpenAuth.Core.csproj"> + <Project>{60426312-6AE5-4835-8667-37EDEA670222}</Project> + <Name>DotNetOpenAuth.Core</Name> + </ProjectReference> + <ProjectReference Include="..\DotNetOpenAuth.OpenId.Provider\DotNetOpenAuth.OpenId.Provider.csproj"> + <Project>{F8284738-3B5D-4733-A511-38C23F4A763F}</Project> + <Name>DotNetOpenAuth.OpenId.Provider</Name> + </ProjectReference> + <ProjectReference Include="..\DotNetOpenAuth.OpenId.UI\DotNetOpenAuth.OpenId.UI.csproj"> + <Project>{75E13AAE-7D51-4421-ABFD-3F3DC91F576E}</Project> + <Name>DotNetOpenAuth.OpenId.UI</Name> + </ProjectReference> + <ProjectReference Include="..\DotNetOpenAuth.OpenId\DotNetOpenAuth.OpenId.csproj"> + <Project>{3896A32A-E876-4C23-B9B8-78E17D134CD3}</Project> + <Name>DotNetOpenAuth.OpenId</Name> + </ProjectReference> + <ProjectReference Include="..\Org.Mentalis.Security.Cryptography\Org.Mentalis.Security.Cryptography.csproj"> + <Project>{26DC877F-5987-48DD-9DDB-E62F2DE0E150}</Project> + <Name>Org.Mentalis.Security.Cryptography</Name> + </ProjectReference> + </ItemGroup> + <ItemGroup> + <Reference Include="System" /> + </ItemGroup> + <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))' != '' " /> +</Project>
\ No newline at end of file diff --git a/src/DotNetOpenAuth.OpenId.Provider.UI/OpenId/Provider/AnonymousRequestEventArgs.cs b/src/DotNetOpenAuth.OpenId.Provider.UI/OpenId/Provider/AnonymousRequestEventArgs.cs new file mode 100644 index 0000000..2cf680d --- /dev/null +++ b/src/DotNetOpenAuth.OpenId.Provider.UI/OpenId/Provider/AnonymousRequestEventArgs.cs @@ -0,0 +1,31 @@ +//----------------------------------------------------------------------- +// <copyright file="AnonymousRequestEventArgs.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.Provider { + using System; + using System.Diagnostics.Contracts; + using DotNetOpenAuth.Messaging; + + /// <summary> + /// The event arguments that include details of the incoming request. + /// </summary> + public class AnonymousRequestEventArgs : EventArgs { + /// <summary> + /// Initializes a new instance of the <see cref="AnonymousRequestEventArgs"/> class. + /// </summary> + /// <param name="request">The incoming OpenID request.</param> + internal AnonymousRequestEventArgs(IAnonymousRequest request) { + Requires.NotNull(request, "request"); + + this.Request = request; + } + + /// <summary> + /// Gets the incoming OpenID request. + /// </summary> + public IAnonymousRequest Request { get; private set; } + } +} diff --git a/src/DotNetOpenAuth/OpenId/Provider/AuthenticationChallengeEventArgs.cs b/src/DotNetOpenAuth.OpenId.Provider.UI/OpenId/Provider/AuthenticationChallengeEventArgs.cs index 1594994..1594994 100644 --- a/src/DotNetOpenAuth/OpenId/Provider/AuthenticationChallengeEventArgs.cs +++ b/src/DotNetOpenAuth.OpenId.Provider.UI/OpenId/Provider/AuthenticationChallengeEventArgs.cs diff --git a/src/DotNetOpenAuth/OpenId/Provider/IdentityEndpoint.cs b/src/DotNetOpenAuth.OpenId.Provider.UI/OpenId/Provider/IdentityEndpoint.cs index 3a18b70..3a18b70 100644 --- a/src/DotNetOpenAuth/OpenId/Provider/IdentityEndpoint.cs +++ b/src/DotNetOpenAuth.OpenId.Provider.UI/OpenId/Provider/IdentityEndpoint.cs diff --git a/src/DotNetOpenAuth/OpenId/Provider/IdentityEndpointNormalizationEventArgs.cs b/src/DotNetOpenAuth.OpenId.Provider.UI/OpenId/Provider/IdentityEndpointNormalizationEventArgs.cs index d190792..d190792 100644 --- a/src/DotNetOpenAuth/OpenId/Provider/IdentityEndpointNormalizationEventArgs.cs +++ b/src/DotNetOpenAuth.OpenId.Provider.UI/OpenId/Provider/IdentityEndpointNormalizationEventArgs.cs diff --git a/src/DotNetOpenAuth.OpenId.Provider.UI/OpenId/Provider/ProviderEndpoint.cs b/src/DotNetOpenAuth.OpenId.Provider.UI/OpenId/Provider/ProviderEndpoint.cs new file mode 100644 index 0000000..d0dee75 --- /dev/null +++ b/src/DotNetOpenAuth.OpenId.Provider.UI/OpenId/Provider/ProviderEndpoint.cs @@ -0,0 +1,267 @@ +//----------------------------------------------------------------------- +// <copyright file="ProviderEndpoint.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.Provider { + using System; + using System.Collections.Generic; + using System.ComponentModel; + using System.Diagnostics.Contracts; + using System.Text; + using System.Web; + using System.Web.UI; + using System.Web.UI.WebControls; + using DotNetOpenAuth.Configuration; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OpenId.Messages; + + /// <summary> + /// An OpenID Provider control that automatically responds to certain + /// automated OpenID messages, and routes authentication requests to + /// custom code via an event handler. + /// </summary> + [DefaultEvent("AuthenticationChallenge")] + [ToolboxData("<{0}:ProviderEndpoint runat='server' />")] + public class ProviderEndpoint : Control { + /// <summary> + /// The key used to store the pending authentication request in the ASP.NET session. + /// </summary> + private const string PendingRequestKey = "pendingRequest"; + + /// <summary> + /// The default value for the <see cref="Enabled"/> property. + /// </summary> + private const bool EnabledDefault = true; + + /// <summary> + /// The view state key in which to store the value of the <see cref="Enabled"/> property. + /// </summary> + private const string EnabledViewStateKey = "Enabled"; + + /// <summary> + /// Backing field for the <see cref="Provider"/> property. + /// </summary> + private static OpenIdProvider provider; + + /// <summary> + /// The lock that must be obtained when initializing the provider field. + /// </summary> + private static object providerInitializerLock = new object(); + + /// <summary> + /// Fired when an incoming OpenID request is an authentication challenge + /// that must be responded to by the Provider web site according to its + /// own user database and policies. + /// </summary> + public event EventHandler<AuthenticationChallengeEventArgs> AuthenticationChallenge; + + /// <summary> + /// Fired when an incoming OpenID message carries extension requests + /// but is not regarding any OpenID identifier. + /// </summary> + public event EventHandler<AnonymousRequestEventArgs> AnonymousRequest; + + /// <summary> + /// Gets or sets the <see cref="OpenIdProvider"/> instance to use for all instances of this control. + /// </summary> + /// <value>The default value is an <see cref="OpenIdProvider"/> instance initialized according to the web.config file.</value> + public static OpenIdProvider Provider { + get { + Contract.Ensures(Contract.Result<OpenIdProvider>() != null); + if (provider == null) { + lock (providerInitializerLock) { + if (provider == null) { + provider = CreateProvider(); + } + } + } + + return provider; + } + + set { + Requires.NotNull(value, "value"); + provider = value; + } + } + + /// <summary> + /// Gets or sets an incoming OpenID authentication request that has not yet been responded to. + /// </summary> + /// <remarks> + /// This request is stored in the ASP.NET Session state, so it will survive across + /// redirects, postbacks, and transfers. This allows you to authenticate the user + /// yourself, and confirm his/her desire to authenticate to the relying party site + /// before responding to the relying party's authentication request. + /// </remarks> + public static IAuthenticationRequest PendingAuthenticationRequest { + get { + Requires.ValidState(HttpContext.Current != null, MessagingStrings.HttpContextRequired); + Requires.ValidState(HttpContext.Current.Session != null, MessagingStrings.SessionRequired); + Contract.Ensures(Contract.Result<IAuthenticationRequest>() == null || PendingRequest != null); + return HttpContext.Current.Session[PendingRequestKey] as IAuthenticationRequest; + } + + set { + Requires.ValidState(HttpContext.Current != null, MessagingStrings.HttpContextRequired); + Requires.ValidState(HttpContext.Current.Session != null, MessagingStrings.SessionRequired); + HttpContext.Current.Session[PendingRequestKey] = value; + } + } + + /// <summary> + /// Gets or sets an incoming OpenID anonymous request that has not yet been responded to. + /// </summary> + /// <remarks> + /// This request is stored in the ASP.NET Session state, so it will survive across + /// redirects, postbacks, and transfers. This allows you to authenticate the user + /// yourself, and confirm his/her desire to provide data to the relying party site + /// before responding to the relying party's request. + /// </remarks> + public static IAnonymousRequest PendingAnonymousRequest { + get { + Requires.ValidState(HttpContext.Current != null, MessagingStrings.HttpContextRequired); + Requires.ValidState(HttpContext.Current.Session != null, MessagingStrings.SessionRequired); + Contract.Ensures(Contract.Result<IAnonymousRequest>() == null || PendingRequest != null); + return HttpContext.Current.Session[PendingRequestKey] as IAnonymousRequest; + } + + set { + Requires.ValidState(HttpContext.Current != null, MessagingStrings.HttpContextRequired); + Requires.ValidState(HttpContext.Current.Session != null, MessagingStrings.SessionRequired); + HttpContext.Current.Session[PendingRequestKey] = value; + } + } + + /// <summary> + /// Gets or sets an incoming OpenID request that has not yet been responded to. + /// </summary> + /// <remarks> + /// This request is stored in the ASP.NET Session state, so it will survive across + /// redirects, postbacks, and transfers. This allows you to authenticate the user + /// yourself, and confirm his/her desire to provide data to the relying party site + /// before responding to the relying party's request. + /// </remarks> + public static IHostProcessedRequest PendingRequest { + get { + Requires.ValidState(HttpContext.Current != null, MessagingStrings.HttpContextRequired); + Requires.ValidState(HttpContext.Current.Session != null, MessagingStrings.SessionRequired); + return HttpContext.Current.Session[PendingRequestKey] as IHostProcessedRequest; + } + + set { + Requires.ValidState(HttpContext.Current != null, MessagingStrings.HttpContextRequired); + Requires.ValidState(HttpContext.Current.Session != null, MessagingStrings.SessionRequired); + HttpContext.Current.Session[PendingRequestKey] = value; + } + } + + /// <summary> + /// Gets or sets a value indicating whether or not this control should + /// be listening for and responding to incoming OpenID requests. + /// </summary> + [Category("Behavior"), DefaultValue(EnabledDefault)] + public bool Enabled { + get { + return ViewState[EnabledViewStateKey] == null ? + EnabledDefault : (bool)ViewState[EnabledViewStateKey]; + } + + set { + ViewState[EnabledViewStateKey] = value; + } + } + + /// <summary> + /// Sends the response for the <see cref="PendingAuthenticationRequest"/> and clears the property. + /// </summary> + public static void SendResponse() { + var pendingRequest = PendingRequest; + PendingRequest = null; + Provider.SendResponse(pendingRequest); + } + + /// <summary> + /// Checks for incoming OpenID requests, responds to ones it can + /// respond to without policy checks, and fires events for custom + /// handling of the ones it cannot decide on automatically. + /// </summary> + /// <param name="e">The <see cref="T:System.EventArgs"/> object that contains the event data.</param> + protected override void OnLoad(EventArgs e) { + base.OnLoad(e); + + // There is the unusual scenario that this control is hosted by + // an ASP.NET web page that has other UI on it to that the user + // might see, including controls that cause a postback to occur. + // We definitely want to ignore postbacks, since any openid messages + // they contain will be old. + if (this.Enabled && !this.Page.IsPostBack) { + // Use the explicitly given state store on this control if there is one. + // Then try the configuration file specified one. Finally, use the default + // in-memory one that's built into OpenIdProvider. + // determine what incoming message was received + IRequest request = Provider.GetRequest(); + if (request != null) { + PendingRequest = null; + + // process the incoming message appropriately and send the response + IAuthenticationRequest idrequest; + IAnonymousRequest anonRequest; + if ((idrequest = request as IAuthenticationRequest) != null) { + PendingAuthenticationRequest = idrequest; + this.OnAuthenticationChallenge(idrequest); + } else if ((anonRequest = request as IAnonymousRequest) != null) { + PendingAnonymousRequest = anonRequest; + if (!this.OnAnonymousRequest(anonRequest)) { + // This is a feature not supported by the OP, so + // go ahead and set disapproved so we can send a response. + Logger.OpenId.Warn("An incoming anonymous OpenID request message was detected, but the ProviderEndpoint.AnonymousRequest event is not handled, so returning cancellation message to relying party."); + anonRequest.IsApproved = false; + } + } + if (request.IsResponseReady) { + PendingAuthenticationRequest = null; + Provider.SendResponse(request); + } + } + } + } + + /// <summary> + /// Fires the <see cref="AuthenticationChallenge"/> event. + /// </summary> + /// <param name="request">The request to include in the event args.</param> + protected virtual void OnAuthenticationChallenge(IAuthenticationRequest request) { + var authenticationChallenge = this.AuthenticationChallenge; + if (authenticationChallenge != null) { + authenticationChallenge(this, new AuthenticationChallengeEventArgs(request)); + } + } + + /// <summary> + /// Fires the <see cref="AnonymousRequest"/> event. + /// </summary> + /// <param name="request">The request to include in the event args.</param> + /// <returns><c>true</c> if there were any anonymous request handlers.</returns> + protected virtual bool OnAnonymousRequest(IAnonymousRequest request) { + var anonymousRequest = this.AnonymousRequest; + if (anonymousRequest != null) { + anonymousRequest(this, new AnonymousRequestEventArgs(request)); + return true; + } else { + return false; + } + } + + /// <summary> + /// Creates the default OpenIdProvider to use. + /// </summary> + /// <returns>The new instance of OpenIdProvider.</returns> + private static OpenIdProvider CreateProvider() { + Contract.Ensures(Contract.Result<OpenIdProvider>() != null); + return new OpenIdProvider(OpenIdElement.Configuration.Provider.ApplicationStore.CreateInstance(OpenIdProvider.HttpApplicationStore)); + } + } +} diff --git a/src/DotNetOpenAuth.OpenId.Provider.UI/Properties/AssemblyInfo.cs b/src/DotNetOpenAuth.OpenId.Provider.UI/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..2fbaebd --- /dev/null +++ b/src/DotNetOpenAuth.OpenId.Provider.UI/Properties/AssemblyInfo.cs @@ -0,0 +1,56 @@ +//----------------------------------------------------------------------- +// <copyright file="AssemblyInfo.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +// We DON'T put an AssemblyVersionAttribute in here because it is generated in the build. + +using System; +using System.Diagnostics.Contracts; +using System.Net; +using System.Reflection; +using System.Resources; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Security; +using System.Security.Permissions; +using System.Web.UI; + +[assembly: TagPrefix("DotNetOpenAuth.OpenId.Provider", "op")] + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("DotNetOpenAuth OpenID")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("DotNetOpenAuth")] +[assembly: AssemblyCopyright("Copyright © 2008")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: NeutralResourcesLanguage("en-US")] +[assembly: CLSCompliant(true)] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("7d73990c-47c0-4256-9f20-a893add9e289")] + +[assembly: ContractVerification(true)] + +#if StrongNameSigned +// See comment at top of this file. We need this so that strong-naming doesn't +// keep this assembly from being useful to shared host (medium trust) web sites. +[assembly: AllowPartiallyTrustedCallers] + +[assembly: InternalsVisibleTo("DotNetOpenAuth.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100AD093C3765257C89A7010E853F2C7C741FF92FA8ACE06D7B8254702CAD5CF99104447F63AB05F8BB6F51CE0D81C8C93D2FCE8C20AAFF7042E721CBA16EAAE98778611DED11C0ABC8900DC5667F99B50A9DADEC24DBD8F2C91E3E8AD300EF64F1B4B9536CEB16FB440AF939F57624A9B486F867807C649AE4830EAB88C6C03998")] +[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")] +#else +[assembly: InternalsVisibleTo("DotNetOpenAuth.Test")] +[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] +#endif diff --git a/src/DotNetOpenAuth.OpenId.Provider/DotNetOpenAuth.OpenId.Provider.csproj b/src/DotNetOpenAuth.OpenId.Provider/DotNetOpenAuth.OpenId.Provider.csproj new file mode 100644 index 0000000..433a8b6 --- /dev/null +++ b/src/DotNetOpenAuth.OpenId.Provider/DotNetOpenAuth.OpenId.Provider.csproj @@ -0,0 +1,88 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), EnlistmentInfo.props))\EnlistmentInfo.props" Condition=" '$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), EnlistmentInfo.props))' != '' " /> + <PropertyGroup> + <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> + <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> + </PropertyGroup> + <Import Project="$(ProjectRoot)tools\DotNetOpenAuth.props" /> + <PropertyGroup> + <SchemaVersion>2.0</SchemaVersion> + <ProjectGuid>{F8284738-3B5D-4733-A511-38C23F4A763F}</ProjectGuid> + <OutputType>Library</OutputType> + <AppDesignerFolder>Properties</AppDesignerFolder> + <RootNamespace>DotNetOpenAuth</RootNamespace> + <AssemblyName>DotNetOpenAuth.OpenId.Provider</AssemblyName> + </PropertyGroup> + <Import Project="$(ProjectRoot)tools\DotNetOpenAuth.Product.props" /> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> + </PropertyGroup> + <ItemGroup> + <Compile Include="OpenId\Provider\Behaviors\AXFetchAsSregTransform.cs" /> + <Compile Include="OpenId\Provider\Behaviors\GsaIcamProfile.cs" /> + <Compile Include="OpenId\Provider\Behaviors\PpidGeneration.cs" /> + <Compile Include="OpenId\ChannelElements\AssociateUnencryptedProviderRequest.cs" /> + <Compile Include="OpenId\ChannelElements\OpenIdProviderChannel.cs" /> + <Compile Include="OpenId\ChannelElements\OpenIdProviderMessageFactory.cs" /> + <Compile Include="OpenId\ChannelElements\ProviderSigningBindingElement.cs" /> + <Compile Include="OpenId\Provider\Extensions\ExtensionsInteropHelper.cs" /> + <Compile Include="OpenId\Provider\Extensions\UI\UIRequestTools.cs" /> + <Compile Include="OpenId\Provider\HmacShaAssociationProvider.cs" /> + <Compile Include="OpenId\Messages\AssociateDiffieHellmanProviderRequest.cs" /> + <Compile Include="OpenId\Messages\AssociateDiffieHellmanProviderResponse.cs" Condition=" '$(ExcludeDiffieHellman)' != 'true' " /> + <Compile Include="OpenId\Messages\IAssociateRequestProvider.cs" /> + <Compile Include="OpenId\Messages\AssociateRequestProviderTools.cs" /> + <Compile Include="OpenId\Messages\AssociateSuccessfulResponseProvider.cs" /> + <Compile Include="OpenId\Messages\AssociateSuccessfulResponseProviderContract.cs" /> + <Compile Include="OpenId\Messages\AssociateUnencryptedResponseProvider.cs" /> + <Compile Include="OpenId\Messages\CheckAuthenticationResponseProvider.cs" /> + <Compile Include="OpenId\Provider\OpenIdProviderUtilities.cs" /> + <Compile Include="OpenId\Provider\AssociationDataBag.cs" /> + <Compile Include="OpenId\Provider\IProviderAssociationStore.cs" /> + <Compile Include="OpenId\Provider\ProviderAssociationHandleEncoder.cs" /> + <Compile Include="OpenId\Provider\ProviderAssociationKeyStorage.cs" /> + <Compile Include="OpenId\Provider\AssociationRelyingPartyType.cs" /> + <Compile Include="OpenId\Provider\PrivatePersonalIdentifierProviderBase.cs" /> + <Compile Include="OpenId\Provider\AnonymousRequest.cs" /> + <Compile Include="OpenId\Provider\AuthenticationRequest.cs" /> + <Compile Include="OpenId\Provider\AutoResponsiveRequest.cs" /> + <Compile Include="OpenId\Provider\HostProcessedRequest.cs" /> + <Compile Include="OpenId\Provider\IAnonymousRequest.cs" /> + <Compile Include="OpenId\Provider\IDirectedIdentityIdentifierProvider.cs" /> + <Compile Include="OpenId\Provider\IErrorReporting.cs" /> + <Compile Include="OpenId\Provider\Request.cs" /> + <Compile Include="OpenId\Provider\RequestContract.cs" /> + <Compile Include="OpenId\Provider\StandardProviderApplicationStore.cs" /> + <Compile Include="OpenId\Provider\OpenIdProvider.cs" /> + <Compile Include="Properties\AssemblyInfo.cs" /> + </ItemGroup> + <ItemGroup> + <ProjectReference Include="..\DotNetOpenAuth.Core\DotNetOpenAuth.Core.csproj"> + <Project>{60426312-6AE5-4835-8667-37EDEA670222}</Project> + <Name>DotNetOpenAuth.Core</Name> + </ProjectReference> + <ProjectReference Include="..\DotNetOpenAuth.OpenId.RelyingParty\DotNetOpenAuth.OpenId.RelyingParty.csproj"> + <Project>{F458AB60-BA1C-43D9-8CEF-EC01B50BE87B}</Project> + <Name>DotNetOpenAuth.OpenId.RelyingParty</Name> + </ProjectReference> + <ProjectReference Include="..\DotNetOpenAuth.OpenId\DotNetOpenAuth.OpenId.csproj"> + <Project>{3896A32A-E876-4C23-B9B8-78E17D134CD3}</Project> + <Name>DotNetOpenAuth.OpenId</Name> + </ProjectReference> + <ProjectReference Include="..\Org.Mentalis.Security.Cryptography\Org.Mentalis.Security.Cryptography.csproj" Condition=" '$(ExcludeDiffieHellman)' != 'true' "> + <Project>{26DC877F-5987-48DD-9DDB-E62F2DE0E150}</Project> + <Name>Org.Mentalis.Security.Cryptography</Name> + </ProjectReference> + </ItemGroup> + <ItemGroup> + <Reference Include="System" /> + </ItemGroup> + <ItemGroup> + <Folder Include="Configuration\" /> + </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))' != '' " /> +</Project>
\ No newline at end of file diff --git a/src/DotNetOpenAuth.OpenId.Provider/OpenId/ChannelElements/AssociateUnencryptedProviderRequest.cs b/src/DotNetOpenAuth.OpenId.Provider/OpenId/ChannelElements/AssociateUnencryptedProviderRequest.cs new file mode 100644 index 0000000..322c435 --- /dev/null +++ b/src/DotNetOpenAuth.OpenId.Provider/OpenId/ChannelElements/AssociateUnencryptedProviderRequest.cs @@ -0,0 +1,41 @@ +//----------------------------------------------------------------------- +// <copyright file="AssociateUnencryptedProviderRequest.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.ChannelElements { + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OpenId.Messages; + + /// <summary> + /// Represents an association request received by the OpenID Provider that is sent using HTTPS and + /// otherwise communicates the shared secret in plain text. + /// </summary> + internal class AssociateUnencryptedProviderRequest : AssociateUnencryptedRequest, IAssociateRequestProvider { + /// <summary> + /// Initializes a new instance of the <see cref="AssociateUnencryptedProviderRequest"/> class. + /// </summary> + /// <param name="version">The OpenID version this message must comply with.</param> + /// <param name="providerEndpoint">The OpenID Provider endpoint.</param> + internal AssociateUnencryptedProviderRequest(Version version, Uri providerEndpoint) + : base(version, providerEndpoint) { + } + + /// <summary> + /// Creates a Provider's response to an incoming association request. + /// </summary> + /// <returns> + /// The appropriate association response message. + /// </returns> + public IProtocolMessage CreateResponseCore() { + var response = new AssociateUnencryptedResponseProvider(this.Version, this); + response.AssociationType = this.AssociationType; + return response; + } + } +} diff --git a/src/DotNetOpenAuth.OpenId.Provider/OpenId/ChannelElements/OpenIdProviderChannel.cs b/src/DotNetOpenAuth.OpenId.Provider/OpenId/ChannelElements/OpenIdProviderChannel.cs new file mode 100644 index 0000000..5812a96 --- /dev/null +++ b/src/DotNetOpenAuth.OpenId.Provider/OpenId/ChannelElements/OpenIdProviderChannel.cs @@ -0,0 +1,76 @@ +//----------------------------------------------------------------------- +// <copyright file="OpenIdProviderChannel.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.ChannelElements { + using System; + using System.Collections.Generic; + using System.Diagnostics.Contracts; + using System.Linq; + using System.Text; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.Messaging.Bindings; + using DotNetOpenAuth.OpenId.Extensions; + using DotNetOpenAuth.OpenId.Provider; + + /// <summary> + /// The messaging channel for OpenID Providers. + /// </summary> + internal class OpenIdProviderChannel : OpenIdChannel { + /// <summary> + /// Initializes a new instance of the <see cref="OpenIdProviderChannel"/> class. + /// </summary> + /// <param name="cryptoKeyStore">The OpenID Provider's association store or handle encoder.</param> + /// <param name="nonceStore">The nonce store to use.</param> + /// <param name="securitySettings">The security settings.</param> + internal OpenIdProviderChannel(IProviderAssociationStore cryptoKeyStore, INonceStore nonceStore, ProviderSecuritySettings securitySettings) + : this(cryptoKeyStore, nonceStore, new OpenIdProviderMessageFactory(), securitySettings) { + Requires.NotNull(cryptoKeyStore, "cryptoKeyStore"); + Requires.NotNull(securitySettings, "securitySettings"); + } + + /// <summary> + /// Initializes a new instance of the <see cref="OpenIdProviderChannel"/> class. + /// </summary> + /// <param name="cryptoKeyStore">The association store to use.</param> + /// <param name="nonceStore">The nonce store to use.</param> + /// <param name="messageTypeProvider">An object that knows how to distinguish the various OpenID message types for deserialization purposes.</param> + /// <param name="securitySettings">The security settings.</param> + private OpenIdProviderChannel(IProviderAssociationStore cryptoKeyStore, INonceStore nonceStore, IMessageFactory messageTypeProvider, ProviderSecuritySettings securitySettings) + : base(messageTypeProvider, InitializeBindingElements(cryptoKeyStore, nonceStore, securitySettings)) { + Requires.NotNull(cryptoKeyStore, "cryptoKeyStore"); + Requires.NotNull(messageTypeProvider, "messageTypeProvider"); + Requires.NotNull(securitySettings, "securitySettings"); + } + + /// <summary> + /// Initializes the binding elements. + /// </summary> + /// <param name="cryptoKeyStore">The OpenID Provider's crypto key store.</param> + /// <param name="nonceStore">The nonce store to use.</param> + /// <param name="securitySettings">The security settings to apply. Must be an instance of either RelyingPartySecuritySettings or ProviderSecuritySettings.</param> + /// <returns> + /// An array of binding elements which may be used to construct the channel. + /// </returns> + private static IChannelBindingElement[] InitializeBindingElements(IProviderAssociationStore cryptoKeyStore, INonceStore nonceStore, ProviderSecuritySettings securitySettings) { + Requires.NotNull(cryptoKeyStore, "cryptoKeyStore"); + Requires.NotNull(securitySettings, "securitySettings"); + Requires.NotNull(nonceStore, "nonceStore"); + + SigningBindingElement signingElement; + signingElement = new ProviderSigningBindingElement(cryptoKeyStore, securitySettings); + + var extensionFactory = OpenIdExtensionFactoryAggregator.LoadFromConfiguration(); + + List<IChannelBindingElement> elements = new List<IChannelBindingElement>(8); + elements.Add(new ExtensionsBindingElement(extensionFactory, securitySettings, true)); + elements.Add(new StandardReplayProtectionBindingElement(nonceStore, true)); + elements.Add(new StandardExpirationBindingElement()); + elements.Add(signingElement); + + return elements.ToArray(); + } + } +} diff --git a/src/DotNetOpenAuth.OpenId.Provider/OpenId/ChannelElements/OpenIdProviderMessageFactory.cs b/src/DotNetOpenAuth.OpenId.Provider/OpenId/ChannelElements/OpenIdProviderMessageFactory.cs new file mode 100644 index 0000000..3fab06b --- /dev/null +++ b/src/DotNetOpenAuth.OpenId.Provider/OpenId/ChannelElements/OpenIdProviderMessageFactory.cs @@ -0,0 +1,86 @@ +//----------------------------------------------------------------------- +// <copyright file="OpenIdProviderMessageFactory.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.ChannelElements { + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OpenId.Messages; + + /// <summary> + /// OpenID Provider message factory. + /// </summary> + internal class OpenIdProviderMessageFactory : IMessageFactory { + /// <summary> + /// Analyzes an incoming request message payload to discover what kind of + /// message is embedded in it and returns the type, or null if no match is found. + /// </summary> + /// <param name="recipient">The intended or actual recipient of the request message.</param> + /// <param name="fields">The name/value pairs that make up the message payload.</param> + /// <returns> + /// A newly instantiated <see cref="IProtocolMessage"/>-derived object that this message can + /// deserialize to. Null if the request isn't recognized as a valid protocol message. + /// </returns> + public IDirectedProtocolMessage GetNewRequestMessage(MessageReceivingEndpoint recipient, IDictionary<string, string> fields) { + RequestBase message = null; + + // Discern the OpenID version of the message. + Protocol protocol = Protocol.V11; + string ns; + if (fields.TryGetValue(Protocol.V20.openid.ns, out ns)) { + ErrorUtilities.VerifyProtocol(string.Equals(ns, Protocol.OpenId2Namespace, StringComparison.Ordinal), MessagingStrings.UnexpectedMessagePartValue, Protocol.V20.openid.ns, ns); + protocol = Protocol.V20; + } + + string mode; + if (fields.TryGetValue(protocol.openid.mode, out mode)) { + if (string.Equals(mode, protocol.Args.Mode.associate)) { + if (fields.ContainsKey(protocol.openid.dh_consumer_public)) { + message = new AssociateDiffieHellmanProviderRequest(protocol.Version, recipient.Location); + } else { + message = new AssociateUnencryptedProviderRequest(protocol.Version, recipient.Location); + } + } else if (string.Equals(mode, protocol.Args.Mode.checkid_setup) || + string.Equals(mode, protocol.Args.Mode.checkid_immediate)) { + AuthenticationRequestMode authMode = string.Equals(mode, protocol.Args.Mode.checkid_immediate) ? AuthenticationRequestMode.Immediate : AuthenticationRequestMode.Setup; + if (fields.ContainsKey(protocol.openid.identity)) { + message = new CheckIdRequest(protocol.Version, recipient.Location, authMode); + } else { + ErrorUtilities.VerifyProtocol(!fields.ContainsKey(protocol.openid.claimed_id), OpenIdStrings.IdentityAndClaimedIdentifierMustBeBothPresentOrAbsent); + message = new SignedResponseRequest(protocol.Version, recipient.Location, authMode); + } + } else if (string.Equals(mode, protocol.Args.Mode.check_authentication)) { + message = new CheckAuthenticationRequest(protocol.Version, recipient.Location); + } else { + ErrorUtilities.ThrowProtocol(MessagingStrings.UnexpectedMessagePartValue, protocol.openid.mode, mode); + } + } + + if (message != null) { + message.SetAsIncoming(); + } + + return message; + } + + /// <summary> + /// Analyzes an incoming request message payload to discover what kind of + /// message is embedded in it and returns the type, or null if no match is found. + /// </summary> + /// <param name="request">The message that was sent as a request that resulted in the response.</param> + /// <param name="fields">The name/value pairs that make up the message payload.</param> + /// <returns> + /// A newly instantiated <see cref="IProtocolMessage"/>-derived object that this message can + /// deserialize to. Null if the request isn't recognized as a valid protocol message. + /// </returns> + public IDirectResponseProtocolMessage GetNewResponseMessage(IDirectedProtocolMessage request, IDictionary<string, string> fields) { + // OpenID Providers make no outbound requests, and thus receive no direct response messages. + throw new NotImplementedException(); + } + } +} diff --git a/src/DotNetOpenAuth.OpenId.Provider/OpenId/ChannelElements/ProviderSigningBindingElement.cs b/src/DotNetOpenAuth.OpenId.Provider/OpenId/ChannelElements/ProviderSigningBindingElement.cs new file mode 100644 index 0000000..e257e63 --- /dev/null +++ b/src/DotNetOpenAuth.OpenId.Provider/OpenId/ChannelElements/ProviderSigningBindingElement.cs @@ -0,0 +1,251 @@ +//----------------------------------------------------------------------- +// <copyright file="ProviderSigningBindingElement.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.ChannelElements { + using System; + using System.Collections.Generic; + using System.Diagnostics.Contracts; + using System.Linq; + using System.Text; + using System.Web; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.Messaging.Bindings; + using DotNetOpenAuth.Messaging.Reflection; + using DotNetOpenAuth.OpenId.Messages; + using DotNetOpenAuth.OpenId.Provider; + + /// <summary> + /// The signing binding element for OpenID Providers. + /// </summary> + internal class ProviderSigningBindingElement : SigningBindingElement { + /// <summary> + /// The association store used by Providers to look up the secrets needed for signing. + /// </summary> + private readonly IProviderAssociationStore opAssociations; + + /// <summary> + /// The security settings at the Provider. + /// Only defined when this element is instantiated to service a Provider. + /// </summary> + private readonly ProviderSecuritySettings opSecuritySettings; + + /// <summary> + /// Initializes a new instance of the <see cref="ProviderSigningBindingElement"/> class. + /// </summary> + /// <param name="associationStore">The association store used to look up the secrets needed for signing.</param> + /// <param name="securitySettings">The security settings.</param> + internal ProviderSigningBindingElement(IProviderAssociationStore associationStore, ProviderSecuritySettings securitySettings) { + Requires.NotNull(associationStore, "associationStore"); + Requires.NotNull(securitySettings, "securitySettings"); + + this.opAssociations = associationStore; + this.opSecuritySettings = securitySettings; + } + + /// <summary> + /// Gets a value indicating whether this binding element is on a Provider channel. + /// </summary> + protected override bool IsOnProvider { + get { return true; } + } + + /// <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> + public override MessageProtections? ProcessOutgoingMessage(IProtocolMessage message) { + var result = base.ProcessOutgoingMessage(message); + if (result != null) { + return result; + } + + var signedMessage = message as ITamperResistantOpenIdMessage; + if (signedMessage != null) { + Logger.Bindings.DebugFormat("Signing {0} message.", message.GetType().Name); + Association association = this.GetAssociation(signedMessage); + signedMessage.AssociationHandle = association.Handle; + signedMessage.SignedParameterOrder = this.GetSignedParameterOrder(signedMessage); + signedMessage.Signature = this.GetSignature(signedMessage, association); + return MessageProtections.TamperProtection; + } + + return null; + } + + /// <summary> + /// Gets the association to use to sign or verify a message. + /// </summary> + /// <param name="signedMessage">The message to sign or verify.</param> + /// <returns> + /// The association to use to sign or verify the message. + /// </returns> + protected override Association GetAssociation(ITamperResistantOpenIdMessage signedMessage) { + // We're on a Provider to either sign (smart/dumb) or verify a dumb signature. + bool signing = string.IsNullOrEmpty(signedMessage.Signature); + + if (signing) { + // If the RP has no replay protection, coerce use of a private association + // instead of a shared one (if security settings indicate) + // to protect the authenticating user from replay attacks. + bool forcePrivateAssociation = this.opSecuritySettings.ProtectDownlevelReplayAttacks + && IsRelyingPartyVulnerableToReplays(null, (IndirectSignedResponse)signedMessage); + + if (forcePrivateAssociation) { + if (!string.IsNullOrEmpty(signedMessage.AssociationHandle)) { + Logger.Signatures.Info("An OpenID 1.x authentication request with a shared association handle will be responded to with a private association in order to provide OP-side replay protection."); + } + + return this.GetDumbAssociationForSigning(); + } else { + return this.GetSpecificAssociation(signedMessage) ?? this.GetDumbAssociationForSigning(); + } + } else { + return this.GetSpecificAssociation(signedMessage); + } + } + + /// <summary> + /// Gets a specific association referenced in a given message's association handle. + /// </summary> + /// <param name="signedMessage">The signed message whose association handle should be used to lookup the association to return.</param> + /// <returns> + /// The referenced association; or <c>null</c> if such an association cannot be found. + /// </returns> + protected override Association GetSpecificAssociation(ITamperResistantOpenIdMessage signedMessage) { + Association association = null; + + if (!string.IsNullOrEmpty(signedMessage.AssociationHandle)) { + IndirectSignedResponse indirectSignedMessage = signedMessage as IndirectSignedResponse; + + // Since we have an association handle, we're either signing with a smart association, + // or verifying a dumb one. + bool signing = string.IsNullOrEmpty(signedMessage.Signature); + bool isPrivateAssociation = !signing; + association = this.opAssociations.Deserialize(signedMessage, isPrivateAssociation, signedMessage.AssociationHandle); + if (association == null) { + // There was no valid association with the requested handle. + // Let's tell the RP to forget about that association. + signedMessage.InvalidateHandle = signedMessage.AssociationHandle; + signedMessage.AssociationHandle = null; + } + } + + return association; + } + + /// <summary> + /// Gets a private Provider association used for signing messages in "dumb" mode. + /// </summary> + /// <returns>An existing or newly created association.</returns> + protected override Association GetDumbAssociationForSigning() { + // If no assoc_handle was given or it was invalid, the only thing + // left to do is sign a message using a 'dumb' mode association. + Protocol protocol = Protocol.Default; + Association association = HmacShaAssociationProvider.Create(protocol, protocol.Args.SignatureAlgorithm.HMAC_SHA256, AssociationRelyingPartyType.Dumb, this.opAssociations, this.opSecuritySettings); + return association; + } + + /// <summary> + /// Verifies the signature by unrecognized handle. + /// </summary> + /// <param name="message">The message.</param> + /// <param name="signedMessage">The signed message.</param> + /// <param name="protectionsApplied">The protections applied.</param> + /// <returns> + /// The applied protections. + /// </returns> + protected override MessageProtections VerifySignatureByUnrecognizedHandle(IProtocolMessage message, ITamperResistantOpenIdMessage signedMessage, MessageProtections protectionsApplied) { + // If we're on the Provider, then the RP sent us a check_auth with a signature + // we don't have an association for. (It may have expired, or it may be a faulty RP). + throw new InvalidSignatureException(message); + } + + /// <summary> + /// Determines whether the relying party sending an authentication request is + /// vulnerable to replay attacks. + /// </summary> + /// <param name="request">The request message from the Relying Party. Useful, but may be null for conservative estimate results.</param> + /// <param name="response">The response message to be signed.</param> + /// <returns> + /// <c>true</c> if the relying party is vulnerable; otherwise, <c>false</c>. + /// </returns> + private static bool IsRelyingPartyVulnerableToReplays(SignedResponseRequest request, IndirectSignedResponse response) { + Requires.NotNull(response, "response"); + + // OpenID 2.0 includes replay protection as part of the protocol. + if (response.Version.Major >= 2) { + return false; + } + + // This library's RP may be on the remote end, and may be using 1.x merely because + // discovery on the Claimed Identifier suggested this was a 1.x OP. + // Since this library's RP has a built-in request_nonce parameter for replay + // protection, we'll allow for that. + var returnToArgs = HttpUtility.ParseQueryString(response.ReturnTo.Query); + if (!string.IsNullOrEmpty(returnToArgs[ReturnToNonceBindingElement.NonceParameter])) { + return false; + } + + // If the OP endpoint _AND_ RP return_to URL uses HTTPS then no one + // can steal and replay the positive assertion. + // We can only ascertain this if the request message was handed to us + // so we know what our own OP endpoint is. If we don't have a request + // message, then we'll default to assuming it's insecure. + if (request != null) { + if (request.Recipient.IsTransportSecure() && response.Recipient.IsTransportSecure()) { + return false; + } + } + + // Nothing left to protect against replays. RP is vulnerable. + return true; + } + + /// <summary> + /// Gets the value to use for the openid.signed parameter. + /// </summary> + /// <param name="signedMessage">The signable message.</param> + /// <returns> + /// A comma-delimited list of parameter names, omitting the 'openid.' prefix, that determines + /// the inclusion and order of message parts that will be signed. + /// </returns> + private string GetSignedParameterOrder(ITamperResistantOpenIdMessage signedMessage) { + Requires.ValidState(this.Channel != null); + Requires.NotNull(signedMessage, "signedMessage"); + + Protocol protocol = Protocol.Lookup(signedMessage.Version); + + MessageDescription description = this.Channel.MessageDescriptions.Get(signedMessage); + var signedParts = from part in description.Mapping.Values + where (part.RequiredProtection & System.Net.Security.ProtectionLevel.Sign) != 0 + && part.GetValue(signedMessage) != null + select part.Name; + string prefix = Protocol.V20.openid.Prefix; + ErrorUtilities.VerifyInternal(signedParts.All(name => name.StartsWith(prefix, StringComparison.Ordinal)), "All signed message parts must start with 'openid.'."); + + if (this.opSecuritySettings.SignOutgoingExtensions) { + // Tack on any ExtraData parameters that start with 'openid.'. + List<string> extraSignedParameters = new List<string>(signedMessage.ExtraData.Count); + foreach (string key in signedMessage.ExtraData.Keys) { + if (key.StartsWith(protocol.openid.Prefix, StringComparison.Ordinal)) { + extraSignedParameters.Add(key); + } else { + Logger.Signatures.DebugFormat("The extra parameter '{0}' will not be signed because it does not start with 'openid.'.", key); + } + } + signedParts = signedParts.Concat(extraSignedParameters); + } + + int skipLength = prefix.Length; + string signedFields = string.Join(",", signedParts.Select(name => name.Substring(skipLength)).ToArray()); + return signedFields; + } + } +} diff --git a/src/DotNetOpenAuth.OpenId.Provider/OpenId/Messages/AssociateDiffieHellmanProviderRequest.cs b/src/DotNetOpenAuth.OpenId.Provider/OpenId/Messages/AssociateDiffieHellmanProviderRequest.cs new file mode 100644 index 0000000..1a3cf5d --- /dev/null +++ b/src/DotNetOpenAuth.OpenId.Provider/OpenId/Messages/AssociateDiffieHellmanProviderRequest.cs @@ -0,0 +1,43 @@ +//----------------------------------------------------------------------- +// <copyright file="AssociateDiffieHellmanProviderRequest.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.Messages { + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using DotNetOpenAuth.Messaging; + + /// <summary> + /// An OpenID direct request from Relying Party to Provider to initiate an association that uses Diffie-Hellman encryption. + /// </summary> + internal class AssociateDiffieHellmanProviderRequest : AssociateDiffieHellmanRequest, IAssociateRequestProvider { + /// <summary> + /// Initializes a new instance of the <see cref="AssociateDiffieHellmanProviderRequest"/> class. + /// </summary> + /// <param name="version">The OpenID version this message must comply with.</param> + /// <param name="providerEndpoint">The OpenID Provider endpoint.</param> + internal AssociateDiffieHellmanProviderRequest(Version version, Uri providerEndpoint) + : base(version, providerEndpoint) { + } + + /// <summary> + /// Creates a Provider's response to an incoming association request. + /// </summary> + /// <returns> + /// The appropriate association response message. + /// </returns> + public IProtocolMessage CreateResponseCore() { +#if !ExcludeDiffieHellman + var response = new AssociateDiffieHellmanProviderResponse(this.Version, this); + response.AssociationType = this.AssociationType; + return response; +#else + throw new NotSupportedException(); +#endif + } + } +} diff --git a/src/DotNetOpenAuth.OpenId.Provider/OpenId/Messages/AssociateDiffieHellmanProviderResponse.cs b/src/DotNetOpenAuth.OpenId.Provider/OpenId/Messages/AssociateDiffieHellmanProviderResponse.cs new file mode 100644 index 0000000..3dd1e8e --- /dev/null +++ b/src/DotNetOpenAuth.OpenId.Provider/OpenId/Messages/AssociateDiffieHellmanProviderResponse.cs @@ -0,0 +1,92 @@ +//----------------------------------------------------------------------- +// <copyright file="AssociateDiffieHellmanProviderResponse.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.Messages { + using System; + using System.Diagnostics.Contracts; + using System.Security.Cryptography; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.Messaging.Reflection; + using DotNetOpenAuth.OpenId.Provider; + using Org.Mentalis.Security.Cryptography; + + /// <summary> + /// The successful Diffie-Hellman association response message. + /// </summary> + /// <remarks> + /// Association response messages are described in OpenID 2.0 section 8.2. This type covers section 8.2.3. + /// </remarks> + internal class AssociateDiffieHellmanProviderResponse : AssociateDiffieHellmanResponse, IAssociateSuccessfulResponseProvider { + /// <summary> + /// Initializes a new instance of the <see cref="AssociateDiffieHellmanProviderResponse"/> class. + /// </summary> + /// <param name="responseVersion">The OpenID version of the response message.</param> + /// <param name="originatingRequest">The originating request.</param> + internal AssociateDiffieHellmanProviderResponse(Version responseVersion, AssociateDiffieHellmanRequest originatingRequest) + : base(responseVersion, originatingRequest) { + } + + /// <summary> + /// Gets or sets the lifetime, in seconds, of this association. The Relying Party MUST NOT use the association after this time has passed. + /// </summary> + /// <value> + /// An integer, represented in base 10 ASCII. + /// </value> + long IAssociateSuccessfulResponseProvider.ExpiresIn { + get { return this.ExpiresIn; } + set { this.ExpiresIn = value; } + } + + /// <summary> + /// Gets or sets the association handle is used as a key to refer to this association in subsequent messages. + /// </summary> + /// <value> + /// A string 255 characters or less in length. It MUST consist only of ASCII characters in the range 33-126 inclusive (printable non-whitespace characters). + /// </value> + string IAssociateSuccessfulResponseProvider.AssociationHandle { + get { return this.AssociationHandle; } + set { this.AssociationHandle = value; } + } + + /// <summary> + /// Creates the association at the provider side after the association request has been received. + /// </summary> + /// <param name="request">The association request.</param> + /// <param name="associationStore">The OpenID Provider's association store or handle encoder.</param> + /// <param name="securitySettings">The security settings of the Provider.</param> + /// <returns> + /// The newly created association. + /// </returns> + /// <remarks> + /// The response message is updated to include the details of the created association by this method, + /// but the resulting association is <i>not</i> added to the association store and must be done by the caller. + /// </remarks> + public Association CreateAssociationAtProvider(AssociateRequest request, IProviderAssociationStore associationStore, ProviderSecuritySettings securitySettings) { + var diffieHellmanRequest = request as AssociateDiffieHellmanRequest; + ErrorUtilities.VerifyInternal(diffieHellmanRequest != null, "Expected a DH request type."); + + this.SessionType = this.SessionType ?? request.SessionType; + + // Go ahead and create the association first, complete with its secret that we're about to share. + Association association = HmacShaAssociationProvider.Create(this.Protocol, this.AssociationType, AssociationRelyingPartyType.Smart, associationStore, securitySettings); + + // We now need to securely communicate the secret to the relying party using Diffie-Hellman. + // We do this by performing a DH algorithm on the secret and setting a couple of properties + // that will be transmitted to the Relying Party. The RP will perform an inverse operation + // using its part of a DH secret in order to decrypt the shared secret we just invented + // above when we created the association. + using (DiffieHellman dh = new DiffieHellmanManaged( + diffieHellmanRequest.DiffieHellmanModulus ?? AssociateDiffieHellmanRequest.DefaultMod, + diffieHellmanRequest.DiffieHellmanGen ?? AssociateDiffieHellmanRequest.DefaultGen, + AssociateDiffieHellmanRequest.DefaultX)) { + HashAlgorithm hasher = DiffieHellmanUtilities.Lookup(this.Protocol, this.SessionType); + this.DiffieHellmanServerPublic = DiffieHellmanUtilities.EnsurePositive(dh.CreateKeyExchange()); + this.EncodedMacKey = DiffieHellmanUtilities.SHAHashXorSecret(hasher, dh, diffieHellmanRequest.DiffieHellmanConsumerPublic, association.SecretKey); + } + return association; + } + } +} diff --git a/src/DotNetOpenAuth.OpenId.Provider/OpenId/Messages/AssociateRequestProviderTools.cs b/src/DotNetOpenAuth.OpenId.Provider/OpenId/Messages/AssociateRequestProviderTools.cs new file mode 100644 index 0000000..e09b37e --- /dev/null +++ b/src/DotNetOpenAuth.OpenId.Provider/OpenId/Messages/AssociateRequestProviderTools.cs @@ -0,0 +1,100 @@ +//----------------------------------------------------------------------- +// <copyright file="AssociateRequestProviderTools.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.Messages { + using System; + using System.Collections.Generic; + using System.Diagnostics.Contracts; + using System.Linq; + using System.Text; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OpenId.Provider; + + /// <summary> + /// OpenID Provider tools for receiving association requests. + /// </summary> + internal static class AssociateRequestProviderTools { + /// <summary> + /// Creates a Provider's response to an incoming association request. + /// </summary> + /// <param name="requestMessage">The request message.</param> + /// <param name="associationStore">The association store.</param> + /// <param name="securitySettings">The security settings on the Provider.</param> + /// <returns> + /// The appropriate association response that is ready to be sent back to the Relying Party. + /// </returns> + /// <remarks> + /// <para>If an association is created, it will be automatically be added to the provided + /// association store.</para> + /// <para>Successful association response messages will derive from <see cref="AssociateSuccessfulResponse"/>. + /// Failed association response messages will derive from <see cref="AssociateUnsuccessfulResponse"/>.</para> + /// </remarks> + internal static IProtocolMessage CreateResponse(IAssociateRequestProvider requestMessage, IProviderAssociationStore associationStore, ProviderSecuritySettings securitySettings) { + Requires.NotNull(requestMessage, "requestMessage"); + Requires.NotNull(associationStore, "associationStore"); + Requires.NotNull(securitySettings, "securitySettings"); + + AssociateRequest request = (AssociateRequest)requestMessage; + IProtocolMessage response; + var protocol = requestMessage.GetProtocol(); + if (securitySettings.IsAssociationInPermittedRange(protocol, request.AssociationType) && + HmacShaAssociation.IsDHSessionCompatible(protocol, request.AssociationType, request.SessionType)) { + response = requestMessage.CreateResponseCore(); + + // Create and store the association if this is a successful response. + var successResponse = response as IAssociateSuccessfulResponseProvider; + if (successResponse != null) { + OpenIdProviderUtilities.CreateAssociation(request, successResponse, associationStore, securitySettings); + } + } else { + response = CreateUnsuccessfulResponse(requestMessage, securitySettings); + } + + return response; + } + + /// <summary> + /// Creates a response that notifies the Relying Party that the requested + /// association type is not supported by this Provider, and offers + /// an alternative association type, if possible. + /// </summary> + /// <param name="requestMessage">The request message.</param> + /// <param name="securitySettings">The security settings that apply to this Provider.</param> + /// <returns> + /// The response to send to the Relying Party. + /// </returns> + private static AssociateUnsuccessfulResponse CreateUnsuccessfulResponse(IAssociateRequestProvider requestMessage, ProviderSecuritySettings securitySettings) { + Requires.NotNull(requestMessage, "requestMessage"); + Requires.NotNull(securitySettings, "securitySettings"); + + var unsuccessfulResponse = new AssociateUnsuccessfulResponse(requestMessage.Version, (AssociateRequest)requestMessage); + + // The strategy here is to suggest that the RP try again with the lowest + // permissible security settings, giving the RP the best chance of being + // able to match with a compatible request. + bool unencryptedAllowed = requestMessage.Recipient.IsTransportSecure(); + bool useDiffieHellman = !unencryptedAllowed; + var request = (AssociateRequest)requestMessage; + var protocol = requestMessage.GetProtocol(); + string associationType, sessionType; + if (HmacShaAssociation.TryFindBestAssociation(protocol, false, securitySettings, useDiffieHellman, out associationType, out sessionType)) { + ErrorUtilities.VerifyInternal(request.AssociationType != associationType, "The RP asked for an association that should have been allowed, but the OP is trying to suggest the same one as an alternative!"); + unsuccessfulResponse.AssociationType = associationType; + unsuccessfulResponse.SessionType = sessionType; + Logger.OpenId.InfoFormat( + "Association requested of type '{0}' and session '{1}', which the Provider does not support. Sending back suggested alternative of '{0}' with session '{1}'.", + request.AssociationType, + request.SessionType, + unsuccessfulResponse.AssociationType, + unsuccessfulResponse.SessionType); + } else { + Logger.OpenId.InfoFormat("Association requested of type '{0}' and session '{1}', which the Provider does not support. No alternative association type qualified for suggesting back to the Relying Party.", request.AssociationType, request.SessionType); + } + + return unsuccessfulResponse; + } + } +} diff --git a/src/DotNetOpenAuth.OpenId.Provider/OpenId/Messages/AssociateSuccessfulResponseProvider.cs b/src/DotNetOpenAuth.OpenId.Provider/OpenId/Messages/AssociateSuccessfulResponseProvider.cs new file mode 100644 index 0000000..d057b9c --- /dev/null +++ b/src/DotNetOpenAuth.OpenId.Provider/OpenId/Messages/AssociateSuccessfulResponseProvider.cs @@ -0,0 +1,55 @@ +//----------------------------------------------------------------------- +// <copyright file="AssociateSuccessfulResponseProvider.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.Messages { + using System; + using System.Collections.Generic; + using System.Diagnostics.Contracts; + using System.Linq; + using System.Text; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OpenId.Provider; + + /// <summary> + /// An outgoing successful association response from the OpenID Provider. + /// </summary> + [ContractClass(typeof(IAssociateSuccessfulResponseProviderContract))] + internal interface IAssociateSuccessfulResponseProvider : IProtocolMessage { + /// <summary> + /// Gets or sets the expires in. + /// </summary> + /// <value> + /// The expires in. + /// </value> + long ExpiresIn { get; set; } + + /// <summary> + /// Gets or sets the association handle. + /// </summary> + /// <value> + /// The association handle. + /// </value> + string AssociationHandle { get; set; } + + /// <summary> + /// Called to create the Association based on a request previously given by the Relying Party. + /// </summary> + /// <param name="request">The prior request for an association.</param> + /// <param name="associationStore">The Provider's association store.</param> + /// <param name="securitySettings">The security settings of the Provider.</param> + /// <returns> + /// The created association. + /// </returns> + /// <remarks> + /// <para>The caller will update this message's <see cref="AssociateSuccessfulResponse.ExpiresIn"/> and <see cref="AssociateSuccessfulResponse.AssociationHandle"/> + /// properties based on the <see cref="Association"/> returned by this method, but any other + /// association type specific properties must be set by this method.</para> + /// <para>The response message is updated to include the details of the created association by this method, + /// but the resulting association is <i>not</i> added to the association store and must be done by the caller.</para> + /// </remarks> + Association CreateAssociationAtProvider(AssociateRequest request, IProviderAssociationStore associationStore, ProviderSecuritySettings securitySettings); + } +} diff --git a/src/DotNetOpenAuth.OpenId.Provider/OpenId/Messages/AssociateSuccessfulResponseProviderContract.cs b/src/DotNetOpenAuth.OpenId.Provider/OpenId/Messages/AssociateSuccessfulResponseProviderContract.cs new file mode 100644 index 0000000..af60d5e --- /dev/null +++ b/src/DotNetOpenAuth.OpenId.Provider/OpenId/Messages/AssociateSuccessfulResponseProviderContract.cs @@ -0,0 +1,96 @@ +//----------------------------------------------------------------------- +// <copyright file="AssociateSuccessfulResponseProviderContract.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.Messages { + using System; + using System.Collections.Generic; + using System.Diagnostics.Contracts; + using System.Linq; + using System.Text; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OpenId.Provider; + + /// <summary> + /// Code contract for the <see cref="IAssociateSuccessfulResponseProvider"/> interface. + /// </summary> + [ContractClassFor(typeof(IAssociateSuccessfulResponseProvider))] + internal abstract class IAssociateSuccessfulResponseProviderContract : IAssociateSuccessfulResponseProvider { + /// <summary> + /// Gets or sets the expires in. + /// </summary> + /// <value> + /// The expires in. + /// </value> + long IAssociateSuccessfulResponseProvider.ExpiresIn { + get { throw new NotImplementedException(); } + set { throw new NotImplementedException(); } + } + + /// <summary> + /// Gets or sets the association handle. + /// </summary> + /// <value> + /// The association handle. + /// </value> + string IAssociateSuccessfulResponseProvider.AssociationHandle { + get { throw new NotImplementedException(); } + set { throw new NotImplementedException(); } + } + + /// <summary> + /// Gets the level of protection this message requires. + /// </summary> + Messaging.MessageProtections Messaging.IProtocolMessage.RequiredProtection { + get { throw new NotImplementedException(); } + } + + /// <summary> + /// Gets a value indicating whether this is a direct or indirect message. + /// </summary> + Messaging.MessageTransport Messaging.IProtocolMessage.Transport { + get { throw new NotImplementedException(); } + } + + /// <summary> + /// Gets the version of the protocol or extension this message is prepared to implement. + /// </summary> + Version Messaging.IMessage.Version { + get { throw new NotImplementedException(); } + } + + /// <summary> + /// Gets the extra, non-standard Protocol parameters included in the message. + /// </summary> + IDictionary<string, string> Messaging.IMessage.ExtraData { + get { throw new NotImplementedException(); } + } + + /// <summary> + /// Called to create the Association based on a request previously given by the Relying Party. + /// </summary> + /// <param name="request">The prior request for an association.</param> + /// <param name="associationStore">The Provider's association store.</param> + /// <param name="securitySettings">The security settings of the Provider.</param> + /// <returns> + /// The created association. + /// </returns> + Association IAssociateSuccessfulResponseProvider.CreateAssociationAtProvider(AssociateRequest request, IProviderAssociationStore associationStore, ProviderSecuritySettings securitySettings) { + Requires.NotNull(request, "request"); + Requires.NotNull(associationStore, "associationStore"); + Requires.NotNull(securitySettings, "securitySettings"); + throw new NotImplementedException(); + } + + /// <summary> + /// Checks the message state for conformity to the protocol specification + /// and throws an exception if the message is invalid. + /// </summary> + /// <exception cref="ProtocolException">Thrown if the message is invalid.</exception> + void Messaging.IMessage.EnsureValidMessage() { + throw new NotImplementedException(); + } + } +} diff --git a/src/DotNetOpenAuth.OpenId.Provider/OpenId/Messages/AssociateUnencryptedResponseProvider.cs b/src/DotNetOpenAuth.OpenId.Provider/OpenId/Messages/AssociateUnencryptedResponseProvider.cs new file mode 100644 index 0000000..1466fe2 --- /dev/null +++ b/src/DotNetOpenAuth.OpenId.Provider/OpenId/Messages/AssociateUnencryptedResponseProvider.cs @@ -0,0 +1,73 @@ +//----------------------------------------------------------------------- +// <copyright file="AssociateUnencryptedResponseProvider.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.Messages { + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using DotNetOpenAuth.OpenId.Provider; + + /// <summary> + /// An unencrypted association response as it is sent by the Provider. + /// </summary> + internal class AssociateUnencryptedResponseProvider : AssociateUnencryptedResponse, IAssociateSuccessfulResponseProvider { + /// <summary> + /// Initializes a new instance of the <see cref="AssociateUnencryptedResponseProvider"/> class. + /// </summary> + /// <param name="version">The version.</param> + /// <param name="request">The request.</param> + internal AssociateUnencryptedResponseProvider(Version version, AssociateUnencryptedRequest request) + : base(version, request) { + } + + /// <summary> + /// Gets or sets the lifetime, in seconds, of this association. The Relying Party MUST NOT use the association after this time has passed. + /// </summary> + /// <value> + /// An integer, represented in base 10 ASCII. + /// </value> + long IAssociateSuccessfulResponseProvider.ExpiresIn { + get { return this.ExpiresIn; } + set { this.ExpiresIn = value; } + } + + /// <summary> + /// Gets or sets the association handle is used as a key to refer to this association in subsequent messages. + /// </summary> + /// <value> + /// A string 255 characters or less in length. It MUST consist only of ASCII characters in the range 33-126 inclusive (printable non-whitespace characters). + /// </value> + string IAssociateSuccessfulResponseProvider.AssociationHandle { + get { return this.AssociationHandle; } + set { this.AssociationHandle = value; } + } + + /// <summary> + /// Called to create the Association based on a request previously given by the Relying Party. + /// </summary> + /// <param name="request">The prior request for an association.</param> + /// <param name="associationStore">The Provider's association store.</param> + /// <param name="securitySettings">The security settings of the Provider.</param> + /// <returns> + /// The created association. + /// </returns> + /// <remarks> + /// <para>The caller will update this message's + /// <see cref="AssociateSuccessfulResponse.ExpiresIn"/> and + /// <see cref="AssociateSuccessfulResponse.AssociationHandle"/> + /// properties based on the <see cref="Association"/> returned by this method, but any other + /// association type specific properties must be set by this method.</para> + /// <para>The response message is updated to include the details of the created association by this method, + /// but the resulting association is <i>not</i> added to the association store and must be done by the caller.</para> + /// </remarks> + public Association CreateAssociationAtProvider(AssociateRequest request, IProviderAssociationStore associationStore, ProviderSecuritySettings securitySettings) { + Association association = HmacShaAssociationProvider.Create(Protocol, this.AssociationType, AssociationRelyingPartyType.Smart, associationStore, securitySettings); + this.MacKey = association.SecretKey; + return association; + } + } +} diff --git a/src/DotNetOpenAuth.OpenId.Provider/OpenId/Messages/CheckAuthenticationResponseProvider.cs b/src/DotNetOpenAuth.OpenId.Provider/OpenId/Messages/CheckAuthenticationResponseProvider.cs new file mode 100644 index 0000000..1e417e6 --- /dev/null +++ b/src/DotNetOpenAuth.OpenId.Provider/OpenId/Messages/CheckAuthenticationResponseProvider.cs @@ -0,0 +1,42 @@ +//----------------------------------------------------------------------- +// <copyright file="CheckAuthenticationResponseProvider.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.Messages { + using System; + using System.Collections.Generic; + using System.Diagnostics.Contracts; + using System.Linq; + using System.Text; + using DotNetOpenAuth.OpenId.ChannelElements; + using DotNetOpenAuth.OpenId.Provider; + + /// <summary> + /// The check_auth response message, as it is seen by the OpenID Provider. + /// </summary> + internal class CheckAuthenticationResponseProvider : CheckAuthenticationResponse { + /// <summary> + /// Initializes a new instance of the <see cref="CheckAuthenticationResponseProvider"/> class. + /// </summary> + /// <param name="request">The request that this message is responding to.</param> + /// <param name="provider">The OpenID Provider that is preparing to send this response.</param> + internal CheckAuthenticationResponseProvider(CheckAuthenticationRequest request, OpenIdProvider provider) + : base(request.Version, request) { + Requires.NotNull(provider, "provider"); + + // The channel's binding elements have already set the request's IsValid property + // appropriately. We just copy it into the response message. + this.IsValid = request.IsValid; + + // Confirm the RP should invalidate the association handle only if the association + // is not valid (any longer). OpenID 2.0 section 11.4.2.2. + IndirectSignedResponse signedResponse = new IndirectSignedResponse(request, provider.Channel); + string invalidateHandle = ((ITamperResistantOpenIdMessage)signedResponse).InvalidateHandle; + if (!string.IsNullOrEmpty(invalidateHandle) && !provider.AssociationStore.IsValid(signedResponse, false, invalidateHandle)) { + this.InvalidateHandle = invalidateHandle; + } + } + } +} diff --git a/src/DotNetOpenAuth.OpenId.Provider/OpenId/Messages/IAssociateRequestProvider.cs b/src/DotNetOpenAuth.OpenId.Provider/OpenId/Messages/IAssociateRequestProvider.cs new file mode 100644 index 0000000..539a18e --- /dev/null +++ b/src/DotNetOpenAuth.OpenId.Provider/OpenId/Messages/IAssociateRequestProvider.cs @@ -0,0 +1,29 @@ +//----------------------------------------------------------------------- +// <copyright file="IAssociateRequestProvider.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.Messages { + using DotNetOpenAuth.Messaging; + + /// <summary> + /// The openid.mode=associate message as it is received at the OpenID Provider. + /// </summary> + internal interface IAssociateRequestProvider : IDirectedProtocolMessage { + /// <summary> + /// Creates a Provider's response to an incoming association request. + /// </summary> + /// <returns> + /// The appropriate association response message. + /// </returns> + /// <remarks> + /// <para>If an association can be successfully created, the + /// AssociateSuccessfulResponse.CreateAssociation method must not be + /// called by this method.</para> + /// <para>Successful association response messages will derive from <see cref="AssociateSuccessfulResponse"/>. + /// Failed association response messages will derive from <see cref="AssociateUnsuccessfulResponse"/>.</para> + /// </remarks> + IProtocolMessage CreateResponseCore(); + } +} diff --git a/src/DotNetOpenAuth.OpenId.Provider/OpenId/Provider/AnonymousRequest.cs b/src/DotNetOpenAuth.OpenId.Provider/OpenId/Provider/AnonymousRequest.cs new file mode 100644 index 0000000..581d39e --- /dev/null +++ b/src/DotNetOpenAuth.OpenId.Provider/OpenId/Provider/AnonymousRequest.cs @@ -0,0 +1,92 @@ +//----------------------------------------------------------------------- +// <copyright file="AnonymousRequest.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.Provider { + using System; + using System.Diagnostics.CodeAnalysis; + using System.Diagnostics.Contracts; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OpenId.Messages; + + /// <summary> + /// Provides access to a host Provider to read an incoming extension-only checkid request message, + /// and supply extension responses or a cancellation message to the RP. + /// </summary> + [Serializable] + internal class AnonymousRequest : HostProcessedRequest, IAnonymousRequest { + /// <summary> + /// The extension-response message to send, if the host site chooses to send it. + /// </summary> + private readonly IndirectSignedResponse positiveResponse; + + /// <summary> + /// Initializes a new instance of the <see cref="AnonymousRequest"/> class. + /// </summary> + /// <param name="provider">The provider that received the request.</param> + /// <param name="request">The incoming authentication request message.</param> + [SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", MessageId = "System.Diagnostics.Contracts.__ContractsRuntime.Requires<System.ArgumentException>(System.Boolean,System.String,System.String)", Justification = "Code contracts"), SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "AuthenticationRequest", Justification = "Type name"), SuppressMessage("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily", Justification = "Code contracts require it.")] + internal AnonymousRequest(OpenIdProvider provider, SignedResponseRequest request) + : base(provider, request) { + Requires.NotNull(provider, "provider"); + Requires.True(!(request is CheckIdRequest), "request"); + + this.positiveResponse = new IndirectSignedResponse(request); + } + + #region HostProcessedRequest members + + /// <summary> + /// Gets or sets the provider endpoint. + /// </summary> + /// <value> + /// The default value is the URL that the request came in on from the relying party. + /// </value> + public override Uri ProviderEndpoint { + get { return this.positiveResponse.ProviderEndpoint; } + set { this.positiveResponse.ProviderEndpoint = value; } + } + + #endregion + + #region IAnonymousRequest Members + + /// <summary> + /// Gets or sets a value indicating whether the user approved sending any data to the relying party. + /// </summary> + /// <value><c>true</c> if approved; otherwise, <c>false</c>.</value> + public bool? IsApproved { get; set; } + + #endregion + + #region Request members + + /// <summary> + /// Gets a value indicating whether the response is ready to be sent to the user agent. + /// </summary> + /// <remarks> + /// This property returns false if there are properties that must be set on this + /// request instance before the response can be sent. + /// </remarks> + public override bool IsResponseReady { + get { return this.IsApproved.HasValue; } + } + + /// <summary> + /// Gets the response message, once <see cref="IsResponseReady"/> is <c>true</c>. + /// </summary> + protected override IProtocolMessage ResponseMessage { + get { + if (this.IsApproved.HasValue) { + return this.IsApproved.Value ? (IProtocolMessage)this.positiveResponse : this.NegativeResponse; + } else { + return null; + } + } + } + + #endregion + } +} diff --git a/src/DotNetOpenAuth.OpenId.Provider/OpenId/Provider/AssociationDataBag.cs b/src/DotNetOpenAuth.OpenId.Provider/OpenId/Provider/AssociationDataBag.cs new file mode 100644 index 0000000..bf3d909 --- /dev/null +++ b/src/DotNetOpenAuth.OpenId.Provider/OpenId/Provider/AssociationDataBag.cs @@ -0,0 +1,95 @@ +//----------------------------------------------------------------------- +// <copyright file="AssociationDataBag.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.Provider { + using System; + using System.Collections.Generic; + using System.Diagnostics.Contracts; + using System.IO; + using System.Linq; + using System.Text; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.Messaging.Bindings; + + /// <summary> + /// A signed and encrypted serialization of an association. + /// </summary> + internal class AssociationDataBag : DataBag, IStreamSerializingDataBag { + /// <summary> + /// Initializes a new instance of the <see cref="AssociationDataBag"/> class. + /// </summary> + public AssociationDataBag() { + } + + /// <summary> + /// Gets or sets the association secret. + /// </summary> + [MessagePart(IsRequired = true)] + internal byte[] Secret { get; set; } + + /// <summary> + /// Gets or sets the UTC time that this association expires. + /// </summary> + [MessagePart(IsRequired = true)] + internal DateTime ExpiresUtc { get; set; } + + /// <summary> + /// Gets or sets a value indicating whether this instance is for "dumb" mode RPs. + /// </summary> + /// <value> + /// <c>true</c> if this instance is private association; otherwise, <c>false</c>. + /// </value> + [MessagePart(IsRequired = true)] + internal bool IsPrivateAssociation { + get { return this.AssociationType == AssociationRelyingPartyType.Dumb; } + set { this.AssociationType = value ? AssociationRelyingPartyType.Dumb : AssociationRelyingPartyType.Smart; } + } + + /// <summary> + /// Gets or sets the type of the association (shared or private, a.k.a. smart or dumb). + /// </summary> + internal AssociationRelyingPartyType AssociationType { get; set; } + + /// <summary> + /// Serializes the instance to the specified stream. + /// </summary> + /// <param name="stream">The stream.</param> + public void Serialize(Stream stream) { + var writer = new BinaryWriter(stream); + writer.Write(this.IsPrivateAssociation); + writer.WriteBuffer(this.Secret); + writer.Write((int)(this.ExpiresUtc - TimestampEncoder.Epoch).TotalSeconds); + writer.Flush(); + } + + /// <summary> + /// Initializes the fields on this instance from the specified stream. + /// </summary> + /// <param name="stream">The stream.</param> + public void Deserialize(Stream stream) { + var reader = new BinaryReader(stream); + this.IsPrivateAssociation = reader.ReadBoolean(); + this.Secret = reader.ReadBuffer(); + this.ExpiresUtc = TimestampEncoder.Epoch + TimeSpan.FromSeconds(reader.ReadInt32()); + } + + /// <summary> + /// Creates the formatter used for serialization of this type. + /// </summary> + /// <param name="cryptoKeyStore">The crypto key store used when signing or encrypting.</param> + /// <param name="bucket">The bucket in which symmetric keys are stored for signing/encrypting data.</param> + /// <param name="minimumAge">The minimum age.</param> + /// <returns> + /// A formatter for serialization. + /// </returns> + internal static IDataBagFormatter<AssociationDataBag> CreateFormatter(ICryptoKeyStore cryptoKeyStore, string bucket, TimeSpan? minimumAge = null) { + Requires.NotNull(cryptoKeyStore, "cryptoKeyStore"); + Requires.NotNullOrEmpty(bucket, "bucket"); + Contract.Ensures(Contract.Result<IDataBagFormatter<AssociationDataBag>>() != null); + return new BinaryDataBagFormatter<AssociationDataBag>(cryptoKeyStore, bucket, signed: true, encrypted: true, minimumAge: minimumAge); + } + } +} diff --git a/src/DotNetOpenAuth/OpenId/Provider/AssociationRelyingPartyType.cs b/src/DotNetOpenAuth.OpenId.Provider/OpenId/Provider/AssociationRelyingPartyType.cs index 4d121b1..4d121b1 100644 --- a/src/DotNetOpenAuth/OpenId/Provider/AssociationRelyingPartyType.cs +++ b/src/DotNetOpenAuth.OpenId.Provider/OpenId/Provider/AssociationRelyingPartyType.cs diff --git a/src/DotNetOpenAuth.OpenId.Provider/OpenId/Provider/AuthenticationRequest.cs b/src/DotNetOpenAuth.OpenId.Provider/OpenId/Provider/AuthenticationRequest.cs new file mode 100644 index 0000000..09b1073 --- /dev/null +++ b/src/DotNetOpenAuth.OpenId.Provider/OpenId/Provider/AuthenticationRequest.cs @@ -0,0 +1,227 @@ +//----------------------------------------------------------------------- +// <copyright file="AuthenticationRequest.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.Provider { + using System; + using System.Diagnostics.Contracts; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OpenId.Messages; + + /// <summary> + /// Implements the <see cref="IAuthenticationRequest"/> interface + /// so that OpenID Provider sites can easily respond to authentication + /// requests. + /// </summary> + [Serializable] + internal class AuthenticationRequest : HostProcessedRequest, IAuthenticationRequest { + /// <summary> + /// The positive assertion to send, if the host site chooses to send it. + /// </summary> + private readonly PositiveAssertionResponse positiveResponse; + + /// <summary> + /// Initializes a new instance of the <see cref="AuthenticationRequest"/> class. + /// </summary> + /// <param name="provider">The provider that received the request.</param> + /// <param name="request">The incoming authentication request message.</param> + internal AuthenticationRequest(OpenIdProvider provider, CheckIdRequest request) + : base(provider, request) { + Requires.NotNull(provider, "provider"); + + this.positiveResponse = new PositiveAssertionResponse(request); + + if (this.ClaimedIdentifier == Protocol.ClaimedIdentifierForOPIdentifier && + Protocol.ClaimedIdentifierForOPIdentifier != null) { + // Force the hosting OP to deal with identifier_select by nulling out the two identifiers. + this.IsDirectedIdentity = true; + this.positiveResponse.ClaimedIdentifier = null; + this.positiveResponse.LocalIdentifier = null; + } + + // URL delegation is only detectable from 2.0 RPs, since openid.claimed_id isn't included from 1.0 RPs. + // If the openid.claimed_id is present, and if it's different than the openid.identity argument, then + // the RP has discovered a claimed identifier that has delegated authentication to this Provider. + this.IsDelegatedIdentifier = this.ClaimedIdentifier != null && this.ClaimedIdentifier != this.LocalIdentifier; + + Reporting.RecordEventOccurrence("AuthenticationRequest.IsDelegatedIdentifier", this.IsDelegatedIdentifier.ToString()); + } + + #region HostProcessedRequest members + + /// <summary> + /// Gets or sets the provider endpoint. + /// </summary> + /// <value> + /// The default value is the URL that the request came in on from the relying party. + /// </value> + public override Uri ProviderEndpoint { + get { return this.positiveResponse.ProviderEndpoint; } + set { this.positiveResponse.ProviderEndpoint = value; } + } + + #endregion + + /// <summary> + /// Gets a value indicating whether the response is ready to be created and sent. + /// </summary> + public override bool IsResponseReady { + get { + // The null checks on the identifiers is to make sure that an identifier_select + // has been resolved to actual identifiers. + return this.IsAuthenticated.HasValue && + (!this.IsAuthenticated.Value || !this.IsDirectedIdentity || (this.LocalIdentifier != null && this.ClaimedIdentifier != null)); + } + } + + #region IAuthenticationRequest Properties + + /// <summary> + /// Gets a value indicating whether the Provider should help the user + /// select a Claimed Identifier to send back to the relying party. + /// </summary> + public bool IsDirectedIdentity { get; private set; } + + /// <summary> + /// Gets a value indicating whether the requesting Relying Party is using a delegated URL. + /// </summary> + /// <remarks> + /// When delegated identifiers are used, the <see cref="ClaimedIdentifier"/> should not + /// be changed at the Provider during authentication. + /// Delegation is only detectable on requests originating from OpenID 2.0 relying parties. + /// A relying party implementing only OpenID 1.x may use delegation and this property will + /// return false anyway. + /// </remarks> + public bool IsDelegatedIdentifier { get; private set; } + + /// <summary> + /// Gets or sets the Local Identifier to this OpenID Provider of the user attempting + /// to authenticate. Check <see cref="IsDirectedIdentity"/> to see if + /// this value is valid. + /// </summary> + /// <remarks> + /// This may or may not be the same as the Claimed Identifier that the user agent + /// originally supplied to the relying party. The Claimed Identifier + /// endpoint may be delegating authentication to this provider using + /// this provider's local id, which is what this property contains. + /// Use this identifier when looking up this user in the provider's user account + /// list. + /// </remarks> + public Identifier LocalIdentifier { + get { + return this.positiveResponse.LocalIdentifier; + } + + set { + // Keep LocalIdentifier and ClaimedIdentifier in sync for directed identity. + if (this.IsDirectedIdentity) { + if (this.ClaimedIdentifier != null && this.ClaimedIdentifier != value) { + throw new InvalidOperationException(OpenIdStrings.IdentifierSelectRequiresMatchingIdentifiers); + } + + this.positiveResponse.ClaimedIdentifier = value; + } + + this.positiveResponse.LocalIdentifier = value; + } + } + + /// <summary> + /// Gets or sets the identifier that the user agent is claiming at the relying party site. + /// Check <see cref="IsDirectedIdentity"/> to see if this value is valid. + /// </summary> + /// <remarks> + /// <para>This property can only be set if <see cref="IsDelegatedIdentifier"/> is + /// false, to prevent breaking URL delegation.</para> + /// <para>This will not be the same as this provider's local identifier for the user + /// if the user has set up his/her own identity page that points to this + /// provider for authentication.</para> + /// <para>The provider may use this identifier for displaying to the user when + /// asking for the user's permission to authenticate to the relying party.</para> + /// </remarks> + /// <exception cref="InvalidOperationException">Thrown from the setter + /// if <see cref="IsDelegatedIdentifier"/> is true.</exception> + public Identifier ClaimedIdentifier { + get { + return this.positiveResponse.ClaimedIdentifier; + } + + set { + // Keep LocalIdentifier and ClaimedIdentifier in sync for directed identity. + if (this.IsDirectedIdentity) { + this.positiveResponse.LocalIdentifier = value; + } + + this.positiveResponse.ClaimedIdentifier = value; + } + } + + /// <summary> + /// Gets or sets a value indicating whether the provider has determined that the + /// <see cref="ClaimedIdentifier"/> belongs to the currently logged in user + /// and wishes to share this information with the consumer. + /// </summary> + public bool? IsAuthenticated { get; set; } + + #endregion + + /// <summary> + /// Gets the original request message. + /// </summary> + protected new CheckIdRequest RequestMessage { + get { return (CheckIdRequest)base.RequestMessage; } + } + + /// <summary> + /// Gets the response message, once <see cref="IsResponseReady"/> is <c>true</c>. + /// </summary> + protected override IProtocolMessage ResponseMessage { + get { + if (this.IsAuthenticated.HasValue) { + return this.IsAuthenticated.Value ? (IProtocolMessage)this.positiveResponse : this.NegativeResponse; + } else { + return null; + } + } + } + + #region IAuthenticationRequest Methods + + /// <summary> + /// Adds an optional fragment (#fragment) portion to the ClaimedIdentifier. + /// Useful for identifier recycling. + /// </summary> + /// <param name="fragment">Should not include the # prefix character as that will be added internally. + /// May be null or the empty string to clear a previously set fragment.</param> + /// <remarks> + /// <para>Unlike the <see cref="ClaimedIdentifier"/> property, which can only be set if + /// using directed identity, this method can be called on any URI claimed identifier.</para> + /// <para>Because XRI claimed identifiers (the canonical IDs) are never recycled, + /// this method should<i>not</i> be called for XRIs.</para> + /// </remarks> + /// <exception cref="InvalidOperationException"> + /// Thrown when this method is called on an XRI, or on a directed identity + /// request before the <see cref="ClaimedIdentifier"/> property is set. + /// </exception> + public void SetClaimedIdentifierFragment(string fragment) { + UriBuilder builder = new UriBuilder(this.ClaimedIdentifier); + builder.Fragment = fragment; + this.positiveResponse.ClaimedIdentifier = builder.Uri; + } + + /// <summary> + /// Sets the Claimed and Local identifiers even after they have been initially set. + /// </summary> + /// <param name="identifier">The value to set to the <see cref="ClaimedIdentifier"/> and <see cref="LocalIdentifier"/> properties.</param> + internal void ResetClaimedAndLocalIdentifiers(Identifier identifier) { + Requires.NotNull(identifier, "identifier"); + + this.positiveResponse.ClaimedIdentifier = identifier; + this.positiveResponse.LocalIdentifier = identifier; + } + + #endregion + } +} diff --git a/src/DotNetOpenAuth.OpenId.Provider/OpenId/Provider/AutoResponsiveRequest.cs b/src/DotNetOpenAuth.OpenId.Provider/OpenId/Provider/AutoResponsiveRequest.cs new file mode 100644 index 0000000..0d98e67 --- /dev/null +++ b/src/DotNetOpenAuth.OpenId.Provider/OpenId/Provider/AutoResponsiveRequest.cs @@ -0,0 +1,78 @@ +//----------------------------------------------------------------------- +// <copyright file="AutoResponsiveRequest.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.Provider { + using System; + using System.Collections.Generic; + using System.Diagnostics.Contracts; + using System.Linq; + using System.Text; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OpenId.Messages; + + /// <summary> + /// Handles messages coming into an OpenID Provider for which the entire + /// response message can be automatically determined without help from + /// the hosting web site. + /// </summary> + internal class AutoResponsiveRequest : Request { + /// <summary> + /// The response message to send. + /// </summary> + private readonly IProtocolMessage response; + + /// <summary> + /// Initializes a new instance of the <see cref="AutoResponsiveRequest"/> class. + /// </summary> + /// <param name="request">The request message.</param> + /// <param name="response">The response that is ready for transmittal.</param> + /// <param name="securitySettings">The security settings.</param> + internal AutoResponsiveRequest(IDirectedProtocolMessage request, IProtocolMessage response, ProviderSecuritySettings securitySettings) + : base(request, securitySettings) { + Requires.NotNull(response, "response"); + + this.response = response; + } + + /// <summary> + /// Initializes a new instance of the <see cref="AutoResponsiveRequest"/> class + /// for a response to an unrecognizable request. + /// </summary> + /// <param name="response">The response that is ready for transmittal.</param> + /// <param name="securitySettings">The security settings.</param> + internal AutoResponsiveRequest(IProtocolMessage response, ProviderSecuritySettings securitySettings) + : base(IndirectResponseBase.GetVersion(response), securitySettings) { + Requires.NotNull(response, "response"); + + this.response = response; + } + + /// <summary> + /// Gets a value indicating whether the response is ready to be sent to the user agent. + /// </summary> + /// <remarks> + /// This property returns false if there are properties that must be set on this + /// request instance before the response can be sent. + /// </remarks> + public override bool IsResponseReady { + get { return true; } + } + + /// <summary> + /// Gets the response message, once <see cref="IsResponseReady"/> is <c>true</c>. + /// </summary> + internal IProtocolMessage ResponseMessageTestHook { + get { return this.ResponseMessage; } + } + + /// <summary> + /// Gets the response message, once <see cref="IsResponseReady"/> is <c>true</c>. + /// </summary> + protected override IProtocolMessage ResponseMessage { + get { return this.response; } + } + } +} diff --git a/src/DotNetOpenAuth.OpenId.Provider/OpenId/Provider/Behaviors/AXFetchAsSregTransform.cs b/src/DotNetOpenAuth.OpenId.Provider/OpenId/Provider/Behaviors/AXFetchAsSregTransform.cs new file mode 100644 index 0000000..3a72c5e --- /dev/null +++ b/src/DotNetOpenAuth.OpenId.Provider/OpenId/Provider/Behaviors/AXFetchAsSregTransform.cs @@ -0,0 +1,87 @@ +//----------------------------------------------------------------------- +// <copyright file="AXFetchAsSregTransform.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.Provider.Behaviors { + using System; + using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; + using System.Linq; + using System.Text; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OpenId.Behaviors; + using DotNetOpenAuth.OpenId.Extensions; + using DotNetOpenAuth.OpenId.Extensions.AttributeExchange; + using DotNetOpenAuth.OpenId.Extensions.SimpleRegistration; + using DotNetOpenAuth.OpenId.Provider; + using DotNetOpenAuth.OpenId.Provider.Extensions; + using DotNetOpenAuth.OpenId.RelyingParty; + + /// <summary> + /// An Attribute Exchange and Simple Registration filter to make all incoming attribute + /// requests look like Simple Registration requests, and to convert the response + /// to the originally requested extension and format. + /// </summary> + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Sreg", Justification = "Abbreviation")] + public sealed class AXFetchAsSregTransform : AXFetchAsSregTransformBase, IProviderBehavior { + /// <summary> + /// Initializes a new instance of the <see cref="AXFetchAsSregTransform"/> class. + /// </summary> + public AXFetchAsSregTransform() { + } + + #region IProviderBehavior Members + + /// <summary> + /// Applies a well known set of security requirements to a default set of security settings. + /// </summary> + /// <param name="securitySettings">The security settings to enhance with the requirements of this profile.</param> + /// <remarks> + /// Care should be taken to never decrease security when applying a profile. + /// Profiles should only enhance security requirements to avoid being + /// incompatible with each other. + /// </remarks> + void IProviderBehavior.ApplySecuritySettings(ProviderSecuritySettings securitySettings) { + // Nothing to do here. + } + + /// <summary> + /// Called when a request is received by the Provider. + /// </summary> + /// <param name="request">The incoming request.</param> + /// <returns> + /// <c>true</c> if this behavior owns this request and wants to stop other behaviors + /// from handling it; <c>false</c> to allow other behaviors to process this request. + /// </returns> + /// <remarks> + /// Implementations may set a new value to <see cref="IRequest.SecuritySettings"/> but + /// should not change the properties on the instance of <see cref="ProviderSecuritySettings"/> + /// itself as that instance may be shared across many requests. + /// </remarks> + bool IProviderBehavior.OnIncomingRequest(IRequest request) { + var extensionRequest = request as Provider.HostProcessedRequest; + if (extensionRequest != null) { + extensionRequest.UnifyExtensionsAsSreg(); + } + + return false; + } + + /// <summary> + /// Called when the Provider is preparing to send a response to an authentication request. + /// </summary> + /// <param name="request">The request that is configured to generate the outgoing response.</param> + /// <returns> + /// <c>true</c> if this behavior owns this request and wants to stop other behaviors + /// from handling it; <c>false</c> to allow other behaviors to process this request. + /// </returns> + bool IProviderBehavior.OnOutgoingResponse(Provider.IAuthenticationRequest request) { + request.ConvertSregToMatchRequest(); + return false; + } + + #endregion + } +} diff --git a/src/DotNetOpenAuth.OpenId.Provider/OpenId/Provider/Behaviors/GsaIcamProfile.cs b/src/DotNetOpenAuth.OpenId.Provider/OpenId/Provider/Behaviors/GsaIcamProfile.cs new file mode 100644 index 0000000..38f2ae7 --- /dev/null +++ b/src/DotNetOpenAuth.OpenId.Provider/OpenId/Provider/Behaviors/GsaIcamProfile.cs @@ -0,0 +1,193 @@ +//----------------------------------------------------------------------- +// <copyright file="GsaIcamProfile.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.Provider.Behaviors { + using System; + using System.Diagnostics.CodeAnalysis; + using System.Diagnostics.Contracts; + using System.Linq; + using DotNetOpenAuth.Configuration; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OpenId.Behaviors; + using DotNetOpenAuth.OpenId.Extensions.AttributeExchange; + using DotNetOpenAuth.OpenId.Extensions.ProviderAuthenticationPolicy; + using DotNetOpenAuth.OpenId.Extensions.SimpleRegistration; + using DotNetOpenAuth.OpenId.Provider; + using DotNetOpenAuth.OpenId.RelyingParty; + + /// <summary> + /// Implements the Identity, Credential, & Access Management (ICAM) OpenID 2.0 Profile + /// for the General Services Administration (GSA). + /// </summary> + /// <remarks> + /// <para>Relying parties that include this profile are always held to the terms required by the profile, + /// but Providers are only affected by the special behaviors of the profile when the RP specifically + /// indicates that they want to use this profile. </para> + /// </remarks> + [Serializable] + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Icam", Justification = "Acronym")] + public sealed class GsaIcamProfile : GsaIcamProfileBase, IProviderBehavior { + /// <summary> + /// The maximum time a shared association can live. + /// </summary> + private static readonly TimeSpan MaximumAssociationLifetime = TimeSpan.FromSeconds(86400); + + /// <summary> + /// Initializes a new instance of the <see cref="GsaIcamProfile"/> class. + /// </summary> + public GsaIcamProfile() { + if (DisableSslRequirement) { + Logger.OpenId.Warn("GSA level 1 behavior has its RequireSsl requirement disabled."); + } + } + + /// <summary> + /// Gets or sets the provider for generating PPID identifiers. + /// </summary> + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Ppid", Justification = "Acronym")] + public static IDirectedIdentityIdentifierProvider PpidIdentifierProvider { get; set; } + + #region IProviderBehavior Members + + /// <summary> + /// Adapts the default security settings to the requirements of this behavior. + /// </summary> + /// <param name="securitySettings">The original security settings.</param> + void IProviderBehavior.ApplySecuritySettings(ProviderSecuritySettings securitySettings) { + if (securitySettings.MaximumHashBitLength < 256) { + securitySettings.MaximumHashBitLength = 256; + } + + SetMaximumAssociationLifetimeToNotExceed(Protocol.Default.Args.SignatureAlgorithm.HMAC_SHA256, MaximumAssociationLifetime, securitySettings); + SetMaximumAssociationLifetimeToNotExceed(Protocol.Default.Args.SignatureAlgorithm.HMAC_SHA1, MaximumAssociationLifetime, securitySettings); + } + + /// <summary> + /// Called when a request is received by the Provider. + /// </summary> + /// <param name="request">The incoming request.</param> + /// <returns> + /// <c>true</c> if this behavior owns this request and wants to stop other behaviors + /// from handling it; <c>false</c> to allow other behaviors to process this request. + /// </returns> + /// <remarks> + /// Implementations may set a new value to <see cref="IRequest.SecuritySettings"/> but + /// should not change the properties on the instance of <see cref="ProviderSecuritySettings"/> + /// itself as that instance may be shared across many requests. + /// </remarks> + bool IProviderBehavior.OnIncomingRequest(IRequest request) { + var hostProcessedRequest = request as IHostProcessedRequest; + if (hostProcessedRequest != null) { + // Only apply our special policies if the RP requested it. + var papeRequest = request.GetExtension<PolicyRequest>(); + if (papeRequest != null) { + if (papeRequest.PreferredPolicies.Contains(AuthenticationPolicies.USGovernmentTrustLevel1)) { + // Whenever we see this GSA policy requested, we MUST also see the PPID policy requested. + ErrorUtilities.VerifyProtocol(papeRequest.PreferredPolicies.Contains(AuthenticationPolicies.PrivatePersonalIdentifier), BehaviorStrings.PapeRequestMissingRequiredPolicies); + ErrorUtilities.VerifyProtocol(string.Equals(hostProcessedRequest.Realm.Scheme, Uri.UriSchemeHttps, StringComparison.Ordinal) || DisableSslRequirement, BehaviorStrings.RealmMustBeHttps); + + // Apply GSA-specific security to this individual request. + request.SecuritySettings.RequireSsl = !DisableSslRequirement; + return true; + } + } + } + + return false; + } + + /// <summary> + /// Called when the Provider is preparing to send a response to an authentication request. + /// </summary> + /// <param name="request">The request that is configured to generate the outgoing response.</param> + /// <returns> + /// <c>true</c> if this behavior owns this request and wants to stop other behaviors + /// from handling it; <c>false</c> to allow other behaviors to process this request. + /// </returns> + bool IProviderBehavior.OnOutgoingResponse(Provider.IAuthenticationRequest request) { + bool result = false; + + // Nothing to do for negative assertions. + if (!request.IsAuthenticated.Value) { + return result; + } + + var requestInternal = (Provider.AuthenticationRequest)request; + var responseMessage = (IProtocolMessageWithExtensions)requestInternal.Response; + + // Only apply our special policies if the RP requested it. + var papeRequest = request.GetExtension<PolicyRequest>(); + if (papeRequest != null) { + var papeResponse = responseMessage.Extensions.OfType<PolicyResponse>().SingleOrDefault(); + if (papeResponse == null) { + request.AddResponseExtension(papeResponse = new PolicyResponse()); + } + + if (papeRequest.PreferredPolicies.Contains(AuthenticationPolicies.USGovernmentTrustLevel1)) { + result = true; + if (!papeResponse.ActualPolicies.Contains(AuthenticationPolicies.USGovernmentTrustLevel1)) { + papeResponse.ActualPolicies.Add(AuthenticationPolicies.USGovernmentTrustLevel1); + } + + // The spec requires that the OP perform discovery and if that fails, it must either sternly + // warn the user of a potential threat or just abort the authentication. + // We can't verify that the OP displayed anything to the user at this level, but we can + // at least verify that the OP performed the discovery on the realm and halt things if it didn't. + ErrorUtilities.VerifyHost(requestInternal.HasRealmDiscoveryBeenPerformed, BehaviorStrings.RealmDiscoveryNotPerformed); + } + + if (papeRequest.PreferredPolicies.Contains(AuthenticationPolicies.PrivatePersonalIdentifier)) { + ErrorUtilities.VerifyProtocol(request.ClaimedIdentifier == request.LocalIdentifier, OpenIdStrings.DelegatingIdentifiersNotAllowed); + + // Mask the user's identity with a PPID. + ErrorUtilities.VerifyHost(PpidIdentifierProvider != null, BehaviorStrings.PpidProviderNotGiven); + Identifier ppidIdentifier = PpidIdentifierProvider.GetIdentifier(request.LocalIdentifier, request.Realm); + requestInternal.ResetClaimedAndLocalIdentifiers(ppidIdentifier); + + // Indicate that the RP is receiving a PPID claimed_id + if (!papeResponse.ActualPolicies.Contains(AuthenticationPolicies.PrivatePersonalIdentifier)) { + papeResponse.ActualPolicies.Add(AuthenticationPolicies.PrivatePersonalIdentifier); + } + } + + if (papeRequest.PreferredPolicies.Contains(AuthenticationPolicies.NoPersonallyIdentifiableInformation)) { + ErrorUtilities.VerifyProtocol( + !responseMessage.Extensions.OfType<ClaimsResponse>().Any() && + !responseMessage.Extensions.OfType<FetchResponse>().Any(), + BehaviorStrings.PiiIncludedWithNoPiiPolicy); + + // If no PII is given in extensions, and the claimed_id is a PPID, then we can state we issue no PII. + if (papeResponse.ActualPolicies.Contains(AuthenticationPolicies.PrivatePersonalIdentifier)) { + if (!papeResponse.ActualPolicies.Contains(AuthenticationPolicies.NoPersonallyIdentifiableInformation)) { + papeResponse.ActualPolicies.Add(AuthenticationPolicies.NoPersonallyIdentifiableInformation); + } + } + } + + Reporting.RecordEventOccurrence(this, "OP"); + } + + return result; + } + + #endregion + + /// <summary> + /// Ensures the maximum association lifetime does not exceed a given limit. + /// </summary> + /// <param name="associationType">Type of the association.</param> + /// <param name="maximumLifetime">The maximum lifetime.</param> + /// <param name="securitySettings">The security settings to adjust.</param> + private static void SetMaximumAssociationLifetimeToNotExceed(string associationType, TimeSpan maximumLifetime, ProviderSecuritySettings securitySettings) { + Contract.Requires(!String.IsNullOrEmpty(associationType)); + Contract.Requires(maximumLifetime.TotalSeconds > 0); + if (!securitySettings.AssociationLifetimes.ContainsKey(associationType) || + securitySettings.AssociationLifetimes[associationType] > maximumLifetime) { + securitySettings.AssociationLifetimes[associationType] = maximumLifetime; + } + } + } +} diff --git a/src/DotNetOpenAuth.OpenId.Provider/OpenId/Provider/Behaviors/PpidGeneration.cs b/src/DotNetOpenAuth.OpenId.Provider/OpenId/Provider/Behaviors/PpidGeneration.cs new file mode 100644 index 0000000..1a6898e --- /dev/null +++ b/src/DotNetOpenAuth.OpenId.Provider/OpenId/Provider/Behaviors/PpidGeneration.cs @@ -0,0 +1,122 @@ +//----------------------------------------------------------------------- +// <copyright file="PpidGeneration.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.Provider.Behaviors { + using System; + using System.Diagnostics.CodeAnalysis; + using System.Diagnostics.Contracts; + using System.Linq; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OpenId.Behaviors; + using DotNetOpenAuth.OpenId.Extensions.ProviderAuthenticationPolicy; + using DotNetOpenAuth.OpenId.Provider; + + /// <summary> + /// Offers OpenID Providers automatic PPID Claimed Identifier generation when requested + /// by a PAPE request. + /// </summary> + /// <remarks> + /// <para>PPIDs are set on positive authentication responses when the PAPE request includes + /// the <see cref="AuthenticationPolicies.PrivatePersonalIdentifier"/> authentication policy.</para> + /// <para>The static member <see cref="PpidGeneration.PpidIdentifierProvider"/> MUST + /// be set prior to any PPID requests come in. Typically this should be set in the + /// <c>Application_Start</c> method in the global.asax.cs file.</para> + /// </remarks> + [Serializable] + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Ppid", Justification = "Abbreviation")] + public sealed class PpidGeneration : IProviderBehavior { + /// <summary> + /// Gets or sets the provider for generating PPID identifiers. + /// </summary> + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Ppid", Justification = "Abbreviation")] + public static IDirectedIdentityIdentifierProvider PpidIdentifierProvider { get; set; } + + #region IProviderBehavior Members + + /// <summary> + /// Applies a well known set of security requirements to a default set of security settings. + /// </summary> + /// <param name="securitySettings">The security settings to enhance with the requirements of this profile.</param> + /// <remarks> + /// Care should be taken to never decrease security when applying a profile. + /// Profiles should only enhance security requirements to avoid being + /// incompatible with each other. + /// </remarks> + void IProviderBehavior.ApplySecuritySettings(ProviderSecuritySettings securitySettings) { + // No special security to apply here. + } + + /// <summary> + /// Called when a request is received by the Provider. + /// </summary> + /// <param name="request">The incoming request.</param> + /// <returns> + /// <c>true</c> if this behavior owns this request and wants to stop other behaviors + /// from handling it; <c>false</c> to allow other behaviors to process this request. + /// </returns> + /// <remarks> + /// Implementations may set a new value to <see cref="IRequest.SecuritySettings"/> but + /// should not change the properties on the instance of <see cref="ProviderSecuritySettings"/> + /// itself as that instance may be shared across many requests. + /// </remarks> + bool IProviderBehavior.OnIncomingRequest(IRequest request) { + return false; + } + + /// <summary> + /// Called when the Provider is preparing to send a response to an authentication request. + /// </summary> + /// <param name="request">The request that is configured to generate the outgoing response.</param> + /// <returns> + /// <c>true</c> if this behavior owns this request and wants to stop other behaviors + /// from handling it; <c>false</c> to allow other behaviors to process this request. + /// </returns> + bool IProviderBehavior.OnOutgoingResponse(IAuthenticationRequest request) { + // Nothing to do for negative assertions. + if (!request.IsAuthenticated.Value) { + return false; + } + + var requestInternal = (Provider.AuthenticationRequest)request; + var responseMessage = (IProtocolMessageWithExtensions)requestInternal.Response; + + // Only apply our special policies if the RP requested it. + var papeRequest = request.GetExtension<PolicyRequest>(); + if (papeRequest != null) { + if (papeRequest.PreferredPolicies.Contains(AuthenticationPolicies.PrivatePersonalIdentifier)) { + ErrorUtilities.VerifyProtocol(request.ClaimedIdentifier == request.LocalIdentifier, OpenIdStrings.DelegatingIdentifiersNotAllowed); + + if (PpidIdentifierProvider == null) { + Logger.OpenId.Error(BehaviorStrings.PpidProviderNotGiven); + return false; + } + + // Mask the user's identity with a PPID. + if (PpidIdentifierProvider.IsUserLocalIdentifier(request.LocalIdentifier)) { + Identifier ppidIdentifier = PpidIdentifierProvider.GetIdentifier(request.LocalIdentifier, request.Realm); + requestInternal.ResetClaimedAndLocalIdentifiers(ppidIdentifier); + } + + // Indicate that the RP is receiving a PPID claimed_id + var papeResponse = responseMessage.Extensions.OfType<PolicyResponse>().SingleOrDefault(); + if (papeResponse == null) { + request.AddResponseExtension(papeResponse = new PolicyResponse()); + } + + if (!papeResponse.ActualPolicies.Contains(AuthenticationPolicies.PrivatePersonalIdentifier)) { + papeResponse.ActualPolicies.Add(AuthenticationPolicies.PrivatePersonalIdentifier); + } + + Reporting.RecordEventOccurrence(this, string.Empty); + } + } + + return false; + } + + #endregion + } +} diff --git a/src/DotNetOpenAuth.OpenId.Provider/OpenId/Provider/Extensions/ExtensionsInteropHelper.cs b/src/DotNetOpenAuth.OpenId.Provider/OpenId/Provider/Extensions/ExtensionsInteropHelper.cs new file mode 100644 index 0000000..eda768b --- /dev/null +++ b/src/DotNetOpenAuth.OpenId.Provider/OpenId/Provider/Extensions/ExtensionsInteropHelper.cs @@ -0,0 +1,162 @@ +//----------------------------------------------------------------------- +// <copyright file="ExtensionsInteropHelper.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.Provider.Extensions { + using System; + using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; + using System.Diagnostics.Contracts; + using System.Linq; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OpenId.Extensions; + using DotNetOpenAuth.OpenId.Extensions.AttributeExchange; + using DotNetOpenAuth.OpenId.Extensions.SimpleRegistration; + using DotNetOpenAuth.OpenId.Messages; + + /// <summary> + /// A set of methods designed to assist in improving interop across different + /// OpenID implementations and their extensions. + /// </summary> + internal static class ExtensionsInteropHelper { + /// <summary> + /// Transforms an AX attribute type URI from the axschema.org format into a given format. + /// </summary> + /// <param name="axSchemaOrgFormatTypeUri">The ax schema org format type URI.</param> + /// <param name="targetFormat">The target format. Only one flag should be set.</param> + /// <returns>The AX attribute type URI in the target format.</returns> + internal static string TransformAXFormatTestHook(string axSchemaOrgFormatTypeUri, AXAttributeFormats targetFormat) { + return OpenIdExtensionsInteropHelper.TransformAXFormat(axSchemaOrgFormatTypeUri, targetFormat); + } + + /// <summary> + /// Looks for Simple Registration and Attribute Exchange (all known formats) + /// request extensions and returns them as a Simple Registration extension, + /// and adds the new extension to the original request message if it was absent. + /// </summary> + /// <param name="request">The authentication request.</param> + /// <returns> + /// The Simple Registration request if found, + /// or a fabricated one based on the Attribute Exchange extension if found, + /// or <c>null</c> if no attribute extension request is found.</returns> + internal static ClaimsRequest UnifyExtensionsAsSreg(this Provider.IHostProcessedRequest request) { + Requires.NotNull(request, "request"); + + var req = (Provider.HostProcessedRequest)request; + var sreg = req.GetExtension<ClaimsRequest>(); + if (sreg != null) { + return sreg; + } + + var ax = req.GetExtension<FetchRequest>(); + if (ax != null) { + sreg = new ClaimsRequest(DotNetOpenAuth.OpenId.Extensions.SimpleRegistration.Constants.sreg_ns); + sreg.Synthesized = true; + ((IProtocolMessageWithExtensions)req.RequestMessage).Extensions.Add(sreg); + sreg.BirthDate = GetDemandLevelFor(ax, WellKnownAttributes.BirthDate.WholeBirthDate); + sreg.Country = GetDemandLevelFor(ax, WellKnownAttributes.Contact.HomeAddress.Country); + sreg.Email = GetDemandLevelFor(ax, WellKnownAttributes.Contact.Email); + sreg.FullName = GetDemandLevelFor(ax, WellKnownAttributes.Name.FullName); + sreg.Gender = GetDemandLevelFor(ax, WellKnownAttributes.Person.Gender); + sreg.Language = GetDemandLevelFor(ax, WellKnownAttributes.Preferences.Language); + sreg.Nickname = GetDemandLevelFor(ax, WellKnownAttributes.Name.Alias); + sreg.PostalCode = GetDemandLevelFor(ax, WellKnownAttributes.Contact.HomeAddress.PostalCode); + sreg.TimeZone = GetDemandLevelFor(ax, WellKnownAttributes.Preferences.TimeZone); + } + + return sreg; + } + + /// <summary> + /// Converts the Simple Registration extension response to whatever format the original + /// attribute request extension came in. + /// </summary> + /// <param name="request">The authentication request with the response extensions already added.</param> + /// <remarks> + /// If the original attribute request came in as AX, the Simple Registration extension is converted + /// to an AX response and then the Simple Registration extension is removed from the response. + /// </remarks> + internal static void ConvertSregToMatchRequest(this Provider.IHostProcessedRequest request) { + var req = (Provider.HostProcessedRequest)request; + var response = req.Response as IProtocolMessageWithExtensions; // negative responses don't support extensions. + var sregRequest = request.GetExtension<ClaimsRequest>(); + if (sregRequest != null && response != null) { + if (sregRequest.Synthesized) { + var axRequest = request.GetExtension<FetchRequest>(); + ErrorUtilities.VerifyInternal(axRequest != null, "How do we have a synthesized Sreg request without an AX request?"); + + var sregResponse = response.Extensions.OfType<ClaimsResponse>().SingleOrDefault(); + if (sregResponse == null) { + // No Sreg response to copy from. + return; + } + + // Remove the sreg response since the RP didn't ask for it. + response.Extensions.Remove(sregResponse); + + AXAttributeFormats format = OpenIdExtensionsInteropHelper.DetectAXFormat(axRequest.Attributes.Select(att => att.TypeUri)); + if (format == AXAttributeFormats.None) { + // No recognized AX attributes were requested. + return; + } + + var axResponse = response.Extensions.OfType<FetchResponse>().SingleOrDefault(); + if (axResponse == null) { + axResponse = new FetchResponse(); + response.Extensions.Add(axResponse); + } + + AddAXAttributeValue(axResponse, WellKnownAttributes.BirthDate.WholeBirthDate, format, sregResponse.BirthDateRaw); + AddAXAttributeValue(axResponse, WellKnownAttributes.Contact.HomeAddress.Country, format, sregResponse.Country); + AddAXAttributeValue(axResponse, WellKnownAttributes.Contact.HomeAddress.PostalCode, format, sregResponse.PostalCode); + AddAXAttributeValue(axResponse, WellKnownAttributes.Contact.Email, format, sregResponse.Email); + AddAXAttributeValue(axResponse, WellKnownAttributes.Name.FullName, format, sregResponse.FullName); + AddAXAttributeValue(axResponse, WellKnownAttributes.Name.Alias, format, sregResponse.Nickname); + AddAXAttributeValue(axResponse, WellKnownAttributes.Preferences.TimeZone, format, sregResponse.TimeZone); + AddAXAttributeValue(axResponse, WellKnownAttributes.Preferences.Language, format, sregResponse.Language); + if (sregResponse.Gender.HasValue) { + AddAXAttributeValue(axResponse, WellKnownAttributes.Person.Gender, format, OpenIdExtensionsInteropHelper.GenderEncoder.Encode(sregResponse.Gender)); + } + } + } + } + + /// <summary> + /// Adds the AX attribute value to the response if it is non-empty. + /// </summary> + /// <param name="ax">The AX Fetch response to add the attribute value to.</param> + /// <param name="typeUri">The attribute type URI in axschema.org format.</param> + /// <param name="format">The target format of the actual attribute to write out.</param> + /// <param name="value">The value of the attribute.</param> + private static void AddAXAttributeValue(FetchResponse ax, string typeUri, AXAttributeFormats format, string value) { + if (!string.IsNullOrEmpty(value)) { + string targetTypeUri = OpenIdExtensionsInteropHelper.TransformAXFormat(typeUri, format); + if (!ax.Attributes.Contains(targetTypeUri)) { + ax.Attributes.Add(targetTypeUri, value); + } + } + } + + /// <summary> + /// Gets the demand level for an AX attribute. + /// </summary> + /// <param name="ax">The AX fetch request to search for the attribute.</param> + /// <param name="typeUri">The type URI of the attribute in axschema.org format.</param> + /// <returns>The demand level for the attribute.</returns> + private static DemandLevel GetDemandLevelFor(FetchRequest ax, string typeUri) { + Requires.NotNull(ax, "ax"); + Requires.NotNullOrEmpty(typeUri, "typeUri"); + + foreach (AXAttributeFormats format in OpenIdExtensionsInteropHelper.ForEachFormat(AXAttributeFormats.All)) { + string typeUriInFormat = OpenIdExtensionsInteropHelper.TransformAXFormat(typeUri, format); + if (ax.Attributes.Contains(typeUriInFormat)) { + return ax.Attributes[typeUriInFormat].IsRequired ? DemandLevel.Require : DemandLevel.Request; + } + } + + return DemandLevel.NoRequest; + } + } +} diff --git a/src/DotNetOpenAuth.OpenId.Provider/OpenId/Provider/Extensions/UI/UIRequestTools.cs b/src/DotNetOpenAuth.OpenId.Provider/OpenId/Provider/Extensions/UI/UIRequestTools.cs new file mode 100644 index 0000000..80ee2f1 --- /dev/null +++ b/src/DotNetOpenAuth.OpenId.Provider/OpenId/Provider/Extensions/UI/UIRequestTools.cs @@ -0,0 +1,68 @@ +//----------------------------------------------------------------------- +// <copyright file="UIRequestTools.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.Provider.Extensions.UI { + using System; + using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; + using System.Diagnostics.Contracts; + using System.Globalization; + using System.Linq; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OpenId.Extensions.UI; + using DotNetOpenAuth.OpenId.Messages; + using DotNetOpenAuth.OpenId.Provider; + using DotNetOpenAuth.Xrds; + + /// <summary> + /// OpenID User Interface extension 1.0 request message. + /// </summary> + /// <remarks> + /// <para>Implements the extension described by: http://wiki.openid.net/f/openid_ui_extension_draft01.html </para> + /// <para>This extension only applies to checkid_setup requests, since checkid_immediate requests display + /// no UI to the user. </para> + /// <para>For rules about how the popup window should be displayed, please see the documentation of + /// <see cref="UIModes.Popup"/>. </para> + /// <para>An RP may determine whether an arbitrary OP supports this extension (and thereby determine + /// whether to use a standard full window redirect or a popup) via the + /// <see cref="IdentifierDiscoveryResult.IsExtensionSupported<T>()"/> method.</para> + /// </remarks> + public static class UIRequestTools { + /// <summary> + /// Gets the URL of the RP icon for the OP to display. + /// </summary> + /// <param name="realm">The realm of the RP where the authentication request originated.</param> + /// <param name="webRequestHandler">The web request handler to use for discovery. + /// Usually available via <see cref="Channel.WebRequestHandler">OpenIdProvider.Channel.WebRequestHandler</see>.</param> + /// <returns> + /// A sequence of the RP's icons it has available for the Provider to display, in decreasing preferred order. + /// </returns> + /// <value>The icon URL.</value> + /// <remarks> + /// This property is automatically set for the OP with the result of RP discovery. + /// RPs should set this value by including an entry such as this in their XRDS document. + /// <example> + /// <Service xmlns="xri://$xrd*($v*2.0)"> + /// <Type>http://specs.openid.net/extensions/ui/icon</Type> + /// <URI>http://consumer.example.com/images/image.jpg</URI> + /// </Service> + /// </example> + /// </remarks> + public static IEnumerable<Uri> GetRelyingPartyIconUrls(Realm realm, IDirectWebRequestHandler webRequestHandler) { + Contract.Requires(realm != null); + Contract.Requires(webRequestHandler != null); + ErrorUtilities.VerifyArgumentNotNull(realm, "realm"); + ErrorUtilities.VerifyArgumentNotNull(webRequestHandler, "webRequestHandler"); + + XrdsDocument xrds = realm.Discover(webRequestHandler, false); + if (xrds == null) { + return Enumerable.Empty<Uri>(); + } else { + return xrds.FindRelyingPartyIcons(); + } + } + } +} diff --git a/src/DotNetOpenAuth.OpenId.Provider/OpenId/Provider/HmacShaAssociationProvider.cs b/src/DotNetOpenAuth.OpenId.Provider/OpenId/Provider/HmacShaAssociationProvider.cs new file mode 100644 index 0000000..3cfc0b6 --- /dev/null +++ b/src/DotNetOpenAuth.OpenId.Provider/OpenId/Provider/HmacShaAssociationProvider.cs @@ -0,0 +1,71 @@ +//----------------------------------------------------------------------- +// <copyright file="HmacShaAssociationProvider.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.Provider { + using System; + using System.Collections.Generic; + using System.Diagnostics.Contracts; + using System.Linq; + using System.Text; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OpenId.Provider; + + /// <summary> + /// OpenID Provider utility methods for HMAC-SHA* associations. + /// </summary> + internal static class HmacShaAssociationProvider { + /// <summary> + /// The default lifetime of a shared association when no lifetime is given + /// for a specific association type. + /// </summary> + private static readonly TimeSpan DefaultMaximumLifetime = TimeSpan.FromDays(14); + + /// <summary> + /// Creates a new association of a given type at an OpenID Provider. + /// </summary> + /// <param name="protocol">The protocol.</param> + /// <param name="associationType">Type of the association (i.e. HMAC-SHA1 or HMAC-SHA256)</param> + /// <param name="associationUse">A value indicating whether the new association will be used privately by the Provider for "dumb mode" authentication + /// or shared with the Relying Party for "smart mode" authentication.</param> + /// <param name="associationStore">The Provider's association store.</param> + /// <param name="securitySettings">The security settings of the Provider.</param> + /// <returns> + /// The newly created association. + /// </returns> + /// <remarks> + /// The new association is NOT automatically put into an association store. This must be done by the caller. + /// </remarks> + internal static HmacShaAssociation Create(Protocol protocol, string associationType, AssociationRelyingPartyType associationUse, IProviderAssociationStore associationStore, ProviderSecuritySettings securitySettings) { + Requires.NotNull(protocol, "protocol"); + Requires.NotNullOrEmpty(associationType, "associationType"); + Requires.NotNull(associationStore, "associationStore"); + Requires.NotNull(securitySettings, "securitySettings"); + Contract.Ensures(Contract.Result<HmacShaAssociation>() != null); + + int secretLength = HmacShaAssociation.GetSecretLength(protocol, associationType); + + // Generate the secret that will be used for signing + byte[] secret = MessagingUtilities.GetCryptoRandomData(secretLength); + + TimeSpan lifetime; + if (associationUse == AssociationRelyingPartyType.Smart) { + if (!securitySettings.AssociationLifetimes.TryGetValue(associationType, out lifetime)) { + lifetime = DefaultMaximumLifetime; + } + } else { + lifetime = HmacShaAssociation.DumbSecretLifetime; + } + + string handle = associationStore.Serialize(secret, DateTime.UtcNow + lifetime, associationUse == AssociationRelyingPartyType.Dumb); + + Contract.Assert(protocol != null); // All the way up to the method call, the condition holds, yet we get a Requires failure next + Contract.Assert(secret != null); + Contract.Assert(!String.IsNullOrEmpty(associationType)); + var result = HmacShaAssociation.Create(protocol, associationType, handle, secret, lifetime); + return result; + } + } +} diff --git a/src/DotNetOpenAuth.OpenId.Provider/OpenId/Provider/HostProcessedRequest.cs b/src/DotNetOpenAuth.OpenId.Provider/OpenId/Provider/HostProcessedRequest.cs new file mode 100644 index 0000000..3647a63 --- /dev/null +++ b/src/DotNetOpenAuth.OpenId.Provider/OpenId/Provider/HostProcessedRequest.cs @@ -0,0 +1,182 @@ +//----------------------------------------------------------------------- +// <copyright file="HostProcessedRequest.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.Provider { + using System; + using System.Collections.Generic; + using System.Diagnostics.Contracts; + using System.Linq; + using System.Net; + using System.Text; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OpenId.Messages; + + /// <summary> + /// A base class from which identity and non-identity RP requests can derive. + /// </summary> + [Serializable] + internal abstract class HostProcessedRequest : Request, IHostProcessedRequest { + /// <summary> + /// The negative assertion to send, if the host site chooses to send it. + /// </summary> + private readonly NegativeAssertionResponse negativeResponse; + + /// <summary> + /// A cache of the result from discovery of the Realm URL. + /// </summary> + private RelyingPartyDiscoveryResult? realmDiscoveryResult; + + /// <summary> + /// Initializes a new instance of the <see cref="HostProcessedRequest"/> class. + /// </summary> + /// <param name="provider">The provider that received the request.</param> + /// <param name="request">The incoming request message.</param> + protected HostProcessedRequest(OpenIdProvider provider, SignedResponseRequest request) + : base(request, provider.SecuritySettings) { + Requires.NotNull(provider, "provider"); + + this.negativeResponse = new NegativeAssertionResponse(request, provider.Channel); + Reporting.RecordEventOccurrence(this, request.Realm); + } + + #region IHostProcessedRequest Properties + + /// <summary> + /// Gets the version of OpenID being used by the relying party that sent the request. + /// </summary> + public ProtocolVersion RelyingPartyVersion { + get { return Protocol.Lookup(this.RequestMessage.Version).ProtocolVersion; } + } + + /// <summary> + /// Gets a value indicating whether the consumer demands an immediate response. + /// If false, the consumer is willing to wait for the identity provider + /// to authenticate the user. + /// </summary> + public bool Immediate { + get { return this.RequestMessage.Immediate; } + } + + /// <summary> + /// Gets the URL the consumer site claims to use as its 'base' address. + /// </summary> + public Realm Realm { + get { return this.RequestMessage.Realm; } + } + + /// <summary> + /// Gets or sets the provider endpoint. + /// </summary> + /// <value> + /// The default value is the URL that the request came in on from the relying party. + /// </value> + public abstract Uri ProviderEndpoint { get; set; } + + #endregion + + /// <summary> + /// Gets a value indicating whether realm discovery been performed. + /// </summary> + internal bool HasRealmDiscoveryBeenPerformed { + get { return this.realmDiscoveryResult.HasValue; } + } + + /// <summary> + /// Gets the negative response. + /// </summary> + protected NegativeAssertionResponse NegativeResponse { + get { return this.negativeResponse; } + } + + /// <summary> + /// Gets the original request message. + /// </summary> + /// <value>This may be null in the case of an unrecognizable message.</value> + protected new SignedResponseRequest RequestMessage { + get { return (SignedResponseRequest)base.RequestMessage; } + } + + #region IHostProcessedRequest Methods + + /// <summary> + /// Gets a value indicating whether verification of the return URL claimed by the Relying Party + /// succeeded. + /// </summary> + /// <param name="requestHandler">The request handler.</param> + /// <returns> + /// Result of realm discovery. + /// </returns> + /// <remarks> + /// Return URL verification is only attempted if this property is queried. + /// The result of the verification is cached per request so calling this + /// property getter multiple times in one request is not a performance hit. + /// See OpenID Authentication 2.0 spec section 9.2.1. + /// </remarks> + public RelyingPartyDiscoveryResult IsReturnUrlDiscoverable(IDirectWebRequestHandler requestHandler) { + if (!this.realmDiscoveryResult.HasValue) { + this.realmDiscoveryResult = this.IsReturnUrlDiscoverableCore(requestHandler); + } + + return this.realmDiscoveryResult.Value; + } + + /// <summary> + /// Gets a value indicating whether verification of the return URL claimed by the Relying Party + /// succeeded. + /// </summary> + /// <param name="requestHandler">The request handler.</param> + /// <returns> + /// Result of realm discovery. + /// </returns> + private RelyingPartyDiscoveryResult IsReturnUrlDiscoverableCore(IDirectWebRequestHandler requestHandler) { + Requires.NotNull(requestHandler, "requestHandler"); + + ErrorUtilities.VerifyInternal(this.Realm != null, "Realm should have been read or derived by now."); + + try { + if (this.SecuritySettings.RequireSsl && this.Realm.Scheme != Uri.UriSchemeHttps) { + Logger.OpenId.WarnFormat("RP discovery failed because RequireSsl is true and RP discovery would begin at insecure URL {0}.", this.Realm); + return RelyingPartyDiscoveryResult.NoServiceDocument; + } + + var returnToEndpoints = this.Realm.DiscoverReturnToEndpoints(requestHandler, false); + if (returnToEndpoints == null) { + return RelyingPartyDiscoveryResult.NoServiceDocument; + } + + foreach (var returnUrl in returnToEndpoints) { + Realm discoveredReturnToUrl = returnUrl.ReturnToEndpoint; + + // The spec requires that the return_to URLs given in an RPs XRDS doc + // do not contain wildcards. + if (discoveredReturnToUrl.DomainWildcard) { + Logger.Yadis.WarnFormat("Realm {0} contained return_to URL {1} which contains a wildcard, which is not allowed.", Realm, discoveredReturnToUrl); + continue; + } + + // Use the same rules as return_to/realm matching to check whether this + // URL fits the return_to URL we were given. + if (discoveredReturnToUrl.Contains(this.RequestMessage.ReturnTo)) { + // no need to keep looking after we find a match + return RelyingPartyDiscoveryResult.Success; + } + } + } catch (ProtocolException ex) { + // Don't do anything else. We quietly fail at return_to verification and return false. + Logger.Yadis.InfoFormat("Relying party discovery at URL {0} failed. {1}", Realm, ex); + return RelyingPartyDiscoveryResult.NoServiceDocument; + } catch (WebException ex) { + // Don't do anything else. We quietly fail at return_to verification and return false. + Logger.Yadis.InfoFormat("Relying party discovery at URL {0} failed. {1}", Realm, ex); + return RelyingPartyDiscoveryResult.NoServiceDocument; + } + + return RelyingPartyDiscoveryResult.NoMatchingReturnTo; + } + + #endregion + } +} diff --git a/src/DotNetOpenAuth/OpenId/Provider/IAnonymousRequest.cs b/src/DotNetOpenAuth.OpenId.Provider/OpenId/Provider/IAnonymousRequest.cs index ec2c175..ec2c175 100644 --- a/src/DotNetOpenAuth/OpenId/Provider/IAnonymousRequest.cs +++ b/src/DotNetOpenAuth.OpenId.Provider/OpenId/Provider/IAnonymousRequest.cs diff --git a/src/DotNetOpenAuth.OpenId.Provider/OpenId/Provider/IDirectedIdentityIdentifierProvider.cs b/src/DotNetOpenAuth.OpenId.Provider/OpenId/Provider/IDirectedIdentityIdentifierProvider.cs new file mode 100644 index 0000000..9197761 --- /dev/null +++ b/src/DotNetOpenAuth.OpenId.Provider/OpenId/Provider/IDirectedIdentityIdentifierProvider.cs @@ -0,0 +1,83 @@ +//----------------------------------------------------------------------- +// <copyright file="IDirectedIdentityIdentifierProvider.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.Provider { + using System; + using System.Diagnostics.Contracts; + + /// <summary> + /// An interface to provide custom identifiers for users logging into specific relying parties. + /// </summary> + /// <remarks> + /// This interface would allow, for example, the Provider to offer PPIDs to their users, + /// allowing the users to log into RPs without leaving any clue as to their true identity, + /// and preventing multiple RPs from colluding to track user activity across realms. + /// </remarks> + [ContractClass(typeof(IDirectedIdentityIdentifierProviderContract))] + public interface IDirectedIdentityIdentifierProvider { + /// <summary> + /// Gets the Identifier to use for the Claimed Identifier and Local Identifier of + /// an outgoing positive assertion. + /// </summary> + /// <param name="localIdentifier">The OP local identifier for the authenticating user.</param> + /// <param name="relyingPartyRealm">The realm of the relying party receiving the assertion.</param> + /// <returns> + /// A valid, discoverable OpenID Identifier that should be used as the value for the + /// openid.claimed_id and openid.local_id parameters. Must not be null. + /// </returns> + Uri GetIdentifier(Identifier localIdentifier, Realm relyingPartyRealm); + + /// <summary> + /// Determines whether a given identifier is the primary (non-PPID) local identifier for some user. + /// </summary> + /// <param name="identifier">The identifier in question.</param> + /// <returns> + /// <c>true</c> if the given identifier is the valid, unique identifier for some uesr (and NOT a PPID); otherwise, <c>false</c>. + /// </returns> + [Pure] + bool IsUserLocalIdentifier(Identifier identifier); + } + + /// <summary> + /// Contract class for the <see cref="IDirectedIdentityIdentifierProvider"/> type. + /// </summary> + [ContractClassFor(typeof(IDirectedIdentityIdentifierProvider))] + internal abstract class IDirectedIdentityIdentifierProviderContract : IDirectedIdentityIdentifierProvider { + #region IDirectedIdentityIdentifierProvider Members + + /// <summary> + /// Gets the Identifier to use for the Claimed Identifier and Local Identifier of + /// an outgoing positive assertion. + /// </summary> + /// <param name="localIdentifier">The OP local identifier for the authenticating user.</param> + /// <param name="relyingPartyRealm">The realm of the relying party receiving the assertion.</param> + /// <returns> + /// A valid, discoverable OpenID Identifier that should be used as the value for the + /// openid.claimed_id and openid.local_id parameters. Must not be null. + /// </returns> + Uri IDirectedIdentityIdentifierProvider.GetIdentifier(Identifier localIdentifier, Realm relyingPartyRealm) { + Requires.NotNull(localIdentifier, "localIdentifier"); + Requires.NotNull(relyingPartyRealm, "relyingPartyRealm"); + Requires.True(((IDirectedIdentityIdentifierProvider)this).IsUserLocalIdentifier(localIdentifier), "localIdentifier", OpenIdStrings.ArgumentIsPpidIdentifier); + throw new NotImplementedException(); + } + + /// <summary> + /// Determines whether a given identifier is the primary (non-PPID) local identifier for some user. + /// </summary> + /// <param name="identifier">The identifier in question.</param> + /// <returns> + /// <c>true</c> if the given identifier is the valid, unique identifier for some uesr (and NOT a PPID); otherwise, <c>false</c>. + /// </returns> + bool IDirectedIdentityIdentifierProvider.IsUserLocalIdentifier(Identifier identifier) { + Requires.NotNull(identifier, "identifier"); + + throw new NotImplementedException(); + } + + #endregion + } +} diff --git a/src/DotNetOpenAuth/OpenId/Provider/IErrorReporting.cs b/src/DotNetOpenAuth.OpenId.Provider/OpenId/Provider/IErrorReporting.cs index 1c73595..1c73595 100644 --- a/src/DotNetOpenAuth/OpenId/Provider/IErrorReporting.cs +++ b/src/DotNetOpenAuth.OpenId.Provider/OpenId/Provider/IErrorReporting.cs diff --git a/src/DotNetOpenAuth.OpenId.Provider/OpenId/Provider/IProviderAssociationStore.cs b/src/DotNetOpenAuth.OpenId.Provider/OpenId/Provider/IProviderAssociationStore.cs new file mode 100644 index 0000000..6c749f6 --- /dev/null +++ b/src/DotNetOpenAuth.OpenId.Provider/OpenId/Provider/IProviderAssociationStore.cs @@ -0,0 +1,90 @@ +//----------------------------------------------------------------------- +// <copyright file="IProviderAssociationStore.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.Provider { + using System; + using System.Collections.Generic; + using System.Diagnostics.Contracts; + using System.Linq; + using System.Text; + using DotNetOpenAuth.Messaging; + + /// <summary> + /// Provides association serialization and deserialization. + /// </summary> + /// <remarks> + /// Implementations may choose to store the association details in memory or a database table and simply return a + /// short, randomly generated string that is the key to that data. Alternatively, an implementation may + /// sign and encrypt the association details and then encode the results as a base64 string and return that value + /// as the association handle, thereby avoiding any association persistence at the OpenID Provider. + /// When taking the latter approach however, it is of course imperative that the association be encrypted + /// to avoid disclosing the secret to anyone who sees the association handle, which itself isn't considered to + /// be confidential. + /// </remarks> + [ContractClass(typeof(IProviderAssociationStoreContract))] + internal interface IProviderAssociationStore { + /// <summary> + /// Stores an association and returns a handle for it. + /// </summary> + /// <param name="secret">The association secret.</param> + /// <param name="expiresUtc">The UTC time that the association should expire.</param> + /// <param name="privateAssociation">A value indicating whether this is a private association.</param> + /// <returns> + /// The association handle that represents this association. + /// </returns> + string Serialize(byte[] secret, DateTime expiresUtc, bool privateAssociation); + + /// <summary> + /// Retrieves an association given an association handle. + /// </summary> + /// <param name="containingMessage">The OpenID message that referenced this association handle.</param> + /// <param name="privateAssociation">A value indicating whether a private association is expected.</param> + /// <param name="handle">The association handle.</param> + /// <returns> + /// An association instance, or <c>null</c> if the association has expired or the signature is incorrect (which may be because the OP's symmetric key has changed). + /// </returns> + /// <exception cref="ProtocolException">Thrown if the association is not of the expected type.</exception> + Association Deserialize(IProtocolMessage containingMessage, bool privateAssociation, string handle); + } + + /// <summary> + /// Code contract for the <see cref="IProviderAssociationStore"/> interface. + /// </summary> + [ContractClassFor(typeof(IProviderAssociationStore))] + internal abstract class IProviderAssociationStoreContract : IProviderAssociationStore { + /// <summary> + /// Stores an association and returns a handle for it. + /// </summary> + /// <param name="secret">The association secret.</param> + /// <param name="expiresUtc">The expires UTC.</param> + /// <param name="privateAssociation">A value indicating whether this is a private association.</param> + /// <returns> + /// The association handle that represents this association. + /// </returns> + string IProviderAssociationStore.Serialize(byte[] secret, DateTime expiresUtc, bool privateAssociation) { + Requires.NotNull(secret, "secret"); + Requires.True(expiresUtc.Kind == DateTimeKind.Utc, "expiresUtc"); + Contract.Ensures(!String.IsNullOrEmpty(Contract.Result<string>())); + throw new NotImplementedException(); + } + + /// <summary> + /// Retrieves an association given an association handle. + /// </summary> + /// <param name="containingMessage">The OpenID message that referenced this association handle.</param> + /// <param name="privateAssociation">A value indicating whether a private association is expected.</param> + /// <param name="handle">The association handle.</param> + /// <returns> + /// An association instance, or <c>null</c> if the association has expired or the signature is incorrect (which may be because the OP's symmetric key has changed). + /// </returns> + /// <exception cref="ProtocolException">Thrown if the association is not of the expected type.</exception> + Association IProviderAssociationStore.Deserialize(IProtocolMessage containingMessage, bool privateAssociation, string handle) { + Requires.NotNull(containingMessage, "containingMessage"); + Requires.NotNullOrEmpty(handle, "handle"); + throw new NotImplementedException(); + } + } +} diff --git a/src/DotNetOpenAuth.OpenId.Provider/OpenId/Provider/OpenIdProvider.cs b/src/DotNetOpenAuth.OpenId.Provider/OpenId/Provider/OpenIdProvider.cs new file mode 100644 index 0000000..6b78098 --- /dev/null +++ b/src/DotNetOpenAuth.OpenId.Provider/OpenId/Provider/OpenIdProvider.cs @@ -0,0 +1,674 @@ +//----------------------------------------------------------------------- +// <copyright file="OpenIdProvider.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.Provider { + using System; + using System.Collections.Generic; + using System.Collections.ObjectModel; + using System.Collections.Specialized; + using System.ComponentModel; + using System.Diagnostics.CodeAnalysis; + using System.Diagnostics.Contracts; + using System.Linq; + using System.Threading; + using System.Web; + using DotNetOpenAuth.Configuration; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.Messaging.Bindings; + using DotNetOpenAuth.OpenId.ChannelElements; + using DotNetOpenAuth.OpenId.Messages; + using RP = DotNetOpenAuth.OpenId.RelyingParty; + + /// <summary> + /// Offers services for a web page that is acting as an OpenID identity server. + /// </summary> + [SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Justification = "By design")] + [ContractVerification(true)] + public sealed class OpenIdProvider : IDisposable { + /// <summary> + /// The name of the key to use in the HttpApplication cache to store the + /// instance of <see cref="StandardProviderApplicationStore"/> to use. + /// </summary> + private const string ApplicationStoreKey = "DotNetOpenAuth.OpenId.Provider.OpenIdProvider.ApplicationStore"; + + /// <summary> + /// Backing store for the <see cref="Behaviors"/> property. + /// </summary> + private readonly ObservableCollection<IProviderBehavior> behaviors = new ObservableCollection<IProviderBehavior>(); + + /// <summary> + /// A type initializer that ensures that another type initializer runs in order to guarantee that + /// types are serializable. + /// </summary> + private static Identifier dummyIdentifierToInvokeStaticCtor = "http://localhost/"; + + /// <summary> + /// A type initializer that ensures that another type initializer runs in order to guarantee that + /// types are serializable. + /// </summary> + private static Realm dummyRealmToInvokeStaticCtor = "http://localhost/"; + + /// <summary> + /// Backing field for the <see cref="SecuritySettings"/> property. + /// </summary> + private ProviderSecuritySettings securitySettings; + + /// <summary> + /// The relying party used to perform discovery on identifiers being sent in + /// unsolicited positive assertions. + /// </summary> + private RP.OpenIdRelyingParty relyingParty; + + /// <summary> + /// Initializes a new instance of the <see cref="OpenIdProvider"/> class. + /// </summary> + public OpenIdProvider() + : this(OpenIdElement.Configuration.Provider.ApplicationStore.CreateInstance(HttpApplicationStore)) { + Contract.Ensures(this.SecuritySettings != null); + Contract.Ensures(this.Channel != null); + } + + /// <summary> + /// Initializes a new instance of the <see cref="OpenIdProvider"/> class. + /// </summary> + /// <param name="applicationStore">The application store to use. Cannot be null.</param> + public OpenIdProvider(IOpenIdApplicationStore applicationStore) + : this((INonceStore)applicationStore, (ICryptoKeyStore)applicationStore) { + Requires.NotNull(applicationStore, "applicationStore"); + Contract.Ensures(this.SecuritySettings != null); + Contract.Ensures(this.Channel != null); + } + + /// <summary> + /// Initializes a new instance of the <see cref="OpenIdProvider"/> class. + /// </summary> + /// <param name="nonceStore">The nonce store to use. Cannot be null.</param> + /// <param name="cryptoKeyStore">The crypto key store. Cannot be null.</param> + private OpenIdProvider(INonceStore nonceStore, ICryptoKeyStore cryptoKeyStore) { + Requires.NotNull(nonceStore, "nonceStore"); + Requires.NotNull(cryptoKeyStore, "cryptoKeyStore"); + Contract.Ensures(this.SecuritySettings != null); + Contract.Ensures(this.Channel != null); + + this.SecuritySettings = OpenIdElement.Configuration.Provider.SecuritySettings.CreateSecuritySettings(); + this.behaviors.CollectionChanged += this.OnBehaviorsChanged; + foreach (var behavior in OpenIdElement.Configuration.Provider.Behaviors.CreateInstances(false)) { + this.behaviors.Add(behavior); + } + + this.AssociationStore = new SwitchingAssociationStore(cryptoKeyStore, this.SecuritySettings); + this.Channel = new OpenIdProviderChannel(this.AssociationStore, nonceStore, this.SecuritySettings); + this.CryptoKeyStore = cryptoKeyStore; + + Reporting.RecordFeatureAndDependencyUse(this, nonceStore); + } + + /// <summary> + /// Gets the standard state storage mechanism that uses ASP.NET's + /// HttpApplication state dictionary to store associations and nonces. + /// </summary> + [EditorBrowsable(EditorBrowsableState.Advanced)] + public static IOpenIdApplicationStore HttpApplicationStore { + get { + Requires.ValidState(HttpContext.Current != null && HttpContext.Current.Request != null, MessagingStrings.HttpContextRequired); + Contract.Ensures(Contract.Result<IOpenIdApplicationStore>() != null); + HttpContext context = HttpContext.Current; + var store = (IOpenIdApplicationStore)context.Application[ApplicationStoreKey]; + if (store == null) { + context.Application.Lock(); + try { + if ((store = (IOpenIdApplicationStore)context.Application[ApplicationStoreKey]) == null) { + context.Application[ApplicationStoreKey] = store = new StandardProviderApplicationStore(); + } + } finally { + context.Application.UnLock(); + } + } + + return store; + } + } + + /// <summary> + /// Gets the channel to use for sending/receiving messages. + /// </summary> + public Channel Channel { get; internal set; } + + /// <summary> + /// Gets the security settings used by this Provider. + /// </summary> + public ProviderSecuritySettings SecuritySettings { + get { + Contract.Ensures(Contract.Result<ProviderSecuritySettings>() != null); + Contract.Assume(this.securitySettings != null); + return this.securitySettings; + } + + internal set { + Requires.NotNull(value, "value"); + this.securitySettings = value; + } + } + + /// <summary> + /// Gets the extension factories. + /// </summary> + public IList<IOpenIdExtensionFactory> ExtensionFactories { + get { return this.Channel.GetExtensionFactories(); } + } + + /// <summary> + /// Gets or sets the mechanism a host site can use to receive + /// notifications of errors when communicating with remote parties. + /// </summary> + public IErrorReporting ErrorReporting { get; set; } + + /// <summary> + /// Gets a list of custom behaviors to apply to OpenID actions. + /// </summary> + /// <remarks> + /// Adding behaviors can impact the security settings of the <see cref="OpenIdProvider"/> + /// in ways that subsequently removing the behaviors will not reverse. + /// </remarks> + public ICollection<IProviderBehavior> Behaviors { + get { return this.behaviors; } + } + + /// <summary> + /// Gets the crypto key store. + /// </summary> + public ICryptoKeyStore CryptoKeyStore { get; private set; } + + /// <summary> + /// Gets the association store. + /// </summary> + internal IProviderAssociationStore AssociationStore { get; private set; } + + /// <summary> + /// Gets the channel. + /// </summary> + internal OpenIdChannel OpenIdChannel { + get { return (OpenIdChannel)this.Channel; } + } + + /// <summary> + /// Gets the list of services that can perform discovery on identifiers given to this relying party. + /// </summary> + internal IList<IIdentifierDiscoveryService> DiscoveryServices { + get { return this.RelyingParty.DiscoveryServices; } + } + + /// <summary> + /// Gets the web request handler to use for discovery and the part of + /// authentication where direct messages are sent to an untrusted remote party. + /// </summary> + internal IDirectWebRequestHandler WebRequestHandler { + get { return this.Channel.WebRequestHandler; } + } + + /// <summary> + /// Gets the relying party used for discovery of identifiers sent in unsolicited assertions. + /// </summary> + private RP.OpenIdRelyingParty RelyingParty { + get { + if (this.relyingParty == null) { + lock (this) { + if (this.relyingParty == null) { + // we just need an RP that's capable of discovery, so stateless mode is fine. + this.relyingParty = new RP.OpenIdRelyingParty(null); + } + } + } + + this.relyingParty.Channel.WebRequestHandler = this.WebRequestHandler; + return this.relyingParty; + } + } + + /// <summary> + /// Gets the incoming OpenID request if there is one, or null if none was detected. + /// </summary> + /// <returns>The request that the hosting Provider should possibly process and then transmit the response for.</returns> + /// <remarks> + /// <para>Requests may be infrastructural to OpenID and allow auto-responses, or they may + /// be authentication requests where the Provider site has to make decisions based + /// on its own user database and policies.</para> + /// <para>Requires an <see cref="HttpContext.Current">HttpContext.Current</see> context.</para> + /// </remarks> + /// <exception cref="InvalidOperationException">Thrown if <see cref="HttpContext.Current">HttpContext.Current</see> == <c>null</c>.</exception> + /// <exception cref="ProtocolException">Thrown if the incoming message is recognized but deviates from the protocol specification irrecoverably.</exception> + public IRequest GetRequest() { + return this.GetRequest(this.Channel.GetRequestFromContext()); + } + + /// <summary> + /// Gets the incoming OpenID request if there is one, or null if none was detected. + /// </summary> + /// <param name="httpRequestInfo">The incoming HTTP request to extract the message from.</param> + /// <returns> + /// The request that the hosting Provider should process and then transmit the response for. + /// Null if no valid OpenID request was detected in the given HTTP request. + /// </returns> + /// <remarks> + /// Requests may be infrastructural to OpenID and allow auto-responses, or they may + /// be authentication requests where the Provider site has to make decisions based + /// on its own user database and policies. + /// </remarks> + /// <exception cref="ProtocolException">Thrown if the incoming message is recognized + /// but deviates from the protocol specification irrecoverably.</exception> + public IRequest GetRequest(HttpRequestInfo httpRequestInfo) { + Requires.NotNull(httpRequestInfo, "httpRequestInfo"); + IDirectedProtocolMessage incomingMessage = null; + + try { + incomingMessage = this.Channel.ReadFromRequest(httpRequestInfo); + if (incomingMessage == null) { + // If the incoming request does not resemble an OpenID message at all, + // it's probably a user who just navigated to this URL, and we should + // just return null so the host can display a message to the user. + if (httpRequestInfo.HttpMethod == "GET" && !httpRequestInfo.UrlBeforeRewriting.QueryStringContainPrefixedParameters(Protocol.Default.openid.Prefix)) { + return null; + } + + ErrorUtilities.ThrowProtocol(MessagingStrings.UnexpectedMessageReceivedOfMany); + } + + IRequest result = null; + + var checkIdMessage = incomingMessage as CheckIdRequest; + if (checkIdMessage != null) { + result = new AuthenticationRequest(this, checkIdMessage); + } + + if (result == null) { + var extensionOnlyRequest = incomingMessage as SignedResponseRequest; + if (extensionOnlyRequest != null) { + result = new AnonymousRequest(this, extensionOnlyRequest); + } + } + + if (result == null) { + var checkAuthMessage = incomingMessage as CheckAuthenticationRequest; + if (checkAuthMessage != null) { + result = new AutoResponsiveRequest(incomingMessage, new CheckAuthenticationResponseProvider(checkAuthMessage, this), this.SecuritySettings); + } + } + + if (result == null) { + var associateMessage = incomingMessage as IAssociateRequestProvider; + if (associateMessage != null) { + result = new AutoResponsiveRequest(incomingMessage, AssociateRequestProviderTools.CreateResponse(associateMessage, this.AssociationStore, this.SecuritySettings), this.SecuritySettings); + } + } + + if (result != null) { + foreach (var behavior in this.Behaviors) { + if (behavior.OnIncomingRequest(result)) { + // This behavior matched this request. + break; + } + } + + return result; + } + + throw ErrorUtilities.ThrowProtocol(MessagingStrings.UnexpectedMessageReceivedOfMany); + } catch (ProtocolException ex) { + IRequest errorResponse = this.GetErrorResponse(ex, httpRequestInfo, incomingMessage); + if (errorResponse == null) { + throw; + } + + return errorResponse; + } + } + + /// <summary> + /// Sends the response to a received request. + /// </summary> + /// <param name="request">The incoming OpenID request whose response is to be sent.</param> + /// <exception cref="ThreadAbortException">Thrown by ASP.NET in order to prevent additional data from the page being sent to the client and corrupting the response.</exception> + /// <remarks> + /// <para>Requires an HttpContext.Current context. If one is not available, the caller should use + /// <see cref="PrepareResponse"/> instead and manually send the <see cref="OutgoingWebResponse"/> + /// to the client.</para> + /// </remarks> + /// <exception cref="InvalidOperationException">Thrown if <see cref="IRequest.IsResponseReady"/> is <c>false</c>.</exception> + [SuppressMessage("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily", Justification = "Code Contract requires that we cast early.")] + [EditorBrowsable(EditorBrowsableState.Never)] + public void SendResponse(IRequest request) { + Requires.ValidState(HttpContext.Current != null, MessagingStrings.CurrentHttpContextRequired); + Requires.NotNull(request, "request"); + Requires.True(request.IsResponseReady, "request"); + + this.ApplyBehaviorsToResponse(request); + Request requestInternal = (Request)request; + this.Channel.Send(requestInternal.Response); + } + + /// <summary> + /// Sends the response to a received request. + /// </summary> + /// <param name="request">The incoming OpenID request whose response is to be sent.</param> + /// <remarks> + /// <para>Requires an HttpContext.Current context. If one is not available, the caller should use + /// <see cref="PrepareResponse"/> instead and manually send the <see cref="OutgoingWebResponse"/> + /// to the client.</para> + /// </remarks> + /// <exception cref="InvalidOperationException">Thrown if <see cref="IRequest.IsResponseReady"/> is <c>false</c>.</exception> + [SuppressMessage("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily", Justification = "Code Contract requires that we cast early.")] + public void Respond(IRequest request) { + Requires.ValidState(HttpContext.Current != null, MessagingStrings.CurrentHttpContextRequired); + Requires.NotNull(request, "request"); + Requires.True(request.IsResponseReady, "request"); + + this.ApplyBehaviorsToResponse(request); + Request requestInternal = (Request)request; + this.Channel.Respond(requestInternal.Response); + } + + /// <summary> + /// Gets the response to a received request. + /// </summary> + /// <param name="request">The request.</param> + /// <returns>The response that should be sent to the client.</returns> + /// <exception cref="InvalidOperationException">Thrown if <see cref="IRequest.IsResponseReady"/> is <c>false</c>.</exception> + [SuppressMessage("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily", Justification = "Code Contract requires that we cast early.")] + public OutgoingWebResponse PrepareResponse(IRequest request) { + Requires.NotNull(request, "request"); + Requires.True(request.IsResponseReady, "request"); + + this.ApplyBehaviorsToResponse(request); + Request requestInternal = (Request)request; + return this.Channel.PrepareResponse(requestInternal.Response); + } + + /// <summary> + /// Sends an identity assertion on behalf of one of this Provider's + /// members in order to redirect the user agent to a relying party + /// web site and log him/her in immediately in one uninterrupted step. + /// </summary> + /// <param name="providerEndpoint">The absolute URL on the Provider site that receives OpenID messages.</param> + /// <param name="relyingPartyRealm">The URL of the Relying Party web site. + /// This will typically be the home page, but may be a longer URL if + /// that Relying Party considers the scope of its realm to be more specific. + /// The URL provided here must allow discovery of the Relying Party's + /// XRDS document that advertises its OpenID RP endpoint.</param> + /// <param name="claimedIdentifier">The Identifier you are asserting your member controls.</param> + /// <param name="localIdentifier">The Identifier you know your user by internally. This will typically + /// be the same as <paramref name="claimedIdentifier"/>.</param> + /// <param name="extensions">The extensions.</param> + public void SendUnsolicitedAssertion(Uri providerEndpoint, Realm relyingPartyRealm, Identifier claimedIdentifier, Identifier localIdentifier, params IExtensionMessage[] extensions) { + Requires.ValidState(HttpContext.Current != null, MessagingStrings.HttpContextRequired); + Requires.NotNull(providerEndpoint, "providerEndpoint"); + Requires.True(providerEndpoint.IsAbsoluteUri, "providerEndpoint"); + Requires.NotNull(relyingPartyRealm, "relyingPartyRealm"); + Requires.NotNull(claimedIdentifier, "claimedIdentifier"); + Requires.NotNull(localIdentifier, "localIdentifier"); + + this.PrepareUnsolicitedAssertion(providerEndpoint, relyingPartyRealm, claimedIdentifier, localIdentifier, extensions).Send(); + } + + /// <summary> + /// Prepares an identity assertion on behalf of one of this Provider's + /// members in order to redirect the user agent to a relying party + /// web site and log him/her in immediately in one uninterrupted step. + /// </summary> + /// <param name="providerEndpoint">The absolute URL on the Provider site that receives OpenID messages.</param> + /// <param name="relyingPartyRealm">The URL of the Relying Party web site. + /// This will typically be the home page, but may be a longer URL if + /// that Relying Party considers the scope of its realm to be more specific. + /// The URL provided here must allow discovery of the Relying Party's + /// XRDS document that advertises its OpenID RP endpoint.</param> + /// <param name="claimedIdentifier">The Identifier you are asserting your member controls.</param> + /// <param name="localIdentifier">The Identifier you know your user by internally. This will typically + /// be the same as <paramref name="claimedIdentifier"/>.</param> + /// <param name="extensions">The extensions.</param> + /// <returns> + /// A <see cref="OutgoingWebResponse"/> object describing the HTTP response to send + /// the user agent to allow the redirect with assertion to happen. + /// </returns> + public OutgoingWebResponse PrepareUnsolicitedAssertion(Uri providerEndpoint, Realm relyingPartyRealm, Identifier claimedIdentifier, Identifier localIdentifier, params IExtensionMessage[] extensions) { + Requires.NotNull(providerEndpoint, "providerEndpoint"); + Requires.True(providerEndpoint.IsAbsoluteUri, "providerEndpoint"); + Requires.NotNull(relyingPartyRealm, "relyingPartyRealm"); + Requires.NotNull(claimedIdentifier, "claimedIdentifier"); + Requires.NotNull(localIdentifier, "localIdentifier"); + Requires.ValidState(this.Channel.WebRequestHandler != null); + + // Although the RP should do their due diligence to make sure that this OP + // is authorized to send an assertion for the given claimed identifier, + // do due diligence by performing our own discovery on the claimed identifier + // and make sure that it is tied to this OP and OP local identifier. + if (this.SecuritySettings.UnsolicitedAssertionVerification != ProviderSecuritySettings.UnsolicitedAssertionVerificationLevel.NeverVerify) { + var serviceEndpoint = IdentifierDiscoveryResult.CreateForClaimedIdentifier(claimedIdentifier, localIdentifier, new ProviderEndpointDescription(providerEndpoint, Protocol.Default.Version), null, null); + var discoveredEndpoints = this.RelyingParty.Discover(claimedIdentifier); + if (!discoveredEndpoints.Contains(serviceEndpoint)) { + Logger.OpenId.WarnFormat( + "Failed to send unsolicited assertion for {0} because its discovered services did not include this endpoint: {1}{2}{1}Discovered endpoints: {1}{3}", + claimedIdentifier, + Environment.NewLine, + serviceEndpoint, + discoveredEndpoints.ToStringDeferred(true)); + + // Only FAIL if the setting is set for it. + if (this.securitySettings.UnsolicitedAssertionVerification == ProviderSecuritySettings.UnsolicitedAssertionVerificationLevel.RequireSuccess) { + ErrorUtilities.ThrowProtocol(OpenIdStrings.UnsolicitedAssertionForUnrelatedClaimedIdentifier, claimedIdentifier); + } + } + } + + Logger.OpenId.InfoFormat("Preparing unsolicited assertion for {0}", claimedIdentifier); + RelyingPartyEndpointDescription returnToEndpoint = null; + var returnToEndpoints = relyingPartyRealm.DiscoverReturnToEndpoints(this.WebRequestHandler, true); + if (returnToEndpoints != null) { + returnToEndpoint = returnToEndpoints.FirstOrDefault(); + } + ErrorUtilities.VerifyProtocol(returnToEndpoint != null, OpenIdStrings.NoRelyingPartyEndpointDiscovered, relyingPartyRealm); + + var positiveAssertion = new PositiveAssertionResponse(returnToEndpoint) { + ProviderEndpoint = providerEndpoint, + ClaimedIdentifier = claimedIdentifier, + LocalIdentifier = localIdentifier, + }; + + if (extensions != null) { + foreach (IExtensionMessage extension in extensions) { + positiveAssertion.Extensions.Add(extension); + } + } + + Reporting.RecordEventOccurrence(this, "PrepareUnsolicitedAssertion"); + return this.Channel.PrepareResponse(positiveAssertion); + } + + #region IDisposable Members + + /// <summary> + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// </summary> + public void Dispose() { + this.Dispose(true); + GC.SuppressFinalize(this); + } + + /// <summary> + /// Releases unmanaged and - optionally - managed resources + /// </summary> + /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param> + private void Dispose(bool disposing) { + if (disposing) { + // Tear off the instance member as a local variable for thread safety. + IDisposable channel = this.Channel as IDisposable; + if (channel != null) { + channel.Dispose(); + } + + if (this.relyingParty != null) { + this.relyingParty.Dispose(); + } + } + } + + #endregion + + /// <summary> + /// Applies all behaviors to the response message. + /// </summary> + /// <param name="request">The request.</param> + private void ApplyBehaviorsToResponse(IRequest request) { + var authRequest = request as IAuthenticationRequest; + if (authRequest != null) { + foreach (var behavior in this.Behaviors) { + if (behavior.OnOutgoingResponse(authRequest)) { + // This behavior matched this request. + break; + } + } + } + } + + /// <summary> + /// Prepares the return value for the GetRequest method in the event of an exception. + /// </summary> + /// <param name="ex">The exception that forms the basis of the error response. Must not be null.</param> + /// <param name="httpRequestInfo">The incoming HTTP request. Must not be null.</param> + /// <param name="incomingMessage">The incoming message. May be null in the case that it was malformed.</param> + /// <returns> + /// Either the <see cref="IRequest"/> to return to the host site or null to indicate no response could be reasonably created and that the caller should rethrow the exception. + /// </returns> + private IRequest GetErrorResponse(ProtocolException ex, HttpRequestInfo httpRequestInfo, IDirectedProtocolMessage incomingMessage) { + Requires.NotNull(ex, "ex"); + Requires.NotNull(httpRequestInfo, "httpRequestInfo"); + + Logger.OpenId.Error("An exception was generated while processing an incoming OpenID request.", ex); + IErrorMessage errorMessage; + + // We must create the appropriate error message type (direct vs. indirect) + // based on what we see in the request. + string returnTo = httpRequestInfo.QueryString[Protocol.Default.openid.return_to]; + if (returnTo != null) { + // An indirect request message from the RP + // We need to return an indirect response error message so the RP can consume it. + // Consistent with OpenID 2.0 section 5.2.3. + var indirectRequest = incomingMessage as SignedResponseRequest; + if (indirectRequest != null) { + errorMessage = new IndirectErrorResponse(indirectRequest); + } else { + errorMessage = new IndirectErrorResponse(Protocol.Default.Version, new Uri(returnTo)); + } + } else if (httpRequestInfo.HttpMethod == "POST") { + // A direct request message from the RP + // We need to return a direct response error message so the RP can consume it. + // Consistent with OpenID 2.0 section 5.1.2.2. + errorMessage = new DirectErrorResponse(Protocol.Default.Version, incomingMessage); + } else { + // This may be an indirect request from an RP that was so badly + // formed that we cannot even return an error to the RP. + // The best we can do is display an error to the user. + // Returning null cues the caller to "throw;" + return null; + } + + errorMessage.ErrorMessage = ex.ToStringDescriptive(); + + // Allow host to log this error and issue a ticket #. + // We tear off the field to a local var for thread safety. + IErrorReporting hostErrorHandler = this.ErrorReporting; + if (hostErrorHandler != null) { + errorMessage.Contact = hostErrorHandler.Contact; + errorMessage.Reference = hostErrorHandler.LogError(ex); + } + + if (incomingMessage != null) { + return new AutoResponsiveRequest(incomingMessage, errorMessage, this.SecuritySettings); + } else { + return new AutoResponsiveRequest(errorMessage, this.SecuritySettings); + } + } + + /// <summary> + /// Called by derived classes when behaviors are added or removed. + /// </summary> + /// <param name="sender">The collection being modified.</param> + /// <param name="e">The <see cref="System.Collections.Specialized.NotifyCollectionChangedEventArgs"/> instance containing the event data.</param> + private void OnBehaviorsChanged(object sender, NotifyCollectionChangedEventArgs e) { + foreach (IProviderBehavior profile in e.NewItems) { + profile.ApplySecuritySettings(this.SecuritySettings); + Reporting.RecordFeatureUse(profile); + } + } + + /// <summary> + /// Provides a single OP association store instance that can handle switching between + /// association handle encoding modes. + /// </summary> + private class SwitchingAssociationStore : IProviderAssociationStore { + /// <summary> + /// The security settings of the Provider. + /// </summary> + private readonly ProviderSecuritySettings securitySettings; + + /// <summary> + /// The association store that records association secrets in the association handles themselves. + /// </summary> + private IProviderAssociationStore associationHandleEncoder; + + /// <summary> + /// The association store that records association secrets in a secret store. + /// </summary> + private IProviderAssociationStore associationSecretStorage; + + /// <summary> + /// Initializes a new instance of the <see cref="SwitchingAssociationStore"/> class. + /// </summary> + /// <param name="cryptoKeyStore">The crypto key store.</param> + /// <param name="securitySettings">The security settings.</param> + internal SwitchingAssociationStore(ICryptoKeyStore cryptoKeyStore, ProviderSecuritySettings securitySettings) { + Requires.NotNull(cryptoKeyStore, "cryptoKeyStore"); + Requires.NotNull(securitySettings, "securitySettings"); + this.securitySettings = securitySettings; + + this.associationHandleEncoder = new ProviderAssociationHandleEncoder(cryptoKeyStore); + this.associationSecretStorage = new ProviderAssociationKeyStorage(cryptoKeyStore); + } + + /// <summary> + /// Gets the association store that applies given the Provider's current security settings. + /// </summary> + internal IProviderAssociationStore AssociationStore { + get { return this.securitySettings.EncodeAssociationSecretsInHandles ? this.associationHandleEncoder : this.associationSecretStorage; } + } + + /// <summary> + /// Stores an association and returns a handle for it. + /// </summary> + /// <param name="secret">The association secret.</param> + /// <param name="expiresUtc">The UTC time that the association should expire.</param> + /// <param name="privateAssociation">A value indicating whether this is a private association.</param> + /// <returns> + /// The association handle that represents this association. + /// </returns> + public string Serialize(byte[] secret, DateTime expiresUtc, bool privateAssociation) { + return this.AssociationStore.Serialize(secret, expiresUtc, privateAssociation); + } + + /// <summary> + /// Retrieves an association given an association handle. + /// </summary> + /// <param name="containingMessage">The OpenID message that referenced this association handle.</param> + /// <param name="isPrivateAssociation">A value indicating whether a private association is expected.</param> + /// <param name="handle">The association handle.</param> + /// <returns> + /// An association instance, or <c>null</c> if the association has expired or the signature is incorrect (which may be because the OP's symmetric key has changed). + /// </returns> + /// <exception cref="ProtocolException">Thrown if the association is not of the expected type.</exception> + public Association Deserialize(IProtocolMessage containingMessage, bool isPrivateAssociation, string handle) { + return this.AssociationStore.Deserialize(containingMessage, isPrivateAssociation, handle); + } + } + } +} diff --git a/src/DotNetOpenAuth.OpenId.Provider/OpenId/Provider/OpenIdProviderUtilities.cs b/src/DotNetOpenAuth.OpenId.Provider/OpenId/Provider/OpenIdProviderUtilities.cs new file mode 100644 index 0000000..cf525f1 --- /dev/null +++ b/src/DotNetOpenAuth.OpenId.Provider/OpenId/Provider/OpenIdProviderUtilities.cs @@ -0,0 +1,70 @@ +//----------------------------------------------------------------------- +// <copyright file="OpenIdProviderUtilities.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.Provider { + using System; + using System.Collections.Generic; + using System.Diagnostics.Contracts; + using System.Linq; + using System.Text; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OpenId.Messages; + using DotNetOpenAuth.OpenId.Provider; + + /// <summary> + /// Utility methods for OpenID Providers. + /// </summary> + internal static class OpenIdProviderUtilities { + /// <summary> + /// Called to create the Association based on a request previously given by the Relying Party. + /// </summary> + /// <param name="request">The prior request for an association.</param> + /// <param name="response">The response.</param> + /// <param name="associationStore">The Provider's association store.</param> + /// <param name="securitySettings">The security settings for the Provider. Should be <c>null</c> for Relying Parties.</param> + /// <returns> + /// The created association. + /// </returns> + /// <remarks> + /// The response message is updated to include the details of the created association by this method. + /// This method is called by both the Provider and the Relying Party, but actually performs + /// quite different operations in either scenario. + /// </remarks> + internal static Association CreateAssociation(AssociateRequest request, IAssociateSuccessfulResponseProvider response, IProviderAssociationStore associationStore, ProviderSecuritySettings securitySettings) { + Requires.NotNull(request, "request"); + Requires.NotNull(response, "response"); + Requires.NotNull(securitySettings, "securitySettings"); + + // We need to initialize some common properties based on the created association. + var association = response.CreateAssociationAtProvider(request, associationStore, securitySettings); + response.ExpiresIn = association.SecondsTillExpiration; + response.AssociationHandle = association.Handle; + + return association; + } + + /// <summary> + /// Determines whether the association with the specified handle is (still) valid. + /// </summary> + /// <param name="associationStore">The association store.</param> + /// <param name="containingMessage">The OpenID message that referenced this association handle.</param> + /// <param name="isPrivateAssociation">A value indicating whether a private association is expected.</param> + /// <param name="handle">The association handle.</param> + /// <returns> + /// <c>true</c> if the specified containing message is valid; otherwise, <c>false</c>. + /// </returns> + internal static bool IsValid(this IProviderAssociationStore associationStore, IProtocolMessage containingMessage, bool isPrivateAssociation, string handle) { + Requires.NotNull(associationStore, "associationStore"); + Requires.NotNull(containingMessage, "containingMessage"); + Requires.NotNullOrEmpty(handle, "handle"); + try { + return associationStore.Deserialize(containingMessage, isPrivateAssociation, handle) != null; + } catch (ProtocolException) { + return false; + } + } + } +} diff --git a/src/DotNetOpenAuth.OpenId.Provider/OpenId/Provider/PrivatePersonalIdentifierProviderBase.cs b/src/DotNetOpenAuth.OpenId.Provider/OpenId/Provider/PrivatePersonalIdentifierProviderBase.cs new file mode 100644 index 0000000..9ebae1d --- /dev/null +++ b/src/DotNetOpenAuth.OpenId.Provider/OpenId/Provider/PrivatePersonalIdentifierProviderBase.cs @@ -0,0 +1,223 @@ +//----------------------------------------------------------------------- +// <copyright file="PrivatePersonalIdentifierProviderBase.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.Provider { + using System; + using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; + using System.Diagnostics.Contracts; + using System.Globalization; + using System.Linq; + using System.Security.Cryptography; + using System.Text; + using DotNetOpenAuth.Messaging; + + /// <summary> + /// Provides standard PPID Identifiers to users to protect their identity from individual relying parties + /// and from colluding groups of relying parties. + /// </summary> + public abstract class PrivatePersonalIdentifierProviderBase : IDirectedIdentityIdentifierProvider { + /// <summary> + /// The type of hash function to use for the <see cref="Hasher"/> property. + /// </summary> + private const string HashAlgorithmName = "SHA256"; + + /// <summary> + /// The length of the salt to generate for first time PPID-users. + /// </summary> + private int newSaltLength = 20; + + /// <summary> + /// Initializes a new instance of the <see cref="PrivatePersonalIdentifierProviderBase"/> class. + /// </summary> + /// <param name="baseIdentifier">The base URI on which to append the anonymous part.</param> + protected PrivatePersonalIdentifierProviderBase(Uri baseIdentifier) { + Requires.NotNull(baseIdentifier, "baseIdentifier"); + + this.Hasher = HashAlgorithm.Create(HashAlgorithmName); + this.Encoder = Encoding.UTF8; + this.BaseIdentifier = baseIdentifier; + this.PairwiseUnique = AudienceScope.Realm; + } + + /// <summary> + /// A granularity description for who wide of an audience sees the same generated PPID. + /// </summary> + [SuppressMessage("Microsoft.Design", "CA1034:NestedTypesShouldNotBeVisible", Justification = "Breaking change")] + public enum AudienceScope { + /// <summary> + /// A unique Identifier is generated for every realm. This is the highest security setting. + /// </summary> + Realm, + + /// <summary> + /// Only the host name in the realm is used in calculating the PPID, + /// allowing for some level of sharing of the PPID Identifiers between RPs + /// that are able to share the same realm host value. + /// </summary> + RealmHost, + + /// <summary> + /// Although the user's Identifier is still opaque to the RP so they cannot determine + /// who the user is at the OP, the same Identifier is used at all RPs so collusion + /// between the RPs is possible. + /// </summary> + Global, + } + + /// <summary> + /// Gets the base URI on which to append the anonymous part. + /// </summary> + public Uri BaseIdentifier { get; private set; } + + /// <summary> + /// Gets or sets a value indicating whether each Realm will get its own private identifier + /// for the authenticating uesr. + /// </summary> + /// <value>The default value is <see cref="AudienceScope.Realm"/>.</value> + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Pairwise", Justification = "Meaningful word")] + public AudienceScope PairwiseUnique { get; set; } + + /// <summary> + /// Gets the hash function to use to perform the one-way transform of a personal identifier + /// to an "anonymous" looking one. + /// </summary> + protected HashAlgorithm Hasher { get; private set; } + + /// <summary> + /// Gets the encoder to use for transforming the personal identifier into bytes for hashing. + /// </summary> + protected Encoding Encoder { get; private set; } + + /// <summary> + /// Gets or sets the new length of the salt. + /// </summary> + /// <value>The new length of the salt.</value> + protected int NewSaltLength { + get { + return this.newSaltLength; + } + + set { + Requires.InRange(value > 0, "value"); + this.newSaltLength = value; + } + } + + #region IDirectedIdentityIdentifierProvider Members + + /// <summary> + /// Gets the Identifier to use for the Claimed Identifier and Local Identifier of + /// an outgoing positive assertion. + /// </summary> + /// <param name="localIdentifier">The OP local identifier for the authenticating user.</param> + /// <param name="relyingPartyRealm">The realm of the relying party receiving the assertion.</param> + /// <returns> + /// A valid, discoverable OpenID Identifier that should be used as the value for the + /// openid.claimed_id and openid.local_id parameters. Must not be null. + /// </returns> + public Uri GetIdentifier(Identifier localIdentifier, Realm relyingPartyRealm) { + byte[] salt = this.GetHashSaltForLocalIdentifier(localIdentifier); + string valueToHash = localIdentifier + "#"; + switch (this.PairwiseUnique) { + case AudienceScope.Realm: + valueToHash += relyingPartyRealm; + break; + case AudienceScope.RealmHost: + valueToHash += relyingPartyRealm.Host; + break; + case AudienceScope.Global: + break; + default: + throw new InvalidOperationException( + string.Format( + CultureInfo.CurrentCulture, + OpenIdStrings.UnexpectedEnumPropertyValue, + "PairwiseUnique", + this.PairwiseUnique)); + } + + byte[] valueAsBytes = this.Encoder.GetBytes(valueToHash); + byte[] bytesToHash = new byte[valueAsBytes.Length + salt.Length]; + valueAsBytes.CopyTo(bytesToHash, 0); + salt.CopyTo(bytesToHash, valueAsBytes.Length); + byte[] hash = this.Hasher.ComputeHash(bytesToHash); + string base64Hash = Convert.ToBase64String(hash); + Uri anonymousIdentifier = this.AppendIdentifiers(base64Hash); + return anonymousIdentifier; + } + + /// <summary> + /// Determines whether a given identifier is the primary (non-PPID) local identifier for some user. + /// </summary> + /// <param name="identifier">The identifier in question.</param> + /// <returns> + /// <c>true</c> if the given identifier is the valid, unique identifier for some uesr (and NOT a PPID); otherwise, <c>false</c>. + /// </returns> + public virtual bool IsUserLocalIdentifier(Identifier identifier) { + return !identifier.ToString().StartsWith(this.BaseIdentifier.AbsoluteUri, StringComparison.Ordinal); + } + + #endregion + + /// <summary> + /// Creates a new salt to assign to a user. + /// </summary> + /// <returns>A non-null buffer of length <see cref="NewSaltLength"/> filled with a random salt.</returns> + protected virtual byte[] CreateSalt() { + // We COULD use a crypto random function, but for a salt it seems overkill. + return MessagingUtilities.GetNonCryptoRandomData(this.NewSaltLength); + } + + /// <summary> + /// Creates a new PPID Identifier by appending a pseudonymous identifier suffix to + /// the <see cref="BaseIdentifier"/>. + /// </summary> + /// <param name="uriHash">The unique part of the Identifier to append to the common first part.</param> + /// <returns>The full PPID Identifier.</returns> + [SuppressMessage("Microsoft.Usage", "CA2234:PassSystemUriObjectsInsteadOfStrings", Justification = "NOT equivalent overload. The recommended one breaks on relative URIs.")] + protected virtual Uri AppendIdentifiers(string uriHash) { + Requires.NotNullOrEmpty(uriHash, "uriHash"); + + if (string.IsNullOrEmpty(this.BaseIdentifier.Query)) { + // The uriHash will appear on the path itself. + string pathEncoded = Uri.EscapeUriString(uriHash.Replace('/', '_')); + return new Uri(this.BaseIdentifier, pathEncoded); + } else { + // The uriHash will appear on the query string. + string dataEncoded = Uri.EscapeDataString(uriHash); + return new Uri(this.BaseIdentifier + dataEncoded); + } + } + + /// <summary> + /// Gets the salt to use for generating an anonymous identifier for a given OP local identifier. + /// </summary> + /// <param name="localIdentifier">The OP local identifier.</param> + /// <returns>The salt to use in the hash.</returns> + /// <remarks> + /// It is important that this method always return the same value for a given + /// <paramref name="localIdentifier"/>. + /// New salts can be generated for local identifiers without previously assigned salt + /// values by calling <see cref="CreateSalt"/> or by a custom method. + /// </remarks> + protected abstract byte[] GetHashSaltForLocalIdentifier(Identifier localIdentifier); + +#if CONTRACTS_FULL + /// <summary> + /// Verifies conditions that should be true for any valid state of this object. + /// </summary> + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called by code contracts.")] + [ContractInvariantMethod] + private void ObjectInvariant() { + Contract.Invariant(this.Hasher != null); + Contract.Invariant(this.Encoder != null); + Contract.Invariant(this.BaseIdentifier != null); + Contract.Invariant(this.NewSaltLength > 0); + } +#endif + } +} diff --git a/src/DotNetOpenAuth.OpenId.Provider/OpenId/Provider/ProviderAssociationHandleEncoder.cs b/src/DotNetOpenAuth.OpenId.Provider/OpenId/Provider/ProviderAssociationHandleEncoder.cs new file mode 100644 index 0000000..0e7c174 --- /dev/null +++ b/src/DotNetOpenAuth.OpenId.Provider/OpenId/Provider/ProviderAssociationHandleEncoder.cs @@ -0,0 +1,84 @@ +//----------------------------------------------------------------------- +// <copyright file="ProviderAssociationHandleEncoder.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.Provider { + using System; + using System.Diagnostics.Contracts; + using System.Threading; + using DotNetOpenAuth.Configuration; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.Messaging.Bindings; + + /// <summary> + /// Provides association storage in the association handle itself, but embedding signed and encrypted association + /// details in the handle. + /// </summary> + public class ProviderAssociationHandleEncoder : IProviderAssociationStore { + /// <summary> + /// The name of the bucket in which to store keys that encrypt association data into association handles. + /// </summary> + internal const string AssociationHandleEncodingSecretBucket = "https://localhost/dnoa/association_handles"; + + /// <summary> + /// The crypto key store used to persist encryption keys. + /// </summary> + private readonly ICryptoKeyStore cryptoKeyStore; + + /// <summary> + /// Initializes a new instance of the <see cref="ProviderAssociationHandleEncoder"/> class. + /// </summary> + /// <param name="cryptoKeyStore">The crypto key store.</param> + public ProviderAssociationHandleEncoder(ICryptoKeyStore cryptoKeyStore) { + Requires.NotNull(cryptoKeyStore, "cryptoKeyStore"); + this.cryptoKeyStore = cryptoKeyStore; + } + + /// <summary> + /// Encodes the specified association data bag. + /// </summary> + /// <param name="secret">The symmetric secret.</param> + /// <param name="expiresUtc">The UTC time that the association should expire.</param> + /// <param name="privateAssociation">A value indicating whether this is a private association.</param> + /// <returns> + /// The association handle that represents this association. + /// </returns> + public string Serialize(byte[] secret, DateTime expiresUtc, bool privateAssociation) { + var associationDataBag = new AssociationDataBag { + Secret = secret, + IsPrivateAssociation = privateAssociation, + ExpiresUtc = expiresUtc, + }; + + var formatter = AssociationDataBag.CreateFormatter(this.cryptoKeyStore, AssociationHandleEncodingSecretBucket, expiresUtc - DateTime.UtcNow); + return formatter.Serialize(associationDataBag); + } + + /// <summary> + /// Retrieves an association given an association handle. + /// </summary> + /// <param name="containingMessage">The OpenID message that referenced this association handle.</param> + /// <param name="privateAssociation">A value indicating whether a private association is expected.</param> + /// <param name="handle">The association handle.</param> + /// <returns> + /// An association instance, or <c>null</c> if the association has expired or the signature is incorrect (which may be because the OP's symmetric key has changed). + /// </returns> + /// <exception cref="ProtocolException">Thrown if the association is not of the expected type.</exception> + public Association Deserialize(IProtocolMessage containingMessage, bool privateAssociation, string handle) { + var formatter = AssociationDataBag.CreateFormatter(this.cryptoKeyStore, AssociationHandleEncodingSecretBucket); + AssociationDataBag bag; + try { + bag = formatter.Deserialize(containingMessage, handle); + } catch (ProtocolException ex) { + Logger.OpenId.Error("Rejecting an association because deserialization of the encoded handle failed.", ex); + return null; + } + + ErrorUtilities.VerifyProtocol(bag.IsPrivateAssociation == privateAssociation, "Unexpected association type."); + Association assoc = Association.Deserialize(handle, bag.ExpiresUtc, bag.Secret); + return assoc.IsExpired ? null : assoc; + } + } +} diff --git a/src/DotNetOpenAuth.OpenId.Provider/OpenId/Provider/ProviderAssociationKeyStorage.cs b/src/DotNetOpenAuth.OpenId.Provider/OpenId/Provider/ProviderAssociationKeyStorage.cs new file mode 100644 index 0000000..179699a --- /dev/null +++ b/src/DotNetOpenAuth.OpenId.Provider/OpenId/Provider/ProviderAssociationKeyStorage.cs @@ -0,0 +1,79 @@ +//----------------------------------------------------------------------- +// <copyright file="ProviderAssociationKeyStorage.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.Provider { + using System; + using System.Diagnostics.Contracts; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.Messaging.Bindings; + + /// <summary> + /// An association storage mechanism that stores the association secrets in a private store, + /// and returns randomly generated association handles to refer to these secrets. + /// </summary> + internal class ProviderAssociationKeyStorage : IProviderAssociationStore { + /// <summary> + /// The bucket to use when recording shared associations. + /// </summary> + internal const string SharedAssociationBucket = "https://localhost/dnoa/shared_associations"; + + /// <summary> + /// The bucket to use when recording private associations. + /// </summary> + internal const string PrivateAssociationBucket = "https://localhost/dnoa/private_associations"; + + /// <summary> + /// The backing crypto key store. + /// </summary> + private readonly ICryptoKeyStore cryptoKeyStore; + + /// <summary> + /// Initializes a new instance of the <see cref="ProviderAssociationKeyStorage"/> class. + /// </summary> + /// <param name="cryptoKeyStore">The store where association secrets will be recorded.</param> + internal ProviderAssociationKeyStorage(ICryptoKeyStore cryptoKeyStore) { + Requires.NotNull(cryptoKeyStore, "cryptoKeyStore"); + this.cryptoKeyStore = cryptoKeyStore; + } + + /// <summary> + /// Stores an association and returns a handle for it. + /// </summary> + /// <param name="secret">The association secret.</param> + /// <param name="expiresUtc">The UTC time that the association should expire.</param> + /// <param name="privateAssociation">A value indicating whether this is a private association.</param> + /// <returns> + /// The association handle that represents this association. + /// </returns> + public string Serialize(byte[] secret, DateTime expiresUtc, bool privateAssociation) { + string handle; + this.cryptoKeyStore.StoreKey( + privateAssociation ? PrivateAssociationBucket : SharedAssociationBucket, + handle = OpenIdUtilities.GenerateRandomAssociationHandle(), + new CryptoKey(secret, expiresUtc)); + return handle; + } + + /// <summary> + /// Retrieves an association given an association handle. + /// </summary> + /// <param name="containingMessage">The OpenID message that referenced this association handle.</param> + /// <param name="isPrivateAssociation">A value indicating whether a private association is expected.</param> + /// <param name="handle">The association handle.</param> + /// <returns> + /// An association instance, or <c>null</c> if the association has expired or the signature is incorrect (which may be because the OP's symmetric key has changed). + /// </returns> + /// <exception cref="ProtocolException">Thrown if the association is not of the expected type.</exception> + public Association Deserialize(IProtocolMessage containingMessage, bool isPrivateAssociation, string handle) { + var key = this.cryptoKeyStore.GetKey(isPrivateAssociation ? PrivateAssociationBucket : SharedAssociationBucket, handle); + if (key != null) { + return Association.Deserialize(handle, key.ExpiresUtc, key.Key); + } + + return null; + } + } +} diff --git a/src/DotNetOpenAuth.OpenId.Provider/OpenId/Provider/Request.cs b/src/DotNetOpenAuth.OpenId.Provider/OpenId/Provider/Request.cs new file mode 100644 index 0000000..c5b6dac --- /dev/null +++ b/src/DotNetOpenAuth.OpenId.Provider/OpenId/Provider/Request.cs @@ -0,0 +1,210 @@ +//----------------------------------------------------------------------- +// <copyright file="Request.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.Provider { + using System; + using System.Collections.Generic; + using System.Diagnostics.Contracts; + using System.Linq; + using System.Text; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OpenId.Messages; + + /// <summary> + /// Implements the <see cref="IRequest"/> interface for all incoming + /// request messages to an OpenID Provider. + /// </summary> + [Serializable] + [ContractClass(typeof(RequestContract))] + [ContractVerification(true)] + internal abstract class Request : IRequest { + /// <summary> + /// The incoming request message. + /// </summary> + private readonly IDirectedProtocolMessage request; + + /// <summary> + /// The incoming request message cast to its extensible form. + /// Or null if the message does not support extensions. + /// </summary> + private readonly IProtocolMessageWithExtensions extensibleMessage; + + /// <summary> + /// The version of the OpenID protocol to use. + /// </summary> + private readonly Version protocolVersion; + + /// <summary> + /// Backing store for the <see cref="Protocol"/> property. + /// </summary> + [NonSerialized] + private Protocol protocol; + + /// <summary> + /// The list of extensions to add to the response message. + /// </summary> + private List<IOpenIdMessageExtension> responseExtensions = new List<IOpenIdMessageExtension>(); + + /// <summary> + /// Initializes a new instance of the <see cref="Request"/> class. + /// </summary> + /// <param name="request">The incoming request message.</param> + /// <param name="securitySettings">The security settings from the channel.</param> + protected Request(IDirectedProtocolMessage request, ProviderSecuritySettings securitySettings) { + Requires.NotNull(request, "request"); + Requires.NotNull(securitySettings, "securitySettings"); + + this.request = request; + this.SecuritySettings = securitySettings; + this.protocolVersion = this.request.Version; + this.extensibleMessage = request as IProtocolMessageWithExtensions; + } + + /// <summary> + /// Initializes a new instance of the <see cref="Request"/> class. + /// </summary> + /// <param name="version">The version.</param> + /// <param name="securitySettings">The security settings.</param> + protected Request(Version version, ProviderSecuritySettings securitySettings) { + Requires.NotNull(version, "version"); + Requires.NotNull(securitySettings, "securitySettings"); + + this.protocolVersion = version; + this.SecuritySettings = securitySettings; + } + + #region IRequest Properties + + /// <summary> + /// Gets a value indicating whether the response is ready to be sent to the user agent. + /// </summary> + /// <remarks> + /// This property returns false if there are properties that must be set on this + /// request instance before the response can be sent. + /// </remarks> + public abstract bool IsResponseReady { get; } + + /// <summary> + /// Gets or sets the security settings that apply to this request. + /// </summary> + /// <value>Defaults to the <see cref="OpenIdProvider.SecuritySettings"/> on the <see cref="OpenIdProvider"/>.</value> + public ProviderSecuritySettings SecuritySettings { get; set; } + + /// <summary> + /// Gets the response to send to the user agent. + /// </summary> + /// <exception cref="InvalidOperationException">Thrown if <see cref="IsResponseReady"/> is <c>false</c>.</exception> + internal IProtocolMessage Response { + get { + Requires.ValidState(this.IsResponseReady, OpenIdStrings.ResponseNotReady); + Contract.Ensures(Contract.Result<IProtocolMessage>() != null); + + if (this.responseExtensions.Count > 0) { + var extensibleResponse = this.ResponseMessage as IProtocolMessageWithExtensions; + ErrorUtilities.VerifyOperation(extensibleResponse != null, MessagingStrings.MessageNotExtensible, this.ResponseMessage.GetType().Name); + foreach (var extension in this.responseExtensions) { + // It's possible that a prior call to this property + // has already added some/all of the extensions to the message. + // We don't have to worry about deleting old ones because + // this class provides no facility for removing extensions + // that are previously added. + if (!extensibleResponse.Extensions.Contains(extension)) { + extensibleResponse.Extensions.Add(extension); + } + } + } + + return this.ResponseMessage; + } + } + + #endregion + + /// <summary> + /// Gets the original request message. + /// </summary> + /// <value>This may be null in the case of an unrecognizable message.</value> + protected internal IDirectedProtocolMessage RequestMessage { + get { return this.request; } + } + + /// <summary> + /// Gets the response message, once <see cref="IsResponseReady"/> is <c>true</c>. + /// </summary> + protected abstract IProtocolMessage ResponseMessage { get; } + + /// <summary> + /// Gets the protocol version used in the request. + /// </summary> + protected Protocol Protocol { + get { + if (this.protocol == null) { + this.protocol = Protocol.Lookup(this.protocolVersion); + } + + return this.protocol; + } + } + + #region IRequest Methods + + /// <summary> + /// Adds an extension to the response to send to the relying party. + /// </summary> + /// <param name="extension">The extension to add to the response message.</param> + public void AddResponseExtension(IOpenIdMessageExtension extension) { + // Because the derived AuthenticationRequest class can swap out + // one response message for another (auth vs. no-auth), and because + // some response messages support extensions while others don't, + // we just add the extensions to a collection here and add them + // to the response on the way out. + this.responseExtensions.Add(extension); + } + + /// <summary> + /// Removes any response extensions previously added using <see cref="AddResponseExtension"/>. + /// </summary> + /// <remarks> + /// This should be called before sending a negative response back to the relying party + /// if extensions were already added, since negative responses cannot carry extensions. + /// </remarks> + public void ClearResponseExtensions() { + this.responseExtensions.Clear(); + } + + /// <summary> + /// Gets an extension sent from the relying party. + /// </summary> + /// <typeparam name="T">The type of the extension.</typeparam> + /// <returns> + /// An instance of the extension initialized with values passed in with the request. + /// </returns> + public T GetExtension<T>() where T : IOpenIdMessageExtension, new() { + if (this.extensibleMessage != null) { + return this.extensibleMessage.Extensions.OfType<T>().SingleOrDefault(); + } else { + return default(T); + } + } + + /// <summary> + /// Gets an extension sent from the relying party. + /// </summary> + /// <param name="extensionType">The type of the extension.</param> + /// <returns> + /// An instance of the extension initialized with values passed in with the request. + /// </returns> + public IOpenIdMessageExtension GetExtension(Type extensionType) { + if (this.extensibleMessage != null) { + return this.extensibleMessage.Extensions.OfType<IOpenIdMessageExtension>().Where(ext => extensionType.IsInstanceOfType(ext)).SingleOrDefault(); + } else { + return null; + } + } + + #endregion + } +} diff --git a/src/DotNetOpenAuth.OpenId.Provider/OpenId/Provider/RequestContract.cs b/src/DotNetOpenAuth.OpenId.Provider/OpenId/Provider/RequestContract.cs new file mode 100644 index 0000000..ae7104c --- /dev/null +++ b/src/DotNetOpenAuth.OpenId.Provider/OpenId/Provider/RequestContract.cs @@ -0,0 +1,48 @@ +//----------------------------------------------------------------------- +// <copyright file="RequestContract.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.Provider { + using System; + using System.Collections.Generic; + using System.Diagnostics.Contracts; + using System.Linq; + using System.Text; + using DotNetOpenAuth.Messaging; + + /// <summary> + /// Code contract for the <see cref="Request"/> class. + /// </summary> + [ContractClassFor(typeof(Request))] + internal abstract class RequestContract : Request { + /// <summary> + /// Prevents a default instance of the <see cref="RequestContract"/> class from being created. + /// </summary> + private RequestContract() : base((Version)null, null) { + } + + /// <summary> + /// Gets a value indicating whether the response is ready to be sent to the user agent. + /// </summary> + /// <remarks> + /// This property returns false if there are properties that must be set on this + /// request instance before the response can be sent. + /// </remarks> + public override bool IsResponseReady { + get { throw new NotImplementedException(); } + } + + /// <summary> + /// Gets the response message, once <see cref="IsResponseReady"/> is <c>true</c>. + /// </summary> + protected override IProtocolMessage ResponseMessage { + get { + Requires.ValidState(this.IsResponseReady); + Contract.Ensures(Contract.Result<IProtocolMessage>() != null); + throw new NotImplementedException(); + } + } + } +} diff --git a/src/DotNetOpenAuth.OpenId.Provider/OpenId/Provider/StandardProviderApplicationStore.cs b/src/DotNetOpenAuth.OpenId.Provider/OpenId/Provider/StandardProviderApplicationStore.cs new file mode 100644 index 0000000..b5880d3 --- /dev/null +++ b/src/DotNetOpenAuth.OpenId.Provider/OpenId/Provider/StandardProviderApplicationStore.cs @@ -0,0 +1,117 @@ +//----------------------------------------------------------------------- +// <copyright file="StandardProviderApplicationStore.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.Provider { + using System; + using System.Collections.Generic; + using DotNetOpenAuth.Configuration; + using DotNetOpenAuth.Messaging.Bindings; + + /// <summary> + /// An in-memory store for Providers, suitable for single server, single process + /// ASP.NET web sites. + /// </summary> + /// <remarks> + /// This class provides only a basic implementation that is likely to work + /// out of the box on most single-server web sites. It is highly recommended + /// that high traffic web sites consider using a database to store the information + /// used by an OpenID Provider and write a custom implementation of the + /// <see cref="IOpenIdApplicationStore"/> interface to use instead of this + /// class. + /// </remarks> + public class StandardProviderApplicationStore : IOpenIdApplicationStore { + /// <summary> + /// The nonce store to use. + /// </summary> + private readonly INonceStore nonceStore; + + /// <summary> + /// The crypto key store where symmetric keys are persisted. + /// </summary> + private readonly ICryptoKeyStore cryptoKeyStore; + + /// <summary> + /// Initializes a new instance of the <see cref="StandardProviderApplicationStore"/> class. + /// </summary> + public StandardProviderApplicationStore() { + this.nonceStore = new NonceMemoryStore(OpenIdElement.Configuration.MaxAuthenticationTime); + this.cryptoKeyStore = new MemoryCryptoKeyStore(); + } + + #region INonceStore Members + + /// <summary> + /// Stores a given nonce and timestamp. + /// </summary> + /// <param name="context">The context, or namespace, within which the <paramref name="nonce"/> must be unique.</param> + /// <param name="nonce">A series of random characters.</param> + /// <param name="timestampUtc">The timestamp that together with the nonce string make it unique. + /// The timestamp may also be used by the data store to clear out old nonces.</param> + /// <returns> + /// True if the nonce+timestamp (combination) was not previously in the database. + /// False if the nonce was stored previously with the same timestamp. + /// </returns> + /// <remarks> + /// The nonce must be stored for no less than the maximum time window a message may + /// be processed within before being discarded as an expired message. + /// If the binding element is applicable to your channel, this expiration window + /// is retrieved or set using the + /// <see cref="StandardExpirationBindingElement.MaximumMessageAge"/> property. + /// </remarks> + public bool StoreNonce(string context, string nonce, DateTime timestampUtc) { + return this.nonceStore.StoreNonce(context, nonce, timestampUtc); + } + + #endregion + + #region ICryptoKeyStore + + /// <summary> + /// Gets the key in a given bucket and handle. + /// </summary> + /// <param name="bucket">The bucket name. Case sensitive.</param> + /// <param name="handle">The key handle. Case sensitive.</param> + /// <returns> + /// The cryptographic key, or <c>null</c> if no matching key was found. + /// </returns> + public CryptoKey GetKey(string bucket, string handle) { + return this.cryptoKeyStore.GetKey(bucket, handle); + } + + /// <summary> + /// Gets a sequence of existing keys within a given bucket. + /// </summary> + /// <param name="bucket">The bucket name. Case sensitive.</param> + /// <returns> + /// A sequence of handles and keys, ordered by descending <see cref="CryptoKey.ExpiresUtc"/>. + /// </returns> + public IEnumerable<KeyValuePair<string, CryptoKey>> GetKeys(string bucket) { + return this.cryptoKeyStore.GetKeys(bucket); + } + + /// <summary> + /// Stores a cryptographic key. + /// </summary> + /// <param name="bucket">The name of the bucket to store the key in. Case sensitive.</param> + /// <param name="handle">The handle to the key, unique within the bucket. Case sensitive.</param> + /// <param name="key">The key to store.</param> + /// <exception cref="CryptoKeyCollisionException">Thrown in the event of a conflict with an existing key in the same bucket and with the same handle.</exception> + public void StoreKey(string bucket, string handle, CryptoKey key) { + this.cryptoKeyStore.StoreKey(bucket, handle, key); + } + + /// <summary> + /// Removes the key. + /// </summary> + /// <param name="bucket">The bucket name. Case sensitive.</param> + /// <param name="handle">The key handle. Case sensitive.</param> + public void RemoveKey(string bucket, string handle) { + this.cryptoKeyStore.RemoveKey(bucket, handle); + } + + #endregion + } +} diff --git a/src/DotNetOpenAuth.OpenId.Provider/Properties/AssemblyInfo.cs b/src/DotNetOpenAuth.OpenId.Provider/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..c90d56f --- /dev/null +++ b/src/DotNetOpenAuth.OpenId.Provider/Properties/AssemblyInfo.cs @@ -0,0 +1,54 @@ +//----------------------------------------------------------------------- +// <copyright file="AssemblyInfo.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +// We DON'T put an AssemblyVersionAttribute in here because it is generated in the build. + +using System; +using System.Diagnostics.Contracts; +using System.Net; +using System.Reflection; +using System.Resources; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Security; +using System.Security.Permissions; +using System.Web.UI; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("DotNetOpenAuth OpenID")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("DotNetOpenAuth")] +[assembly: AssemblyCopyright("Copyright © 2008")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: NeutralResourcesLanguage("en-US")] +[assembly: CLSCompliant(true)] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("7d73990c-47c0-4256-9f20-a893add9e289")] + +[assembly: ContractVerification(true)] + +#if StrongNameSigned +// See comment at top of this file. We need this so that strong-naming doesn't +// keep this assembly from being useful to shared host (medium trust) web sites. +[assembly: AllowPartiallyTrustedCallers] + +[assembly: InternalsVisibleTo("DotNetOpenAuth.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100AD093C3765257C89A7010E853F2C7C741FF92FA8ACE06D7B8254702CAD5CF99104447F63AB05F8BB6F51CE0D81C8C93D2FCE8C20AAFF7042E721CBA16EAAE98778611DED11C0ABC8900DC5667F99B50A9DADEC24DBD8F2C91E3E8AD300EF64F1B4B9536CEB16FB440AF939F57624A9B486F867807C649AE4830EAB88C6C03998")] +[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")] +#else +[assembly: InternalsVisibleTo("DotNetOpenAuth.Test")] +[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] +#endif diff --git a/src/DotNetOpenAuth/ComponentModel/IdentifierConverter.cs b/src/DotNetOpenAuth.OpenId.RelyingParty.UI/ComponentModel/IdentifierConverter.cs index 61c0fd8..61c0fd8 100644 --- a/src/DotNetOpenAuth/ComponentModel/IdentifierConverter.cs +++ b/src/DotNetOpenAuth.OpenId.RelyingParty.UI/ComponentModel/IdentifierConverter.cs diff --git a/src/DotNetOpenAuth.OpenId.RelyingParty.UI/DotNetOpenAuth.OpenId.RelyingParty.UI.csproj b/src/DotNetOpenAuth.OpenId.RelyingParty.UI/DotNetOpenAuth.OpenId.RelyingParty.UI.csproj new file mode 100644 index 0000000..4d376bf --- /dev/null +++ b/src/DotNetOpenAuth.OpenId.RelyingParty.UI/DotNetOpenAuth.OpenId.RelyingParty.UI.csproj @@ -0,0 +1,87 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), EnlistmentInfo.props))\EnlistmentInfo.props" Condition=" '$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), EnlistmentInfo.props))' != '' " /> + <PropertyGroup> + <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> + <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> + </PropertyGroup> + <Import Project="$(ProjectRoot)tools\DotNetOpenAuth.props" /> + <PropertyGroup> + <SchemaVersion>2.0</SchemaVersion> + <ProjectGuid>{1ED8D424-F8AB-4050-ACEB-F27F4F909484}</ProjectGuid> + <OutputType>Library</OutputType> + <AppDesignerFolder>Properties</AppDesignerFolder> + <RootNamespace>DotNetOpenAuth</RootNamespace> + <AssemblyName>DotNetOpenAuth.OpenId.RelyingParty.UI</AssemblyName> + </PropertyGroup> + <Import Project="$(ProjectRoot)tools\DotNetOpenAuth.Product.props" /> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> + </PropertyGroup> + <ItemGroup> + <Compile Include="ComponentModel\IdentifierConverter.cs" /> + <Compile Include="OpenId\Mvc\OpenIdAjaxOptions.cs" /> + <Compile Include="OpenId\Mvc\OpenIdHelper.cs" /> + <Compile Include="OpenId\RelyingParty\OpenIdAjaxRelyingParty.cs" /> + <Compile Include="OpenId\RelyingParty\OpenIdAjaxTextBox.cs" /> + <Compile Include="OpenId\RelyingParty\OpenIdButton.cs" /> + <Compile Include="OpenId\RelyingParty\OpenIdEventArgs.cs" /> + <Compile Include="OpenId\RelyingParty\OpenIdLogin.cs" /> + <Compile Include="OpenId\RelyingParty\OpenIdMobileTextBox.cs" Condition=" '$(ClrVersion)' != '4' "/> + <Compile Include="OpenId\RelyingParty\OpenIdRelyingPartyAjaxControlBase.cs" /> + <Compile Include="OpenId\RelyingParty\OpenIdRelyingPartyControlBase.cs" /> + <Compile Include="OpenId\RelyingParty\OpenIdSelector.cs" /> + <Compile Include="OpenId\RelyingParty\OpenIdTextBox.cs" /> + <Compile Include="OpenId\RelyingParty\PopupBehavior.cs" /> + <Compile Include="OpenId\RelyingParty\SelectorButton.cs" /> + <Compile Include="OpenId\RelyingParty\SelectorButtonContract.cs" /> + <Compile Include="OpenId\RelyingParty\SelectorOpenIdButton.cs" /> + <Compile Include="OpenId\RelyingParty\SelectorProviderButton.cs" /> + <Compile Include="Properties\AssemblyInfo.cs" /> + </ItemGroup> + <ItemGroup> + <ProjectReference Include="..\DotNetOpenAuth.Core.UI\DotNetOpenAuth.Core.UI.csproj"> + <Project>{173E7B8D-E751-46E2-A133-F72297C0D2F4}</Project> + <Name>DotNetOpenAuth.Core.UI</Name> + </ProjectReference> + <ProjectReference Include="..\DotNetOpenAuth.Core\DotNetOpenAuth.Core.csproj"> + <Project>{60426312-6AE5-4835-8667-37EDEA670222}</Project> + <Name>DotNetOpenAuth.Core</Name> + </ProjectReference> + <ProjectReference Include="..\DotNetOpenAuth.OpenId.RelyingParty\DotNetOpenAuth.OpenId.RelyingParty.csproj"> + <Project>{F458AB60-BA1C-43D9-8CEF-EC01B50BE87B}</Project> + <Name>DotNetOpenAuth.OpenId.RelyingParty</Name> + </ProjectReference> + <ProjectReference Include="..\DotNetOpenAuth.OpenId\DotNetOpenAuth.OpenId.csproj"> + <Project>{3896A32A-E876-4C23-B9B8-78E17D134CD3}</Project> + <Name>DotNetOpenAuth.OpenId</Name> + </ProjectReference> + <ProjectReference Include="..\Org.Mentalis.Security.Cryptography\Org.Mentalis.Security.Cryptography.csproj"> + <Project>{26DC877F-5987-48DD-9DDB-E62F2DE0E150}</Project> + <Name>Org.Mentalis.Security.Cryptography</Name> + </ProjectReference> + </ItemGroup> + <ItemGroup> + <Reference Include="System" /> + </ItemGroup> + <ItemGroup> + <EmbeddedResource Include="OpenId\RelyingParty\login_failure.png" /> + <EmbeddedResource Include="OpenId\RelyingParty\login_success %28lock%29.png" /> + <EmbeddedResource Include="OpenId\RelyingParty\login_success.png" /> + <EmbeddedResource Include="OpenId\RelyingParty\OpenIdAjaxTextBox.css" /> + <EmbeddedResource Include="OpenId\RelyingParty\OpenIdAjaxTextBox.js" /> + <EmbeddedResource Include="OpenId\RelyingParty\OpenIdRelyingPartyAjaxControlBase.js" /> + <EmbeddedResource Include="OpenId\RelyingParty\OpenIdRelyingPartyControlBase.js" /> + <EmbeddedResource Include="OpenId\RelyingParty\OpenIdSelector.css" /> + <EmbeddedResource Include="OpenId\RelyingParty\OpenIdSelector.js" /> + <EmbeddedResource Include="OpenId\RelyingParty\openid_login.png" /> + <EmbeddedResource Include="OpenId\RelyingParty\spinner.gif" /> + </ItemGroup> + <ItemGroup> + <None Include="OpenId\RelyingParty\Controls.cd" /> + </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))' != '' " /> +</Project>
\ No newline at end of file diff --git a/src/DotNetOpenAuth/Mvc/OpenIdAjaxOptions.cs b/src/DotNetOpenAuth.OpenId.RelyingParty.UI/OpenId/Mvc/OpenIdAjaxOptions.cs index 4b88d04..4b88d04 100644 --- a/src/DotNetOpenAuth/Mvc/OpenIdAjaxOptions.cs +++ b/src/DotNetOpenAuth.OpenId.RelyingParty.UI/OpenId/Mvc/OpenIdAjaxOptions.cs diff --git a/src/DotNetOpenAuth.OpenId.RelyingParty.UI/OpenId/Mvc/OpenIdHelper.cs b/src/DotNetOpenAuth.OpenId.RelyingParty.UI/OpenId/Mvc/OpenIdHelper.cs new file mode 100644 index 0000000..3325084 --- /dev/null +++ b/src/DotNetOpenAuth.OpenId.RelyingParty.UI/OpenId/Mvc/OpenIdHelper.cs @@ -0,0 +1,431 @@ +//----------------------------------------------------------------------- +// <copyright file="OpenIdHelper.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Mvc { + using System; + using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; + using System.Diagnostics.Contracts; + using System.Globalization; + using System.IO; + using System.Linq; + using System.Text; + using System.Web; + using System.Web.Mvc; + using System.Web.Routing; + using System.Web.UI; + using DotNetOpenAuth.Configuration; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OpenId; + using DotNetOpenAuth.OpenId.RelyingParty; + + /// <summary> + /// Methods that generate HTML or Javascript for hosting AJAX OpenID "controls" on + /// ASP.NET MVC web sites. + /// </summary> + public static class OpenIdHelper { + /// <summary> + /// Emits a series of stylesheet import tags to support the AJAX OpenID Selector. + /// </summary> + /// <param name="html">The <see cref="HtmlHelper"/> on the view.</param> + /// <returns>HTML that should be sent directly to the browser.</returns> + public static string OpenIdSelectorStyles(this HtmlHelper html) { + Requires.NotNull(html, "html"); + Contract.Ensures(Contract.Result<string>() != null); + + using (var result = new StringWriter(CultureInfo.CurrentCulture)) { + result.WriteStylesheetLink(OpenId.RelyingParty.OpenIdSelector.EmbeddedStylesheetResourceName); + result.WriteStylesheetLink(OpenId.RelyingParty.OpenIdAjaxTextBox.EmbeddedStylesheetResourceName); + return result.ToString(); + } + } + + /// <summary> + /// Emits a series of script import tags and some inline script to support the AJAX OpenID Selector. + /// </summary> + /// <param name="html">The <see cref="HtmlHelper"/> on the view.</param> + /// <returns>HTML that should be sent directly to the browser.</returns> + public static string OpenIdSelectorScripts(this HtmlHelper html) { + return OpenIdSelectorScripts(html, null, null); + } + + /// <summary> + /// Emits a series of script import tags and some inline script to support the AJAX OpenID Selector. + /// </summary> + /// <param name="html">The <see cref="HtmlHelper"/> on the view.</param> + /// <param name="selectorOptions">An optional instance of an <see cref="OpenIdSelector"/> control, whose properties have been customized to express how this MVC control should be rendered.</param> + /// <param name="additionalOptions">An optional set of additional script customizations.</param> + /// <returns> + /// HTML that should be sent directly to the browser. + /// </returns> + [SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "False positive")] + public static string OpenIdSelectorScripts(this HtmlHelper html, OpenIdSelector selectorOptions, OpenIdAjaxOptions additionalOptions) { + Requires.NotNull(html, "html"); + Contract.Ensures(Contract.Result<string>() != null); + + bool selectorOptionsOwned = false; + if (selectorOptions == null) { + selectorOptionsOwned = true; + selectorOptions = new OpenId.RelyingParty.OpenIdSelector(); + } + try { + if (additionalOptions == null) { + additionalOptions = new OpenIdAjaxOptions(); + } + + using (StringWriter result = new StringWriter(CultureInfo.CurrentCulture)) { + if (additionalOptions.ShowDiagnosticIFrame || additionalOptions.ShowDiagnosticTrace) { + string scriptFormat = @"window.openid_visible_iframe = {0}; // causes the hidden iframe to show up +window.openid_trace = {1}; // causes lots of messages"; + result.WriteScriptBlock(string.Format( + CultureInfo.InvariantCulture, + scriptFormat, + additionalOptions.ShowDiagnosticIFrame ? "true" : "false", + additionalOptions.ShowDiagnosticTrace ? "true" : "false")); + } + var scriptResources = new[] { + OpenIdRelyingPartyControlBase.EmbeddedJavascriptResource, + OpenIdRelyingPartyAjaxControlBase.EmbeddedAjaxJavascriptResource, + OpenId.RelyingParty.OpenIdAjaxTextBox.EmbeddedScriptResourceName, + }; + result.WriteScriptTags(scriptResources); + + if (selectorOptions.DownloadYahooUILibrary) { + result.WriteScriptTagsUrls(new[] { "https://ajax.googleapis.com/ajax/libs/yui/2.8.0r4/build/yuiloader/yuiloader-min.js" }); + } + + using (var blockBuilder = new StringWriter(CultureInfo.CurrentCulture)) { + if (selectorOptions.DownloadYahooUILibrary) { + blockBuilder.WriteLine(@" try { + if (YAHOO) { + var loader = new YAHOO.util.YUILoader({ + require: ['button', 'menu'], + loadOptional: false, + combine: true + }); + + loader.insert(); + } + } catch (e) { }"); + } + + blockBuilder.WriteLine("window.aspnetapppath = '{0}';", VirtualPathUtility.AppendTrailingSlash(HttpContext.Current.Request.ApplicationPath)); + + // Positive assertions can last no longer than this library is willing to consider them valid, + // and when they come with OP private associations they last no longer than the OP is willing + // to consider them valid. We assume the OP will hold them valid for at least five minutes. + double assertionLifetimeInMilliseconds = Math.Min(TimeSpan.FromMinutes(5).TotalMilliseconds, Math.Min(OpenIdElement.Configuration.MaxAuthenticationTime.TotalMilliseconds, DotNetOpenAuthSection.Messaging.MaximumMessageLifetime.TotalMilliseconds)); + blockBuilder.WriteLine( + "{0} = {1};", + OpenIdRelyingPartyAjaxControlBase.MaxPositiveAssertionLifetimeJsName, + assertionLifetimeInMilliseconds.ToString(CultureInfo.InvariantCulture)); + + if (additionalOptions.PreloadedDiscoveryResults != null) { + blockBuilder.WriteLine(additionalOptions.PreloadedDiscoveryResults); + } + + string discoverUrl = VirtualPathUtility.AppendTrailingSlash(HttpContext.Current.Request.ApplicationPath) + html.RouteCollection["OpenIdDiscover"].GetVirtualPath(html.ViewContext.RequestContext, new RouteValueDictionary(new { identifier = "xxx" })).VirtualPath; + string blockFormat = @" {0} = function (argument, resultFunction, errorCallback) {{ + jQuery.ajax({{ + async: true, + dataType: 'text', + error: function (request, status, error) {{ errorCallback(status, argument); }}, + success: function (result) {{ resultFunction(result, argument); }}, + url: '{1}'.replace('xxx', encodeURIComponent(argument)) + }}); + }};"; + blockBuilder.WriteLine(blockFormat, OpenIdRelyingPartyAjaxControlBase.CallbackJSFunctionAsync, discoverUrl); + + blockFormat = @" window.postLoginAssertion = function (positiveAssertion) {{ + $('#{0}')[0].setAttribute('value', positiveAssertion); + if ($('#{1}')[0] && !$('#{1}')[0].value) {{ // popups have no ReturnUrl predefined, but full page LogOn does. + $('#{1}')[0].setAttribute('value', window.parent.location.href); + }} + document.forms[{2}].submit(); + }};"; + blockBuilder.WriteLine( + blockFormat, + additionalOptions.AssertionHiddenFieldId, + additionalOptions.ReturnUrlHiddenFieldId, + additionalOptions.FormKey); + + blockFormat = @" $(function () {{ + var box = document.getElementsByName('openid_identifier')[0]; + initAjaxOpenId(box, {0}, {1}, {2}, {3}, {4}, {5}, + null, // js function to invoke on receiving a positive assertion + {6}, {7}, {8}, {9}, {10}, {11}, {12}, {13}, {14}, {15}, {16}, {17}, + false, // auto postback + null); // PostBackEventReference (unused in MVC) + }});"; + blockBuilder.WriteLine( + blockFormat, + MessagingUtilities.GetSafeJavascriptValue(Util.GetWebResourceUrl(typeof(OpenIdRelyingPartyControlBase), OpenIdTextBox.EmbeddedLogoResourceName)), + MessagingUtilities.GetSafeJavascriptValue(Util.GetWebResourceUrl(typeof(OpenIdRelyingPartyControlBase), OpenId.RelyingParty.OpenIdAjaxTextBox.EmbeddedSpinnerResourceName)), + MessagingUtilities.GetSafeJavascriptValue(Util.GetWebResourceUrl(typeof(OpenIdRelyingPartyControlBase), OpenId.RelyingParty.OpenIdAjaxTextBox.EmbeddedLoginSuccessResourceName)), + MessagingUtilities.GetSafeJavascriptValue(Util.GetWebResourceUrl(typeof(OpenIdRelyingPartyControlBase), OpenId.RelyingParty.OpenIdAjaxTextBox.EmbeddedLoginFailureResourceName)), + selectorOptions.Throttle, + selectorOptions.Timeout.TotalMilliseconds, + MessagingUtilities.GetSafeJavascriptValue(selectorOptions.TextBox.LogOnText), + MessagingUtilities.GetSafeJavascriptValue(selectorOptions.TextBox.LogOnToolTip), + selectorOptions.TextBox.ShowLogOnPostBackButton ? "true" : "false", + MessagingUtilities.GetSafeJavascriptValue(selectorOptions.TextBox.LogOnPostBackToolTip), + MessagingUtilities.GetSafeJavascriptValue(selectorOptions.TextBox.RetryText), + MessagingUtilities.GetSafeJavascriptValue(selectorOptions.TextBox.RetryToolTip), + MessagingUtilities.GetSafeJavascriptValue(selectorOptions.TextBox.BusyToolTip), + MessagingUtilities.GetSafeJavascriptValue(selectorOptions.TextBox.IdentifierRequiredMessage), + MessagingUtilities.GetSafeJavascriptValue(selectorOptions.TextBox.LogOnInProgressMessage), + MessagingUtilities.GetSafeJavascriptValue(selectorOptions.TextBox.AuthenticationSucceededToolTip), + MessagingUtilities.GetSafeJavascriptValue(selectorOptions.TextBox.AuthenticatedAsToolTip), + MessagingUtilities.GetSafeJavascriptValue(selectorOptions.TextBox.AuthenticationFailedToolTip)); + + result.WriteScriptBlock(blockBuilder.ToString()); + result.WriteScriptTags(OpenId.RelyingParty.OpenIdSelector.EmbeddedScriptResourceName); + + Reporting.RecordFeatureUse("MVC " + typeof(OpenIdSelector).Name); + return result.ToString(); + } + } + } catch { + if (selectorOptionsOwned) { + selectorOptions.Dispose(); + } + + throw; + } + } + + /// <summary> + /// Emits the HTML to render an OpenID Provider button as a part of the overall OpenID Selector UI. + /// </summary> + /// <param name="html">The <see cref="HtmlHelper"/> on the view.</param> + /// <param name="providerIdentifier">The OP Identifier.</param> + /// <param name="imageUrl">The URL of the image to display on the button.</param> + /// <returns> + /// HTML that should be sent directly to the browser. + /// </returns> + public static string OpenIdSelectorOPButton(this HtmlHelper html, Identifier providerIdentifier, string imageUrl) { + Requires.NotNull(html, "html"); + Requires.NotNull(providerIdentifier, "providerIdentifier"); + Requires.NotNullOrEmpty(imageUrl, "imageUrl"); + Contract.Ensures(Contract.Result<string>() != null); + + return OpenIdSelectorButton(html, providerIdentifier, "OPButton", imageUrl); + } + + /// <summary> + /// Emits the HTML to render a generic OpenID button as a part of the overall OpenID Selector UI, + /// allowing the user to enter their own OpenID. + /// </summary> + /// <param name="html">The <see cref="HtmlHelper"/> on the view.</param> + /// <param name="imageUrl">The URL of the image to display on the button.</param> + /// <returns> + /// HTML that should be sent directly to the browser. + /// </returns> + public static string OpenIdSelectorOpenIdButton(this HtmlHelper html, string imageUrl) { + Requires.NotNull(html, "html"); + Requires.NotNullOrEmpty(imageUrl, "imageUrl"); + Contract.Ensures(Contract.Result<string>() != null); + + return OpenIdSelectorButton(html, "OpenIDButton", "OpenIDButton", imageUrl); + } + + /// <summary> + /// Emits the HTML to render the entire OpenID Selector UI. + /// </summary> + /// <param name="html">The <see cref="HtmlHelper"/> on the view.</param> + /// <param name="buttons">The buttons to include on the selector.</param> + /// <returns> + /// HTML that should be sent directly to the browser. + /// </returns> + [SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times", Justification = "Not a problem for this type.")] + public static string OpenIdSelector(this HtmlHelper html, params SelectorButton[] buttons) { + Requires.NotNull(html, "html"); + Requires.NotNull(buttons, "buttons"); + Contract.Ensures(Contract.Result<string>() != null); + + using (var writer = new StringWriter(CultureInfo.CurrentCulture)) { + using (var h = new HtmlTextWriter(writer)) { + h.AddAttribute(HtmlTextWriterAttribute.Class, "OpenIdProviders"); + h.RenderBeginTag(HtmlTextWriterTag.Ul); + + foreach (SelectorButton button in buttons) { + var op = button as SelectorProviderButton; + if (op != null) { + h.Write(OpenIdSelectorOPButton(html, op.OPIdentifier, op.Image)); + continue; + } + + var openid = button as SelectorOpenIdButton; + if (openid != null) { + h.Write(OpenIdSelectorOpenIdButton(html, openid.Image)); + continue; + } + + ErrorUtilities.VerifySupported(false, "The {0} button is not yet supported for MVC.", button.GetType().Name); + } + + h.RenderEndTag(); // ul + + if (buttons.OfType<SelectorOpenIdButton>().Any()) { + h.Write(OpenIdAjaxTextBox(html)); + } + } + + return writer.ToString(); + } + } + + /// <summary> + /// Emits the HTML to render the <see cref="OpenIdAjaxTextBox"/> control as a part of the overall + /// OpenID Selector UI. + /// </summary> + /// <param name="html">The <see cref="HtmlHelper"/> on the view.</param> + /// <returns> + /// HTML that should be sent directly to the browser. + /// </returns> + [SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "html", Justification = "Breaking change, and it's an extension method so it's useful.")] + public static string OpenIdAjaxTextBox(this HtmlHelper html) { + return @"<div style='display: none' id='OpenIDForm'> + <span class='OpenIdAjaxTextBox' style='display: inline-block; position: relative; font-size: 16px'> + <input name='openid_identifier' id='openid_identifier' size='40' style='padding-left: 18px; border-style: solid; border-width: 1px; border-color: lightgray' /> + </span> + </div>"; + } + + /// <summary> + /// Emits the HTML to render a button as a part of the overall OpenID Selector UI. + /// </summary> + /// <param name="html">The <see cref="HtmlHelper"/> on the view.</param> + /// <param name="id">The value to assign to the HTML id attribute.</param> + /// <param name="cssClass">The value to assign to the HTML class attribute.</param> + /// <param name="imageUrl">The URL of the image to draw on the button.</param> + /// <returns> + /// HTML that should be sent directly to the browser. + /// </returns> + [SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times", Justification = "Not a problem for this type.")] + private static string OpenIdSelectorButton(this HtmlHelper html, string id, string cssClass, string imageUrl) { + Requires.NotNull(html, "html"); + Requires.NotNull(id, "id"); + Requires.NotNullOrEmpty(imageUrl, "imageUrl"); + Contract.Ensures(Contract.Result<string>() != null); + + using (var writer = new StringWriter(CultureInfo.CurrentCulture)) { + using (var h = new HtmlTextWriter(writer)) { + h.AddAttribute(HtmlTextWriterAttribute.Id, id); + if (!string.IsNullOrEmpty(cssClass)) { + h.AddAttribute(HtmlTextWriterAttribute.Class, cssClass); + } + h.RenderBeginTag(HtmlTextWriterTag.Li); + + h.AddAttribute(HtmlTextWriterAttribute.Href, "#"); + h.RenderBeginTag(HtmlTextWriterTag.A); + + h.RenderBeginTag(HtmlTextWriterTag.Div); + h.RenderBeginTag(HtmlTextWriterTag.Div); + + h.AddAttribute(HtmlTextWriterAttribute.Src, imageUrl); + h.RenderBeginTag(HtmlTextWriterTag.Img); + h.RenderEndTag(); + + h.AddAttribute(HtmlTextWriterAttribute.Src, Util.GetWebResourceUrl(typeof(OpenIdSelector), OpenId.RelyingParty.OpenIdAjaxTextBox.EmbeddedLoginSuccessResourceName)); + h.AddAttribute(HtmlTextWriterAttribute.Class, "loginSuccess"); + h.AddAttribute(HtmlTextWriterAttribute.Title, "Authenticated as {0}"); + h.RenderBeginTag(HtmlTextWriterTag.Img); + h.RenderEndTag(); + + h.RenderEndTag(); // div + + h.AddAttribute(HtmlTextWriterAttribute.Class, "ui-widget-overlay"); + h.RenderBeginTag(HtmlTextWriterTag.Div); + h.RenderEndTag(); // div + + h.RenderEndTag(); // div + h.RenderEndTag(); // a + h.RenderEndTag(); // li + } + + return writer.ToString(); + } + } + + /// <summary> + /// Emits <script> tags that import a given set of scripts given their URLs. + /// </summary> + /// <param name="writer">The writer to emit the tags to.</param> + /// <param name="scriptUrls">The locations of the scripts to import.</param> + private static void WriteScriptTagsUrls(this TextWriter writer, IEnumerable<string> scriptUrls) { + Requires.NotNull(writer, "writer"); + Requires.NotNull(scriptUrls, "scriptUrls"); + + foreach (string script in scriptUrls) { + writer.WriteLine("<script type='text/javascript' src='{0}'></script>", script); + } + } + + /// <summary> + /// Writes out script tags that import a script from resources embedded in this assembly. + /// </summary> + /// <param name="writer">The writer to emit the tags to.</param> + /// <param name="resourceName">Name of the resource.</param> + private static void WriteScriptTags(this TextWriter writer, string resourceName) { + Requires.NotNull(writer, "writer"); + Requires.NotNullOrEmpty(resourceName, "resourceName"); + + WriteScriptTags(writer, new[] { resourceName }); + } + + /// <summary> + /// Writes out script tags that import scripts from resources embedded in this assembly. + /// </summary> + /// <param name="writer">The writer to emit the tags to.</param> + /// <param name="resourceNames">The resource names.</param> + private static void WriteScriptTags(this TextWriter writer, IEnumerable<string> resourceNames) { + Requires.NotNull(writer, "writer"); + Requires.NotNull(resourceNames, "resourceNames"); + + writer.WriteScriptTagsUrls(resourceNames.Select(r => Util.GetWebResourceUrl(typeof(OpenIdRelyingPartyControlBase), r))); + } + + /// <summary> + /// Writes a given script block, surrounding it with <script> and CDATA tags. + /// </summary> + /// <param name="writer">The writer to emit the tags to.</param> + /// <param name="script">The script to inline on the page.</param> + private static void WriteScriptBlock(this TextWriter writer, string script) { + Requires.NotNull(writer, "writer"); + Requires.NotNullOrEmpty(script, "script"); + + writer.WriteLine("<script type='text/javascript' language='javascript'><!--"); + writer.WriteLine("//<![CDATA["); + writer.WriteLine(script); + writer.WriteLine("//]]>--></script>"); + } + + /// <summary> + /// Writes a given CSS link. + /// </summary> + /// <param name="writer">The writer to emit the tags to.</param> + /// <param name="resourceName">Name of the resource containing the CSS content.</param> + private static void WriteStylesheetLink(this TextWriter writer, string resourceName) { + Requires.NotNull(writer, "writer"); + Requires.NotNullOrEmpty(resourceName, "resourceName"); + + WriteStylesheetLinkUrl(writer, Util.GetWebResourceUrl(typeof(OpenIdRelyingPartyAjaxControlBase), resourceName)); + } + + /// <summary> + /// Writes a given CSS link. + /// </summary> + /// <param name="writer">The writer to emit the tags to.</param> + /// <param name="stylesheet">The stylesheet to link in.</param> + private static void WriteStylesheetLinkUrl(this TextWriter writer, string stylesheet) { + Requires.NotNull(writer, "writer"); + Requires.NotNullOrEmpty(stylesheet, "stylesheet"); + + writer.WriteLine("<link rel='stylesheet' type='text/css' href='{0}' />", stylesheet); + } + } +} diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/Controls.cd b/src/DotNetOpenAuth.OpenId.RelyingParty.UI/OpenId/RelyingParty/Controls.cd index f96db36..f96db36 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/Controls.cd +++ b/src/DotNetOpenAuth.OpenId.RelyingParty.UI/OpenId/RelyingParty/Controls.cd diff --git a/src/DotNetOpenAuth.OpenId.RelyingParty.UI/OpenId/RelyingParty/OpenIdAjaxRelyingParty.cs b/src/DotNetOpenAuth.OpenId.RelyingParty.UI/OpenId/RelyingParty/OpenIdAjaxRelyingParty.cs new file mode 100644 index 0000000..8e915cd --- /dev/null +++ b/src/DotNetOpenAuth.OpenId.RelyingParty.UI/OpenId/RelyingParty/OpenIdAjaxRelyingParty.cs @@ -0,0 +1,242 @@ +//----------------------------------------------------------------------- +// <copyright file="OpenIdAjaxRelyingParty.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.RelyingParty { + using System; + using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; + using System.Diagnostics.Contracts; + using System.Globalization; + using System.Linq; + using System.Net; + using System.Net.Mime; + using System.Text; + using System.Web; + using System.Web.Script.Serialization; + + using DotNetOpenAuth.Configuration; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OpenId.Extensions; + using DotNetOpenAuth.OpenId.Extensions.UI; + + /// <summary> + /// Provides the programmatic facilities to act as an AJAX-enabled OpenID relying party. + /// </summary> + public class OpenIdAjaxRelyingParty : OpenIdRelyingParty { + /// <summary> + /// Initializes a new instance of the <see cref="OpenIdAjaxRelyingParty"/> class. + /// </summary> + public OpenIdAjaxRelyingParty() { + Reporting.RecordFeatureUse(this); + } + + /// <summary> + /// Initializes a new instance of the <see cref="OpenIdAjaxRelyingParty"/> class. + /// </summary> + /// <param name="applicationStore">The application store. If <c>null</c>, the relying party will always operate in "dumb mode".</param> + public OpenIdAjaxRelyingParty(IOpenIdApplicationStore applicationStore) + : base(applicationStore) { + Reporting.RecordFeatureUse(this); + } + + /// <summary> + /// Generates AJAX-ready authentication requests that can satisfy the requirements of some OpenID Identifier. + /// </summary> + /// <param name="userSuppliedIdentifier">The Identifier supplied by the user. This may be a URL, an XRI or i-name.</param> + /// <param name="realm">The shorest URL that describes this relying party web site's address. + /// For example, if your login page is found at https://www.example.com/login.aspx, + /// your realm would typically be https://www.example.com/.</param> + /// <param name="returnToUrl">The URL of the login page, or the page prepared to receive authentication + /// responses from the OpenID Provider.</param> + /// <returns> + /// A sequence of authentication requests, any of which constitutes a valid identity assertion on the Claimed Identifier. + /// Never null, but may be empty. + /// </returns> + /// <remarks> + /// <para>Any individual generated request can satisfy the authentication. + /// The generated requests are sorted in preferred order. + /// Each request is generated as it is enumerated to. Associations are created only as + /// <see cref="IAuthenticationRequest.RedirectingResponse"/> is called.</para> + /// <para>No exception is thrown if no OpenID endpoints were discovered. + /// An empty enumerable is returned instead.</para> + /// </remarks> + public override IEnumerable<IAuthenticationRequest> CreateRequests(Identifier userSuppliedIdentifier, Realm realm, Uri returnToUrl) { + var requests = base.CreateRequests(userSuppliedIdentifier, realm, returnToUrl); + + // Alter the requests so that have AJAX characteristics. + // Some OPs may be listed multiple times (one with HTTPS and the other with HTTP, for example). + // Since we're gathering OPs to try one after the other, just take the first choice of each OP + // and don't try it multiple times. + requests = requests.Distinct(DuplicateRequestedHostsComparer.Instance); + + // Configure each generated request. + int reqIndex = 0; + foreach (var req in requests) { + // Inform ourselves in return_to that we're in a popup. + req.SetUntrustedCallbackArgument(OpenIdRelyingPartyControlBase.UIPopupCallbackKey, "1"); + + if (req.DiscoveryResult.IsExtensionSupported<UIRequest>()) { + // Inform the OP that we'll be using a popup window consistent with the UI extension. + req.AddExtension(new UIRequest()); + + // Provide a hint for the client javascript about whether the OP supports the UI extension. + // This is so the window can be made the correct size for the extension. + // If the OP doesn't advertise support for the extension, the javascript will use + // a bigger popup window. + req.SetUntrustedCallbackArgument(OpenIdRelyingPartyControlBase.PopupUISupportedJSHint, "1"); + } + + req.SetUntrustedCallbackArgument("index", (reqIndex++).ToString(CultureInfo.InvariantCulture)); + + // If the ReturnToUrl was explicitly set, we'll need to reset our first parameter + if (OpenIdElement.Configuration.RelyingParty.PreserveUserSuppliedIdentifier) { + if (string.IsNullOrEmpty(HttpUtility.ParseQueryString(req.ReturnToUrl.Query)[AuthenticationRequest.UserSuppliedIdentifierParameterName])) { + req.SetUntrustedCallbackArgument(AuthenticationRequest.UserSuppliedIdentifierParameterName, userSuppliedIdentifier.OriginalString); + } + } + + // Our javascript needs to let the user know which endpoint responded. So we force it here. + // This gives us the info even for 1.0 OPs and 2.0 setup_required responses. + req.SetUntrustedCallbackArgument(OpenIdRelyingPartyAjaxControlBase.OPEndpointParameterName, req.Provider.Uri.AbsoluteUri); + req.SetUntrustedCallbackArgument(OpenIdRelyingPartyAjaxControlBase.ClaimedIdParameterName, (string)req.ClaimedIdentifier ?? string.Empty); + + // Inform ourselves in return_to that we're in a popup or iframe. + req.SetUntrustedCallbackArgument(OpenIdRelyingPartyAjaxControlBase.UIPopupCallbackKey, "1"); + + // We append a # at the end so that if the OP happens to support it, + // the OpenID response "query string" is appended after the hash rather than before, resulting in the + // browser being super-speedy in closing the popup window since it doesn't try to pull a newer version + // of the static resource down from the server merely because of a changed URL. + // http://www.nabble.com/Re:-Defining-how-OpenID-should-behave-with-fragments-in-the-return_to-url-p22694227.html + ////TODO: + + yield return req; + } + } + + /// <summary> + /// Serializes discovery results on some <i>single</i> identifier on behalf of Javascript running on the browser. + /// </summary> + /// <param name="requests">The discovery results from just <i>one</i> identifier to serialize as a JSON response.</param> + /// <returns> + /// The JSON result to return to the user agent. + /// </returns> + /// <remarks> + /// We prepare a JSON object with this interface: + /// <code> + /// class jsonResponse { + /// string claimedIdentifier; + /// Array requests; // never null + /// string error; // null if no error + /// } + /// </code> + /// Each element in the requests array looks like this: + /// <code> + /// class jsonAuthRequest { + /// string endpoint; // URL to the OP endpoint + /// string immediate; // URL to initiate an immediate request + /// string setup; // URL to initiate a setup request. + /// } + /// </code> + /// </remarks> + public OutgoingWebResponse AsAjaxDiscoveryResult(IEnumerable<IAuthenticationRequest> requests) { + Requires.NotNull(requests, "requests"); + + var serializer = new JavaScriptSerializer(); + return new OutgoingWebResponse { + Body = serializer.Serialize(this.AsJsonDiscoveryResult(requests)), + }; + } + + /// <summary> + /// Serializes discovery on a set of identifiers for preloading into an HTML page that carries + /// an AJAX-aware OpenID control. + /// </summary> + /// <param name="requests">The discovery results to serialize as a JSON response.</param> + /// <returns> + /// The JSON result to return to the user agent. + /// </returns> + public string AsAjaxPreloadedDiscoveryResult(IEnumerable<IAuthenticationRequest> requests) { + Requires.NotNull(requests, "requests"); + + var serializer = new JavaScriptSerializer(); + string json = serializer.Serialize(this.AsJsonPreloadedDiscoveryResult(requests)); + + string script = "window.dnoa_internal.loadPreloadedDiscoveryResults(" + json + ");"; + return script; + } + + /// <summary> + /// Converts a sequence of authentication requests to a JSON object for seeding an AJAX-enabled login page. + /// </summary> + /// <param name="requests">The discovery results from just <i>one</i> identifier to serialize as a JSON response.</param> + /// <returns>A JSON object, not yet serialized.</returns> + internal object AsJsonDiscoveryResult(IEnumerable<IAuthenticationRequest> requests) { + Requires.NotNull(requests, "requests"); + + requests = requests.CacheGeneratedResults(); + + if (requests.Any()) { + return new { + claimedIdentifier = (string)requests.First().ClaimedIdentifier, + requests = requests.Select(req => new { + endpoint = req.Provider.Uri.AbsoluteUri, + immediate = this.GetRedirectUrl(req, true), + setup = this.GetRedirectUrl(req, false), + }).ToArray() + }; + } else { + return new { + requests = new object[0], + error = OpenIdStrings.OpenIdEndpointNotFound, + }; + } + } + + /// <summary> + /// Serializes discovery on a set of identifiers for preloading into an HTML page that carries + /// an AJAX-aware OpenID control. + /// </summary> + /// <param name="requests">The discovery results to serialize as a JSON response.</param> + /// <returns> + /// A JSON object, not yet serialized to a string. + /// </returns> + private object AsJsonPreloadedDiscoveryResult(IEnumerable<IAuthenticationRequest> requests) { + Requires.NotNull(requests, "requests"); + + // We prepare a JSON object with this interface: + // Array discoveryWrappers; + // Where each element in the above array has this interface: + // class discoveryWrapper { + // string userSuppliedIdentifier; + // jsonResponse discoveryResult; // contains result of call to SerializeDiscoveryAsJson(Identifier) + // } + var json = (from request in requests + group request by request.DiscoveryResult.UserSuppliedIdentifier into requestsByIdentifier + select new { + userSuppliedIdentifier = (string)requestsByIdentifier.Key, + discoveryResult = this.AsJsonDiscoveryResult(requestsByIdentifier), + }).ToArray(); + + return json; + } + + /// <summary> + /// Gets the full URL that carries an OpenID message, even if it exceeds the normal maximum size of a URL, + /// for purposes of sending to an AJAX component running in the browser. + /// </summary> + /// <param name="request">The authentication request.</param> + /// <param name="immediate"><c>true</c>to create a checkid_immediate request; + /// <c>false</c> to create a checkid_setup request.</param> + /// <returns>The absolute URL that carries the entire OpenID message.</returns> + private Uri GetRedirectUrl(IAuthenticationRequest request, bool immediate) { + Requires.NotNull(request, "request"); + + request.Mode = immediate ? AuthenticationRequestMode.Immediate : AuthenticationRequestMode.Setup; + return request.RedirectingResponse.GetDirectUriRequest(this.Channel); + } + } +} diff --git a/src/DotNetOpenAuth.OpenId.RelyingParty.UI/OpenId/RelyingParty/OpenIdAjaxTextBox.cs b/src/DotNetOpenAuth.OpenId.RelyingParty.UI/OpenId/RelyingParty/OpenIdAjaxTextBox.cs new file mode 100644 index 0000000..5440be8 --- /dev/null +++ b/src/DotNetOpenAuth.OpenId.RelyingParty.UI/OpenId/RelyingParty/OpenIdAjaxTextBox.cs @@ -0,0 +1,877 @@ +//----------------------------------------------------------------------- +// <copyright file="OpenIdAjaxTextBox.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +[assembly: System.Web.UI.WebResource(DotNetOpenAuth.OpenId.RelyingParty.OpenIdAjaxTextBox.EmbeddedScriptResourceName, "text/javascript")] +[assembly: System.Web.UI.WebResource(DotNetOpenAuth.OpenId.RelyingParty.OpenIdAjaxTextBox.EmbeddedStylesheetResourceName, "text/css")] +[assembly: System.Web.UI.WebResource(DotNetOpenAuth.OpenId.RelyingParty.OpenIdAjaxTextBox.EmbeddedSpinnerResourceName, "image/gif")] +[assembly: System.Web.UI.WebResource(DotNetOpenAuth.OpenId.RelyingParty.OpenIdAjaxTextBox.EmbeddedLoginSuccessResourceName, "image/png")] +[assembly: System.Web.UI.WebResource(DotNetOpenAuth.OpenId.RelyingParty.OpenIdAjaxTextBox.EmbeddedLoginFailureResourceName, "image/png")] + +#pragma warning disable 0809 // marking inherited, unsupported properties as obsolete to discourage their use + +namespace DotNetOpenAuth.OpenId.RelyingParty { + using System; + using System.Collections.Generic; + using System.Collections.Specialized; + using System.ComponentModel; + using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; + using System.Diagnostics.Contracts; + using System.Drawing.Design; + using System.Globalization; + using System.Text; + using System.Web.UI; + using System.Web.UI.HtmlControls; + using DotNetOpenAuth.Messaging; + + /// <summary> + /// An ASP.NET control that provides a minimal text box that is OpenID-aware and uses AJAX for + /// a premium login experience. + /// </summary> + [DefaultProperty("Text"), ValidationProperty("Text")] + [ToolboxData("<{0}:OpenIdAjaxTextBox runat=\"server\" />")] + public class OpenIdAjaxTextBox : OpenIdRelyingPartyAjaxControlBase, IEditableTextControl, ITextControl, IPostBackDataHandler { + /// <summary> + /// The name of the manifest stream containing the OpenIdAjaxTextBox.js file. + /// </summary> + internal const string EmbeddedScriptResourceName = Util.DefaultNamespace + ".OpenId.RelyingParty.OpenIdAjaxTextBox.js"; + + /// <summary> + /// The name of the manifest stream containing the OpenIdAjaxTextBox.css file. + /// </summary> + internal const string EmbeddedStylesheetResourceName = Util.DefaultNamespace + ".OpenId.RelyingParty.OpenIdAjaxTextBox.css"; + + /// <summary> + /// The name of the manifest stream containing the spinner.gif file. + /// </summary> + internal const string EmbeddedSpinnerResourceName = Util.DefaultNamespace + ".OpenId.RelyingParty.spinner.gif"; + + /// <summary> + /// The name of the manifest stream containing the login_success.png file. + /// </summary> + internal const string EmbeddedLoginSuccessResourceName = Util.DefaultNamespace + ".OpenId.RelyingParty.login_success.png"; + + /// <summary> + /// The name of the manifest stream containing the login_failure.png file. + /// </summary> + internal const string EmbeddedLoginFailureResourceName = Util.DefaultNamespace + ".OpenId.RelyingParty.login_failure.png"; + + /// <summary> + /// The default value for the <see cref="DownloadYahooUILibrary"/> property. + /// </summary> + internal const bool DownloadYahooUILibraryDefault = true; + + /// <summary> + /// The default value for the <see cref="Throttle"/> property. + /// </summary> + internal const int ThrottleDefault = 3; + + #region Property viewstate keys + + /// <summary> + /// The viewstate key to use for storing the value of the <see cref="AutoPostBack"/> property. + /// </summary> + private const string AutoPostBackViewStateKey = "AutoPostback"; + + /// <summary> + /// The viewstate key to use for the <see cref="Text"/> property. + /// </summary> + private const string TextViewStateKey = "Text"; + + /// <summary> + /// The viewstate key to use for storing the value of the <see cref="Columns"/> property. + /// </summary> + private const string ColumnsViewStateKey = "Columns"; + + /// <summary> + /// The viewstate key to use for the <see cref="CssClass"/> property. + /// </summary> + private const string CssClassViewStateKey = "CssClass"; + + /// <summary> + /// The viewstate key to use for storing the value of the <see cref="OnClientAssertionReceived"/> property. + /// </summary> + private const string OnClientAssertionReceivedViewStateKey = "OnClientAssertionReceived"; + + /// <summary> + /// The viewstate key to use for storing the value of the <see cref="AuthenticatedAsToolTip"/> property. + /// </summary> + private const string AuthenticatedAsToolTipViewStateKey = "AuthenticatedAsToolTip"; + + /// <summary> + /// The viewstate key to use for storing the value of the <see cref="AuthenticationSucceededToolTip"/> property. + /// </summary> + private const string AuthenticationSucceededToolTipViewStateKey = "AuthenticationSucceededToolTip"; + + /// <summary> + /// The viewstate key to use for storing the value of the <see cref="LogOnInProgressMessage"/> property. + /// </summary> + private const string LogOnInProgressMessageViewStateKey = "BusyToolTip"; + + /// <summary> + /// The viewstate key to use for storing the value of the <see cref="AuthenticationFailedToolTip"/> property. + /// </summary> + private const string AuthenticationFailedToolTipViewStateKey = "AuthenticationFailedToolTip"; + + /// <summary> + /// The viewstate key to use for storing the value of the <see cref="IdentifierRequiredMessage"/> property. + /// </summary> + private const string IdentifierRequiredMessageViewStateKey = "BusyToolTip"; + + /// <summary> + /// The viewstate key to use for storing the value of the <see cref="BusyToolTip"/> property. + /// </summary> + private const string BusyToolTipViewStateKey = "BusyToolTip"; + + /// <summary> + /// The viewstate key to use for storing the value of the <see cref="LogOnText"/> property. + /// </summary> + private const string LogOnTextViewStateKey = "LoginText"; + + /// <summary> + /// The viewstate key to use for storing the value of the <see cref="Throttle"/> property. + /// </summary> + private const string ThrottleViewStateKey = "Throttle"; + + /// <summary> + /// The viewstate key to use for storing the value of the <see cref="LogOnToolTip"/> property. + /// </summary> + private const string LogOnToolTipViewStateKey = "LoginToolTip"; + + /// <summary> + /// The viewstate key to use for storing the value of the <see cref="LogOnPostBackToolTip"/> property. + /// </summary> + private const string LogOnPostBackToolTipViewStateKey = "LoginPostBackToolTip"; + + /// <summary> + /// The viewstate key to use for storing the value of the <see cref="Name"/> property. + /// </summary> + private const string NameViewStateKey = "Name"; + + /// <summary> + /// The viewstate key to use for storing the value of the <see cref="Timeout"/> property. + /// </summary> + private const string TimeoutViewStateKey = "Timeout"; + + /// <summary> + /// The viewstate key to use for storing the value of the <see cref="TabIndex"/> property. + /// </summary> + private const string TabIndexViewStateKey = "TabIndex"; + + /// <summary> + /// The viewstate key to use for the <see cref="Enabled"/> property. + /// </summary> + private const string EnabledViewStateKey = "Enabled"; + + /// <summary> + /// The viewstate key to use for storing the value of the <see cref="RetryToolTip"/> property. + /// </summary> + private const string RetryToolTipViewStateKey = "RetryToolTip"; + + /// <summary> + /// The viewstate key to use for storing the value of the <see cref="RetryText"/> property. + /// </summary> + private const string RetryTextViewStateKey = "RetryText"; + + /// <summary> + /// The viewstate key to use for storing the value of the <see cref="DownloadYahooUILibrary"/> property. + /// </summary> + private const string DownloadYahooUILibraryViewStateKey = "DownloadYahooUILibrary"; + + /// <summary> + /// The viewstate key to use for storing the value of the <see cref="ShowLogOnPostBackButton"/> property. + /// </summary> + private const string ShowLogOnPostBackButtonViewStateKey = "ShowLogOnPostBackButton"; + + #endregion + + #region Property defaults + + /// <summary> + /// The default value for the <see cref="AutoPostBack"/> property. + /// </summary> + private const bool AutoPostBackDefault = false; + + /// <summary> + /// The default value for the <see cref="Columns"/> property. + /// </summary> + private const int ColumnsDefault = 40; + + /// <summary> + /// The default value for the <see cref="CssClass"/> property. + /// </summary> + private const string CssClassDefault = "openid"; + + /// <summary> + /// The default value for the <see cref="LogOnInProgressMessage"/> property. + /// </summary> + private const string LogOnInProgressMessageDefault = "Please wait for login to complete."; + + /// <summary> + /// The default value for the <see cref="AuthenticationSucceededToolTip"/> property. + /// </summary> + private const string AuthenticationSucceededToolTipDefault = "Authenticated by {0}."; + + /// <summary> + /// The default value for the <see cref="AuthenticatedAsToolTip"/> property. + /// </summary> + private const string AuthenticatedAsToolTipDefault = "Authenticated as {0}."; + + /// <summary> + /// The default value for the <see cref="AuthenticationFailedToolTip"/> property. + /// </summary> + private const string AuthenticationFailedToolTipDefault = "Authentication failed."; + + /// <summary> + /// The default value for the <see cref="LogOnText"/> property. + /// </summary> + private const string LogOnTextDefault = "LOG IN"; + + /// <summary> + /// The default value for the <see cref="BusyToolTip"/> property. + /// </summary> + private const string BusyToolTipDefault = "Discovering/authenticating"; + + /// <summary> + /// The default value for the <see cref="IdentifierRequiredMessage"/> property. + /// </summary> + private const string IdentifierRequiredMessageDefault = "Please correct errors in OpenID identifier and allow login to complete before submitting."; + + /// <summary> + /// The default value for the <see cref="Name"/> property. + /// </summary> + private const string NameDefault = "openid_identifier"; + + /// <summary> + /// Default value for <see cref="TabIndex"/> property. + /// </summary> + private const short TabIndexDefault = 0; + + /// <summary> + /// The default value for the <see cref="RetryToolTip"/> property. + /// </summary> + private const string RetryToolTipDefault = "Retry a failed identifier discovery."; + + /// <summary> + /// The default value for the <see cref="LogOnToolTip"/> property. + /// </summary> + private const string LogOnToolTipDefault = "Click here to log in using a pop-up window."; + + /// <summary> + /// The default value for the <see cref="LogOnPostBackToolTip"/> property. + /// </summary> + private const string LogOnPostBackToolTipDefault = "Click here to log in immediately."; + + /// <summary> + /// The default value for the <see cref="RetryText"/> property. + /// </summary> + private const string RetryTextDefault = "RETRY"; + + /// <summary> + /// The default value for the <see cref="ShowLogOnPostBackButton"/> property. + /// </summary> + private const bool ShowLogOnPostBackButtonDefault = false; + + #endregion + + /// <summary> + /// The path where the YUI control library should be downloaded from for HTTP pages. + /// </summary> + private const string YuiLoaderHttp = "http://ajax.googleapis.com/ajax/libs/yui/2.8.0r4/build/yuiloader/yuiloader-min.js"; + + /// <summary> + /// The path where the YUI control library should be downloaded from for HTTPS pages. + /// </summary> + private const string YuiLoaderHttps = "https://ajax.googleapis.com/ajax/libs/yui/2.8.0r4/build/yuiloader/yuiloader-min.js"; + + /// <summary> + /// Initializes a new instance of the <see cref="OpenIdAjaxTextBox"/> class. + /// </summary> + public OpenIdAjaxTextBox() { + this.HookFormSubmit = true; + } + + #region Events + + /// <summary> + /// Fired when the content of the text changes between posts to the server. + /// </summary> + [Description("Occurs when the content of the text changes between posts to the server."), Category(BehaviorCategory)] + public event EventHandler TextChanged; + + /// <summary> + /// Gets or sets the client-side script that executes when an authentication + /// assertion is received (but before it is verified). + /// </summary> + /// <remarks> + /// <para>In the context of the executing javascript set in this property, the + /// local variable <i>sender</i> is set to the openid_identifier input box + /// that is executing this code. + /// This variable has a getClaimedIdentifier() method that may be used to + /// identify the user who is being authenticated.</para> + /// <para>It is <b>very</b> important to note that when this code executes, + /// the authentication has not been verified and may have been spoofed. + /// No security-sensitive operations should take place in this javascript code. + /// The authentication is verified on the server by the time the + /// <see cref="OpenIdRelyingPartyControlBase.LoggedIn"/> server-side event fires.</para> + /// </remarks> + [Description("Gets or sets the client-side script that executes when an authentication assertion is received (but before it is verified).")] + [Bindable(true), DefaultValue(""), Category(BehaviorCategory)] + public string OnClientAssertionReceived { + get { return this.ViewState[OnClientAssertionReceivedViewStateKey] as string; } + set { this.ViewState[OnClientAssertionReceivedViewStateKey] = value; } + } + + #endregion + + #region Properties + + /// <summary> + /// Gets or sets the value in the text field, completely unprocessed or normalized. + /// </summary> + [Bindable(true), DefaultValue(""), Category(AppearanceCategory)] + [Description("The content of the text box.")] + public string Text { + get { + return this.Identifier != null ? this.Identifier.OriginalString : (this.ViewState[TextViewStateKey] as string ?? string.Empty); + } + + set { + // Try to store it as a validated identifier, + // but failing that at least store the text. + Identifier id; + if (Identifier.TryParse(value, out id)) { + this.Identifier = id; + } else { + // Be sure to set the viewstate AFTER setting the Identifier, + // since setting the Identifier clears the viewstate in OnIdentifierChanged. + this.Identifier = null; + this.ViewState[TextViewStateKey] = value; + } + } + } + + /// <summary> + /// Gets or sets a value indicating whether a postback is made to fire the + /// <see cref="OpenIdRelyingPartyControlBase.LoggedIn"/> event as soon as authentication has completed + /// successfully. + /// </summary> + /// <value> + /// <c>true</c> if a postback should be made automatically upon authentication; + /// otherwise, <c>false</c> to delay the <see cref="OpenIdRelyingPartyControlBase.LoggedIn"/> + /// event from firing at the server until a postback is made by some other control. + /// </value> + [Bindable(true), Category(BehaviorCategory), DefaultValue(AutoPostBackDefault)] + [Description("Whether the LoggedIn event fires on the server as soon as authentication completes successfully.")] + public bool AutoPostBack { + get { return (bool)(this.ViewState[AutoPostBackViewStateKey] ?? AutoPostBackDefault); } + set { this.ViewState[AutoPostBackViewStateKey] = value; } + } + + /// <summary> + /// Gets or sets the width of the text box in characters. + /// </summary> + [Bindable(true), Category(AppearanceCategory), DefaultValue(ColumnsDefault)] + [Description("The width of the text box in characters.")] + public int Columns { + get { + return (int)(this.ViewState[ColumnsViewStateKey] ?? ColumnsDefault); + } + + set { + Requires.InRange(value >= 0, "value"); + this.ViewState[ColumnsViewStateKey] = value; + } + } + + /// <summary> + /// Gets or sets the CSS class assigned to the text box. + /// </summary> + [Bindable(true), DefaultValue(CssClassDefault), Category(AppearanceCategory)] + [Description("The CSS class assigned to the text box.")] + public string CssClass { + get { return (string)this.ViewState[CssClassViewStateKey]; } + set { this.ViewState[CssClassViewStateKey] = value; } + } + + /// <summary> + /// Gets or sets the tab index of the text box control. Use 0 to omit an explicit tabindex. + /// </summary> + [Bindable(true), Category(BehaviorCategory), DefaultValue(TabIndexDefault)] + [Description("The tab index of the text box control. Use 0 to omit an explicit tabindex.")] + public virtual short TabIndex { + get { return (short)(this.ViewState[TabIndexViewStateKey] ?? TabIndexDefault); } + set { this.ViewState[TabIndexViewStateKey] = value; } + } + + /// <summary> + /// Gets or sets a value indicating whether this <see cref="OpenIdTextBox"/> is enabled + /// in the browser for editing and will respond to incoming OpenID messages. + /// </summary> + /// <value><c>true</c> if enabled; otherwise, <c>false</c>.</value> + [Bindable(true), DefaultValue(true), Category(BehaviorCategory)] + [Description("Whether the control is editable in the browser and will respond to OpenID messages.")] + public bool Enabled { + get { return (bool)(this.ViewState[EnabledViewStateKey] ?? true); } + set { this.ViewState[EnabledViewStateKey] = value; } + } + + /// <summary> + /// Gets or sets the HTML name to assign to the text field. + /// </summary> + [Bindable(true), DefaultValue(NameDefault), Category("Misc")] + [Description("The HTML name to assign to the text field.")] + public string Name { + get { + return (string)(this.ViewState[NameViewStateKey] ?? NameDefault); + } + + set { + Requires.NotNullOrEmpty(value, "value"); + this.ViewState[NameViewStateKey] = value ?? string.Empty; + } + } + + /// <summary> + /// Gets or sets the time duration for the AJAX control to wait for an OP to respond before reporting failure to the user. + /// </summary> + [Browsable(true), DefaultValue(typeof(TimeSpan), "00:00:08"), Category(BehaviorCategory)] + [Description("The time duration for the AJAX control to wait for an OP to respond before reporting failure to the user.")] + public TimeSpan Timeout { + get { + return (TimeSpan)(this.ViewState[TimeoutViewStateKey] ?? TimeoutDefault); + } + + set { + Requires.InRange(value.TotalMilliseconds > 0, "value"); + this.ViewState[TimeoutViewStateKey] = value; + } + } + + /// <summary> + /// Gets or sets the maximum number of OpenID Providers to simultaneously try to authenticate with. + /// </summary> + [Browsable(true), DefaultValue(ThrottleDefault), Category(BehaviorCategory)] + [Description("The maximum number of OpenID Providers to simultaneously try to authenticate with.")] + public int Throttle { + get { + return (int)(this.ViewState[ThrottleViewStateKey] ?? ThrottleDefault); + } + + set { + Requires.InRange(value > 0, "value"); + this.ViewState[ThrottleViewStateKey] = value; + } + } + + /// <summary> + /// Gets or sets the text that appears on the LOG IN button in cases where immediate (invisible) authentication fails. + /// </summary> + [Bindable(true), DefaultValue(LogOnTextDefault), Localizable(true), Category(AppearanceCategory)] + [Description("The text that appears on the LOG IN button in cases where immediate (invisible) authentication fails.")] + public string LogOnText { + get { + return (string)(this.ViewState[LogOnTextViewStateKey] ?? LogOnTextDefault); + } + + set { + Requires.NotNullOrEmpty(value, "value"); + this.ViewState[LogOnTextViewStateKey] = value ?? string.Empty; + } + } + + /// <summary> + /// Gets or sets the rool tip text that appears on the LOG IN button in cases where immediate (invisible) authentication fails. + /// </summary> + [Bindable(true), DefaultValue(LogOnToolTipDefault), Localizable(true), Category(AppearanceCategory)] + [Description("The tool tip text that appears on the LOG IN button in cases where immediate (invisible) authentication fails.")] + public string LogOnToolTip { + get { return (string)(this.ViewState[LogOnToolTipViewStateKey] ?? LogOnToolTipDefault); } + set { this.ViewState[LogOnToolTipViewStateKey] = value ?? string.Empty; } + } + + /// <summary> + /// Gets or sets the rool tip text that appears on the LOG IN button when clicking the button will result in an immediate postback. + /// </summary> + [Bindable(true), DefaultValue(LogOnPostBackToolTipDefault), Localizable(true), Category(AppearanceCategory)] + [Description("The tool tip text that appears on the LOG IN button when clicking the button will result in an immediate postback.")] + public string LogOnPostBackToolTip { + get { return (string)(this.ViewState[LogOnPostBackToolTipViewStateKey] ?? LogOnPostBackToolTipDefault); } + set { this.ViewState[LogOnPostBackToolTipViewStateKey] = value ?? string.Empty; } + } + + /// <summary> + /// Gets or sets the text that appears on the RETRY button in cases where authentication times out. + /// </summary> + [Bindable(true), DefaultValue(RetryTextDefault), Localizable(true), Category(AppearanceCategory)] + [Description("The text that appears on the RETRY button in cases where authentication times out.")] + public string RetryText { + get { + return (string)(this.ViewState[RetryTextViewStateKey] ?? RetryTextDefault); + } + + set { + Requires.NotNullOrEmpty(value, "value"); + this.ViewState[RetryTextViewStateKey] = value ?? string.Empty; + } + } + + /// <summary> + /// Gets or sets the tool tip text that appears on the RETRY button in cases where authentication times out. + /// </summary> + [Bindable(true), DefaultValue(RetryToolTipDefault), Localizable(true), Category(AppearanceCategory)] + [Description("The tool tip text that appears on the RETRY button in cases where authentication times out.")] + public string RetryToolTip { + get { return (string)(this.ViewState[RetryToolTipViewStateKey] ?? RetryToolTipDefault); } + set { this.ViewState[RetryToolTipViewStateKey] = value ?? string.Empty; } + } + + /// <summary> + /// Gets or sets the tool tip text that appears when authentication succeeds. + /// </summary> + [Bindable(true), DefaultValue(AuthenticationSucceededToolTipDefault), Localizable(true), Category(AppearanceCategory)] + [Description("The tool tip text that appears when authentication succeeds.")] + public string AuthenticationSucceededToolTip { + get { return (string)(this.ViewState[AuthenticationSucceededToolTipViewStateKey] ?? AuthenticationSucceededToolTipDefault); } + set { this.ViewState[AuthenticationSucceededToolTipViewStateKey] = value ?? string.Empty; } + } + + /// <summary> + /// Gets or sets the tool tip text that appears on the green checkmark when authentication succeeds. + /// </summary> + [Bindable(true), DefaultValue(AuthenticatedAsToolTipDefault), Localizable(true), Category(AppearanceCategory)] + [Description("The tool tip text that appears on the green checkmark when authentication succeeds.")] + public string AuthenticatedAsToolTip { + get { return (string)(this.ViewState[AuthenticatedAsToolTipViewStateKey] ?? AuthenticatedAsToolTipDefault); } + set { this.ViewState[AuthenticatedAsToolTipViewStateKey] = value ?? string.Empty; } + } + + /// <summary> + /// Gets or sets the tool tip text that appears when authentication fails. + /// </summary> + [Bindable(true), DefaultValue(AuthenticationFailedToolTipDefault), Localizable(true), Category(AppearanceCategory)] + [Description("The tool tip text that appears when authentication fails.")] + public string AuthenticationFailedToolTip { + get { return (string)(this.ViewState[AuthenticationFailedToolTipViewStateKey] ?? AuthenticationFailedToolTipDefault); } + set { this.ViewState[AuthenticationFailedToolTipViewStateKey] = value ?? string.Empty; } + } + + /// <summary> + /// Gets or sets the tool tip text that appears over the text box when it is discovering and authenticating. + /// </summary> + [Bindable(true), DefaultValue(BusyToolTipDefault), Localizable(true), Category(AppearanceCategory)] + [Description("The tool tip text that appears over the text box when it is discovering and authenticating.")] + public string BusyToolTip { + get { return (string)(this.ViewState[BusyToolTipViewStateKey] ?? BusyToolTipDefault); } + set { this.ViewState[BusyToolTipViewStateKey] = value ?? string.Empty; } + } + + /// <summary> + /// Gets or sets the message that is displayed if a postback is about to occur before the identifier has been supplied. + /// </summary> + [Bindable(true), DefaultValue(IdentifierRequiredMessageDefault), Localizable(true), Category(AppearanceCategory)] + [Description("The message that is displayed if a postback is about to occur before the identifier has been supplied.")] + public string IdentifierRequiredMessage { + get { return (string)(this.ViewState[IdentifierRequiredMessageViewStateKey] ?? IdentifierRequiredMessageDefault); } + set { this.ViewState[IdentifierRequiredMessageViewStateKey] = value ?? string.Empty; } + } + + /// <summary> + /// Gets or sets the message that is displayed if a postback is attempted while login is in process. + /// </summary> + [Bindable(true), DefaultValue(LogOnInProgressMessageDefault), Localizable(true), Category(AppearanceCategory)] + [Description("The message that is displayed if a postback is attempted while login is in process.")] + public string LogOnInProgressMessage { + get { return (string)(this.ViewState[LogOnInProgressMessageViewStateKey] ?? LogOnInProgressMessageDefault); } + set { this.ViewState[LogOnInProgressMessageViewStateKey] = value ?? string.Empty; } + } + + /// <summary> + /// Gets or sets a value indicating whether the Yahoo! User Interface Library (YUI) + /// will be downloaded in order to provide a login split button. + /// </summary> + /// <value> + /// <c>true</c> to use a split button; otherwise, <c>false</c> to use a standard HTML button + /// or a split button by downloading the YUI library yourself on the hosting web page. + /// </value> + /// <remarks> + /// The split button brings in about 180KB of YUI javascript dependencies. + /// </remarks> + [Bindable(true), DefaultValue(DownloadYahooUILibraryDefault), Category(BehaviorCategory)] + [Description("Whether a split button will be used for the \"log in\" when the user provides an identifier that delegates to more than one Provider.")] + public bool DownloadYahooUILibrary { + get { return (bool)(this.ViewState[DownloadYahooUILibraryViewStateKey] ?? DownloadYahooUILibraryDefault); } + set { this.ViewState[DownloadYahooUILibraryViewStateKey] = value; } + } + + /// <summary> + /// Gets or sets a value indicating whether the "Log in" button will be shown + /// to initiate a postback containing the positive assertion. + /// </summary> + [Bindable(true), DefaultValue(ShowLogOnPostBackButtonDefault), Category(AppearanceCategory)] + [Description("Whether the log in button will be shown to initiate a postback containing the positive assertion.")] + public bool ShowLogOnPostBackButton { + get { return (bool)(this.ViewState[ShowLogOnPostBackButtonViewStateKey] ?? ShowLogOnPostBackButtonDefault); } + set { this.ViewState[ShowLogOnPostBackButtonViewStateKey] = value; } + } + + #endregion + + /// <summary> + /// Gets or sets a value indicating whether the ajax text box should hook the form's submit event for special behavior. + /// </summary> + internal bool HookFormSubmit { get; set; } + + /// <summary> + /// Gets the name of the open id auth data form key. + /// </summary> + /// <value> + /// A concatenation of <see cref="Name"/> and <c>"_openidAuthData"</c>. + /// </value> + protected override string OpenIdAuthDataFormKey { + get { return this.Name + "_openidAuthData"; } + } + + /// <summary> + /// Gets the default value for the <see cref="Timeout"/> property. + /// </summary> + /// <value>8 seconds; or eternity if the debugger is attached.</value> + private static TimeSpan TimeoutDefault { + get { + if (Debugger.IsAttached) { + Logger.OpenId.Warn("Debugger is attached. Inflating default OpenIdAjaxTextbox.Timeout value to infinity."); + return TimeSpan.MaxValue; + } else { + return TimeSpan.FromSeconds(8); + } + } + } + + #region IPostBackDataHandler Members + + /// <summary> + /// When implemented by a class, processes postback data for an ASP.NET server control. + /// </summary> + /// <param name="postDataKey">The key identifier for the control.</param> + /// <param name="postCollection">The collection of all incoming name values.</param> + /// <returns> + /// true if the server control's state changes as a result of the postback; otherwise, false. + /// </returns> + bool IPostBackDataHandler.LoadPostData(string postDataKey, NameValueCollection postCollection) { + return this.LoadPostData(postDataKey, postCollection); + } + + /// <summary> + /// When implemented by a class, signals the server control to notify the ASP.NET application that the state of the control has changed. + /// </summary> + void IPostBackDataHandler.RaisePostDataChangedEvent() { + this.RaisePostDataChangedEvent(); + } + + #endregion + + /// <summary> + /// Raises the <see cref="E:Load"/> event. + /// </summary> + /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param> + protected override void OnLoad(EventArgs e) { + base.OnLoad(e); + + this.Page.RegisterRequiresPostBack(this); + } + + /// <summary> + /// Called when the <see cref="Identifier"/> property is changed. + /// </summary> + protected override void OnIdentifierChanged() { + this.ViewState.Remove(TextViewStateKey); + base.OnIdentifierChanged(); + } + + /// <summary> + /// Prepares to render the control. + /// </summary> + /// <param name="e">An <see cref="T:System.EventArgs"/> object that contains the event data.</param> + protected override void OnPreRender(EventArgs e) { + base.OnPreRender(e); + + if (!this.Visible) { + return; + } + + if (this.DownloadYahooUILibrary) { + // Although we'll add the <script> tag to download the YAHOO component, + // a download failure may have occurred, so protect ourselves from a + // script error using an if (YAHOO) block. But apparently at least in IE + // that's not even enough, so we use a try/catch. + string yuiLoadScript = @"try { if (YAHOO) { + var loader = new YAHOO.util.YUILoader({ + require: ['button', 'menu'], + loadOptional: false, + combine: true + }); + + loader.insert(); +} } catch (e) { }"; + this.Page.ClientScript.RegisterClientScriptInclude("yuiloader", this.Page.Request.Url.IsTransportSecure() ? YuiLoaderHttps : YuiLoaderHttp); + this.Page.ClientScript.RegisterClientScriptBlock(this.GetType(), "requiredYuiComponents", yuiLoadScript, true); + } + + var css = new HtmlLink(); + try { + css.Href = this.Page.ClientScript.GetWebResourceUrl(this.GetType(), EmbeddedStylesheetResourceName); + css.Attributes["rel"] = "stylesheet"; + css.Attributes["type"] = "text/css"; + ErrorUtilities.VerifyHost(this.Page.Header != null, OpenIdStrings.HeadTagMustIncludeRunatServer); + this.Page.Header.Controls.AddAt(0, css); // insert at top so host page can override + } catch { + css.Dispose(); + throw; + } + + this.PrepareClientJavascript(); + + // If an Identifier is preset on this control, preload discovery on that identifier, + // but only if we're not already persisting an authentication result since that would + // be redundant. + if (this.Identifier != null && this.AuthenticationResponse == null) { + this.PreloadDiscovery(this.Identifier); + } + } + + /// <summary> + /// Renders the control. + /// </summary> + /// <param name="writer">The <see cref="T:System.Web.UI.HtmlTextWriter"/> object that receives the control content.</param> + protected override void Render(HtmlTextWriter writer) { + base.Render(writer); + + // We surround the textbox with a span so that the .js file can inject a + // login button within the text box with easy placement. + string css = this.CssClass ?? string.Empty; + css += " OpenIdAjaxTextBox"; + writer.AddAttribute(HtmlTextWriterAttribute.Class, css); + + writer.AddStyleAttribute(HtmlTextWriterStyle.Display, "inline-block"); + writer.AddStyleAttribute(HtmlTextWriterStyle.Position, "relative"); + writer.AddStyleAttribute(HtmlTextWriterStyle.FontSize, "16px"); + writer.RenderBeginTag(HtmlTextWriterTag.Span); + + writer.AddAttribute(HtmlTextWriterAttribute.Name, this.Name); + writer.AddAttribute(HtmlTextWriterAttribute.Id, this.ClientID); + writer.AddAttribute(HtmlTextWriterAttribute.Size, this.Columns.ToString(CultureInfo.InvariantCulture)); + if (!string.IsNullOrEmpty(this.Text)) { + writer.AddAttribute(HtmlTextWriterAttribute.Value, this.Text, true); + } + + if (this.TabIndex > 0) { + writer.AddAttribute(HtmlTextWriterAttribute.Tabindex, this.TabIndex.ToString(CultureInfo.InvariantCulture)); + } + if (!this.Enabled) { + writer.AddAttribute(HtmlTextWriterAttribute.Disabled, "true"); + } + if (!string.IsNullOrEmpty(this.CssClass)) { + writer.AddAttribute(HtmlTextWriterAttribute.Class, this.CssClass); + } + writer.AddStyleAttribute(HtmlTextWriterStyle.PaddingLeft, "18px"); + writer.AddStyleAttribute(HtmlTextWriterStyle.BorderStyle, "solid"); + writer.AddStyleAttribute(HtmlTextWriterStyle.BorderWidth, "1px"); + writer.AddStyleAttribute(HtmlTextWriterStyle.BorderColor, "lightgray"); + writer.RenderBeginTag(HtmlTextWriterTag.Input); + writer.RenderEndTag(); // </input> + writer.RenderEndTag(); // </span> + } + + /// <summary> + /// When implemented by a class, processes postback data for an ASP.NET server control. + /// </summary> + /// <param name="postDataKey">The key identifier for the control.</param> + /// <param name="postCollection">The collection of all incoming name values.</param> + /// <returns> + /// true if the server control's state changes as a result of the postback; otherwise, false. + /// </returns> + protected virtual bool LoadPostData(string postDataKey, NameValueCollection postCollection) { + // If the control was temporarily hidden, it won't be in the Form data, + // and we'll just implicitly keep the last Text setting. + if (postCollection[this.Name] != null) { + Identifier identifier = postCollection[this.Name].Length == 0 ? null : postCollection[this.Name]; + if (identifier != this.Identifier) { + this.Identifier = identifier; + return true; + } + } + + return false; + } + + /// <summary> + /// When implemented by a class, signals the server control to notify the ASP.NET application that the state of the control has changed. + /// </summary> + [SuppressMessage("Microsoft.Design", "CA1030:UseEventsWhereAppropriate", Justification = "Predefined signature.")] + protected virtual void RaisePostDataChangedEvent() { + this.OnTextChanged(); + } + + /// <summary> + /// Called on a postback when the Text property has changed. + /// </summary> + protected virtual void OnTextChanged() { + EventHandler textChanged = this.TextChanged; + if (textChanged != null) { + textChanged(this, EventArgs.Empty); + } + } + + /// <summary> + /// Assembles the javascript to send to the client and registers it with ASP.NET for transmission. + /// </summary> + private void PrepareClientJavascript() { + // Import the .js file where most of the code is. + this.Page.ClientScript.RegisterClientScriptResource(typeof(OpenIdAjaxTextBox), EmbeddedScriptResourceName); + + // Call into the .js file with initialization information. + StringBuilder startupScript = new StringBuilder(); + startupScript.AppendFormat("var box = document.getElementsByName('{0}')[0];{1}", this.Name, Environment.NewLine); + startupScript.AppendFormat( + CultureInfo.InvariantCulture, + "initAjaxOpenId(box, {0}, {1}, {2}, {3}, {4}, {5}, {6}, {7}, {8}, {9}, {10}, {11}, {12}, {13}, {14}, {15}, {16}, {17}, {18}, {19}, function() {{{20};}});{21}", + MessagingUtilities.GetSafeJavascriptValue(this.Page.ClientScript.GetWebResourceUrl(this.GetType(), OpenIdTextBox.EmbeddedLogoResourceName)), + MessagingUtilities.GetSafeJavascriptValue(this.Page.ClientScript.GetWebResourceUrl(this.GetType(), EmbeddedSpinnerResourceName)), + MessagingUtilities.GetSafeJavascriptValue(this.Page.ClientScript.GetWebResourceUrl(this.GetType(), EmbeddedLoginSuccessResourceName)), + MessagingUtilities.GetSafeJavascriptValue(this.Page.ClientScript.GetWebResourceUrl(this.GetType(), EmbeddedLoginFailureResourceName)), + this.Throttle, + this.Timeout.TotalMilliseconds, + string.IsNullOrEmpty(this.OnClientAssertionReceived) ? "null" : "'" + this.OnClientAssertionReceived.Replace(@"\", @"\\").Replace("'", @"\'") + "'", + MessagingUtilities.GetSafeJavascriptValue(this.LogOnText), + MessagingUtilities.GetSafeJavascriptValue(this.LogOnToolTip), + this.ShowLogOnPostBackButton ? "true" : "false", + MessagingUtilities.GetSafeJavascriptValue(this.LogOnPostBackToolTip), + MessagingUtilities.GetSafeJavascriptValue(this.RetryText), + MessagingUtilities.GetSafeJavascriptValue(this.RetryToolTip), + MessagingUtilities.GetSafeJavascriptValue(this.BusyToolTip), + MessagingUtilities.GetSafeJavascriptValue(this.IdentifierRequiredMessage), + MessagingUtilities.GetSafeJavascriptValue(this.LogOnInProgressMessage), + MessagingUtilities.GetSafeJavascriptValue(this.AuthenticationSucceededToolTip), + MessagingUtilities.GetSafeJavascriptValue(this.AuthenticatedAsToolTip), + MessagingUtilities.GetSafeJavascriptValue(this.AuthenticationFailedToolTip), + this.AutoPostBack ? "true" : "false", + Page.ClientScript.GetPostBackEventReference(this, null), + Environment.NewLine); + + ScriptManager.RegisterStartupScript(this, this.GetType(), "ajaxstartup", startupScript.ToString(), true); + if (this.HookFormSubmit) { + string htmlFormat = @" +var openidbox = document.getElementsByName('{0}')[0]; +if (!openidbox.dnoi_internal.onSubmit()) {{ return false; }} +"; + Page.ClientScript.RegisterOnSubmitStatement( + this.GetType(), + "loginvalidation", + string.Format(CultureInfo.InvariantCulture, htmlFormat, this.Name)); + } + } + } +} diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdAjaxTextBox.css b/src/DotNetOpenAuth.OpenId.RelyingParty.UI/OpenId/RelyingParty/OpenIdAjaxTextBox.css index bed2e79..bed2e79 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdAjaxTextBox.css +++ b/src/DotNetOpenAuth.OpenId.RelyingParty.UI/OpenId/RelyingParty/OpenIdAjaxTextBox.css diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdAjaxTextBox.js b/src/DotNetOpenAuth.OpenId.RelyingParty.UI/OpenId/RelyingParty/OpenIdAjaxTextBox.js index 9907b4e..9907b4e 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdAjaxTextBox.js +++ b/src/DotNetOpenAuth.OpenId.RelyingParty.UI/OpenId/RelyingParty/OpenIdAjaxTextBox.js diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdButton.cs b/src/DotNetOpenAuth.OpenId.RelyingParty.UI/OpenId/RelyingParty/OpenIdButton.cs index 6243917..6243917 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdButton.cs +++ b/src/DotNetOpenAuth.OpenId.RelyingParty.UI/OpenId/RelyingParty/OpenIdButton.cs diff --git a/src/DotNetOpenAuth.OpenId.RelyingParty.UI/OpenId/RelyingParty/OpenIdEventArgs.cs b/src/DotNetOpenAuth.OpenId.RelyingParty.UI/OpenId/RelyingParty/OpenIdEventArgs.cs new file mode 100644 index 0000000..cf38b61 --- /dev/null +++ b/src/DotNetOpenAuth.OpenId.RelyingParty.UI/OpenId/RelyingParty/OpenIdEventArgs.cs @@ -0,0 +1,74 @@ +//----------------------------------------------------------------------- +// <copyright file="OpenIdEventArgs.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.RelyingParty { + using System; + using System.Collections.Generic; + using System.Diagnostics.Contracts; + using System.Linq; + using System.Text; + using DotNetOpenAuth.Messaging; + + /// <summary> + /// The event details passed to event handlers. + /// </summary> + public class OpenIdEventArgs : EventArgs { + /// <summary> + /// Initializes a new instance of the <see cref="OpenIdEventArgs"/> class + /// with minimal information of an incomplete or failed authentication attempt. + /// </summary> + /// <param name="request">The outgoing authentication request.</param> + internal OpenIdEventArgs(IAuthenticationRequest request) { + Requires.NotNull(request, "request"); + + this.Request = request; + this.ClaimedIdentifier = request.ClaimedIdentifier; + this.IsDirectedIdentity = request.IsDirectedIdentity; + } + + /// <summary> + /// Initializes a new instance of the <see cref="OpenIdEventArgs"/> class + /// with information on a completed authentication attempt + /// (whether that attempt was successful or not). + /// </summary> + /// <param name="response">The incoming authentication response.</param> + internal OpenIdEventArgs(IAuthenticationResponse response) { + Requires.NotNull(response, "response"); + + this.Response = response; + this.ClaimedIdentifier = response.ClaimedIdentifier; + } + + /// <summary> + /// Gets or sets a value indicating whether to cancel + /// the OpenID authentication and/or login process. + /// </summary> + public bool Cancel { get; set; } + + /// <summary> + /// Gets the Identifier the user is claiming to own. Or null if the user + /// is using Directed Identity. + /// </summary> + public Identifier ClaimedIdentifier { get; private set; } + + /// <summary> + /// Gets a value indicating whether the user has selected to let his Provider determine + /// the ClaimedIdentifier to use as part of successful authentication. + /// </summary> + public bool IsDirectedIdentity { get; private set; } + + /// <summary> + /// Gets the details of the OpenID authentication request, + /// and allows for adding extensions. + /// </summary> + public IAuthenticationRequest Request { get; private set; } + + /// <summary> + /// Gets the details of the OpenID authentication response. + /// </summary> + public IAuthenticationResponse Response { get; private set; } + } +} diff --git a/src/DotNetOpenAuth.OpenId.RelyingParty.UI/OpenId/RelyingParty/OpenIdLogin.cs b/src/DotNetOpenAuth.OpenId.RelyingParty.UI/OpenId/RelyingParty/OpenIdLogin.cs new file mode 100644 index 0000000..e65b3f4 --- /dev/null +++ b/src/DotNetOpenAuth.OpenId.RelyingParty.UI/OpenId/RelyingParty/OpenIdLogin.cs @@ -0,0 +1,1001 @@ +//----------------------------------------------------------------------- +// <copyright file="OpenIdLogin.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.RelyingParty { + using System; + using System.ComponentModel; + using System.Diagnostics.CodeAnalysis; + using System.Diagnostics.Contracts; + using System.Globalization; + using System.Linq; + using System.Web.UI; + using System.Web.UI.HtmlControls; + using System.Web.UI.WebControls; + using DotNetOpenAuth.Messaging; + + /// <summary> + /// An ASP.NET control providing a complete OpenID login experience. + /// </summary> + [SuppressMessage("Microsoft.Naming", "CA1726:UsePreferredTerms", MessageId = "Login", Justification = "Legacy code")] + [DefaultProperty("Text"), ValidationProperty("Text")] + [ToolboxData("<{0}:OpenIdLogin runat=\"server\" />")] + public class OpenIdLogin : OpenIdTextBox { + #region Property defaults + + /// <summary> + /// The default value for the <see cref="RegisterToolTip"/> property. + /// </summary> + private const string RegisterToolTipDefault = "Sign up free for an OpenID with MyOpenID now."; + + /// <summary> + /// The default value for the <see cref="RememberMeText"/> property. + /// </summary> + private const string RememberMeTextDefault = "Remember me"; + + /// <summary> + /// The default value for the <see cref="ButtonText"/> property. + /// </summary> + private const string ButtonTextDefault = "Login »"; + + /// <summary> + /// The default value for the <see cref="CanceledText"/> property. + /// </summary> + private const string CanceledTextDefault = "Login canceled."; + + /// <summary> + /// The default value for the <see cref="FailedMessageText"/> property. + /// </summary> + private const string FailedMessageTextDefault = "Login failed: {0}"; + + /// <summary> + /// The default value for the <see cref="ExamplePrefix"/> property. + /// </summary> + private const string ExamplePrefixDefault = "Example:"; + + /// <summary> + /// The default value for the <see cref="ExampleUrl"/> property. + /// </summary> + private const string ExampleUrlDefault = "http://your.name.myopenid.com"; + + /// <summary> + /// The default value for the <see cref="LabelText"/> property. + /// </summary> + private const string LabelTextDefault = "OpenID Login:"; + + /// <summary> + /// The default value for the <see cref="RequiredText"/> property. + /// </summary> + private const string RequiredTextDefault = "Provide an OpenID first."; + + /// <summary> + /// The default value for the <see cref="UriFormatText"/> property. + /// </summary> + private const string UriFormatTextDefault = "Invalid OpenID URL."; + + /// <summary> + /// The default value for the <see cref="RegisterText"/> property. + /// </summary> + private const string RegisterTextDefault = "register"; + + /// <summary> + /// The default value for the <see cref="RegisterUrl"/> property. + /// </summary> + private const string RegisterUrlDefault = "https://www.myopenid.com/signup"; + + /// <summary> + /// The default value for the <see cref="ButtonToolTip"/> property. + /// </summary> + private const string ButtonToolTipDefault = "Account login"; + + /// <summary> + /// The default value for the <see cref="ValidationGroup"/> property. + /// </summary> + private const string ValidationGroupDefault = "OpenIdLogin"; + + /// <summary> + /// The default value for the <see cref="RegisterVisible"/> property. + /// </summary> + private const bool RegisterVisibleDefault = true; + + /// <summary> + /// The default value for the <see cref="RememberMeVisible"/> property. + /// </summary> + private const bool RememberMeVisibleDefault = false; + + /// <summary> + /// The default value for the <see cref="RememberMe"/> property. + /// </summary> + private const bool RememberMeDefault = false; + + /// <summary> + /// The default value for the <see cref="UriValidatorEnabled"/> property. + /// </summary> + private const bool UriValidatorEnabledDefault = true; + + #endregion + + #region Property viewstate keys + + /// <summary> + /// The viewstate key to use for the <see cref="FailedMessageText"/> property. + /// </summary> + private const string FailedMessageTextViewStateKey = "FailedMessageText"; + + /// <summary> + /// The viewstate key to use for the <see cref="CanceledText"/> property. + /// </summary> + private const string CanceledTextViewStateKey = "CanceledText"; + + /// <summary> + /// The viewstate key to use for the <see cref="IdSelectorIdentifier"/> property. + /// </summary> + private const string IdSelectorIdentifierViewStateKey = "IdSelectorIdentifier"; + + #endregion + + /// <summary> + /// The HTML to append to the <see cref="RequiredText"/> property value when rendering. + /// </summary> + private const string RequiredTextSuffix = "<br/>"; + + /// <summary> + /// The number to add to <see cref="TabIndex"/> to get the tab index of the textbox control. + /// </summary> + private const short TextBoxTabIndexOffset = 0; + + /// <summary> + /// The number to add to <see cref="TabIndex"/> to get the tab index of the login button control. + /// </summary> + private const short LoginButtonTabIndexOffset = 1; + + /// <summary> + /// The number to add to <see cref="TabIndex"/> to get the tab index of the remember me checkbox control. + /// </summary> + private const short RememberMeTabIndexOffset = 2; + + /// <summary> + /// The number to add to <see cref="TabIndex"/> to get the tab index of the register link control. + /// </summary> + private const short RegisterTabIndexOffset = 3; + + #region Controls + + /// <summary> + /// The control into which all other controls are added. + /// </summary> + private Panel panel; + + /// <summary> + /// The Login button. + /// </summary> + private Button loginButton; + + /// <summary> + /// The label that presents the text box. + /// </summary> + private HtmlGenericControl label; + + /// <summary> + /// The validator that flags an empty text box. + /// </summary> + private RequiredFieldValidator requiredValidator; + + /// <summary> + /// The validator that flags invalid formats of OpenID identifiers. + /// </summary> + private CustomValidator identifierFormatValidator; + + /// <summary> + /// The label that precedes an example OpenID identifier. + /// </summary> + private Label examplePrefixLabel; + + /// <summary> + /// The label that contains the example OpenID identifier. + /// </summary> + private Label exampleUrlLabel; + + /// <summary> + /// A link to allow the user to create an account with a popular OpenID Provider. + /// </summary> + private HyperLink registerLink; + + /// <summary> + /// The Remember Me checkbox. + /// </summary> + private CheckBox rememberMeCheckBox; + + /// <summary> + /// The javascript snippet that activates the ID Selector javascript control. + /// </summary> + private Literal idselectorJavascript; + + /// <summary> + /// The label that will display login failure messages. + /// </summary> + private Label errorLabel; + + #endregion + + /// <summary> + /// Initializes a new instance of the <see cref="OpenIdLogin"/> class. + /// </summary> + public OpenIdLogin() { + } + + #region Events + + /// <summary> + /// Fired when the Remember Me checkbox is changed by the user. + /// </summary> + [Description("Fires when the Remember Me checkbox is changed by the user.")] + public event EventHandler RememberMeChanged; + + #endregion + + #region Properties + + /// <summary> + /// Gets a <see cref="T:System.Web.UI.ControlCollection"/> object that represents the child controls for a specified server control in the UI hierarchy. + /// </summary> + /// <returns> + /// The collection of child controls for the specified server control. + /// </returns> + public override ControlCollection Controls { + get { + this.EnsureChildControls(); + return base.Controls; + } + } + + /// <summary> + /// Gets or sets the caption that appears before the text box. + /// </summary> + [Bindable(true)] + [Category("Appearance")] + [DefaultValue(LabelTextDefault)] + [Localizable(true)] + [Description("The caption that appears before the text box.")] + public string LabelText { + get { + EnsureChildControls(); + return this.label.InnerText; + } + + set { + EnsureChildControls(); + this.label.InnerText = value; + } + } + + /// <summary> + /// Gets or sets the text that introduces the example OpenID url. + /// </summary> + [Bindable(true)] + [Category("Appearance")] + [DefaultValue(ExamplePrefixDefault)] + [Localizable(true)] + [Description("The text that introduces the example OpenID url.")] + public string ExamplePrefix { + get { + EnsureChildControls(); + return this.examplePrefixLabel.Text; + } + + set { + EnsureChildControls(); + this.examplePrefixLabel.Text = value; + } + } + + /// <summary> + /// Gets or sets the example OpenID Identifier to display to the user. + /// </summary> + [SuppressMessage("Microsoft.Design", "CA1056:UriPropertiesShouldNotBeStrings", Justification = "Property grid only supports primitive types.")] + [Bindable(true)] + [Category("Appearance")] + [DefaultValue(ExampleUrlDefault)] + [Localizable(true)] + [Description("The example OpenID Identifier to display to the user.")] + public string ExampleUrl { + get { + EnsureChildControls(); + return this.exampleUrlLabel.Text; + } + + set { + EnsureChildControls(); + this.exampleUrlLabel.Text = value; + } + } + + /// <summary> + /// Gets or sets the text to display if the user attempts to login + /// without providing an Identifier. + /// </summary> + [SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "br", Justification = "HTML"), Bindable(true)] + [Category("Appearance")] + [DefaultValue(RequiredTextDefault)] + [Localizable(true)] + [Description("The text to display if the user attempts to login without providing an Identifier.")] + public string RequiredText { + get { + EnsureChildControls(); + return this.requiredValidator.Text.Substring(0, this.requiredValidator.Text.Length - RequiredTextSuffix.Length); + } + + set { + EnsureChildControls(); + this.requiredValidator.ErrorMessage = this.requiredValidator.Text = value + RequiredTextSuffix; + } + } + + /// <summary> + /// Gets or sets the text to display if the user provides an invalid form for an Identifier. + /// </summary> + [SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "br", Justification = "HTML"), SuppressMessage("Microsoft.Design", "CA1056:UriPropertiesShouldNotBeStrings", Justification = "Property grid only supports primitive types.")] + [Bindable(true)] + [Category("Appearance")] + [DefaultValue(UriFormatTextDefault)] + [Localizable(true)] + [Description("The text to display if the user provides an invalid form for an Identifier.")] + public string UriFormatText { + get { + EnsureChildControls(); + return this.identifierFormatValidator.Text.Substring(0, this.identifierFormatValidator.Text.Length - RequiredTextSuffix.Length); + } + + set { + EnsureChildControls(); + this.identifierFormatValidator.ErrorMessage = this.identifierFormatValidator.Text = value + RequiredTextSuffix; + } + } + + /// <summary> + /// Gets or sets a value indicating whether to perform Identifier + /// format validation prior to an authentication attempt. + /// </summary> + [Bindable(true)] + [Category("Behavior")] + [DefaultValue(UriValidatorEnabledDefault)] + [Description("Whether to perform Identifier format validation prior to an authentication attempt.")] + public bool UriValidatorEnabled { + get { + EnsureChildControls(); + return this.identifierFormatValidator.Enabled; + } + + set { + EnsureChildControls(); + this.identifierFormatValidator.Enabled = value; + } + } + + /// <summary> + /// Gets or sets the text of the link users can click on to obtain an OpenID. + /// </summary> + [Bindable(true)] + [Category("Appearance")] + [DefaultValue(RegisterTextDefault)] + [Localizable(true)] + [Description("The text of the link users can click on to obtain an OpenID.")] + public string RegisterText { + get { + EnsureChildControls(); + return this.registerLink.Text; + } + + set { + EnsureChildControls(); + this.registerLink.Text = value; + } + } + + /// <summary> + /// Gets or sets the URL to link users to who click the link to obtain a new OpenID. + /// </summary> + [SuppressMessage("Microsoft.Design", "CA1056:UriPropertiesShouldNotBeStrings", Justification = "Property grid only supports primitive types.")] + [Bindable(true)] + [Category("Appearance")] + [DefaultValue(RegisterUrlDefault)] + [Localizable(true)] + [Description("The URL to link users to who click the link to obtain a new OpenID.")] + public string RegisterUrl { + get { + EnsureChildControls(); + return this.registerLink.NavigateUrl; + } + + set { + EnsureChildControls(); + this.registerLink.NavigateUrl = value; + } + } + + /// <summary> + /// Gets or sets the text of the tooltip to display when the user hovers + /// over the link to obtain a new OpenID. + /// </summary> + [Bindable(true)] + [Category("Appearance")] + [DefaultValue(RegisterToolTipDefault)] + [Localizable(true)] + [Description("The text of the tooltip to display when the user hovers over the link to obtain a new OpenID.")] + public string RegisterToolTip { + get { + EnsureChildControls(); + return this.registerLink.ToolTip; + } + + set { + EnsureChildControls(); + this.registerLink.ToolTip = value; + } + } + + /// <summary> + /// Gets or sets a value indicating whether to display a link to + /// allow users to easily obtain a new OpenID. + /// </summary> + [Bindable(true)] + [Category("Appearance")] + [DefaultValue(RegisterVisibleDefault)] + [Description("Whether to display a link to allow users to easily obtain a new OpenID.")] + public bool RegisterVisible { + get { + EnsureChildControls(); + return this.registerLink.Visible; + } + + set { + EnsureChildControls(); + this.registerLink.Visible = value; + } + } + + /// <summary> + /// Gets or sets the text that appears on the button that initiates login. + /// </summary> + [Bindable(true)] + [Category("Appearance")] + [DefaultValue(ButtonTextDefault)] + [Localizable(true)] + [Description("The text that appears on the button that initiates login.")] + public string ButtonText { + get { + EnsureChildControls(); + return this.loginButton.Text; + } + + set { + EnsureChildControls(); + this.loginButton.Text = value; + } + } + + /// <summary> + /// Gets or sets the text of the "Remember Me" checkbox. + /// </summary> + [Bindable(true)] + [Category("Appearance")] + [DefaultValue(RememberMeTextDefault)] + [Localizable(true)] + [Description("The text of the \"Remember Me\" checkbox.")] + public string RememberMeText { + get { + EnsureChildControls(); + return this.rememberMeCheckBox.Text; + } + + set { + EnsureChildControls(); + this.rememberMeCheckBox.Text = value; + } + } + + /// <summary> + /// Gets or sets the message display in the event of a failed + /// authentication. {0} may be used to insert the actual error. + /// </summary> + [Bindable(true)] + [Category("Appearance")] + [DefaultValue(FailedMessageTextDefault)] + [Localizable(true)] + [Description("The message display in the event of a failed authentication. {0} may be used to insert the actual error.")] + public string FailedMessageText { + get { return (string)ViewState[FailedMessageTextViewStateKey] ?? FailedMessageTextDefault; } + set { ViewState[FailedMessageTextViewStateKey] = value; } + } + + /// <summary> + /// Gets or sets the text to display in the event of an authentication canceled at the Provider. + /// </summary> + [Bindable(true)] + [Category("Appearance")] + [DefaultValue(CanceledTextDefault)] + [Localizable(true)] + [Description("The text to display in the event of an authentication canceled at the Provider.")] + public string CanceledText { + get { return (string)ViewState[CanceledTextViewStateKey] ?? CanceledTextDefault; } + set { ViewState[CanceledTextViewStateKey] = value; } + } + + /// <summary> + /// Gets or sets a value indicating whether the "Remember Me" checkbox should be displayed. + /// </summary> + [Bindable(true)] + [Category("Appearance")] + [DefaultValue(RememberMeVisibleDefault)] + [Description("Whether the \"Remember Me\" checkbox should be displayed.")] + public bool RememberMeVisible { + get { + EnsureChildControls(); + return this.rememberMeCheckBox.Visible; + } + + set { + EnsureChildControls(); + this.rememberMeCheckBox.Visible = value; + } + } + + /// <summary> + /// Gets or sets a value indicating whether a successful authentication should result in a persistent + /// cookie being saved to the browser. + /// </summary> + [Bindable(true)] + [Category("Appearance")] + [DefaultValue(RememberMeDefault)] + [Description("Whether a successful authentication should result in a persistent cookie being saved to the browser.")] + public bool RememberMe { + get { return this.UsePersistentCookie != LogOnPersistence.Session; } + set { this.UsePersistentCookie = value ? LogOnPersistence.PersistentAuthentication : LogOnPersistence.Session; } + } + + /// <summary> + /// Gets or sets the starting tab index to distribute across the controls. + /// </summary> + [SuppressMessage("Microsoft.Usage", "CA2233:OperationsShouldNotOverflow", MessageId = "value+1", Justification = "Overflow would provide desired UI behavior.")] + [SuppressMessage("Microsoft.Usage", "CA2233:OperationsShouldNotOverflow", MessageId = "value+2", Justification = "Overflow would provide desired UI behavior.")] + [SuppressMessage("Microsoft.Usage", "CA2233:OperationsShouldNotOverflow", MessageId = "value+3", Justification = "Overflow would provide desired UI behavior.")] + public override short TabIndex { + get { + return base.TabIndex; + } + + set { + unchecked { + EnsureChildControls(); + base.TabIndex = (short)(value + TextBoxTabIndexOffset); + this.loginButton.TabIndex = (short)(value + LoginButtonTabIndexOffset); + this.rememberMeCheckBox.TabIndex = (short)(value + RememberMeTabIndexOffset); + this.registerLink.TabIndex = (short)(value + RegisterTabIndexOffset); + } + } + } + + /// <summary> + /// Gets or sets the tooltip to display when the user hovers over the login button. + /// </summary> + [Bindable(true)] + [Category("Appearance")] + [DefaultValue(ButtonToolTipDefault)] + [Localizable(true)] + [Description("The tooltip to display when the user hovers over the login button.")] + public string ButtonToolTip { + get { + EnsureChildControls(); + return this.loginButton.ToolTip; + } + + set { + EnsureChildControls(); + this.loginButton.ToolTip = value; + } + } + + /// <summary> + /// Gets or sets the validation group that the login button and text box validator belong to. + /// </summary> + [Category("Behavior")] + [DefaultValue(ValidationGroupDefault)] + [Description("The validation group that the login button and text box validator belong to.")] + public string ValidationGroup { + get { + EnsureChildControls(); + return this.requiredValidator.ValidationGroup; + } + + set { + EnsureChildControls(); + this.requiredValidator.ValidationGroup = value; + this.loginButton.ValidationGroup = value; + } + } + + /// <summary> + /// Gets or sets the unique hash string that ends your idselector.com account. + /// </summary> + [Category("Behavior")] + [Description("The unique hash string that ends your idselector.com account.")] + public string IdSelectorIdentifier { + get { return (string)ViewState[IdSelectorIdentifierViewStateKey]; } + set { ViewState[IdSelectorIdentifierViewStateKey] = value; } + } + + #endregion + + #region Properties to hide + + /// <summary> + /// Gets or sets a value indicating whether a FormsAuthentication + /// cookie should persist across user sessions. + /// </summary> + [Browsable(false), Bindable(false)] + public override LogOnPersistence UsePersistentCookie { + get { + return base.UsePersistentCookie; + } + + set { + base.UsePersistentCookie = value; + + if (this.rememberMeCheckBox != null) { + // use conditional here to prevent infinite recursion + // with CheckedChanged event. + bool rememberMe = value != LogOnPersistence.Session; + if (this.rememberMeCheckBox.Checked != rememberMe) { + this.rememberMeCheckBox.Checked = rememberMe; + } + } + } + } + + #endregion + + /// <summary> + /// Outputs server control content to a provided <see cref="T:System.Web.UI.HtmlTextWriter"/> object and stores tracing information about the control if tracing is enabled. + /// </summary> + /// <param name="writer">The <see cref="T:System.Web.UI.HTmlTextWriter"/> object that receives the control content.</param> + public override void RenderControl(HtmlTextWriter writer) { + this.RenderChildren(writer); + } + + /// <summary> + /// Creates the child controls. + /// </summary> + protected override void CreateChildControls() { + this.InitializeControls(); + + // Just add the panel we've assembled earlier. + base.Controls.Add(this.panel); + } + + /// <summary> + /// Raises the <see cref="E:System.Web.UI.Control.PreRender"/> event. + /// </summary> + /// <param name="e">An <see cref="T:System.EventArgs"/> object that contains the event data.</param> + protected override void OnPreRender(EventArgs e) { + base.OnPreRender(e); + + this.EnsureChildControls(); + } + + /// <summary> + /// Initializes the child controls. + /// </summary> + [SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", MessageId = "System.Web.UI.WebControls.WebControl.set_ToolTip(System.String)", Justification = "By design"), SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", MessageId = "System.Web.UI.WebControls.Label.set_Text(System.String)", Justification = "By design"), SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", MessageId = "System.Web.UI.WebControls.HyperLink.set_Text(System.String)", Justification = "By design"), SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", MessageId = "System.Web.UI.WebControls.CheckBox.set_Text(System.String)", Justification = "By design"), SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", MessageId = "System.Web.UI.WebControls.Button.set_Text(System.String)", Justification = "By design"), SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", MessageId = "System.Web.UI.WebControls.BaseValidator.set_ErrorMessage(System.String)", Justification = "By design"), SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "br", Justification = "HTML"), SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "OpenID", Justification = "It is correct"), SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "MyOpenID", Justification = "Correct spelling"), SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "myopenid", Justification = "URL")] + protected void InitializeControls() { + this.panel = new Panel(); + + Table table = new Table(); + try { + TableRow row1, row2, row3; + TableCell cell; + table.Rows.Add(row1 = new TableRow()); + table.Rows.Add(row2 = new TableRow()); + table.Rows.Add(row3 = new TableRow()); + + // top row, left cell + cell = new TableCell(); + try { + this.label = new HtmlGenericControl("label"); + this.label.InnerText = LabelTextDefault; + cell.Controls.Add(this.label); + row1.Cells.Add(cell); + } catch { + cell.Dispose(); + throw; + } + + // top row, middle cell + cell = new TableCell(); + try { + cell.Controls.Add(new InPlaceControl(this)); + row1.Cells.Add(cell); + } catch { + cell.Dispose(); + throw; + } + + // top row, right cell + cell = new TableCell(); + try { + this.loginButton = new Button(); + this.loginButton.ID = this.ID + "_loginButton"; + this.loginButton.Text = ButtonTextDefault; + this.loginButton.ToolTip = ButtonToolTipDefault; + this.loginButton.Click += this.LoginButton_Click; + this.loginButton.ValidationGroup = ValidationGroupDefault; +#if !Mono + this.panel.DefaultButton = this.loginButton.ID; +#endif + cell.Controls.Add(this.loginButton); + row1.Cells.Add(cell); + } catch { + cell.Dispose(); + throw; + } + + // middle row, left cell + row2.Cells.Add(new TableCell()); + + // middle row, middle cell + cell = new TableCell(); + try { + cell.Style[HtmlTextWriterStyle.Color] = "gray"; + cell.Style[HtmlTextWriterStyle.FontSize] = "smaller"; + this.requiredValidator = new RequiredFieldValidator(); + this.requiredValidator.ErrorMessage = RequiredTextDefault + RequiredTextSuffix; + this.requiredValidator.Text = RequiredTextDefault + RequiredTextSuffix; + this.requiredValidator.Display = ValidatorDisplay.Dynamic; + this.requiredValidator.ValidationGroup = ValidationGroupDefault; + cell.Controls.Add(this.requiredValidator); + this.identifierFormatValidator = new CustomValidator(); + this.identifierFormatValidator.ErrorMessage = UriFormatTextDefault + RequiredTextSuffix; + this.identifierFormatValidator.Text = UriFormatTextDefault + RequiredTextSuffix; + this.identifierFormatValidator.ServerValidate += this.IdentifierFormatValidator_ServerValidate; + this.identifierFormatValidator.Enabled = UriValidatorEnabledDefault; + this.identifierFormatValidator.Display = ValidatorDisplay.Dynamic; + this.identifierFormatValidator.ValidationGroup = ValidationGroupDefault; + cell.Controls.Add(this.identifierFormatValidator); + this.errorLabel = new Label(); + this.errorLabel.EnableViewState = false; + this.errorLabel.ForeColor = System.Drawing.Color.Red; + this.errorLabel.Style[HtmlTextWriterStyle.Display] = "block"; // puts it on its own line + this.errorLabel.Visible = false; + cell.Controls.Add(this.errorLabel); + this.examplePrefixLabel = new Label(); + this.examplePrefixLabel.Text = ExamplePrefixDefault; + cell.Controls.Add(this.examplePrefixLabel); + cell.Controls.Add(new LiteralControl(" ")); + this.exampleUrlLabel = new Label(); + this.exampleUrlLabel.Font.Bold = true; + this.exampleUrlLabel.Text = ExampleUrlDefault; + cell.Controls.Add(this.exampleUrlLabel); + row2.Cells.Add(cell); + } catch { + cell.Dispose(); + throw; + } + + // middle row, right cell + cell = new TableCell(); + try { + cell.Style[HtmlTextWriterStyle.Color] = "gray"; + cell.Style[HtmlTextWriterStyle.FontSize] = "smaller"; + cell.Style[HtmlTextWriterStyle.TextAlign] = "center"; + this.registerLink = new HyperLink(); + this.registerLink.Text = RegisterTextDefault; + this.registerLink.ToolTip = RegisterToolTipDefault; + this.registerLink.NavigateUrl = RegisterUrlDefault; + this.registerLink.Visible = RegisterVisibleDefault; + cell.Controls.Add(this.registerLink); + row2.Cells.Add(cell); + } catch { + cell.Dispose(); + throw; + } + + // bottom row, left cell + cell = new TableCell(); + row3.Cells.Add(cell); + + // bottom row, middle cell + cell = new TableCell(); + try { + this.rememberMeCheckBox = new CheckBox(); + this.rememberMeCheckBox.Text = RememberMeTextDefault; + this.rememberMeCheckBox.Checked = this.UsePersistentCookie != LogOnPersistence.Session; + this.rememberMeCheckBox.Visible = RememberMeVisibleDefault; + this.rememberMeCheckBox.CheckedChanged += this.RememberMeCheckBox_CheckedChanged; + cell.Controls.Add(this.rememberMeCheckBox); + row3.Cells.Add(cell); + } catch { + cell.Dispose(); + throw; + } + + // bottom row, right cell + cell = new TableCell(); + try { + row3.Cells.Add(cell); + } catch { + cell.Dispose(); + throw; + } + + // this sets all the controls' tab indexes + this.TabIndex = TabIndexDefault; + + this.panel.Controls.Add(table); + } catch { + table.Dispose(); + throw; + } + + this.idselectorJavascript = new Literal(); + this.panel.Controls.Add(this.idselectorJavascript); + } + + /// <summary> + /// Raises the <see cref="E:System.Web.UI.Control.Init"/> event. + /// </summary> + /// <param name="e">An <see cref="T:System.EventArgs"/> object that contains the event data.</param> + protected override void OnInit(EventArgs e) { + this.SetChildControlReferenceIds(); + + base.OnInit(e); + } + + /// <summary> + /// Renders the child controls. + /// </summary> + /// <param name="writer">The <see cref="T:System.Web.UI.HtmlTextWriter"/> object that receives the rendered content.</param> + [SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", MessageId = "System.Web.UI.WebControls.Literal.set_Text(System.String)", Justification = "By design"), SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "idselector", Justification = "HTML"), SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "charset", Justification = "html"), SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "src", Justification = "html"), SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "openidselector", Justification = "html"), SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "idselectorinputid", Justification = "html")] + protected override void RenderChildren(HtmlTextWriter writer) { + if (!this.DesignMode) { + this.label.Attributes["for"] = this.ClientID; + + if (!string.IsNullOrEmpty(this.IdSelectorIdentifier)) { + this.idselectorJavascript.Visible = true; + this.idselectorJavascript.Text = @"<script type='text/javascript'><!-- +idselector_input_id = '" + this.ClientID + @"'; +// --></script> +<script type='text/javascript' id='__openidselector' src='https://www.idselector.com/selector/" + this.IdSelectorIdentifier + @"' charset='utf-8'></script>"; + } else { + this.idselectorJavascript.Visible = false; + } + } + + base.RenderChildren(writer); + } + + /// <summary> + /// Adds failure handling to display an error message to the user. + /// </summary> + /// <param name="response">The response.</param> + protected override void OnFailed(IAuthenticationResponse response) { + base.OnFailed(response); + + if (!string.IsNullOrEmpty(this.FailedMessageText)) { + this.errorLabel.Text = string.Format(CultureInfo.CurrentCulture, this.FailedMessageText, response.Exception.ToStringDescriptive()); + this.errorLabel.Visible = true; + } + } + + /// <summary> + /// Adds authentication cancellation behavior to display a message to the user. + /// </summary> + /// <param name="response">The response.</param> + protected override void OnCanceled(IAuthenticationResponse response) { + base.OnCanceled(response); + + if (!string.IsNullOrEmpty(this.CanceledText)) { + this.errorLabel.Text = this.CanceledText; + this.errorLabel.Visible = true; + } + } + + /// <summary> + /// Fires the <see cref="RememberMeChanged"/> event. + /// </summary> + protected virtual void OnRememberMeChanged() { + EventHandler rememberMeChanged = this.RememberMeChanged; + if (rememberMeChanged != null) { + rememberMeChanged(this, new EventArgs()); + } + } + + /// <summary> + /// Handles the ServerValidate event of the identifierFormatValidator control. + /// </summary> + /// <param name="source">The source of the event.</param> + /// <param name="args">The <see cref="System.Web.UI.WebControls.ServerValidateEventArgs"/> instance containing the event data.</param> + private void IdentifierFormatValidator_ServerValidate(object source, ServerValidateEventArgs args) { + args.IsValid = Identifier.IsValid(args.Value); + } + + /// <summary> + /// Handles the CheckedChanged event of the rememberMeCheckBox control. + /// </summary> + /// <param name="sender">The source of the event.</param> + /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param> + private void RememberMeCheckBox_CheckedChanged(object sender, EventArgs e) { + this.RememberMe = this.rememberMeCheckBox.Checked; + this.OnRememberMeChanged(); + } + + /// <summary> + /// Handles the Click event of the loginButton control. + /// </summary> + /// <param name="sender">The source of the event.</param> + /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param> + private void LoginButton_Click(object sender, EventArgs e) { + if (!this.Page.IsValid) { + return; + } + + IAuthenticationRequest request = this.CreateRequests().FirstOrDefault(); + if (request != null) { + this.LogOn(request); + } else { + if (!string.IsNullOrEmpty(this.FailedMessageText)) { + this.errorLabel.Text = string.Format(CultureInfo.CurrentCulture, this.FailedMessageText, OpenIdStrings.OpenIdEndpointNotFound); + this.errorLabel.Visible = true; + } + } + } + + /// <summary> + /// Renders the control inner. + /// </summary> + /// <param name="writer">The writer.</param> + private void RenderControlInner(HtmlTextWriter writer) { + base.RenderControl(writer); + } + + /// <summary> + /// Sets child control properties that depend on this control's ID. + /// </summary> + private void SetChildControlReferenceIds() { + this.EnsureChildControls(); + this.EnsureID(); + ErrorUtilities.VerifyInternal(!string.IsNullOrEmpty(this.ID), "No control ID available yet!"); + this.requiredValidator.ControlToValidate = this.ID; + this.requiredValidator.ID = this.ID + "_requiredValidator"; + this.identifierFormatValidator.ControlToValidate = this.ID; + this.identifierFormatValidator.ID = this.ID + "_identifierFormatValidator"; + } + + /// <summary> + /// A control that acts as a placeholder to indicate where + /// the OpenIdLogin control should render its OpenIdTextBox parent. + /// </summary> + private class InPlaceControl : PlaceHolder { + /// <summary> + /// The owning control to render. + /// </summary> + private OpenIdLogin renderControl; + + /// <summary> + /// Initializes a new instance of the <see cref="InPlaceControl"/> class. + /// </summary> + /// <param name="renderControl">The render control.</param> + internal InPlaceControl(OpenIdLogin renderControl) { + this.renderControl = renderControl; + } + + /// <summary> + /// Sends server control content to a provided <see cref="T:System.Web.UI.HtmlTextWriter"/> object, which writes the content to be rendered on the client. + /// </summary> + /// <param name="writer">The <see cref="T:System.Web.UI.HtmlTextWriter"/> object that receives the server control content.</param> + protected override void Render(HtmlTextWriter writer) { + this.renderControl.RenderControlInner(writer); + } + } + } +} diff --git a/src/DotNetOpenAuth.OpenId.RelyingParty.UI/OpenId/RelyingParty/OpenIdMobileTextBox.cs b/src/DotNetOpenAuth.OpenId.RelyingParty.UI/OpenId/RelyingParty/OpenIdMobileTextBox.cs new file mode 100644 index 0000000..d4b6769 --- /dev/null +++ b/src/DotNetOpenAuth.OpenId.RelyingParty.UI/OpenId/RelyingParty/OpenIdMobileTextBox.cs @@ -0,0 +1,778 @@ +//----------------------------------------------------------------------- +// <copyright file="OpenIdMobileTextBox.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +[assembly: System.Web.UI.WebResource(DotNetOpenAuth.OpenId.RelyingParty.OpenIdMobileTextBox.EmbeddedLogoResourceName, "image/gif")] + +namespace DotNetOpenAuth.OpenId.RelyingParty { + using System; + using System.ComponentModel; + using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; + using System.Diagnostics.Contracts; + using System.Globalization; + using System.Text.RegularExpressions; + using System.Web.Security; + using System.Web.UI; + using System.Web.UI.MobileControls; + using DotNetOpenAuth.Configuration; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OpenId.Extensions.SimpleRegistration; + + /// <summary> + /// An ASP.NET control for mobile devices that provides a minimal text box that is OpenID-aware. + /// </summary> + [DefaultProperty("Text"), ValidationProperty("Text")] + [ToolboxData("<{0}:OpenIdMobileTextBox runat=\"server\" />")] + public class OpenIdMobileTextBox : TextBox { + /// <summary> + /// The name of the manifest stream containing the + /// OpenID logo that is placed inside the text box. + /// </summary> + internal const string EmbeddedLogoResourceName = OpenIdTextBox.EmbeddedLogoResourceName; + + /// <summary> + /// Default value of <see cref="UsePersistentCookie"/>. + /// </summary> + protected const bool UsePersistentCookieDefault = false; + + #region Property category constants + + /// <summary> + /// The "Appearance" category for properties. + /// </summary> + private const string AppearanceCategory = "Appearance"; + + /// <summary> + /// The "Simple Registration" category for properties. + /// </summary> + private const string ProfileCategory = "Simple Registration"; + + /// <summary> + /// The "Behavior" category for properties. + /// </summary> + private const string BehaviorCategory = "Behavior"; + + #endregion + + #region Property viewstate keys + + /// <summary> + /// The viewstate key to use for the <see cref="RequestEmail"/> property. + /// </summary> + private const string RequestEmailViewStateKey = "RequestEmail"; + + /// <summary> + /// The viewstate key to use for the <see cref="RequestNickname"/> property. + /// </summary> + private const string RequestNicknameViewStateKey = "RequestNickname"; + + /// <summary> + /// The viewstate key to use for the <see cref="RequestPostalCode"/> property. + /// </summary> + private const string RequestPostalCodeViewStateKey = "RequestPostalCode"; + + /// <summary> + /// The viewstate key to use for the <see cref="RequestCountry"/> property. + /// </summary> + private const string RequestCountryViewStateKey = "RequestCountry"; + + /// <summary> + /// The viewstate key to use for the <see cref="RequireSsl"/> property. + /// </summary> + private const string RequireSslViewStateKey = "RequireSsl"; + + /// <summary> + /// The viewstate key to use for the <see cref="RequestLanguage"/> property. + /// </summary> + private const string RequestLanguageViewStateKey = "RequestLanguage"; + + /// <summary> + /// The viewstate key to use for the <see cref="RequestTimeZone"/> property. + /// </summary> + private const string RequestTimeZoneViewStateKey = "RequestTimeZone"; + + /// <summary> + /// The viewstate key to use for the <see cref="EnableRequestProfile"/> property. + /// </summary> + private const string EnableRequestProfileViewStateKey = "EnableRequestProfile"; + + /// <summary> + /// The viewstate key to use for the <see cref="PolicyUrl"/> property. + /// </summary> + private const string PolicyUrlViewStateKey = "PolicyUrl"; + + /// <summary> + /// The viewstate key to use for the <see cref="RequestFullName"/> property. + /// </summary> + private const string RequestFullNameViewStateKey = "RequestFullName"; + + /// <summary> + /// The viewstate key to use for the <see cref="UsePersistentCookie"/> property. + /// </summary> + private const string UsePersistentCookieViewStateKey = "UsePersistentCookie"; + + /// <summary> + /// The viewstate key to use for the <see cref="RequestGender"/> property. + /// </summary> + private const string RequestGenderViewStateKey = "RequestGender"; + + /// <summary> + /// The viewstate key to use for the <see cref="ReturnToUrl"/> property. + /// </summary> + private const string ReturnToUrlViewStateKey = "ReturnToUrl"; + + /// <summary> + /// The viewstate key to use for the <see cref="Stateless"/> property. + /// </summary> + private const string StatelessViewStateKey = "Stateless"; + + /// <summary> + /// The viewstate key to use for the <see cref="ImmediateMode"/> property. + /// </summary> + private const string ImmediateModeViewStateKey = "ImmediateMode"; + + /// <summary> + /// The viewstate key to use for the <see cref="RequestBirthDate"/> property. + /// </summary> + private const string RequestBirthDateViewStateKey = "RequestBirthDate"; + + /// <summary> + /// The viewstate key to use for the <see cref="RealmUrl"/> property. + /// </summary> + private const string RealmUrlViewStateKey = "RealmUrl"; + + #endregion + + #region Property defaults + + /// <summary> + /// The default value for the <see cref="EnableRequestProfile"/> property. + /// </summary> + private const bool EnableRequestProfileDefault = true; + + /// <summary> + /// The default value for the <see cref="RequireSsl"/> property. + /// </summary> + private const bool RequireSslDefault = false; + + /// <summary> + /// The default value for the <see cref="ImmediateMode"/> property. + /// </summary> + private const bool ImmediateModeDefault = false; + + /// <summary> + /// The default value for the <see cref="Stateless"/> property. + /// </summary> + private const bool StatelessDefault = false; + + /// <summary> + /// The default value for the <see cref="PolicyUrl"/> property. + /// </summary> + private const string PolicyUrlDefault = ""; + + /// <summary> + /// The default value for the <see cref="ReturnToUrl"/> property. + /// </summary> + private const string ReturnToUrlDefault = ""; + + /// <summary> + /// The default value for the <see cref="RealmUrl"/> property. + /// </summary> + private const string RealmUrlDefault = "~/"; + + /// <summary> + /// The default value for the <see cref="RequestEmail"/> property. + /// </summary> + private const DemandLevel RequestEmailDefault = DemandLevel.NoRequest; + + /// <summary> + /// The default value for the <see cref="RequestPostalCode"/> property. + /// </summary> + private const DemandLevel RequestPostalCodeDefault = DemandLevel.NoRequest; + + /// <summary> + /// The default value for the <see cref="RequestCountry"/> property. + /// </summary> + private const DemandLevel RequestCountryDefault = DemandLevel.NoRequest; + + /// <summary> + /// The default value for the <see cref="RequestLanguage"/> property. + /// </summary> + private const DemandLevel RequestLanguageDefault = DemandLevel.NoRequest; + + /// <summary> + /// The default value for the <see cref="RequestTimeZone"/> property. + /// </summary> + private const DemandLevel RequestTimeZoneDefault = DemandLevel.NoRequest; + + /// <summary> + /// The default value for the <see cref="RequestNickname"/> property. + /// </summary> + private const DemandLevel RequestNicknameDefault = DemandLevel.NoRequest; + + /// <summary> + /// The default value for the <see cref="RequestFullName"/> property. + /// </summary> + private const DemandLevel RequestFullNameDefault = DemandLevel.NoRequest; + + /// <summary> + /// The default value for the <see cref="RequestBirthDate"/> property. + /// </summary> + private const DemandLevel RequestBirthDateDefault = DemandLevel.NoRequest; + + /// <summary> + /// The default value for the <see cref="RequestGender"/> property. + /// </summary> + private const DemandLevel RequestGenderDefault = DemandLevel.NoRequest; + + #endregion + + /// <summary> + /// The callback parameter for use with persisting the <see cref="UsePersistentCookie"/> property. + /// </summary> + private const string UsePersistentCookieCallbackKey = "OpenIdTextBox_UsePersistentCookie"; + + /// <summary> + /// Backing field for the <see cref="RelyingParty"/> property. + /// </summary> + private OpenIdRelyingParty relyingParty; + + /// <summary> + /// Initializes a new instance of the <see cref="OpenIdMobileTextBox"/> class. + /// </summary> + public OpenIdMobileTextBox() { + Reporting.RecordFeatureUse(this); + } + + #region Events + + /// <summary> + /// Fired upon completion of a successful login. + /// </summary> + [Description("Fired upon completion of a successful login.")] + public event EventHandler<OpenIdEventArgs> LoggedIn; + + /// <summary> + /// Fired when a login attempt fails. + /// </summary> + [Description("Fired when a login attempt fails.")] + public event EventHandler<OpenIdEventArgs> Failed; + + /// <summary> + /// Fired when an authentication attempt is canceled at the OpenID Provider. + /// </summary> + [Description("Fired when an authentication attempt is canceled at the OpenID Provider.")] + public event EventHandler<OpenIdEventArgs> Canceled; + + /// <summary> + /// Fired when an Immediate authentication attempt fails, and the Provider suggests using non-Immediate mode. + /// </summary> + [Description("Fired when an Immediate authentication attempt fails, and the Provider suggests using non-Immediate mode.")] + public event EventHandler<OpenIdEventArgs> SetupRequired; + + #endregion + + #region Properties + + /// <summary> + /// Gets or sets the OpenID <see cref="Realm"/> of the relying party web site. + /// </summary> + [SuppressMessage("Microsoft.Usage", "CA1806:DoNotIgnoreMethodResults", MessageId = "DotNetOpenAuth.OpenId.Realm", Justification = "Using Realm.ctor for validation.")] + [SuppressMessage("Microsoft.Usage", "CA1806:DoNotIgnoreMethodResults", MessageId = "System.Uri", Justification = "Using Uri.ctor for validation.")] + [SuppressMessage("Microsoft.Usage", "CA1806:DoNotIgnoreMethodResults", MessageId = "DotNetOpenAuth.OpenId", Justification = "Using ctor for validation.")] + [SuppressMessage("Microsoft.Design", "CA1056:UriPropertiesShouldNotBeStrings", Justification = "Bindable property must be simple type")] + [Bindable(true), DefaultValue(RealmUrlDefault), Category(BehaviorCategory)] + [Description("The OpenID Realm of the relying party web site.")] + public string RealmUrl { + get { + return (string)(ViewState[RealmUrlViewStateKey] ?? RealmUrlDefault); + } + + set { + if (Page != null && !DesignMode) { + // Validate new value by trying to construct a Realm object based on it. + new Realm(OpenIdUtilities.GetResolvedRealm(this.Page, value, this.RelyingParty.Channel.GetRequestFromContext())); // throws an exception on failure. + } else { + // We can't fully test it, but it should start with either ~/ or a protocol. + if (Regex.IsMatch(value, @"^https?://")) { + new Uri(value.Replace("*.", string.Empty)); // make sure it's fully-qualified, but ignore wildcards + } else if (value.StartsWith("~/", StringComparison.Ordinal)) { + // this is valid too + } else { + throw new UriFormatException(); + } + } + ViewState[RealmUrlViewStateKey] = value; + } + } + + /// <summary> + /// Gets or sets the OpenID ReturnTo of the relying party web site. + /// </summary> + [SuppressMessage("Microsoft.Usage", "CA2234:PassSystemUriObjectsInsteadOfStrings", Justification = "Uri(Uri, string) accepts second arguments that Uri(Uri, new Uri(string)) does not that we must support.")] + [SuppressMessage("Microsoft.Usage", "CA1806:DoNotIgnoreMethodResults", MessageId = "System.Uri", Justification = "Using Uri.ctor for validation.")] + [SuppressMessage("Microsoft.Design", "CA1056:UriPropertiesShouldNotBeStrings", Justification = "Bindable property must be simple type")] + [Bindable(true), DefaultValue(ReturnToUrlDefault), Category(BehaviorCategory)] + [Description("The OpenID ReturnTo of the relying party web site.")] + public string ReturnToUrl { + get { + return (string)(ViewState[ReturnToUrlViewStateKey] ?? ReturnToUrlDefault); + } + + set { + if (Page != null && !DesignMode) { + // Validate new value by trying to construct a Uri based on it. + new Uri(this.RelyingParty.Channel.GetRequestFromContext().UrlBeforeRewriting, this.Page.ResolveUrl(value)); // throws an exception on failure. + } else { + // We can't fully test it, but it should start with either ~/ or a protocol. + if (Regex.IsMatch(value, @"^https?://")) { + new Uri(value); // make sure it's fully-qualified, but ignore wildcards + } else if (value.StartsWith("~/", StringComparison.Ordinal)) { + // this is valid too + } else { + throw new UriFormatException(); + } + } + + ViewState[ReturnToUrlViewStateKey] = value; + } + } + + /// <summary> + /// Gets or sets a value indicating whether to use immediate mode in the + /// OpenID protocol. + /// </summary> + /// <value> + /// True if a Provider should reply immediately to the authentication request + /// without interacting with the user. False if the Provider can take time + /// to authenticate the user in order to complete an authentication attempt. + /// </value> + /// <remarks> + /// Setting this to true is sometimes useful in AJAX scenarios. Setting this to + /// true can cause failed authentications when the user truly controls an + /// Identifier, but must complete an authentication step with the Provider before + /// the Provider will approve the login from this relying party. + /// </remarks> + [Bindable(true), DefaultValue(ImmediateModeDefault), Category(BehaviorCategory)] + [Description("Whether the Provider should respond immediately to an authentication attempt without interacting with the user.")] + public bool ImmediateMode { + get { return (bool)(ViewState[ImmediateModeViewStateKey] ?? ImmediateModeDefault); } + set { ViewState[ImmediateModeViewStateKey] = value; } + } + + /// <summary> + /// Gets or sets a value indicating whether stateless mode is used. + /// </summary> + [Bindable(true), DefaultValue(StatelessDefault), Category(BehaviorCategory)] + [Description("Controls whether stateless mode is used.")] + public bool Stateless { + get { return (bool)(ViewState[StatelessViewStateKey] ?? StatelessDefault); } + set { ViewState[StatelessViewStateKey] = value; } + } + + /// <summary> + /// Gets or sets a value indicating whether to send a persistent cookie upon successful + /// login so the user does not have to log in upon returning to this site. + /// </summary> + [Bindable(true), DefaultValue(UsePersistentCookieDefault), Category(BehaviorCategory)] + [Description("Whether to send a persistent cookie upon successful " + + "login so the user does not have to log in upon returning to this site.")] + public virtual bool UsePersistentCookie { + get { return (bool)(this.ViewState[UsePersistentCookieViewStateKey] ?? UsePersistentCookieDefault); } + set { this.ViewState[UsePersistentCookieViewStateKey] = value; } + } + + /// <summary> + /// Gets or sets your level of interest in receiving the user's nickname from the Provider. + /// </summary> + [Bindable(true), DefaultValue(RequestNicknameDefault), Category(ProfileCategory)] + [Description("Your level of interest in receiving the user's nickname from the Provider.")] + public DemandLevel RequestNickname { + get { return (DemandLevel)(ViewState[RequestNicknameViewStateKey] ?? RequestNicknameDefault); } + set { ViewState[RequestNicknameViewStateKey] = value; } + } + + /// <summary> + /// Gets or sets your level of interest in receiving the user's email address from the Provider. + /// </summary> + [Bindable(true), DefaultValue(RequestEmailDefault), Category(ProfileCategory)] + [Description("Your level of interest in receiving the user's email address from the Provider.")] + public DemandLevel RequestEmail { + get { return (DemandLevel)(ViewState[RequestEmailViewStateKey] ?? RequestEmailDefault); } + set { ViewState[RequestEmailViewStateKey] = value; } + } + + /// <summary> + /// Gets or sets your level of interest in receiving the user's full name from the Provider. + /// </summary> + [Bindable(true), DefaultValue(RequestFullNameDefault), Category(ProfileCategory)] + [Description("Your level of interest in receiving the user's full name from the Provider")] + public DemandLevel RequestFullName { + get { return (DemandLevel)(ViewState[RequestFullNameViewStateKey] ?? RequestFullNameDefault); } + set { ViewState[RequestFullNameViewStateKey] = value; } + } + + /// <summary> + /// Gets or sets your level of interest in receiving the user's birthdate from the Provider. + /// </summary> + [Bindable(true), DefaultValue(RequestBirthDateDefault), Category(ProfileCategory)] + [Description("Your level of interest in receiving the user's birthdate from the Provider.")] + public DemandLevel RequestBirthDate { + get { return (DemandLevel)(ViewState[RequestBirthDateViewStateKey] ?? RequestBirthDateDefault); } + set { ViewState[RequestBirthDateViewStateKey] = value; } + } + + /// <summary> + /// Gets or sets your level of interest in receiving the user's gender from the Provider. + /// </summary> + [Bindable(true), DefaultValue(RequestGenderDefault), Category(ProfileCategory)] + [Description("Your level of interest in receiving the user's gender from the Provider.")] + public DemandLevel RequestGender { + get { return (DemandLevel)(ViewState[RequestGenderViewStateKey] ?? RequestGenderDefault); } + set { ViewState[RequestGenderViewStateKey] = value; } + } + + /// <summary> + /// Gets or sets your level of interest in receiving the user's postal code from the Provider. + /// </summary> + [Bindable(true), DefaultValue(RequestPostalCodeDefault), Category(ProfileCategory)] + [Description("Your level of interest in receiving the user's postal code from the Provider.")] + public DemandLevel RequestPostalCode { + get { return (DemandLevel)(ViewState[RequestPostalCodeViewStateKey] ?? RequestPostalCodeDefault); } + set { ViewState[RequestPostalCodeViewStateKey] = value; } + } + + /// <summary> + /// Gets or sets your level of interest in receiving the user's country from the Provider. + /// </summary> + [Bindable(true)] + [Category(ProfileCategory)] + [DefaultValue(RequestCountryDefault)] + [Description("Your level of interest in receiving the user's country from the Provider.")] + public DemandLevel RequestCountry { + get { return (DemandLevel)(ViewState[RequestCountryViewStateKey] ?? RequestCountryDefault); } + set { ViewState[RequestCountryViewStateKey] = value; } + } + + /// <summary> + /// Gets or sets your level of interest in receiving the user's preferred language from the Provider. + /// </summary> + [Bindable(true), DefaultValue(RequestLanguageDefault), Category(ProfileCategory)] + [Description("Your level of interest in receiving the user's preferred language from the Provider.")] + public DemandLevel RequestLanguage { + get { return (DemandLevel)(ViewState[RequestLanguageViewStateKey] ?? RequestLanguageDefault); } + set { ViewState[RequestLanguageViewStateKey] = value; } + } + + /// <summary> + /// Gets or sets your level of interest in receiving the user's time zone from the Provider. + /// </summary> + [Bindable(true), DefaultValue(RequestTimeZoneDefault), Category(ProfileCategory)] + [Description("Your level of interest in receiving the user's time zone from the Provider.")] + public DemandLevel RequestTimeZone { + get { return (DemandLevel)(ViewState[RequestTimeZoneViewStateKey] ?? RequestTimeZoneDefault); } + set { ViewState[RequestTimeZoneViewStateKey] = value; } + } + + /// <summary> + /// Gets or sets the URL to your privacy policy page that describes how + /// claims will be used and/or shared. + /// </summary> + [SuppressMessage("Microsoft.Design", "CA1056:UriPropertiesShouldNotBeStrings", Justification = "Bindable property must be simple type")] + [Bindable(true), DefaultValue(PolicyUrlDefault), Category(ProfileCategory)] + [Description("The URL to your privacy policy page that describes how claims will be used and/or shared.")] + public string PolicyUrl { + get { + return (string)ViewState[PolicyUrlViewStateKey] ?? PolicyUrlDefault; + } + + set { + UriUtil.ValidateResolvableUrl(Page, DesignMode, value); + ViewState[PolicyUrlViewStateKey] = value; + } + } + + /// <summary> + /// Gets or sets a value indicating whether to use OpenID extensions + /// to retrieve profile data of the authenticating user. + /// </summary> + [Bindable(true), DefaultValue(EnableRequestProfileDefault), Category(ProfileCategory)] + [Description("Turns the entire Simple Registration extension on or off.")] + public bool EnableRequestProfile { + get { return (bool)(ViewState[EnableRequestProfileViewStateKey] ?? EnableRequestProfileDefault); } + set { ViewState[EnableRequestProfileViewStateKey] = value; } + } + + /// <summary> + /// Gets or sets a value indicating whether to enforce on high security mode, + /// which requires the full authentication pipeline to be protected by SSL. + /// </summary> + [Bindable(true), DefaultValue(RequireSslDefault), Category(BehaviorCategory)] + [Description("Turns on high security mode, requiring the full authentication pipeline to be protected by SSL.")] + public bool RequireSsl { + get { return (bool)(ViewState[RequireSslViewStateKey] ?? RequireSslDefault); } + set { ViewState[RequireSslViewStateKey] = value; } + } + + /// <summary> + /// Gets or sets the type of the custom application store to use, or <c>null</c> to use the default. + /// </summary> + /// <remarks> + /// If set, this property must be set in each Page Load event + /// as it is not persisted across postbacks. + /// </remarks> + public IOpenIdApplicationStore CustomApplicationStore { get; set; } + + #endregion + + /// <summary> + /// Gets or sets the <see cref="OpenIdRelyingParty"/> instance to use. + /// </summary> + /// <value>The default value is an <see cref="OpenIdRelyingParty"/> instance initialized according to the web.config file.</value> + /// <remarks> + /// A performance optimization would be to store off the + /// instance as a static member in your web site and set it + /// to this property in your <see cref="Control.Load">Page.Load</see> + /// event since instantiating these instances can be expensive on + /// heavily trafficked web pages. + /// </remarks> + public OpenIdRelyingParty RelyingParty { + get { + if (this.relyingParty == null) { + this.relyingParty = this.CreateRelyingParty(); + } + return this.relyingParty; + } + + set { + this.relyingParty = value; + } + } + + /// <summary> + /// Gets or sets the OpenID authentication request that is about to be sent. + /// </summary> + protected IAuthenticationRequest Request { get; set; } + + /// <summary> + /// Immediately redirects to the OpenID Provider to verify the Identifier + /// provided in the text box. + /// </summary> + public void LogOn() { + if (this.Request == null) { + this.CreateRequest(); // sets this.Request + } + + if (this.Request != null) { + this.Request.RedirectToProvider(); + } + } + + /// <summary> + /// Constructs the authentication request and returns it. + /// </summary> + /// <returns>The instantiated authentication request.</returns> + /// <remarks> + /// <para>This method need not be called before calling the <see cref="LogOn"/> method, + /// but is offered in the event that adding extensions to the request is desired.</para> + /// <para>The Simple Registration extension arguments are added to the request + /// before returning if <see cref="EnableRequestProfile"/> is set to true.</para> + /// </remarks> + [SuppressMessage("Microsoft.Usage", "CA2234:PassSystemUriObjectsInsteadOfStrings", Justification = "Uri(Uri, string) accepts second arguments that Uri(Uri, new Uri(string)) does not that we must support.")] + public IAuthenticationRequest CreateRequest() { + Requires.ValidState(this.Request == null, OpenIdStrings.CreateRequestAlreadyCalled); + Requires.ValidState(!string.IsNullOrEmpty(this.Text), OpenIdStrings.OpenIdTextBoxEmpty); + + try { + // Resolve the trust root, and swap out the scheme and port if necessary to match the + // return_to URL, since this match is required by OpenId, and the consumer app + // may be using HTTP at some times and HTTPS at others. + UriBuilder realm = OpenIdUtilities.GetResolvedRealm(this.Page, this.RealmUrl, this.RelyingParty.Channel.GetRequestFromContext()); + realm.Scheme = Page.Request.Url.Scheme; + realm.Port = Page.Request.Url.Port; + + // Initiate openid request + // We use TryParse here to avoid throwing an exception which + // might slip through our validator control if it is disabled. + Identifier userSuppliedIdentifier; + if (Identifier.TryParse(this.Text, out userSuppliedIdentifier)) { + Realm typedRealm = new Realm(realm); + if (string.IsNullOrEmpty(this.ReturnToUrl)) { + this.Request = this.RelyingParty.CreateRequest(userSuppliedIdentifier, typedRealm); + } else { + Uri returnTo = new Uri(this.RelyingParty.Channel.GetRequestFromContext().UrlBeforeRewriting, this.ReturnToUrl); + this.Request = this.RelyingParty.CreateRequest(userSuppliedIdentifier, typedRealm, returnTo); + } + this.Request.Mode = this.ImmediateMode ? AuthenticationRequestMode.Immediate : AuthenticationRequestMode.Setup; + if (this.EnableRequestProfile) { + this.AddProfileArgs(this.Request); + } + + // Add state that needs to survive across the redirect. + this.Request.SetUntrustedCallbackArgument(UsePersistentCookieCallbackKey, this.UsePersistentCookie.ToString(CultureInfo.InvariantCulture)); + } else { + Logger.OpenId.WarnFormat("An invalid identifier was entered ({0}), but not caught by any validation routine.", this.Text); + this.Request = null; + } + } catch (ProtocolException ex) { + this.OnFailed(new FailedAuthenticationResponse(ex)); + } + + return this.Request; + } + + /// <summary> + /// Checks for incoming OpenID authentication responses and fires appropriate events. + /// </summary> + /// <param name="e">The <see cref="T:System.EventArgs"/> object that contains the event data.</param> + protected override void OnLoad(EventArgs e) { + base.OnLoad(e); + + if (Page.IsPostBack) { + return; + } + + var response = this.RelyingParty.GetResponse(); + if (response != null) { + string persistentString = response.GetUntrustedCallbackArgument(UsePersistentCookieCallbackKey); + bool persistentBool; + if (persistentString != null && bool.TryParse(persistentString, out persistentBool)) { + this.UsePersistentCookie = persistentBool; + } + + switch (response.Status) { + case AuthenticationStatus.Canceled: + this.OnCanceled(response); + break; + case AuthenticationStatus.Authenticated: + this.OnLoggedIn(response); + break; + case AuthenticationStatus.SetupRequired: + this.OnSetupRequired(response); + break; + case AuthenticationStatus.Failed: + this.OnFailed(response); + break; + default: + throw new InvalidOperationException("Unexpected response status code."); + } + } + } + + #region Events + + /// <summary> + /// Fires the <see cref="LoggedIn"/> event. + /// </summary> + /// <param name="response">The response.</param> + protected virtual void OnLoggedIn(IAuthenticationResponse response) { + Requires.NotNull(response, "response"); + ErrorUtilities.VerifyInternal(response.Status == AuthenticationStatus.Authenticated, "Firing OnLoggedIn event without an authenticated response."); + + var loggedIn = this.LoggedIn; + OpenIdEventArgs args = new OpenIdEventArgs(response); + if (loggedIn != null) { + loggedIn(this, args); + } + + if (!args.Cancel) { + FormsAuthentication.RedirectFromLoginPage(response.ClaimedIdentifier, this.UsePersistentCookie); + } + } + + /// <summary> + /// Fires the <see cref="Failed"/> event. + /// </summary> + /// <param name="response">The response.</param> + protected virtual void OnFailed(IAuthenticationResponse response) { + Requires.NotNull(response, "response"); + ErrorUtilities.VerifyInternal(response.Status == AuthenticationStatus.Failed, "Firing Failed event for the wrong response type."); + + var failed = this.Failed; + if (failed != null) { + failed(this, new OpenIdEventArgs(response)); + } + } + + /// <summary> + /// Fires the <see cref="Canceled"/> event. + /// </summary> + /// <param name="response">The response.</param> + protected virtual void OnCanceled(IAuthenticationResponse response) { + Requires.NotNull(response, "response"); + ErrorUtilities.VerifyInternal(response.Status == AuthenticationStatus.Canceled, "Firing Canceled event for the wrong response type."); + + var canceled = this.Canceled; + if (canceled != null) { + canceled(this, new OpenIdEventArgs(response)); + } + } + + /// <summary> + /// Fires the <see cref="SetupRequired"/> event. + /// </summary> + /// <param name="response">The response.</param> + protected virtual void OnSetupRequired(IAuthenticationResponse response) { + Requires.NotNull(response, "response"); + ErrorUtilities.VerifyInternal(response.Status == AuthenticationStatus.SetupRequired, "Firing SetupRequired event for the wrong response type."); + + // Why are we firing Failed when we're OnSetupRequired? Backward compatibility. + var setupRequired = this.SetupRequired; + if (setupRequired != null) { + setupRequired(this, new OpenIdEventArgs(response)); + } + } + + #endregion + + /// <summary> + /// Adds extensions to a given authentication request to ask the Provider + /// for user profile data. + /// </summary> + /// <param name="request">The authentication request to add the extensions to.</param> + private void AddProfileArgs(IAuthenticationRequest request) { + Requires.NotNull(request, "request"); + + request.AddExtension(new ClaimsRequest() { + Nickname = this.RequestNickname, + Email = this.RequestEmail, + FullName = this.RequestFullName, + BirthDate = this.RequestBirthDate, + Gender = this.RequestGender, + PostalCode = this.RequestPostalCode, + Country = this.RequestCountry, + Language = this.RequestLanguage, + TimeZone = this.RequestTimeZone, + PolicyUrl = string.IsNullOrEmpty(this.PolicyUrl) ? + null : new Uri(this.RelyingParty.Channel.GetRequestFromContext().UrlBeforeRewriting, this.Page.ResolveUrl(this.PolicyUrl)), + }); + } + + /// <summary> + /// Creates the relying party instance used to generate authentication requests. + /// </summary> + /// <returns>The instantiated relying party.</returns> + private OpenIdRelyingParty CreateRelyingParty() { + // If we're in stateful mode, first use the explicitly given one on this control if there + // is one. Then try the configuration file specified one. Finally, use the default + // in-memory one that's built into OpenIdRelyingParty. + IOpenIdApplicationStore store = this.Stateless ? null : + (this.CustomApplicationStore ?? OpenIdElement.Configuration.RelyingParty.ApplicationStore.CreateInstance(OpenIdRelyingParty.HttpApplicationStore)); + var rp = new OpenIdRelyingParty(store); + try { + // Only set RequireSsl to true, as we don't want to override + // a .config setting of true with false. + if (this.RequireSsl) { + rp.SecuritySettings.RequireSsl = true; + } + return rp; + } catch { + rp.Dispose(); + throw; + } + } + } +} diff --git a/src/DotNetOpenAuth.OpenId.RelyingParty.UI/OpenId/RelyingParty/OpenIdRelyingPartyAjaxControlBase.cs b/src/DotNetOpenAuth.OpenId.RelyingParty.UI/OpenId/RelyingParty/OpenIdRelyingPartyAjaxControlBase.cs new file mode 100644 index 0000000..e83cf0e --- /dev/null +++ b/src/DotNetOpenAuth.OpenId.RelyingParty.UI/OpenId/RelyingParty/OpenIdRelyingPartyAjaxControlBase.cs @@ -0,0 +1,468 @@ +//----------------------------------------------------------------------- +// <copyright file="OpenIdRelyingPartyAjaxControlBase.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +[assembly: System.Web.UI.WebResource(DotNetOpenAuth.OpenId.RelyingParty.OpenIdRelyingPartyAjaxControlBase.EmbeddedAjaxJavascriptResource, "text/javascript")] + +namespace DotNetOpenAuth.OpenId.RelyingParty { + using System; + using System.Collections.Generic; + using System.ComponentModel; + using System.Diagnostics.CodeAnalysis; + using System.Diagnostics.Contracts; + using System.Globalization; + using System.Linq; + using System.Text; + using System.Web; + using System.Web.Script.Serialization; + using System.Web.UI; + using DotNetOpenAuth.Configuration; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OpenId.Extensions; + + /// <summary> + /// A common base class for OpenID Relying Party controls. + /// </summary> + public abstract class OpenIdRelyingPartyAjaxControlBase : OpenIdRelyingPartyControlBase, ICallbackEventHandler { + /// <summary> + /// The manifest resource name of the javascript file to include on the hosting page. + /// </summary> + internal const string EmbeddedAjaxJavascriptResource = Util.DefaultNamespace + ".OpenId.RelyingParty.OpenIdRelyingPartyAjaxControlBase.js"; + + /// <summary> + /// The "dnoa.op_endpoint" string. + /// </summary> + internal const string OPEndpointParameterName = OpenIdUtilities.CustomParameterPrefix + "op_endpoint"; + + /// <summary> + /// The "dnoa.claimed_id" string. + /// </summary> + internal const string ClaimedIdParameterName = OpenIdUtilities.CustomParameterPrefix + "claimed_id"; + + /// <summary> + /// The name of the javascript field that stores the maximum time a positive assertion is + /// good for before it must be refreshed. + /// </summary> + internal const string MaxPositiveAssertionLifetimeJsName = "window.dnoa_internal.maxPositiveAssertionLifetime"; + + /// <summary> + /// The name of the javascript function that will initiate an asynchronous callback. + /// </summary> + protected internal const string CallbackJSFunctionAsync = "window.dnoa_internal.callbackAsync"; + + /// <summary> + /// The name of the javascript function that will initiate a synchronous callback. + /// </summary> + protected const string CallbackJSFunction = "window.dnoa_internal.callback"; + + #region Property viewstate keys + + /// <summary> + /// The viewstate key to use for storing the value of a successful authentication. + /// </summary> + private const string AuthDataViewStateKey = "AuthData"; + + /// <summary> + /// The viewstate key to use for storing the value of the <see cref="AuthenticationResponse"/> property. + /// </summary> + private const string AuthenticationResponseViewStateKey = "AuthenticationResponse"; + + /// <summary> + /// The viewstate key to use for storing the value of the <see cref="AuthenticationProcessedAlready"/> property. + /// </summary> + private const string AuthenticationProcessedAlreadyViewStateKey = "AuthenticationProcessedAlready"; + + #endregion + + /// <summary> + /// Default value of the <see cref="Popup"/> property. + /// </summary> + private const PopupBehavior PopupDefault = PopupBehavior.Always; + + /// <summary> + /// Default value of <see cref="LogOnMode"/> property.. + /// </summary> + private const LogOnSiteNotification LogOnModeDefault = LogOnSiteNotification.None; + + /// <summary> + /// The authentication response that just came in. + /// </summary> + private IAuthenticationResponse authenticationResponse; + + /// <summary> + /// Stores the result of an AJAX discovery request while it is waiting + /// to be picked up by ASP.NET on the way down to the user agent. + /// </summary> + private string discoveryResult; + + /// <summary> + /// Initializes a new instance of the <see cref="OpenIdRelyingPartyAjaxControlBase"/> class. + /// </summary> + protected OpenIdRelyingPartyAjaxControlBase() { + // The AJAX login style always uses popups (or invisible iframes). + base.Popup = PopupDefault; + + // The expected use case for the AJAX login box is for comments... not logging in. + this.LogOnMode = LogOnModeDefault; + } + + /// <summary> + /// Fired when a Provider sends back a positive assertion to this control, + /// but the authentication has not yet been verified. + /// </summary> + /// <remarks> + /// <b>No security critical decisions should be made within event handlers + /// for this event</b> as the authenticity of the assertion has not been + /// verified yet. All security related code should go in the event handler + /// for the <see cref="OpenIdRelyingPartyControlBase.LoggedIn"/> event. + /// </remarks> + [Description("Fired when a Provider sends back a positive assertion to this control, but the authentication has not yet been verified.")] + public event EventHandler<OpenIdEventArgs> UnconfirmedPositiveAssertion; + + /// <summary> + /// Gets or sets a value indicating when to use a popup window to complete the login experience. + /// </summary> + /// <value>The default value is <see cref="PopupBehavior.Never"/>.</value> + [Bindable(false), Browsable(false), DefaultValue(PopupDefault)] + public override PopupBehavior Popup { + get { return base.Popup; } + set { ErrorUtilities.VerifySupported(value == base.Popup, OpenIdStrings.PropertyValueNotSupported); } + } + + /// <summary> + /// Gets or sets the way a completed login is communicated to the rest of the web site. + /// </summary> + [Bindable(true), DefaultValue(LogOnModeDefault), Category(BehaviorCategory)] + [Description("The way a completed login is communicated to the rest of the web site.")] + public override LogOnSiteNotification LogOnMode { // override to set new DefaultValue + get { return base.LogOnMode; } + set { base.LogOnMode = value; } + } + + /// <summary> + /// Gets or sets the <see cref="OpenIdRelyingParty"/> instance to use. + /// </summary> + /// <value> + /// The default value is an <see cref="OpenIdRelyingParty"/> instance initialized according to the web.config file. + /// </value> + /// <remarks> + /// A performance optimization would be to store off the + /// instance as a static member in your web site and set it + /// to this property in your <see cref="Control.Load">Page.Load</see> + /// event since instantiating these instances can be expensive on + /// heavily trafficked web pages. + /// </remarks> + public override OpenIdRelyingParty RelyingParty { + get { + return base.RelyingParty; + } + + set { + // Make sure we get an AJAX-ready instance. + ErrorUtilities.VerifyArgument(value is OpenIdAjaxRelyingParty, OpenIdStrings.TypeMustImplementX, typeof(OpenIdAjaxRelyingParty).Name); + base.RelyingParty = value; + } + } + + /// <summary> + /// Gets the completed authentication response. + /// </summary> + public IAuthenticationResponse AuthenticationResponse { + get { + if (this.authenticationResponse == null) { + // We will either validate a new response and return a live AuthenticationResponse + // or we will try to deserialize a previous IAuthenticationResponse (snapshot) + // from viewstate and return that. + IAuthenticationResponse viewstateResponse = this.ViewState[AuthenticationResponseViewStateKey] as IAuthenticationResponse; + string viewstateAuthData = this.ViewState[AuthDataViewStateKey] as string; + string formAuthData = this.Page.Request.Form[this.OpenIdAuthDataFormKey]; + + // First see if there is fresh auth data to be processed into a response. + if (!string.IsNullOrEmpty(formAuthData) && !string.Equals(viewstateAuthData, formAuthData, StringComparison.Ordinal)) { + this.ViewState[AuthDataViewStateKey] = formAuthData; + + Uri authUri = new Uri(formAuthData); + HttpRequestInfo clientResponseInfo = new HttpRequestInfo { + UrlBeforeRewriting = authUri, + }; + + this.authenticationResponse = this.RelyingParty.GetResponse(clientResponseInfo); + Logger.Controls.DebugFormat( + "The {0} control checked for an authentication response and found: {1}", + this.ID, + this.authenticationResponse.Status); + this.AuthenticationProcessedAlready = false; + + // Save out the authentication response to viewstate so we can find it on + // a subsequent postback. + this.ViewState[AuthenticationResponseViewStateKey] = new PositiveAuthenticationResponseSnapshot(this.authenticationResponse); + } else { + this.authenticationResponse = viewstateResponse; + } + } + + return this.authenticationResponse; + } + } + + /// <summary> + /// Gets the relying party as its AJAX type. + /// </summary> + protected OpenIdAjaxRelyingParty AjaxRelyingParty { + get { return (OpenIdAjaxRelyingParty)this.RelyingParty; } + } + + /// <summary> + /// Gets the name of the open id auth data form key (for the value as stored at the user agent as a FORM field). + /// </summary> + /// <value>Usually a concatenation of the control's name and <c>"_openidAuthData"</c>.</value> + protected abstract string OpenIdAuthDataFormKey { get; } + + /// <summary> + /// Gets or sets a value indicating whether an authentication in the page's view state + /// has already been processed and appropriate events fired. + /// </summary> + private bool AuthenticationProcessedAlready { + get { return (bool)(ViewState[AuthenticationProcessedAlreadyViewStateKey] ?? false); } + set { ViewState[AuthenticationProcessedAlreadyViewStateKey] = value; } + } + + /// <summary> + /// Allows an OpenID extension to read data out of an unverified positive authentication assertion + /// and send it down to the client browser so that Javascript running on the page can perform + /// some preprocessing on the extension data. + /// </summary> + /// <typeparam name="T">The extension <i>response</i> type that will read data from the assertion.</typeparam> + /// <param name="propertyName">The property name on the openid_identifier input box object that will be used to store the extension data. For example: sreg</param> + /// <remarks> + /// This method should be called from the <see cref="UnconfirmedPositiveAssertion"/> event handler. + /// </remarks> + [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "By design")] + public void RegisterClientScriptExtension<T>(string propertyName) where T : IClientScriptExtensionResponse { + Requires.NotNullOrEmpty(propertyName, "propertyName"); + this.RelyingParty.RegisterClientScriptExtension<T>(propertyName); + } + + #region ICallbackEventHandler Members + + /// <summary> + /// Returns the result of discovery on some Identifier passed to <see cref="ICallbackEventHandler.RaiseCallbackEvent"/>. + /// </summary> + /// <returns>The result of the callback.</returns> + /// <value>A whitespace delimited list of URLs that can be used to initiate authentication.</value> + string ICallbackEventHandler.GetCallbackResult() { + return this.GetCallbackResult(); + } + + /// <summary> + /// Performs discovery on some OpenID Identifier. Called directly from the user agent via + /// AJAX callback mechanisms. + /// </summary> + /// <param name="eventArgument">The identifier to perform discovery on.</param> + [SuppressMessage("Microsoft.Design", "CA1030:UseEventsWhereAppropriate", Justification = "We want to preserve the signature of the interface.")] + void ICallbackEventHandler.RaiseCallbackEvent(string eventArgument) { + this.RaiseCallbackEvent(eventArgument); + } + + #endregion + + /// <summary> + /// Returns the results of a callback event that targets a control. + /// </summary> + /// <returns>The result of the callback.</returns> + [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "We want to preserve the signature of the interface.")] + protected virtual string GetCallbackResult() { + this.Page.Response.ContentType = "text/javascript"; + return this.discoveryResult; + } + + /// <summary> + /// Processes a callback event that targets a control. + /// </summary> + /// <param name="eventArgument">A string that represents an event argument to pass to the event handler.</param> + [SuppressMessage("Microsoft.Design", "CA1030:UseEventsWhereAppropriate", Justification = "We want to preserve the signature of the interface.")] + protected virtual void RaiseCallbackEvent(string eventArgument) { + string userSuppliedIdentifier = eventArgument; + + ErrorUtilities.VerifyNonZeroLength(userSuppliedIdentifier, "userSuppliedIdentifier"); + Logger.OpenId.InfoFormat("AJAX discovery on {0} requested.", userSuppliedIdentifier); + + this.Identifier = userSuppliedIdentifier; + + var serializer = new JavaScriptSerializer(); + IEnumerable<IAuthenticationRequest> requests = this.CreateRequests(this.Identifier); + this.discoveryResult = serializer.Serialize(this.AjaxRelyingParty.AsJsonDiscoveryResult(requests)); + } + + /// <summary> + /// Creates the relying party instance used to generate authentication requests. + /// </summary> + /// <param name="store">The store to pass to the relying party constructor.</param> + /// <returns>The instantiated relying party.</returns> + protected override OpenIdRelyingParty CreateRelyingParty(IOpenIdApplicationStore store) { + return new OpenIdAjaxRelyingParty(store); + } + + /// <summary> + /// Pre-discovers an identifier and makes the results available to the + /// user agent for javascript as soon as the page loads. + /// </summary> + /// <param name="identifier">The identifier.</param> + protected void PreloadDiscovery(Identifier identifier) { + this.PreloadDiscovery(new[] { identifier }); + } + + /// <summary> + /// Pre-discovers a given set of identifiers and makes the results available to the + /// user agent for javascript as soon as the page loads. + /// </summary> + /// <param name="identifiers">The identifiers to perform discovery on.</param> + protected void PreloadDiscovery(IEnumerable<Identifier> identifiers) { + string script = this.AjaxRelyingParty.AsAjaxPreloadedDiscoveryResult( + identifiers.SelectMany(id => this.CreateRequests(id))); + this.Page.ClientScript.RegisterClientScriptBlock(typeof(OpenIdRelyingPartyAjaxControlBase), this.ClientID, script, true); + } + + /// <summary> + /// Fires the <see cref="UnconfirmedPositiveAssertion"/> event. + /// </summary> + protected virtual void OnUnconfirmedPositiveAssertion() { + var unconfirmedPositiveAssertion = this.UnconfirmedPositiveAssertion; + if (unconfirmedPositiveAssertion != null) { + unconfirmedPositiveAssertion(this, null); + } + } + + /// <summary> + /// Raises the <see cref="E:Load"/> event. + /// </summary> + /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param> + protected override void OnLoad(EventArgs e) { + base.OnLoad(e); + + // Our parent control ignores all OpenID messages included in a postback, + // but our AJAX controls hide an old OpenID message in a postback payload, + // so we deserialize it and process it when appropriate. + if (this.Page.IsPostBack) { + if (this.AuthenticationResponse != null && !this.AuthenticationProcessedAlready) { + // Only process messages targeted at this control. + // Note that Stateless mode causes no receiver to be indicated. + string receiver = this.AuthenticationResponse.GetUntrustedCallbackArgument(ReturnToReceivingControlId); + if (receiver == null || receiver == this.ClientID) { + this.ProcessResponse(this.AuthenticationResponse); + this.AuthenticationProcessedAlready = true; + } + } + } + } + + /// <summary> + /// Called when the <see cref="Identifier"/> property is changed. + /// </summary> + protected override void OnIdentifierChanged() { + base.OnIdentifierChanged(); + + // Since the identifier changed, make sure we reset any cached authentication on the user agent. + this.ViewState.Remove(AuthDataViewStateKey); + } + + /// <summary> + /// Raises the <see cref="E:System.Web.UI.Control.PreRender"/> event. + /// </summary> + /// <param name="e">An <see cref="T:System.EventArgs"/> object that contains the event data.</param> + protected override void OnPreRender(EventArgs e) { + base.OnPreRender(e); + + this.SetWebAppPathOnUserAgent(); + this.Page.ClientScript.RegisterClientScriptResource(typeof(OpenIdRelyingPartyAjaxControlBase), EmbeddedAjaxJavascriptResource); + + StringBuilder initScript = new StringBuilder(); + + initScript.AppendLine(CallbackJSFunctionAsync + " = " + this.GetJsCallbackConvenienceFunction(true)); + initScript.AppendLine(CallbackJSFunction + " = " + this.GetJsCallbackConvenienceFunction(false)); + + // Positive assertions can last no longer than this library is willing to consider them valid, + // and when they come with OP private associations they last no longer than the OP is willing + // to consider them valid. We assume the OP will hold them valid for at least five minutes. + double assertionLifetimeInMilliseconds = Math.Min(TimeSpan.FromMinutes(5).TotalMilliseconds, Math.Min(OpenIdElement.Configuration.MaxAuthenticationTime.TotalMilliseconds, DotNetOpenAuthSection.Messaging.MaximumMessageLifetime.TotalMilliseconds)); + initScript.AppendLine(MaxPositiveAssertionLifetimeJsName + " = " + assertionLifetimeInMilliseconds.ToString(CultureInfo.InvariantCulture) + ";"); + + // We register this callback code explicitly with a specific type rather than the derived-type of the control + // to ensure that this discovery callback function is only set ONCE for the HTML document. + this.Page.ClientScript.RegisterClientScriptBlock(typeof(OpenIdRelyingPartyControlBase), "initializer", initScript.ToString(), true); + } + + /// <summary> + /// Sends server control content to a provided <see cref="T:System.Web.UI.HtmlTextWriter"/> object, which writes the content to be rendered on the client. + /// </summary> + /// <param name="writer">The <see cref="T:System.Web.UI.HtmlTextWriter"/> object that receives the server control content.</param> + protected override void Render(HtmlTextWriter writer) { + Contract.Assume(writer != null, "Missing contract."); + base.Render(writer); + + // Emit a hidden field to let the javascript on the user agent know if an + // authentication has already successfully taken place. + string viewstateAuthData = this.ViewState[AuthDataViewStateKey] as string; + if (!string.IsNullOrEmpty(viewstateAuthData)) { + writer.AddAttribute(HtmlTextWriterAttribute.Name, this.OpenIdAuthDataFormKey); + writer.AddAttribute(HtmlTextWriterAttribute.Value, viewstateAuthData, true); + writer.AddAttribute(HtmlTextWriterAttribute.Type, "hidden"); + writer.RenderBeginTag(HtmlTextWriterTag.Input); + writer.RenderEndTag(); + } + } + + /// <summary> + /// Notifies the user agent via an AJAX response of a completed authentication attempt. + /// </summary> + protected override void ScriptClosingPopupOrIFrame() { + Action<AuthenticationStatus> callback = status => { + if (status == AuthenticationStatus.Authenticated) { + this.OnUnconfirmedPositiveAssertion(); // event handler will fill the clientScriptExtensions collection. + } + }; + + OutgoingWebResponse response = this.RelyingParty.ProcessResponseFromPopup( + this.RelyingParty.Channel.GetRequestFromContext(), + callback); + + response.Send(); + } + + /// <summary> + /// Constructs a function that will initiate an AJAX callback. + /// </summary> + /// <param name="async">if set to <c>true</c> causes the AJAX callback to be a little more asynchronous. Note that <c>false</c> does not mean the call is absolutely synchronous.</param> + /// <returns>The string defining a javascript anonymous function that initiates a callback.</returns> + private string GetJsCallbackConvenienceFunction(bool @async) { + string argumentParameterName = "argument"; + string callbackResultParameterName = "resultFunction"; + string callbackErrorCallbackParameterName = "errorCallback"; + string callback = Page.ClientScript.GetCallbackEventReference( + this, + argumentParameterName, + callbackResultParameterName, + argumentParameterName, + callbackErrorCallbackParameterName, + @async); + return string.Format( + CultureInfo.InvariantCulture, + "function({1}, {2}, {3}) {{{0}\treturn {4};{0}}};", + Environment.NewLine, + argumentParameterName, + callbackResultParameterName, + callbackErrorCallbackParameterName, + callback); + } + + /// <summary> + /// Sets the window.aspnetapppath variable on the user agent so that cookies can be set with the proper path. + /// </summary> + private void SetWebAppPathOnUserAgent() { + string script = "window.aspnetapppath = " + MessagingUtilities.GetSafeJavascriptValue(this.Page.Request.ApplicationPath) + ";"; + this.Page.ClientScript.RegisterClientScriptBlock(typeof(OpenIdRelyingPartyAjaxControlBase), "webapppath", script, true); + } + } +} diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingPartyAjaxControlBase.js b/src/DotNetOpenAuth.OpenId.RelyingParty.UI/OpenId/RelyingParty/OpenIdRelyingPartyAjaxControlBase.js index 4de5188..4de5188 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingPartyAjaxControlBase.js +++ b/src/DotNetOpenAuth.OpenId.RelyingParty.UI/OpenId/RelyingParty/OpenIdRelyingPartyAjaxControlBase.js diff --git a/src/DotNetOpenAuth.OpenId.RelyingParty.UI/OpenId/RelyingParty/OpenIdRelyingPartyControlBase.cs b/src/DotNetOpenAuth.OpenId.RelyingParty.UI/OpenId/RelyingParty/OpenIdRelyingPartyControlBase.cs new file mode 100644 index 0000000..c86ee1d --- /dev/null +++ b/src/DotNetOpenAuth.OpenId.RelyingParty.UI/OpenId/RelyingParty/OpenIdRelyingPartyControlBase.cs @@ -0,0 +1,1054 @@ +//----------------------------------------------------------------------- +// <copyright file="OpenIdRelyingPartyControlBase.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +[assembly: System.Web.UI.WebResource(DotNetOpenAuth.OpenId.RelyingParty.OpenIdRelyingPartyControlBase.EmbeddedJavascriptResource, "text/javascript")] + +namespace DotNetOpenAuth.OpenId.RelyingParty { + using System; + using System.Collections.Generic; + using System.Collections.ObjectModel; + using System.ComponentModel; + using System.Diagnostics.CodeAnalysis; + using System.Diagnostics.Contracts; + using System.Drawing.Design; + using System.Globalization; + using System.Linq; + using System.Text; + using System.Text.RegularExpressions; + using System.Web; + using System.Web.Security; + using System.Web.UI; + using DotNetOpenAuth.ComponentModel; + using DotNetOpenAuth.Configuration; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OpenId.Extensions; + using DotNetOpenAuth.OpenId.Extensions.UI; + using DotNetOpenAuth.OpenId.Messages; + + /// <summary> + /// Methods of indicating to the rest of the web site that the user has logged in. + /// </summary> + [SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "OnSite", Justification = "Two words intended.")] + public enum LogOnSiteNotification { + /// <summary> + /// The rest of the web site is unaware that the user just completed an OpenID login. + /// </summary> + None, + + /// <summary> + /// After the <see cref="OpenIdRelyingPartyControlBase.LoggedIn"/> event is fired + /// the control automatically calls <see cref="System.Web.Security.FormsAuthentication.RedirectFromLoginPage(string, bool)"/> + /// with the <see cref="IAuthenticationResponse.ClaimedIdentifier"/> as the username + /// unless the <see cref="OpenIdRelyingPartyControlBase.LoggedIn"/> event handler sets + /// <see cref="OpenIdEventArgs.Cancel"/> property to true. + /// </summary> + FormsAuthentication, + } + + /// <summary> + /// How an OpenID user session should be persisted across visits. + /// </summary> + public enum LogOnPersistence { + /// <summary> + /// The user should only be logged in as long as the browser window remains open. + /// Nothing is persisted to help the user on a return visit. Public kiosk mode. + /// </summary> + Session, + + /// <summary> + /// The user should only be logged in as long as the browser window remains open. + /// The OpenID Identifier is persisted to help expedite re-authentication when + /// the user visits the next time. + /// </summary> + SessionAndPersistentIdentifier, + + /// <summary> + /// The user is issued a persistent authentication ticket so that no login is + /// necessary on their return visit. + /// </summary> + PersistentAuthentication, + } + + /// <summary> + /// A common base class for OpenID Relying Party controls. + /// </summary> + [DefaultProperty("Identifier"), ValidationProperty("Identifier")] + [ParseChildren(true), PersistChildren(false)] + public abstract class OpenIdRelyingPartyControlBase : Control, IPostBackEventHandler, IDisposable { + /// <summary> + /// The manifest resource name of the javascript file to include on the hosting page. + /// </summary> + internal const string EmbeddedJavascriptResource = Util.DefaultNamespace + ".OpenId.RelyingParty.OpenIdRelyingPartyControlBase.js"; + + /// <summary> + /// The cookie used to persist the Identifier the user logged in with. + /// </summary> + internal const string PersistentIdentifierCookieName = OpenIdUtilities.CustomParameterPrefix + "OpenIDIdentifier"; + + /// <summary> + /// The callback parameter name to use to store which control initiated the auth request. + /// </summary> + internal const string ReturnToReceivingControlId = OpenIdUtilities.CustomParameterPrefix + "receiver"; + + #region Protected internal callback parameter names + + /// <summary> + /// The callback parameter to use for recognizing when the callback is in a popup window or hidden iframe. + /// </summary> + protected internal const string UIPopupCallbackKey = OpenIdUtilities.CustomParameterPrefix + "uipopup"; + + /// <summary> + /// The parameter name to include in the formulated auth request so that javascript can know whether + /// the OP advertises support for the UI extension. + /// </summary> + protected internal const string PopupUISupportedJSHint = OpenIdUtilities.CustomParameterPrefix + "popupUISupported"; + + #endregion + + #region Property category constants + + /// <summary> + /// The "Appearance" category for properties. + /// </summary> + protected const string AppearanceCategory = "Appearance"; + + /// <summary> + /// The "Behavior" category for properties. + /// </summary> + protected const string BehaviorCategory = "Behavior"; + + /// <summary> + /// The "OpenID" category for properties and events. + /// </summary> + protected const string OpenIdCategory = "OpenID"; + + #endregion + + #region Private callback parameter names + + /// <summary> + /// The callback parameter for use with persisting the <see cref="UsePersistentCookie"/> property. + /// </summary> + private const string UsePersistentCookieCallbackKey = OpenIdUtilities.CustomParameterPrefix + "UsePersistentCookie"; + + /// <summary> + /// The callback parameter to use for recognizing when the callback is in the parent window. + /// </summary> + private const string UIPopupCallbackParentKey = OpenIdUtilities.CustomParameterPrefix + "uipopupParent"; + + #endregion + + #region Property default values + + /// <summary> + /// The default value for the <see cref="Stateless"/> property. + /// </summary> + private const bool StatelessDefault = false; + + /// <summary> + /// The default value for the <see cref="ReturnToUrl"/> property. + /// </summary> + private const string ReturnToUrlDefault = ""; + + /// <summary> + /// Default value of <see cref="UsePersistentCookie"/>. + /// </summary> + private const LogOnPersistence UsePersistentCookieDefault = LogOnPersistence.Session; + + /// <summary> + /// Default value of <see cref="LogOnMode"/>. + /// </summary> + private const LogOnSiteNotification LogOnModeDefault = LogOnSiteNotification.FormsAuthentication; + + /// <summary> + /// The default value for the <see cref="RealmUrl"/> property. + /// </summary> + private const string RealmUrlDefault = "~/"; + + /// <summary> + /// The default value for the <see cref="Popup"/> property. + /// </summary> + private const PopupBehavior PopupDefault = PopupBehavior.Never; + + /// <summary> + /// The default value for the <see cref="RequireSsl"/> property. + /// </summary> + private const bool RequireSslDefault = false; + + #endregion + + #region Property view state keys + + /// <summary> + /// The viewstate key to use for storing the value of the <see cref="Extensions"/> property. + /// </summary> + private const string ExtensionsViewStateKey = "Extensions"; + + /// <summary> + /// The viewstate key to use for the <see cref="Stateless"/> property. + /// </summary> + private const string StatelessViewStateKey = "Stateless"; + + /// <summary> + /// The viewstate key to use for the <see cref="UsePersistentCookie"/> property. + /// </summary> + private const string UsePersistentCookieViewStateKey = "UsePersistentCookie"; + + /// <summary> + /// The viewstate key to use for the <see cref="LogOnMode"/> property. + /// </summary> + private const string LogOnModeViewStateKey = "LogOnMode"; + + /// <summary> + /// The viewstate key to use for the <see cref="RealmUrl"/> property. + /// </summary> + private const string RealmUrlViewStateKey = "RealmUrl"; + + /// <summary> + /// The viewstate key to use for the <see cref="ReturnToUrl"/> property. + /// </summary> + private const string ReturnToUrlViewStateKey = "ReturnToUrl"; + + /// <summary> + /// The key under which the value for the <see cref="Identifier"/> property will be stored. + /// </summary> + private const string IdentifierViewStateKey = "Identifier"; + + /// <summary> + /// The viewstate key to use for the <see cref="Popup"/> property. + /// </summary> + private const string PopupViewStateKey = "Popup"; + + /// <summary> + /// The viewstate key to use for the <see cref="RequireSsl"/> property. + /// </summary> + private const string RequireSslViewStateKey = "RequireSsl"; + + #endregion + + /// <summary> + /// The lifetime of the cookie used to persist the Identifier the user logged in with. + /// </summary> + private static readonly TimeSpan PersistentIdentifierTimeToLiveDefault = TimeSpan.FromDays(14); + + /// <summary> + /// Backing field for the <see cref="RelyingParty"/> property. + /// </summary> + private OpenIdRelyingParty relyingParty; + + /// <summary> + /// A value indicating whether the <see cref="relyingParty"/> field contains + /// an instance that we own and should Dispose. + /// </summary> + private bool relyingPartyOwned; + + /// <summary> + /// Initializes a new instance of the <see cref="OpenIdRelyingPartyControlBase"/> class. + /// </summary> + protected OpenIdRelyingPartyControlBase() { + Reporting.RecordFeatureUse(this); + } + + #region Events + + /// <summary> + /// Fired when the user has typed in their identifier, discovery was successful + /// and a login attempt is about to begin. + /// </summary> + [Description("Fired when the user has typed in their identifier, discovery was successful and a login attempt is about to begin."), Category(OpenIdCategory)] + public event EventHandler<OpenIdEventArgs> LoggingIn; + + /// <summary> + /// Fired upon completion of a successful login. + /// </summary> + [Description("Fired upon completion of a successful login."), Category(OpenIdCategory)] + public event EventHandler<OpenIdEventArgs> LoggedIn; + + /// <summary> + /// Fired when a login attempt fails. + /// </summary> + [Description("Fired when a login attempt fails."), Category(OpenIdCategory)] + public event EventHandler<OpenIdEventArgs> Failed; + + /// <summary> + /// Fired when an authentication attempt is canceled at the OpenID Provider. + /// </summary> + [Description("Fired when an authentication attempt is canceled at the OpenID Provider."), Category(OpenIdCategory)] + public event EventHandler<OpenIdEventArgs> Canceled; + + /// <summary> + /// Occurs when the <see cref="Identifier"/> property is changed. + /// </summary> + protected event EventHandler IdentifierChanged; + + #endregion + + /// <summary> + /// Gets or sets the <see cref="OpenIdRelyingParty"/> instance to use. + /// </summary> + /// <value>The default value is an <see cref="OpenIdRelyingParty"/> instance initialized according to the web.config file.</value> + /// <remarks> + /// A performance optimization would be to store off the + /// instance as a static member in your web site and set it + /// to this property in your <see cref="Control.Load">Page.Load</see> + /// event since instantiating these instances can be expensive on + /// heavily trafficked web pages. + /// </remarks> + [Browsable(false)] + public virtual OpenIdRelyingParty RelyingParty { + get { + if (this.relyingParty == null) { + this.relyingParty = this.CreateRelyingParty(); + this.ConfigureRelyingParty(this.relyingParty); + this.relyingPartyOwned = true; + } + return this.relyingParty; + } + + set { + if (this.relyingPartyOwned && this.relyingParty != null) { + this.relyingParty.Dispose(); + } + + this.relyingParty = value; + this.relyingPartyOwned = false; + } + } + + /// <summary> + /// Gets the collection of extension requests this selector should include in generated requests. + /// </summary> + [PersistenceMode(PersistenceMode.InnerProperty)] + public Collection<IOpenIdMessageExtension> Extensions { + get { + if (this.ViewState[ExtensionsViewStateKey] == null) { + var extensions = new Collection<IOpenIdMessageExtension>(); + this.ViewState[ExtensionsViewStateKey] = extensions; + return extensions; + } else { + return (Collection<IOpenIdMessageExtension>)this.ViewState[ExtensionsViewStateKey]; + } + } + } + + /// <summary> + /// Gets or sets a value indicating whether stateless mode is used. + /// </summary> + [Bindable(true), DefaultValue(StatelessDefault), Category(OpenIdCategory)] + [Description("Controls whether stateless mode is used.")] + public bool Stateless { + get { return (bool)(ViewState[StatelessViewStateKey] ?? StatelessDefault); } + set { ViewState[StatelessViewStateKey] = value; } + } + + /// <summary> + /// Gets or sets the OpenID <see cref="Realm"/> of the relying party web site. + /// </summary> + [SuppressMessage("Microsoft.Usage", "CA1806:DoNotIgnoreMethodResults", MessageId = "System.Uri", Justification = "Using Uri.ctor for validation.")] + [SuppressMessage("Microsoft.Usage", "CA1806:DoNotIgnoreMethodResults", MessageId = "DotNetOpenAuth.OpenId.Realm", Justification = "Using ctor for validation.")] + [SuppressMessage("Microsoft.Design", "CA1056:UriPropertiesShouldNotBeStrings", Justification = "Bindable property must be simple type")] + [Bindable(true), DefaultValue(RealmUrlDefault), Category(OpenIdCategory)] + [Description("The OpenID Realm of the relying party web site.")] + [UrlProperty, Editor("System.Web.UI.Design.UrlEditor, System.Design, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", typeof(UITypeEditor))] + public string RealmUrl { + get { + return (string)(ViewState[RealmUrlViewStateKey] ?? RealmUrlDefault); + } + + set { + Requires.NotNullOrEmpty(value, "value"); + + if (Page != null && !DesignMode) { + // Validate new value by trying to construct a Realm object based on it. + new Realm(OpenIdUtilities.GetResolvedRealm(this.Page, value, this.RelyingParty.Channel.GetRequestFromContext())); // throws an exception on failure. + } else { + // We can't fully test it, but it should start with either ~/ or a protocol. + if (Regex.IsMatch(value, @"^https?://")) { + new Uri(value.Replace("*.", string.Empty)); // make sure it's fully-qualified, but ignore wildcards + } else if (value.StartsWith("~/", StringComparison.Ordinal)) { + // this is valid too + } else { + throw new UriFormatException(); + } + } + ViewState[RealmUrlViewStateKey] = value; + } + } + + /// <summary> + /// Gets or sets the OpenID ReturnTo of the relying party web site. + /// </summary> + [SuppressMessage("Microsoft.Usage", "CA2234:PassSystemUriObjectsInsteadOfStrings", Justification = "Bindable property must be simple type")] + [SuppressMessage("Microsoft.Usage", "CA1806:DoNotIgnoreMethodResults", MessageId = "System.Uri", Justification = "Using Uri.ctor for validation.")] + [SuppressMessage("Microsoft.Design", "CA1056:UriPropertiesShouldNotBeStrings", Justification = "Bindable property must be simple type")] + [Bindable(true), DefaultValue(ReturnToUrlDefault), Category(OpenIdCategory)] + [Description("The OpenID ReturnTo of the relying party web site.")] + [UrlProperty, Editor("System.Web.UI.Design.UrlEditor, System.Design, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", typeof(UITypeEditor))] + public string ReturnToUrl { + get { + return (string)(this.ViewState[ReturnToUrlViewStateKey] ?? ReturnToUrlDefault); + } + + set { + if (this.Page != null && !this.DesignMode) { + // Validate new value by trying to construct a Uri based on it. + new Uri(this.RelyingParty.Channel.GetRequestFromContext().UrlBeforeRewriting, this.Page.ResolveUrl(value)); // throws an exception on failure. + } else { + // We can't fully test it, but it should start with either ~/ or a protocol. + if (Regex.IsMatch(value, @"^https?://")) { + new Uri(value); // make sure it's fully-qualified, but ignore wildcards + } else if (value.StartsWith("~/", StringComparison.Ordinal)) { + // this is valid too + } else { + throw new UriFormatException(); + } + } + + this.ViewState[ReturnToUrlViewStateKey] = value; + } + } + + /// <summary> + /// Gets or sets a value indicating whether to send a persistent cookie upon successful + /// login so the user does not have to log in upon returning to this site. + /// </summary> + [Bindable(true), DefaultValue(UsePersistentCookieDefault), Category(BehaviorCategory)] + [Description("Whether to send a persistent cookie upon successful " + + "login so the user does not have to log in upon returning to this site.")] + public virtual LogOnPersistence UsePersistentCookie { + get { return (LogOnPersistence)(this.ViewState[UsePersistentCookieViewStateKey] ?? UsePersistentCookieDefault); } + set { this.ViewState[UsePersistentCookieViewStateKey] = value; } + } + + /// <summary> + /// Gets or sets the way a completed login is communicated to the rest of the web site. + /// </summary> + [Bindable(true), DefaultValue(LogOnModeDefault), Category(BehaviorCategory)] + [Description("The way a completed login is communicated to the rest of the web site.")] + public virtual LogOnSiteNotification LogOnMode { + get { return (LogOnSiteNotification)(this.ViewState[LogOnModeViewStateKey] ?? LogOnModeDefault); } + set { this.ViewState[LogOnModeViewStateKey] = value; } + } + + /// <summary> + /// Gets or sets a value indicating when to use a popup window to complete the login experience. + /// </summary> + /// <value>The default value is <see cref="PopupBehavior.Never"/>.</value> + [Bindable(true), DefaultValue(PopupDefault), Category(BehaviorCategory)] + [Description("When to use a popup window to complete the login experience.")] + public virtual PopupBehavior Popup { + get { return (PopupBehavior)(ViewState[PopupViewStateKey] ?? PopupDefault); } + set { ViewState[PopupViewStateKey] = value; } + } + + /// <summary> + /// Gets or sets a value indicating whether to enforce on high security mode, + /// which requires the full authentication pipeline to be protected by SSL. + /// </summary> + [Bindable(true), DefaultValue(RequireSslDefault), Category(OpenIdCategory)] + [Description("Turns on high security mode, requiring the full authentication pipeline to be protected by SSL.")] + public bool RequireSsl { + get { return (bool)(ViewState[RequireSslViewStateKey] ?? RequireSslDefault); } + set { ViewState[RequireSslViewStateKey] = value; } + } + + /// <summary> + /// Gets or sets the Identifier that will be used to initiate login. + /// </summary> + [Bindable(true), Category(OpenIdCategory)] + [Description("The OpenID Identifier that this button will use to initiate login.")] + [TypeConverter(typeof(IdentifierConverter))] + public virtual Identifier Identifier { + get { + return (Identifier)ViewState[IdentifierViewStateKey]; + } + + set { + ViewState[IdentifierViewStateKey] = value; + this.OnIdentifierChanged(); + } + } + + /// <summary> + /// Gets or sets the default association preference to set on authentication requests. + /// </summary> + internal AssociationPreference AssociationPreference { get; set; } + + /// <summary> + /// Gets ancestor controls, starting with the immediate parent, and progressing to more distant ancestors. + /// </summary> + protected IEnumerable<Control> ParentControls { + get { + Control parent = this; + while ((parent = parent.Parent) != null) { + yield return parent; + } + } + } + + /// <summary> + /// Gets a value indicating whether this control is a child control of a composite OpenID control. + /// </summary> + /// <value> + /// <c>true</c> if this instance is embedded in parent OpenID control; otherwise, <c>false</c>. + /// </value> + protected bool IsEmbeddedInParentOpenIdControl { + get { return this.ParentControls.OfType<OpenIdRelyingPartyControlBase>().Any(); } + } + + /// <summary> + /// Clears any cookie set by this control to help the user on a returning visit next time. + /// </summary> + public static void LogOff() { + HttpContext.Current.Response.SetCookie(CreateIdentifierPersistingCookie(null)); + } + + /// <summary> + /// Immediately redirects to the OpenID Provider to verify the Identifier + /// provided in the text box. + /// </summary> + public void LogOn() { + IAuthenticationRequest request = this.CreateRequests().FirstOrDefault(); + ErrorUtilities.VerifyProtocol(request != null, OpenIdStrings.OpenIdEndpointNotFound); + this.LogOn(request); + } + + /// <summary> + /// Immediately redirects to the OpenID Provider to verify the Identifier + /// provided in the text box. + /// </summary> + /// <param name="request">The request.</param> + public void LogOn(IAuthenticationRequest request) { + Requires.NotNull(request, "request"); + + if (this.IsPopupAppropriate(request)) { + this.ScriptPopupWindow(request); + } else { + request.RedirectToProvider(); + } + } + + #region IPostBackEventHandler Members + + /// <summary> + /// When implemented by a class, enables a server control to process an event raised when a form is posted to the server. + /// </summary> + /// <param name="eventArgument">A <see cref="T:System.String"/> that represents an optional event argument to be passed to the event handler.</param> + void IPostBackEventHandler.RaisePostBackEvent(string eventArgument) { + this.RaisePostBackEvent(eventArgument); + } + + #endregion + + /// <summary> + /// Enables a server control to perform final clean up before it is released from memory. + /// </summary> + [SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times", Justification = "Unavoidable because base class does not expose a protected virtual Dispose(bool) method."), SuppressMessage("Microsoft.Design", "CA1063:ImplementIDisposableCorrectly", Justification = "Base class doesn't implement virtual Dispose(bool), so we must call its Dispose() method.")] + public sealed override void Dispose() { + this.Dispose(true); + base.Dispose(); + GC.SuppressFinalize(this); + } + + /// <summary> + /// Creates the authentication requests for a given user-supplied Identifier. + /// </summary> + /// <param name="identifier">The identifier to create a request for.</param> + /// <returns> + /// A sequence of authentication requests, any one of which may be + /// used to determine the user's control of the <see cref="IAuthenticationRequest.ClaimedIdentifier"/>. + /// </returns> + protected internal virtual IEnumerable<IAuthenticationRequest> CreateRequests(Identifier identifier) { + Requires.NotNull(identifier, "identifier"); + + // If this control is actually a member of another OpenID RP control, + // delegate creation of requests to the parent control. + var parentOwner = this.ParentControls.OfType<OpenIdRelyingPartyControlBase>().FirstOrDefault(); + if (parentOwner != null) { + return parentOwner.CreateRequests(identifier); + } else { + // Delegate to a private method to keep 'yield return' and Code Contract separate. + return this.CreateRequestsCore(identifier); + } + } + + /// <summary> + /// Releases unmanaged and - optionally - managed resources + /// </summary> + /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param> + protected virtual void Dispose(bool disposing) { + if (disposing) { + if (this.relyingPartyOwned && this.relyingParty != null) { + this.relyingParty.Dispose(); + this.relyingParty = null; + } + } + } + + /// <summary> + /// When implemented by a class, enables a server control to process an event raised when a form is posted to the server. + /// </summary> + /// <param name="eventArgument">A <see cref="T:System.String"/> that represents an optional event argument to be passed to the event handler.</param> + [SuppressMessage("Microsoft.Design", "CA1030:UseEventsWhereAppropriate", Justification = "Predefined signature.")] + protected virtual void RaisePostBackEvent(string eventArgument) { + } + + /// <summary> + /// Creates the authentication requests for the value set in the <see cref="Identifier"/> property. + /// </summary> + /// <returns> + /// A sequence of authentication requests, any one of which may be + /// used to determine the user's control of the <see cref="IAuthenticationRequest.ClaimedIdentifier"/>. + /// </returns> + protected IEnumerable<IAuthenticationRequest> CreateRequests() { + Requires.ValidState(this.Identifier != null, OpenIdStrings.NoIdentifierSet); + return this.CreateRequests(this.Identifier); + } + + /// <summary> + /// Raises the <see cref="E:Load"/> event. + /// </summary> + /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param> + protected override void OnLoad(EventArgs e) { + base.OnLoad(e); + + if (Page.IsPostBack) { + // OpenID responses NEVER come in the form of a postback. + return; + } + + if (this.Identifier == null) { + this.TryPresetIdentifierWithCookie(); + } + + // Take an unreliable sneek peek to see if we're in a popup and an OpenID + // assertion is coming in. We shouldn't process assertions in a popup window. + if (this.Page.Request.QueryString[UIPopupCallbackKey] == "1" && this.Page.Request.QueryString[UIPopupCallbackParentKey] == null) { + // We're in a popup window. We need to close it and pass the + // message back to the parent window for processing. + this.ScriptClosingPopupOrIFrame(); + return; // don't do any more processing on it now + } + + // Only sniff for an OpenID response if it is targeted at this control. + // Note that Stateless mode causes no receiver to be indicated, and + // we want to handle that, but only if there isn't a parent control that + // will be handling that. + string receiver = this.Page.Request.QueryString[ReturnToReceivingControlId] ?? this.Page.Request.Form[ReturnToReceivingControlId]; + if (receiver == this.ClientID || (receiver == null && !this.IsEmbeddedInParentOpenIdControl)) { + var response = this.RelyingParty.GetResponse(); + Logger.Controls.DebugFormat( + "The {0} control checked for an authentication response and found: {1}", + this.ID, + response != null ? response.Status.ToString() : "nothing"); + this.ProcessResponse(response); + } + } + + /// <summary> + /// Notifies the user agent via an AJAX response of a completed authentication attempt. + /// </summary> + protected virtual void ScriptClosingPopupOrIFrame() { + this.RelyingParty.ProcessResponseFromPopup(); + } + + /// <summary> + /// Called when the <see cref="Identifier"/> property is changed. + /// </summary> + protected virtual void OnIdentifierChanged() { + var identifierChanged = this.IdentifierChanged; + if (identifierChanged != null) { + identifierChanged(this, EventArgs.Empty); + } + } + + /// <summary> + /// Processes the response. + /// </summary> + /// <param name="response">The response.</param> + protected virtual void ProcessResponse(IAuthenticationResponse response) { + if (response == null) { + return; + } + string persistentString = response.GetUntrustedCallbackArgument(UsePersistentCookieCallbackKey); + if (persistentString != null) { + this.UsePersistentCookie = (LogOnPersistence)Enum.Parse(typeof(LogOnPersistence), persistentString); + } + + switch (response.Status) { + case AuthenticationStatus.Authenticated: + this.OnLoggedIn(response); + break; + case AuthenticationStatus.Canceled: + this.OnCanceled(response); + break; + case AuthenticationStatus.Failed: + this.OnFailed(response); + break; + case AuthenticationStatus.SetupRequired: + case AuthenticationStatus.ExtensionsOnly: + default: + // The NotApplicable (extension-only assertion) is NOT one that we support + // in this control because that scenario is primarily interesting to RPs + // that are asking a specific OP, and it is not user-initiated as this textbox + // is designed for. + throw new InvalidOperationException(MessagingStrings.UnexpectedMessageReceivedOfMany); + } + } + + /// <summary> + /// Raises the <see cref="E:System.Web.UI.Control.PreRender"/> event. + /// </summary> + /// <param name="e">An <see cref="T:System.EventArgs"/> object that contains the event data.</param> + protected override void OnPreRender(EventArgs e) { + base.OnPreRender(e); + + this.Page.ClientScript.RegisterClientScriptResource(typeof(OpenIdRelyingPartyControlBase), EmbeddedJavascriptResource); + } + + /// <summary> + /// Fires the <see cref="LoggedIn"/> event. + /// </summary> + /// <param name="response">The response.</param> + protected virtual void OnLoggedIn(IAuthenticationResponse response) { + Requires.NotNull(response, "response"); + Requires.True(response.Status == AuthenticationStatus.Authenticated, "response"); + + var loggedIn = this.LoggedIn; + OpenIdEventArgs args = new OpenIdEventArgs(response); + if (loggedIn != null) { + loggedIn(this, args); + } + + if (!args.Cancel) { + if (this.UsePersistentCookie == LogOnPersistence.SessionAndPersistentIdentifier) { + Page.Response.SetCookie(CreateIdentifierPersistingCookie(response)); + } + + switch (this.LogOnMode) { + case LogOnSiteNotification.FormsAuthentication: + FormsAuthentication.RedirectFromLoginPage(response.ClaimedIdentifier, this.UsePersistentCookie == LogOnPersistence.PersistentAuthentication); + break; + case LogOnSiteNotification.None: + default: + break; + } + } + } + + /// <summary> + /// Fires the <see cref="LoggingIn"/> event. + /// </summary> + /// <param name="request">The request.</param> + /// <returns> + /// Returns whether the login should proceed. False if some event handler canceled the request. + /// </returns> + protected virtual bool OnLoggingIn(IAuthenticationRequest request) { + Requires.NotNull(request, "request"); + + EventHandler<OpenIdEventArgs> loggingIn = this.LoggingIn; + + OpenIdEventArgs args = new OpenIdEventArgs(request); + if (loggingIn != null) { + loggingIn(this, args); + } + + return !args.Cancel; + } + + /// <summary> + /// Fires the <see cref="Canceled"/> event. + /// </summary> + /// <param name="response">The response.</param> + protected virtual void OnCanceled(IAuthenticationResponse response) { + Requires.NotNull(response, "response"); + Requires.True(response.Status == AuthenticationStatus.Canceled, "response"); + + var canceled = this.Canceled; + if (canceled != null) { + canceled(this, new OpenIdEventArgs(response)); + } + } + + /// <summary> + /// Fires the <see cref="Failed"/> event. + /// </summary> + /// <param name="response">The response.</param> + protected virtual void OnFailed(IAuthenticationResponse response) { + Requires.NotNull(response, "response"); + Requires.True(response.Status == AuthenticationStatus.Failed, "response"); + + var failed = this.Failed; + if (failed != null) { + failed(this, new OpenIdEventArgs(response)); + } + } + + /// <summary> + /// Creates the relying party instance used to generate authentication requests. + /// </summary> + /// <returns>The instantiated relying party.</returns> + protected OpenIdRelyingParty CreateRelyingParty() { + IOpenIdApplicationStore store = this.Stateless ? null : OpenIdElement.Configuration.RelyingParty.ApplicationStore.CreateInstance(OpenIdRelyingParty.HttpApplicationStore); + return this.CreateRelyingParty(store); + } + + /// <summary> + /// Creates the relying party instance used to generate authentication requests. + /// </summary> + /// <param name="store">The store to pass to the relying party constructor.</param> + /// <returns>The instantiated relying party.</returns> + protected virtual OpenIdRelyingParty CreateRelyingParty(IOpenIdApplicationStore store) { + return new OpenIdRelyingParty(store); + } + + /// <summary> + /// Configures the relying party. + /// </summary> + /// <param name="relyingParty">The relying party.</param> + [SuppressMessage("Microsoft.Maintainability", "CA1500:VariableNamesShouldNotMatchFieldNames", MessageId = "relyingParty", Justification = "This makes it possible for overrides to see the value before it is set on a field.")] + protected virtual void ConfigureRelyingParty(OpenIdRelyingParty relyingParty) { + Requires.NotNull(relyingParty, "relyingParty"); + + // Only set RequireSsl to true, as we don't want to override + // a .config setting of true with false. + if (this.RequireSsl) { + relyingParty.SecuritySettings.RequireSsl = true; + } + } + + /// <summary> + /// Detects whether a popup window should be used to show the Provider's UI. + /// </summary> + /// <param name="request">The request.</param> + /// <returns> + /// <c>true</c> if a popup should be used; <c>false</c> otherwise. + /// </returns> + protected virtual bool IsPopupAppropriate(IAuthenticationRequest request) { + Requires.NotNull(request, "request"); + + switch (this.Popup) { + case PopupBehavior.Never: + return false; + case PopupBehavior.Always: + return true; + case PopupBehavior.IfProviderSupported: + return request.DiscoveryResult.IsExtensionSupported<UIRequest>(); + default: + throw ErrorUtilities.ThrowInternal("Unexpected value for Popup property."); + } + } + + /// <summary> + /// Adds attributes to an HTML <A> tag that will be written by the caller using + /// <see cref="HtmlTextWriter.RenderBeginTag(HtmlTextWriterTag)"/> after this method. + /// </summary> + /// <param name="writer">The HTML writer.</param> + /// <param name="request">The outgoing authentication request.</param> + /// <param name="windowStatus">The text to try to display in the status bar on mouse hover.</param> + protected void RenderOpenIdMessageTransmissionAsAnchorAttributes(HtmlTextWriter writer, IAuthenticationRequest request, string windowStatus) { + Requires.NotNull(writer, "writer"); + Requires.NotNull(request, "request"); + + // We render a standard HREF attribute for non-javascript browsers. + writer.AddAttribute(HtmlTextWriterAttribute.Href, request.RedirectingResponse.GetDirectUriRequest(this.RelyingParty.Channel).AbsoluteUri); + + // And for the Javascript ones we do the extra work to use form POST where necessary. + writer.AddAttribute(HtmlTextWriterAttribute.Onclick, this.CreateGetOrPostAHrefValue(request) + " return false;"); + + writer.AddStyleAttribute(HtmlTextWriterStyle.Cursor, "pointer"); + if (!string.IsNullOrEmpty(windowStatus)) { + writer.AddAttribute("onMouseOver", "window.status = " + MessagingUtilities.GetSafeJavascriptValue(windowStatus)); + writer.AddAttribute("onMouseOut", "window.status = null"); + } + } + + /// <summary> + /// Creates the identifier-persisting cookie, either for saving or deleting. + /// </summary> + /// <param name="response">The positive authentication response; or <c>null</c> to clear the cookie.</param> + /// <returns>An persistent cookie.</returns> + private static HttpCookie CreateIdentifierPersistingCookie(IAuthenticationResponse response) { + HttpCookie cookie = new HttpCookie(PersistentIdentifierCookieName); + bool clearingCookie = false; + + // We'll try to store whatever it was the user originally typed in, but fallback + // to the final claimed_id. + if (response != null && response.Status == AuthenticationStatus.Authenticated) { + var positiveResponse = (PositiveAuthenticationResponse)response; + + // We must escape the value because XRIs start with =, and any leading '=' gets dropped (by ASP.NET?) + cookie.Value = Uri.EscapeDataString(positiveResponse.Endpoint.UserSuppliedIdentifier ?? response.ClaimedIdentifier); + } else { + clearingCookie = true; + cookie.Value = string.Empty; + if (HttpContext.Current.Request.Browser["supportsEmptyStringInCookieValue"] == "false") { + cookie.Value = "NoCookie"; + } + } + + if (clearingCookie) { + // mark the cookie has having already expired to cause the user agent to delete + // the old persisted cookie. + cookie.Expires = DateTime.Now.Subtract(TimeSpan.FromDays(1)); + } else { + // Make the cookie persistent by setting an expiration date + cookie.Expires = DateTime.Now + PersistentIdentifierTimeToLiveDefault; + } + + return cookie; + } + + /// <summary> + /// Creates the authentication requests for a given user-supplied Identifier. + /// </summary> + /// <param name="identifier">The identifier to create a request for.</param> + /// <returns> + /// A sequence of authentication requests, any one of which may be + /// used to determine the user's control of the <see cref="IAuthenticationRequest.ClaimedIdentifier"/>. + /// </returns> + private IEnumerable<IAuthenticationRequest> CreateRequestsCore(Identifier identifier) { + ErrorUtilities.VerifyArgumentNotNull(identifier, "identifier"); // NO CODE CONTRACTS! (yield return used here) + IEnumerable<IAuthenticationRequest> requests; + + // Approximate the returnTo (either based on the customize property or the page URL) + // so we can use it to help with Realm resolution. + Uri returnToApproximation; + if (this.ReturnToUrl != null) { + string returnToResolvedPath = this.ResolveUrl(this.ReturnToUrl); + returnToApproximation = new Uri(this.RelyingParty.Channel.GetRequestFromContext().UrlBeforeRewriting, returnToResolvedPath); + } else { + returnToApproximation = this.Page.Request.Url; + } + + // Resolve the trust root, and swap out the scheme and port if necessary to match the + // return_to URL, since this match is required by OpenID, and the consumer app + // may be using HTTP at some times and HTTPS at others. + UriBuilder realm = OpenIdUtilities.GetResolvedRealm(this.Page, this.RealmUrl, this.RelyingParty.Channel.GetRequestFromContext()); + realm.Scheme = returnToApproximation.Scheme; + realm.Port = returnToApproximation.Port; + + // Initiate OpenID request + // We use TryParse here to avoid throwing an exception which + // might slip through our validator control if it is disabled. + Realm typedRealm = new Realm(realm); + if (string.IsNullOrEmpty(this.ReturnToUrl)) { + requests = this.RelyingParty.CreateRequests(identifier, typedRealm); + } else { + // Since the user actually gave us a return_to value, + // the "approximation" is exactly what we want. + requests = this.RelyingParty.CreateRequests(identifier, typedRealm, returnToApproximation); + } + + // Some OPs may be listed multiple times (one with HTTPS and the other with HTTP, for example). + // Since we're gathering OPs to try one after the other, just take the first choice of each OP + // and don't try it multiple times. + requests = requests.Distinct(DuplicateRequestedHostsComparer.Instance); + + // Configure each generated request. + foreach (var req in requests) { + if (this.IsPopupAppropriate(req)) { + // Inform ourselves in return_to that we're in a popup. + req.SetUntrustedCallbackArgument(UIPopupCallbackKey, "1"); + + if (req.DiscoveryResult.IsExtensionSupported<UIRequest>()) { + // Inform the OP that we'll be using a popup window consistent with the UI extension. + // But beware that the extension MAY have already been added if we're using + // the OpenIdAjaxRelyingParty class. + if (!((AuthenticationRequest)req).Extensions.OfType<UIRequest>().Any()) { + req.AddExtension(new UIRequest()); + } + + // Provide a hint for the client javascript about whether the OP supports the UI extension. + // This is so the window can be made the correct size for the extension. + // If the OP doesn't advertise support for the extension, the javascript will use + // a bigger popup window. + req.SetUntrustedCallbackArgument(PopupUISupportedJSHint, "1"); + } + } + + // Add the extensions injected into the control. + foreach (var extension in this.Extensions) { + req.AddExtension(extension); + } + + // Add state that needs to survive across the redirect, but at this point + // only save those properties that are not expected to be changed by a + // LoggingIn event handler. + req.SetUntrustedCallbackArgument(ReturnToReceivingControlId, this.ClientID); + + // Apply the control's association preference to this auth request, but only if + // it is less demanding (greater ordinal value) than the existing one. + // That way, we protect against retrying an association that was already attempted. + var authReq = (AuthenticationRequest)req; + if (authReq.AssociationPreference < this.AssociationPreference) { + authReq.AssociationPreference = this.AssociationPreference; + } + + if (this.OnLoggingIn(req)) { + // We save this property after firing OnLoggingIn so that the host page can + // change its value and have that value saved. + req.SetUntrustedCallbackArgument(UsePersistentCookieCallbackKey, this.UsePersistentCookie.ToString()); + + yield return req; + } + } + } + + /// <summary> + /// Gets the javascript to executee to redirect or POST an OpenID message to a remote party. + /// </summary> + /// <param name="request">The authentication request to send.</param> + /// <returns>The javascript that should execute.</returns> + private string CreateGetOrPostAHrefValue(IAuthenticationRequest request) { + Requires.NotNull(request, "request"); + + Uri directUri = request.RedirectingResponse.GetDirectUriRequest(this.RelyingParty.Channel); + return "window.dnoa_internal.GetOrPost(" + MessagingUtilities.GetSafeJavascriptValue(directUri.AbsoluteUri) + ");"; + } + + /// <summary> + /// Wires the return page to immediately display a popup window with the Provider in it. + /// </summary> + /// <param name="request">The request.</param> + private void ScriptPopupWindow(IAuthenticationRequest request) { + Requires.NotNull(request, "request"); + Requires.ValidState(this.RelyingParty != null); + + StringBuilder startupScript = new StringBuilder(); + + // Add a callback function that the popup window can call on this, the + // parent window, to pass back the authentication result. + startupScript.AppendLine("window.dnoa_internal = {};"); + startupScript.AppendLine("window.dnoa_internal.processAuthorizationResult = function(uri) { window.location = uri; };"); + startupScript.AppendLine("window.dnoa_internal.popupWindow = function() {"); + startupScript.AppendFormat( + @"\tvar openidPopup = {0}", + OpenId.RelyingParty.Extensions.UI.UIUtilities.GetWindowPopupScript(this.RelyingParty, request, "openidPopup")); + startupScript.AppendLine("};"); + + this.Page.ClientScript.RegisterClientScriptBlock(this.GetType(), "loginPopup", startupScript.ToString(), true); + } + + /// <summary> + /// Tries to preset the <see cref="Identifier"/> property based on a persistent + /// cookie on the browser. + /// </summary> + /// <returns> + /// A value indicating whether the <see cref="Identifier"/> property was + /// successfully preset to some non-empty value. + /// </returns> + private bool TryPresetIdentifierWithCookie() { + HttpCookie cookie = this.Page.Request.Cookies[PersistentIdentifierCookieName]; + if (cookie != null) { + this.Identifier = Uri.UnescapeDataString(cookie.Value); + return true; + } + + return false; + } + } +} diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingPartyControlBase.js b/src/DotNetOpenAuth.OpenId.RelyingParty.UI/OpenId/RelyingParty/OpenIdRelyingPartyControlBase.js index 58b283d..58b283d 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingPartyControlBase.js +++ b/src/DotNetOpenAuth.OpenId.RelyingParty.UI/OpenId/RelyingParty/OpenIdRelyingPartyControlBase.js diff --git a/src/DotNetOpenAuth.OpenId.RelyingParty.UI/OpenId/RelyingParty/OpenIdSelector.cs b/src/DotNetOpenAuth.OpenId.RelyingParty.UI/OpenId/RelyingParty/OpenIdSelector.cs new file mode 100644 index 0000000..e666e4e --- /dev/null +++ b/src/DotNetOpenAuth.OpenId.RelyingParty.UI/OpenId/RelyingParty/OpenIdSelector.cs @@ -0,0 +1,385 @@ +//----------------------------------------------------------------------- +// <copyright file="OpenIdSelector.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +[assembly: System.Web.UI.WebResource(DotNetOpenAuth.OpenId.RelyingParty.OpenIdSelector.EmbeddedScriptResourceName, "text/javascript")] +[assembly: System.Web.UI.WebResource(DotNetOpenAuth.OpenId.RelyingParty.OpenIdSelector.EmbeddedStylesheetResourceName, "text/css")] + +namespace DotNetOpenAuth.OpenId.RelyingParty { + using System; + using System.Collections.Generic; + using System.Collections.ObjectModel; + using System.ComponentModel; + using System.Diagnostics.Contracts; + using System.Globalization; + using System.IdentityModel.Claims; + using System.Linq; + using System.Text; + using System.Web; + using System.Web.UI; + using System.Web.UI.HtmlControls; + using System.Web.UI.WebControls; + using DotNetOpenAuth.ComponentModel; + using DotNetOpenAuth.Messaging; + + /// <summary> + /// An ASP.NET control that provides a user-friendly way of logging into a web site using OpenID. + /// </summary> + [ToolboxData("<{0}:OpenIdSelector runat=\"server\"></{0}:OpenIdSelector>")] + public class OpenIdSelector : OpenIdRelyingPartyAjaxControlBase { + /// <summary> + /// The name of the manifest stream containing the OpenIdButtonPanel.js file. + /// </summary> + internal const string EmbeddedScriptResourceName = Util.DefaultNamespace + ".OpenId.RelyingParty.OpenIdSelector.js"; + + /// <summary> + /// The name of the manifest stream containing the OpenIdButtonPanel.css file. + /// </summary> + internal const string EmbeddedStylesheetResourceName = Util.DefaultNamespace + ".OpenId.RelyingParty.OpenIdSelector.css"; + + /// <summary> + /// The substring to append to the end of the id or name of this control to form the + /// unique name of the hidden field that will carry the positive assertion on postback. + /// </summary> + private const string AuthDataFormKeySuffix = "_openidAuthData"; + + #region ViewState keys + + /// <summary> + /// The viewstate key to use for storing the value of the <see cref="Buttons"/> property. + /// </summary> + private const string ButtonsViewStateKey = "Buttons"; + + /// <summary> + /// The viewstate key to use for storing the value of the <see cref="AuthenticatedAsToolTip"/> property. + /// </summary> + private const string AuthenticatedAsToolTipViewStateKey = "AuthenticatedAsToolTip"; + + #endregion + + #region Property defaults + + /// <summary> + /// The default value for the <see cref="AuthenticatedAsToolTip"/> property. + /// </summary> + private const string AuthenticatedAsToolTipDefault = "We recognize you!"; + + #endregion + + /// <summary> + /// The OpenIdAjaxTextBox that remains hidden until the user clicks the OpenID button. + /// </summary> + private OpenIdAjaxTextBox textBox; + + /// <summary> + /// The hidden field that will transmit the positive assertion to the RP. + /// </summary> + private HiddenField positiveAssertionField; + + /// <summary> + /// Initializes a new instance of the <see cref="OpenIdSelector"/> class. + /// </summary> + public OpenIdSelector() { + } + + /// <summary> + /// Gets the text box where applicable. + /// </summary> + public OpenIdAjaxTextBox TextBox { + get { + this.EnsureChildControlsAreCreatedSafe(); + return this.textBox; + } + } + + /// <summary> + /// Gets or sets the maximum number of OpenID Providers to simultaneously try to authenticate with. + /// </summary> + [Browsable(true), DefaultValue(OpenIdAjaxTextBox.ThrottleDefault), Category(BehaviorCategory)] + [Description("The maximum number of OpenID Providers to simultaneously try to authenticate with.")] + public int Throttle { + get { + this.EnsureChildControlsAreCreatedSafe(); + return this.textBox.Throttle; + } + + set { + this.EnsureChildControlsAreCreatedSafe(); + this.textBox.Throttle = value; + } + } + + /// <summary> + /// Gets or sets the time duration for the AJAX control to wait for an OP to respond before reporting failure to the user. + /// </summary> + [Browsable(true), DefaultValue(typeof(TimeSpan), "00:00:08"), Category(BehaviorCategory)] + [Description("The time duration for the AJAX control to wait for an OP to respond before reporting failure to the user.")] + public TimeSpan Timeout { + get { + this.EnsureChildControlsAreCreatedSafe(); + return this.textBox.Timeout; + } + + set { + this.EnsureChildControlsAreCreatedSafe(); + this.textBox.Timeout = value; + } + } + + /// <summary> + /// Gets or sets the tool tip text that appears on the green checkmark when authentication succeeds. + /// </summary> + [Bindable(true), DefaultValue(AuthenticatedAsToolTipDefault), Localizable(true), Category(AppearanceCategory)] + [Description("The tool tip text that appears on the green checkmark when authentication succeeds.")] + public string AuthenticatedAsToolTip { + get { return (string)(this.ViewState[AuthenticatedAsToolTipViewStateKey] ?? AuthenticatedAsToolTipDefault); } + set { this.ViewState[AuthenticatedAsToolTipViewStateKey] = value ?? string.Empty; } + } + + /// <summary> + /// Gets or sets a value indicating whether the Yahoo! User Interface Library (YUI) + /// will be downloaded in order to provide a login split button. + /// </summary> + /// <value> + /// <c>true</c> to use a split button; otherwise, <c>false</c> to use a standard HTML button + /// or a split button by downloading the YUI library yourself on the hosting web page. + /// </value> + /// <remarks> + /// The split button brings in about 180KB of YUI javascript dependencies. + /// </remarks> + [Bindable(true), DefaultValue(OpenIdAjaxTextBox.DownloadYahooUILibraryDefault), Category(BehaviorCategory)] + [Description("Whether a split button will be used for the \"log in\" when the user provides an identifier that delegates to more than one Provider.")] + public bool DownloadYahooUILibrary { + get { + this.EnsureChildControlsAreCreatedSafe(); + return this.textBox.DownloadYahooUILibrary; + } + + set { + this.EnsureChildControlsAreCreatedSafe(); + this.textBox.DownloadYahooUILibrary = value; + } + } + + /// <summary> + /// Gets the collection of buttons this selector should render to the browser. + /// </summary> + [PersistenceMode(PersistenceMode.InnerProperty)] + public Collection<SelectorButton> Buttons { + get { + if (this.ViewState[ButtonsViewStateKey] == null) { + var providers = new Collection<SelectorButton>(); + this.ViewState[ButtonsViewStateKey] = providers; + return providers; + } else { + return (Collection<SelectorButton>)this.ViewState[ButtonsViewStateKey]; + } + } + } + + /// <summary> + /// Gets a <see cref="T:System.Web.UI.ControlCollection"/> object that represents the child controls for a specified server control in the UI hierarchy. + /// </summary> + /// <returns> + /// The collection of child controls for the specified server control. + /// </returns> + public override ControlCollection Controls { + get { + this.EnsureChildControls(); + return base.Controls; + } + } + + /// <summary> + /// Gets the name of the open id auth data form key (for the value as stored at the user agent as a FORM field). + /// </summary> + /// <value> + /// Usually a concatenation of the control's name and <c>"_openidAuthData"</c>. + /// </value> + protected override string OpenIdAuthDataFormKey { + get { return this.UniqueID + AuthDataFormKeySuffix; } + } + + /// <summary> + /// Gets a value indicating whether some button in the selector will want + /// to display the <see cref="OpenIdAjaxTextBox"/> control. + /// </summary> + protected virtual bool OpenIdTextBoxVisible { + get { return this.Buttons.OfType<SelectorOpenIdButton>().Any(); } + } + + /// <summary> + /// Releases unmanaged and - optionally - managed resources + /// </summary> + /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param> + protected override void Dispose(bool disposing) { + if (disposing) { + foreach (var button in this.Buttons.OfType<IDisposable>()) { + button.Dispose(); + } + } + + base.Dispose(disposing); + } + + /// <summary> + /// Called by the ASP.NET page framework to notify server controls that use composition-based implementation to create any child controls they contain in preparation for posting back or rendering. + /// </summary> + protected override void CreateChildControls() { + this.EnsureChildControlsAreCreatedSafe(); + + base.CreateChildControls(); + + // Now do the ID specific work. + this.EnsureID(); + ErrorUtilities.VerifyInternal(!string.IsNullOrEmpty(this.UniqueID), "Control.EnsureID() failed to give us a unique ID. Try setting an ID on the OpenIdSelector control. But please also file this bug with the project owners."); + + this.Controls.Add(this.textBox); + + this.positiveAssertionField.ID = this.ID + AuthDataFormKeySuffix; + this.Controls.Add(this.positiveAssertionField); + } + + /// <summary> + /// Ensures that the child controls have been built, but doesn't set control + /// properties that require executing <see cref="Control.EnsureID"/> in order to avoid + /// certain initialization order problems. + /// </summary> + /// <remarks> + /// We don't just call EnsureChildControls() and then set the property on + /// this.textBox itself because (apparently) setting this property in the ASPX + /// page and thus calling this EnsureID() via EnsureChildControls() this early + /// results in no ID. + /// </remarks> + protected virtual void EnsureChildControlsAreCreatedSafe() { + // If we've already created the child controls, this method is a no-op. + if (this.textBox != null) { + return; + } + + this.textBox = new OpenIdAjaxTextBox(); + this.textBox.ID = "openid_identifier"; + this.textBox.HookFormSubmit = false; + this.textBox.ShowLogOnPostBackButton = true; + + this.positiveAssertionField = new HiddenField(); + } + + /// <summary> + /// Raises the <see cref="E:System.Web.UI.Control.Init"/> event. + /// </summary> + /// <param name="e">An <see cref="T:System.EventArgs"/> object that contains the event data.</param> + protected override void OnInit(EventArgs e) { + base.OnInit(e); + + // We force child control creation here so that they can get postback events. + EnsureChildControls(); + } + + /// <summary> + /// Raises the <see cref="E:System.Web.UI.Control.PreRender"/> event. + /// </summary> + /// <param name="e">An <see cref="T:System.EventArgs"/> object that contains the event data.</param> + protected override void OnPreRender(EventArgs e) { + base.OnPreRender(e); + + this.EnsureValidButtons(); + + var css = new HtmlLink(); + try { + css.Href = this.Page.ClientScript.GetWebResourceUrl(this.GetType(), EmbeddedStylesheetResourceName); + css.Attributes["rel"] = "stylesheet"; + css.Attributes["type"] = "text/css"; + ErrorUtilities.VerifyHost(this.Page.Header != null, OpenIdStrings.HeadTagMustIncludeRunatServer); + this.Page.Header.Controls.AddAt(0, css); // insert at top so host page can override + } catch { + css.Dispose(); + throw; + } + + // Import the .js file where most of the code is. + this.Page.ClientScript.RegisterClientScriptResource(typeof(OpenIdSelector), EmbeddedScriptResourceName); + + // Provide javascript with a way to post the login assertion. + const string PostLoginAssertionMethodName = "postLoginAssertion"; + const string PositiveAssertionParameterName = "positiveAssertion"; + const string ScriptFormat = @"window.{2} = function({0}) {{ + $('#{3}')[0].setAttribute('value', {0}); + {1}; +}};"; + string script = string.Format( + CultureInfo.InvariantCulture, + ScriptFormat, + PositiveAssertionParameterName, + this.Page.ClientScript.GetPostBackEventReference(this, null, false), + PostLoginAssertionMethodName, + this.positiveAssertionField.ClientID); + this.Page.ClientScript.RegisterClientScriptBlock(this.GetType(), "Postback", script, true); + + this.PreloadDiscovery(this.Buttons.OfType<SelectorProviderButton>().Select(op => op.OPIdentifier).Where(id => id != null)); + this.textBox.Visible = this.OpenIdTextBoxVisible; + } + + /// <summary> + /// Sends server control content to a provided <see cref="T:System.Web.UI.HtmlTextWriter"/> object, which writes the content to be rendered on the client. + /// </summary> + /// <param name="writer">The <see cref="T:System.Web.UI.HtmlTextWriter"/> object that receives the server control content.</param> + protected override void Render(HtmlTextWriter writer) { + Contract.Assume(writer != null, "Missing contract"); + writer.AddAttribute(HtmlTextWriterAttribute.Class, "OpenIdProviders"); + writer.RenderBeginTag(HtmlTextWriterTag.Ul); + + foreach (var button in this.Buttons) { + button.RenderLeadingAttributes(writer); + + writer.RenderBeginTag(HtmlTextWriterTag.Li); + + writer.AddAttribute(HtmlTextWriterAttribute.Href, "#"); + writer.RenderBeginTag(HtmlTextWriterTag.A); + + writer.RenderBeginTag(HtmlTextWriterTag.Div); + writer.RenderBeginTag(HtmlTextWriterTag.Div); + + button.RenderButtonContent(writer, this); + + writer.RenderEndTag(); // </div> + + writer.AddAttribute(HtmlTextWriterAttribute.Class, "ui-widget-overlay"); + writer.RenderBeginTag(HtmlTextWriterTag.Div); + writer.RenderEndTag(); + + writer.RenderEndTag(); // </div> + writer.RenderEndTag(); // </a> + writer.RenderEndTag(); // </li> + } + + writer.RenderEndTag(); // </ul> + + if (this.textBox.Visible) { + writer.AddStyleAttribute(HtmlTextWriterStyle.Display, "none"); + writer.AddAttribute(HtmlTextWriterAttribute.Id, "OpenIDForm"); + writer.RenderBeginTag(HtmlTextWriterTag.Div); + + this.textBox.RenderControl(writer); + + writer.RenderEndTag(); // </div> + } + + this.positiveAssertionField.RenderControl(writer); + } + + /// <summary> + /// Ensures the <see cref="Buttons"/> collection has a valid set of buttons. + /// </summary> + private void EnsureValidButtons() { + foreach (var button in this.Buttons) { + button.EnsureValid(); + } + + // Also make sure that there are appropriate numbers of each type of button. + // TODO: code here + } + } +} diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdSelector.css b/src/DotNetOpenAuth.OpenId.RelyingParty.UI/OpenId/RelyingParty/OpenIdSelector.css index e7eafc7..e7eafc7 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdSelector.css +++ b/src/DotNetOpenAuth.OpenId.RelyingParty.UI/OpenId/RelyingParty/OpenIdSelector.css diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdSelector.js b/src/DotNetOpenAuth.OpenId.RelyingParty.UI/OpenId/RelyingParty/OpenIdSelector.js index 297ea23..297ea23 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdSelector.js +++ b/src/DotNetOpenAuth.OpenId.RelyingParty.UI/OpenId/RelyingParty/OpenIdSelector.js diff --git a/src/DotNetOpenAuth.OpenId.RelyingParty.UI/OpenId/RelyingParty/OpenIdTextBox.cs b/src/DotNetOpenAuth.OpenId.RelyingParty.UI/OpenId/RelyingParty/OpenIdTextBox.cs new file mode 100644 index 0000000..1dea39a --- /dev/null +++ b/src/DotNetOpenAuth.OpenId.RelyingParty.UI/OpenId/RelyingParty/OpenIdTextBox.cs @@ -0,0 +1,708 @@ +//----------------------------------------------------------------------- +// <copyright file="OpenIdTextBox.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +[assembly: System.Web.UI.WebResource(DotNetOpenAuth.OpenId.RelyingParty.OpenIdTextBox.EmbeddedLogoResourceName, "image/png")] + +#pragma warning disable 0809 // marking inherited, unsupported properties as obsolete to discourage their use + +namespace DotNetOpenAuth.OpenId.RelyingParty { + using System; + using System.Collections.Generic; + using System.Collections.Specialized; + using System.ComponentModel; + using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; + using System.Diagnostics.Contracts; + using System.Drawing.Design; + using System.Globalization; + using System.Net; + using System.Text; + using System.Text.RegularExpressions; + using System.Web; + using System.Web.Security; + using System.Web.UI; + using System.Web.UI.WebControls; + using DotNetOpenAuth.Configuration; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OpenId.Extensions.SimpleRegistration; + using DotNetOpenAuth.OpenId.Extensions.UI; + + /// <summary> + /// An ASP.NET control that provides a minimal text box that is OpenID-aware. + /// </summary> + /// <remarks> + /// This control offers greater UI flexibility than the <see cref="OpenIdLogin"/> + /// control, but requires more work to be done by the hosting web site to + /// assemble a complete login experience. + /// </remarks> + [DefaultProperty("Text"), ValidationProperty("Text")] + [ToolboxData("<{0}:OpenIdTextBox runat=\"server\" />")] + public class OpenIdTextBox : OpenIdRelyingPartyControlBase, IEditableTextControl, ITextControl, IPostBackDataHandler { + /// <summary> + /// The name of the manifest stream containing the + /// OpenID logo that is placed inside the text box. + /// </summary> + internal const string EmbeddedLogoResourceName = Util.DefaultNamespace + ".OpenId.RelyingParty.openid_login.png"; + + /// <summary> + /// Default value for <see cref="TabIndex"/> property. + /// </summary> + protected const short TabIndexDefault = 0; + + #region Property category constants + + /// <summary> + /// The "Simple Registration" category for properties. + /// </summary> + private const string ProfileCategory = "Simple Registration"; + + #endregion + + #region Property viewstate keys + + /// <summary> + /// The viewstate key to use for the <see cref="RequestEmail"/> property. + /// </summary> + private const string RequestEmailViewStateKey = "RequestEmail"; + + /// <summary> + /// The viewstate key to use for the <see cref="RequestNickname"/> property. + /// </summary> + private const string RequestNicknameViewStateKey = "RequestNickname"; + + /// <summary> + /// The viewstate key to use for the <see cref="RequestPostalCode"/> property. + /// </summary> + private const string RequestPostalCodeViewStateKey = "RequestPostalCode"; + + /// <summary> + /// The viewstate key to use for the <see cref="RequestCountry"/> property. + /// </summary> + private const string RequestCountryViewStateKey = "RequestCountry"; + + /// <summary> + /// The viewstate key to use for the <see cref="RequestLanguage"/> property. + /// </summary> + private const string RequestLanguageViewStateKey = "RequestLanguage"; + + /// <summary> + /// The viewstate key to use for the <see cref="RequestTimeZone"/> property. + /// </summary> + private const string RequestTimeZoneViewStateKey = "RequestTimeZone"; + + /// <summary> + /// The viewstate key to use for the <see cref="EnableRequestProfile"/> property. + /// </summary> + private const string EnableRequestProfileViewStateKey = "EnableRequestProfile"; + + /// <summary> + /// The viewstate key to use for the <see cref="PolicyUrl"/> property. + /// </summary> + private const string PolicyUrlViewStateKey = "PolicyUrl"; + + /// <summary> + /// The viewstate key to use for the <see cref="RequestFullName"/> property. + /// </summary> + private const string RequestFullNameViewStateKey = "RequestFullName"; + + /// <summary> + /// The viewstate key to use for the <see cref="PresetBorder"/> property. + /// </summary> + private const string PresetBorderViewStateKey = "PresetBorder"; + + /// <summary> + /// The viewstate key to use for the <see cref="ShowLogo"/> property. + /// </summary> + private const string ShowLogoViewStateKey = "ShowLogo"; + + /// <summary> + /// The viewstate key to use for the <see cref="RequestGender"/> property. + /// </summary> + private const string RequestGenderViewStateKey = "RequestGender"; + + /// <summary> + /// The viewstate key to use for the <see cref="RequestBirthDate"/> property. + /// </summary> + private const string RequestBirthDateViewStateKey = "RequestBirthDate"; + + /// <summary> + /// The viewstate key to use for the <see cref="CssClass"/> property. + /// </summary> + private const string CssClassViewStateKey = "CssClass"; + + /// <summary> + /// The viewstate key to use for the <see cref="MaxLength"/> property. + /// </summary> + private const string MaxLengthViewStateKey = "MaxLength"; + + /// <summary> + /// The viewstate key to use for the <see cref="Columns"/> property. + /// </summary> + private const string ColumnsViewStateKey = "Columns"; + + /// <summary> + /// The viewstate key to use for the <see cref="TabIndex"/> property. + /// </summary> + private const string TabIndexViewStateKey = "TabIndex"; + + /// <summary> + /// The viewstate key to use for the <see cref="Enabled"/> property. + /// </summary> + private const string EnabledViewStateKey = "Enabled"; + + /// <summary> + /// The viewstate key to use for the <see cref="Name"/> property. + /// </summary> + private const string NameViewStateKey = "Name"; + + /// <summary> + /// The viewstate key to use for the <see cref="Text"/> property. + /// </summary> + private const string TextViewStateKey = "Text"; + + #endregion + + #region Property defaults + + /// <summary> + /// The default value for the <see cref="Columns"/> property. + /// </summary> + private const int ColumnsDefault = 40; + + /// <summary> + /// The default value for the <see cref="MaxLength"/> property. + /// </summary> + private const int MaxLengthDefault = 40; + + /// <summary> + /// The default value for the <see cref="Name"/> property. + /// </summary> + private const string NameDefault = "openid_identifier"; + + /// <summary> + /// The default value for the <see cref="EnableRequestProfile"/> property. + /// </summary> + private const bool EnableRequestProfileDefault = true; + + /// <summary> + /// The default value for the <see cref="ShowLogo"/> property. + /// </summary> + private const bool ShowLogoDefault = true; + + /// <summary> + /// The default value for the <see cref="PresetBorder"/> property. + /// </summary> + private const bool PresetBorderDefault = true; + + /// <summary> + /// The default value for the <see cref="PolicyUrl"/> property. + /// </summary> + private const string PolicyUrlDefault = ""; + + /// <summary> + /// The default value for the <see cref="CssClass"/> property. + /// </summary> + private const string CssClassDefault = "openid"; + + /// <summary> + /// The default value for the <see cref="Text"/> property. + /// </summary> + private const string TextDefault = ""; + + /// <summary> + /// The default value for the <see cref="RequestEmail"/> property. + /// </summary> + private const DemandLevel RequestEmailDefault = DemandLevel.NoRequest; + + /// <summary> + /// The default value for the <see cref="RequestPostalCode"/> property. + /// </summary> + private const DemandLevel RequestPostalCodeDefault = DemandLevel.NoRequest; + + /// <summary> + /// The default value for the <see cref="RequestCountry"/> property. + /// </summary> + private const DemandLevel RequestCountryDefault = DemandLevel.NoRequest; + + /// <summary> + /// The default value for the <see cref="RequestLanguage"/> property. + /// </summary> + private const DemandLevel RequestLanguageDefault = DemandLevel.NoRequest; + + /// <summary> + /// The default value for the <see cref="RequestTimeZone"/> property. + /// </summary> + private const DemandLevel RequestTimeZoneDefault = DemandLevel.NoRequest; + + /// <summary> + /// The default value for the <see cref="RequestNickname"/> property. + /// </summary> + private const DemandLevel RequestNicknameDefault = DemandLevel.NoRequest; + + /// <summary> + /// The default value for the <see cref="RequestFullName"/> property. + /// </summary> + private const DemandLevel RequestFullNameDefault = DemandLevel.NoRequest; + + /// <summary> + /// The default value for the <see cref="RequestBirthDate"/> property. + /// </summary> + private const DemandLevel RequestBirthDateDefault = DemandLevel.NoRequest; + + /// <summary> + /// The default value for the <see cref="RequestGender"/> property. + /// </summary> + private const DemandLevel RequestGenderDefault = DemandLevel.NoRequest; + + #endregion + + /// <summary> + /// An empty sreg request, used to compare with others to see if they too are empty. + /// </summary> + private static readonly ClaimsRequest EmptyClaimsRequest = new ClaimsRequest(); + + /// <summary> + /// Initializes a new instance of the <see cref="OpenIdTextBox"/> class. + /// </summary> + public OpenIdTextBox() { + } + + #region IEditableTextControl Members + + /// <summary> + /// Occurs when the content of the text changes between posts to the server. + /// </summary> + public event EventHandler TextChanged; + + #endregion + + #region Properties + + /// <summary> + /// Gets or sets the content of the text box. + /// </summary> + [Bindable(true), DefaultValue(""), Category(AppearanceCategory)] + [Description("The content of the text box.")] + public string Text { + get { + return this.Identifier != null ? this.Identifier.OriginalString : (this.ViewState[TextViewStateKey] as string ?? string.Empty); + } + + set { + // Try to store it as a validated identifier, + // but failing that at least store the text. + Identifier id; + if (Identifier.TryParse(value, out id)) { + this.Identifier = id; + } else { + // Be sure to set the viewstate AFTER setting the Identifier, + // since setting the Identifier clears the viewstate in OnIdentifierChanged. + this.Identifier = null; + this.ViewState[TextViewStateKey] = value; + } + } + } + + /// <summary> + /// Gets or sets the form name to use for this input field. + /// </summary> + [Bindable(true), DefaultValue(NameDefault), Category(BehaviorCategory)] + [Description("The form name of this input field.")] + public string Name { + get { return (string)(this.ViewState[NameViewStateKey] ?? NameDefault); } + set { this.ViewState[NameViewStateKey] = value; } + } + + /// <summary> + /// Gets or sets the CSS class assigned to the text box. + /// </summary> + [Bindable(true), DefaultValue(CssClassDefault), Category(AppearanceCategory)] + [Description("The CSS class assigned to the text box.")] + public string CssClass { + get { return (string)this.ViewState[CssClassViewStateKey]; } + set { this.ViewState[CssClassViewStateKey] = value; } + } + + /// <summary> + /// Gets or sets a value indicating whether to show the OpenID logo in the text box. + /// </summary> + [Bindable(true), DefaultValue(ShowLogoDefault), Category(AppearanceCategory)] + [Description("The visibility of the OpenID logo in the text box.")] + public bool ShowLogo { + get { return (bool)(this.ViewState[ShowLogoViewStateKey] ?? ShowLogoDefault); } + set { this.ViewState[ShowLogoViewStateKey] = value; } + } + + /// <summary> + /// Gets or sets a value indicating whether to use inline styling to force a solid gray border. + /// </summary> + [Bindable(true), DefaultValue(PresetBorderDefault), Category(AppearanceCategory)] + [Description("Whether to use inline styling to force a solid gray border.")] + public bool PresetBorder { + get { return (bool)(this.ViewState[PresetBorderViewStateKey] ?? PresetBorderDefault); } + set { this.ViewState[PresetBorderViewStateKey] = value; } + } + + /// <summary> + /// Gets or sets the width of the text box in characters. + /// </summary> + [Bindable(true), DefaultValue(ColumnsDefault), Category(AppearanceCategory)] + [Description("The width of the text box in characters.")] + public int Columns { + get { return (int)(this.ViewState[ColumnsViewStateKey] ?? ColumnsDefault); } + set { this.ViewState[ColumnsViewStateKey] = value; } + } + + /// <summary> + /// Gets or sets the maximum number of characters the browser should allow + /// </summary> + [Bindable(true), DefaultValue(MaxLengthDefault), Category(AppearanceCategory)] + [Description("The maximum number of characters the browser should allow.")] + public int MaxLength { + get { return (int)(this.ViewState[MaxLengthViewStateKey] ?? MaxLengthDefault); } + set { this.ViewState[MaxLengthViewStateKey] = value; } + } + + /// <summary> + /// Gets or sets the tab index of the Web server control. + /// </summary> + /// <value></value> + /// <returns> + /// The tab index of the Web server control. The default is 0, which indicates that this property is not set. + /// </returns> + /// <exception cref="T:System.ArgumentOutOfRangeException"> + /// The specified tab index is not between -32768 and 32767. + /// </exception> + [Bindable(true), DefaultValue(TabIndexDefault), Category(BehaviorCategory)] + [Description("The tab index of the text box control.")] + public virtual short TabIndex { + get { return (short)(this.ViewState[TabIndexViewStateKey] ?? TabIndexDefault); } + set { this.ViewState[TabIndexViewStateKey] = value; } + } + + /// <summary> + /// Gets or sets a value indicating whether this <see cref="OpenIdTextBox"/> is enabled + /// in the browser for editing and will respond to incoming OpenID messages. + /// </summary> + /// <value><c>true</c> if enabled; otherwise, <c>false</c>.</value> + [Bindable(true), DefaultValue(true), Category(BehaviorCategory)] + [Description("Whether the control is editable in the browser and will respond to OpenID messages.")] + public bool Enabled { + get { return (bool)(this.ViewState[EnabledViewStateKey] ?? true); } + set { this.ViewState[EnabledViewStateKey] = value; } + } + + /// <summary> + /// Gets or sets your level of interest in receiving the user's nickname from the Provider. + /// </summary> + [Bindable(true), DefaultValue(RequestNicknameDefault), Category(ProfileCategory)] + [Description("Your level of interest in receiving the user's nickname from the Provider.")] + public DemandLevel RequestNickname { + get { return (DemandLevel)(ViewState[RequestNicknameViewStateKey] ?? RequestNicknameDefault); } + set { ViewState[RequestNicknameViewStateKey] = value; } + } + + /// <summary> + /// Gets or sets your level of interest in receiving the user's email address from the Provider. + /// </summary> + [Bindable(true), DefaultValue(RequestEmailDefault), Category(ProfileCategory)] + [Description("Your level of interest in receiving the user's email address from the Provider.")] + public DemandLevel RequestEmail { + get { return (DemandLevel)(ViewState[RequestEmailViewStateKey] ?? RequestEmailDefault); } + set { ViewState[RequestEmailViewStateKey] = value; } + } + + /// <summary> + /// Gets or sets your level of interest in receiving the user's full name from the Provider. + /// </summary> + [Bindable(true), DefaultValue(RequestFullNameDefault), Category(ProfileCategory)] + [Description("Your level of interest in receiving the user's full name from the Provider")] + public DemandLevel RequestFullName { + get { return (DemandLevel)(ViewState[RequestFullNameViewStateKey] ?? RequestFullNameDefault); } + set { ViewState[RequestFullNameViewStateKey] = value; } + } + + /// <summary> + /// Gets or sets your level of interest in receiving the user's birthdate from the Provider. + /// </summary> + [Bindable(true), DefaultValue(RequestBirthDateDefault), Category(ProfileCategory)] + [Description("Your level of interest in receiving the user's birthdate from the Provider.")] + public DemandLevel RequestBirthDate { + get { return (DemandLevel)(ViewState[RequestBirthDateViewStateKey] ?? RequestBirthDateDefault); } + set { ViewState[RequestBirthDateViewStateKey] = value; } + } + + /// <summary> + /// Gets or sets your level of interest in receiving the user's gender from the Provider. + /// </summary> + [Bindable(true), DefaultValue(RequestGenderDefault), Category(ProfileCategory)] + [Description("Your level of interest in receiving the user's gender from the Provider.")] + public DemandLevel RequestGender { + get { return (DemandLevel)(ViewState[RequestGenderViewStateKey] ?? RequestGenderDefault); } + set { ViewState[RequestGenderViewStateKey] = value; } + } + + /// <summary> + /// Gets or sets your level of interest in receiving the user's postal code from the Provider. + /// </summary> + [Bindable(true), DefaultValue(RequestPostalCodeDefault), Category(ProfileCategory)] + [Description("Your level of interest in receiving the user's postal code from the Provider.")] + public DemandLevel RequestPostalCode { + get { return (DemandLevel)(ViewState[RequestPostalCodeViewStateKey] ?? RequestPostalCodeDefault); } + set { ViewState[RequestPostalCodeViewStateKey] = value; } + } + + /// <summary> + /// Gets or sets your level of interest in receiving the user's country from the Provider. + /// </summary> + [Bindable(true)] + [Category(ProfileCategory)] + [DefaultValue(RequestCountryDefault)] + [Description("Your level of interest in receiving the user's country from the Provider.")] + public DemandLevel RequestCountry { + get { return (DemandLevel)(ViewState[RequestCountryViewStateKey] ?? RequestCountryDefault); } + set { ViewState[RequestCountryViewStateKey] = value; } + } + + /// <summary> + /// Gets or sets your level of interest in receiving the user's preferred language from the Provider. + /// </summary> + [Bindable(true), DefaultValue(RequestLanguageDefault), Category(ProfileCategory)] + [Description("Your level of interest in receiving the user's preferred language from the Provider.")] + public DemandLevel RequestLanguage { + get { return (DemandLevel)(ViewState[RequestLanguageViewStateKey] ?? RequestLanguageDefault); } + set { ViewState[RequestLanguageViewStateKey] = value; } + } + + /// <summary> + /// Gets or sets your level of interest in receiving the user's time zone from the Provider. + /// </summary> + [Bindable(true), DefaultValue(RequestTimeZoneDefault), Category(ProfileCategory)] + [Description("Your level of interest in receiving the user's time zone from the Provider.")] + public DemandLevel RequestTimeZone { + get { return (DemandLevel)(ViewState[RequestTimeZoneViewStateKey] ?? RequestTimeZoneDefault); } + set { ViewState[RequestTimeZoneViewStateKey] = value; } + } + + /// <summary> + /// Gets or sets the URL to your privacy policy page that describes how + /// claims will be used and/or shared. + /// </summary> + [SuppressMessage("Microsoft.Design", "CA1056:UriPropertiesShouldNotBeStrings", Justification = "Bindable property must be simple type")] + [Bindable(true), DefaultValue(PolicyUrlDefault), Category(ProfileCategory)] + [Description("The URL to your privacy policy page that describes how claims will be used and/or shared.")] + [UrlProperty, Editor("System.Web.UI.Design.UrlEditor, System.Design, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", typeof(UITypeEditor))] + public string PolicyUrl { + get { + return (string)ViewState[PolicyUrlViewStateKey] ?? PolicyUrlDefault; + } + + set { + UriUtil.ValidateResolvableUrl(Page, DesignMode, value); + ViewState[PolicyUrlViewStateKey] = value; + } + } + + /// <summary> + /// Gets or sets a value indicating whether to use OpenID extensions + /// to retrieve profile data of the authenticating user. + /// </summary> + [Bindable(true), DefaultValue(EnableRequestProfileDefault), Category(ProfileCategory)] + [Description("Turns the entire Simple Registration extension on or off.")] + public bool EnableRequestProfile { + get { return (bool)(ViewState[EnableRequestProfileViewStateKey] ?? EnableRequestProfileDefault); } + set { ViewState[EnableRequestProfileViewStateKey] = value; } + } + + #endregion + + #region IPostBackDataHandler Members + + /// <summary> + /// When implemented by a class, processes postback data for an ASP.NET server control. + /// </summary> + /// <param name="postDataKey">The key identifier for the control.</param> + /// <param name="postCollection">The collection of all incoming name values.</param> + /// <returns> + /// true if the server control's state changes as a result of the postback; otherwise, false. + /// </returns> + bool IPostBackDataHandler.LoadPostData(string postDataKey, NameValueCollection postCollection) { + return this.LoadPostData(postDataKey, postCollection); + } + + /// <summary> + /// When implemented by a class, signals the server control to notify the ASP.NET application that the state of the control has changed. + /// </summary> + void IPostBackDataHandler.RaisePostDataChangedEvent() { + this.RaisePostDataChangedEvent(); + } + + #endregion + + /// <summary> + /// Creates the authentication requests for a given user-supplied Identifier. + /// </summary> + /// <param name="identifier">The identifier to create a request for.</param> + /// <returns> + /// A sequence of authentication requests, any one of which may be + /// used to determine the user's control of the <see cref="IAuthenticationRequest.ClaimedIdentifier"/>. + /// </returns> + protected internal override IEnumerable<IAuthenticationRequest> CreateRequests(Identifier identifier) { + ErrorUtilities.VerifyArgumentNotNull(identifier, "identifier"); + + // We delegate all our logic to another method, since invoking base. methods + // within an iterator method results in unverifiable code. + return this.CreateRequestsCore(base.CreateRequests(identifier)); + } + + /// <summary> + /// Checks for incoming OpenID authentication responses and fires appropriate events. + /// </summary> + /// <param name="e">The <see cref="T:System.EventArgs"/> object that contains the event data.</param> + protected override void OnLoad(EventArgs e) { + if (!this.Enabled) { + return; + } + + this.Page.RegisterRequiresPostBack(this); + base.OnLoad(e); + } + + /// <summary> + /// Called when the <see cref="Identifier"/> property is changed. + /// </summary> + protected override void OnIdentifierChanged() { + this.ViewState.Remove(TextViewStateKey); + base.OnIdentifierChanged(); + } + + /// <summary> + /// Sends server control content to a provided <see cref="T:System.Web.UI.HtmlTextWriter"/> object, which writes the content to be rendered on the client. + /// </summary> + /// <param name="writer">The <see cref="T:System.Web.UI.HtmlTextWriter"/> object that receives the server control content.</param> + protected override void Render(HtmlTextWriter writer) { + Contract.Assume(writer != null, "Missing contract."); + + if (this.ShowLogo) { + string logoUrl = Page.ClientScript.GetWebResourceUrl( + typeof(OpenIdTextBox), EmbeddedLogoResourceName); + writer.AddStyleAttribute( + HtmlTextWriterStyle.BackgroundImage, + string.Format(CultureInfo.InvariantCulture, "url({0})", HttpUtility.HtmlEncode(logoUrl))); + writer.AddStyleAttribute("background-repeat", "no-repeat"); + writer.AddStyleAttribute("background-position", "0 50%"); + writer.AddStyleAttribute(HtmlTextWriterStyle.PaddingLeft, "18px"); + } + + if (this.PresetBorder) { + writer.AddStyleAttribute(HtmlTextWriterStyle.BorderStyle, "solid"); + writer.AddStyleAttribute(HtmlTextWriterStyle.BorderWidth, "1px"); + writer.AddStyleAttribute(HtmlTextWriterStyle.BorderColor, "lightgray"); + } + + if (!string.IsNullOrEmpty(this.CssClass)) { + writer.AddAttribute(HtmlTextWriterAttribute.Class, this.CssClass); + } + + writer.AddAttribute(HtmlTextWriterAttribute.Id, this.ClientID); + writer.AddAttribute(HtmlTextWriterAttribute.Name, HttpUtility.HtmlEncode(this.Name)); + writer.AddAttribute(HtmlTextWriterAttribute.Type, "text"); + writer.AddAttribute(HtmlTextWriterAttribute.Size, this.Columns.ToString(CultureInfo.InvariantCulture)); + writer.AddAttribute(HtmlTextWriterAttribute.Value, HttpUtility.HtmlEncode(this.Text)); + writer.AddAttribute(HtmlTextWriterAttribute.Tabindex, this.TabIndex.ToString(CultureInfo.CurrentCulture)); + + writer.RenderBeginTag(HtmlTextWriterTag.Input); + writer.RenderEndTag(); + } + + /// <summary> + /// When implemented by a class, processes postback data for an ASP.NET server control. + /// </summary> + /// <param name="postDataKey">The key identifier for the control.</param> + /// <param name="postCollection">The collection of all incoming name values.</param> + /// <returns> + /// true if the server control's state changes as a result of the postback; otherwise, false. + /// </returns> + protected virtual bool LoadPostData(string postDataKey, NameValueCollection postCollection) { + Contract.Assume(postCollection != null, "Missing contract"); + + // If the control was temporarily hidden, it won't be in the Form data, + // and we'll just implicitly keep the last Text setting. + if (postCollection[this.Name] != null) { + this.Text = postCollection[this.Name]; + return true; + } + + return false; + } + + /// <summary> + /// When implemented by a class, signals the server control to notify the ASP.NET application that the state of the control has changed. + /// </summary> + [SuppressMessage("Microsoft.Design", "CA1030:UseEventsWhereAppropriate", Justification = "Preserve signature of interface we're implementing.")] + protected virtual void RaisePostDataChangedEvent() { + this.OnTextChanged(); + } + + /// <summary> + /// Called on a postback when the Text property has changed. + /// </summary> + protected virtual void OnTextChanged() { + EventHandler textChanged = this.TextChanged; + if (textChanged != null) { + textChanged(this, EventArgs.Empty); + } + } + + /// <summary> + /// Creates the authentication requests for a given user-supplied Identifier. + /// </summary> + /// <param name="requests">The authentication requests to prepare.</param> + /// <returns> + /// A sequence of authentication requests, any one of which may be + /// used to determine the user's control of the <see cref="IAuthenticationRequest.ClaimedIdentifier"/>. + /// </returns> + private IEnumerable<IAuthenticationRequest> CreateRequestsCore(IEnumerable<IAuthenticationRequest> requests) { + Contract.Requires(requests != null); + + foreach (var request in requests) { + if (this.EnableRequestProfile) { + this.AddProfileArgs(request); + } + + yield return request; + } + } + + /// <summary> + /// Adds extensions to a given authentication request to ask the Provider + /// for user profile data. + /// </summary> + /// <param name="request">The authentication request to add the extensions to.</param> + private void AddProfileArgs(IAuthenticationRequest request) { + Requires.NotNull(request, "request"); + + var sreg = new ClaimsRequest() { + Nickname = this.RequestNickname, + Email = this.RequestEmail, + FullName = this.RequestFullName, + BirthDate = this.RequestBirthDate, + Gender = this.RequestGender, + PostalCode = this.RequestPostalCode, + Country = this.RequestCountry, + Language = this.RequestLanguage, + TimeZone = this.RequestTimeZone, + PolicyUrl = string.IsNullOrEmpty(this.PolicyUrl) ? + null : new Uri(this.RelyingParty.Channel.GetRequestFromContext().UrlBeforeRewriting, this.Page.ResolveUrl(this.PolicyUrl)), + }; + + // Only actually add the extension request if fields are actually being requested. + if (!sreg.Equals(EmptyClaimsRequest)) { + request.AddExtension(sreg); + } + } + } +} diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/PopupBehavior.cs b/src/DotNetOpenAuth.OpenId.RelyingParty.UI/OpenId/RelyingParty/PopupBehavior.cs index e84f4f5..e84f4f5 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/PopupBehavior.cs +++ b/src/DotNetOpenAuth.OpenId.RelyingParty.UI/OpenId/RelyingParty/PopupBehavior.cs diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/SelectorButton.cs b/src/DotNetOpenAuth.OpenId.RelyingParty.UI/OpenId/RelyingParty/SelectorButton.cs index 0be3a5f..0be3a5f 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/SelectorButton.cs +++ b/src/DotNetOpenAuth.OpenId.RelyingParty.UI/OpenId/RelyingParty/SelectorButton.cs diff --git a/src/DotNetOpenAuth.OpenId.RelyingParty.UI/OpenId/RelyingParty/SelectorButtonContract.cs b/src/DotNetOpenAuth.OpenId.RelyingParty.UI/OpenId/RelyingParty/SelectorButtonContract.cs new file mode 100644 index 0000000..709ad2d --- /dev/null +++ b/src/DotNetOpenAuth.OpenId.RelyingParty.UI/OpenId/RelyingParty/SelectorButtonContract.cs @@ -0,0 +1,46 @@ +//----------------------------------------------------------------------- +// <copyright file="SelectorButtonContract.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.RelyingParty { + using System; + using System.Diagnostics.Contracts; + using System.Web.UI; + + /// <summary> + /// The contract class for the <see cref="SelectorButton"/> class. + /// </summary> + [ContractClassFor(typeof(SelectorButton))] + internal abstract class SelectorButtonContract : SelectorButton { + /// <summary> + /// Ensures that this button has been initialized to a valid state. + /// </summary> + /// <remarks> + /// This is "internal" -- NOT "protected internal" deliberately. It makes it impossible + /// to derive from this class outside the assembly, which suits our purposes since the + /// <see cref="OpenIdSelector"/> control is not designed for an extensible set of button types. + /// </remarks> + internal override void EnsureValid() { + } + + /// <summary> + /// Renders the leading attributes for the LI tag. + /// </summary> + /// <param name="writer">The writer.</param> + protected internal override void RenderLeadingAttributes(HtmlTextWriter writer) { + Requires.NotNull(writer, "writer"); + } + + /// <summary> + /// Renders the content of the button. + /// </summary> + /// <param name="writer">The writer.</param> + /// <param name="selector">The containing selector control.</param> + protected internal override void RenderButtonContent(HtmlTextWriter writer, OpenIdSelector selector) { + Requires.NotNull(writer, "writer"); + Requires.NotNull(selector, "selector"); + } + } +} diff --git a/src/DotNetOpenAuth.OpenId.RelyingParty.UI/OpenId/RelyingParty/SelectorOpenIdButton.cs b/src/DotNetOpenAuth.OpenId.RelyingParty.UI/OpenId/RelyingParty/SelectorOpenIdButton.cs new file mode 100644 index 0000000..26b10e8 --- /dev/null +++ b/src/DotNetOpenAuth.OpenId.RelyingParty.UI/OpenId/RelyingParty/SelectorOpenIdButton.cs @@ -0,0 +1,82 @@ +//----------------------------------------------------------------------- +// <copyright file="SelectorOpenIdButton.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.RelyingParty { + using System; + using System.ComponentModel; + using System.Diagnostics.Contracts; + using System.Drawing.Design; + using System.Web.UI; + using DotNetOpenAuth.Messaging; + + /// <summary> + /// A button that appears in the <see cref="OpenIdSelector"/> control that + /// allows the user to type in a user-supplied identifier. + /// </summary> + public class SelectorOpenIdButton : SelectorButton { + /// <summary> + /// Initializes a new instance of the <see cref="SelectorOpenIdButton"/> class. + /// </summary> + public SelectorOpenIdButton() { + Reporting.RecordFeatureUse(this); + } + + /// <summary> + /// Initializes a new instance of the <see cref="SelectorOpenIdButton"/> class. + /// </summary> + /// <param name="imageUrl">The image to display on the button.</param> + public SelectorOpenIdButton(string imageUrl) + : this() { + Requires.NotNullOrEmpty(imageUrl, "imageUrl"); + + this.Image = imageUrl; + } + + /// <summary> + /// Gets or sets the path to the image to display on the button's surface. + /// </summary> + /// <value>The virtual path to the image.</value> + [Editor("System.Web.UI.Design.ImageUrlEditor, System.Design, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", typeof(UITypeEditor))] + [UrlProperty] + public string Image { get; set; } + + /// <summary> + /// Ensures that this button has been initialized to a valid state. + /// </summary> + internal override void EnsureValid() { + Contract.Ensures(!string.IsNullOrEmpty(this.Image)); + + // Every button must have an image. + ErrorUtilities.VerifyOperation(!string.IsNullOrEmpty(this.Image), OpenIdStrings.PropertyNotSet, "SelectorButton.Image"); + } + + /// <summary> + /// Renders the leading attributes for the LI tag. + /// </summary> + /// <param name="writer">The writer.</param> + protected internal override void RenderLeadingAttributes(HtmlTextWriter writer) { + writer.AddAttribute(HtmlTextWriterAttribute.Id, "OpenIDButton"); + writer.AddAttribute(HtmlTextWriterAttribute.Class, "OpenIDButton"); + } + + /// <summary> + /// Renders the content of the button. + /// </summary> + /// <param name="writer">The writer.</param> + /// <param name="selector">The containing selector control.</param> + protected internal override void RenderButtonContent(HtmlTextWriter writer, OpenIdSelector selector) { + writer.AddAttribute(HtmlTextWriterAttribute.Src, selector.Page.ResolveUrl(this.Image)); + writer.RenderBeginTag(HtmlTextWriterTag.Img); + writer.RenderEndTag(); + + writer.AddAttribute(HtmlTextWriterAttribute.Src, selector.Page.ClientScript.GetWebResourceUrl(typeof(OpenIdAjaxTextBox), OpenIdAjaxTextBox.EmbeddedLoginSuccessResourceName)); + writer.AddAttribute(HtmlTextWriterAttribute.Class, "loginSuccess"); + writer.AddAttribute(HtmlTextWriterAttribute.Title, selector.AuthenticatedAsToolTip); + writer.RenderBeginTag(HtmlTextWriterTag.Img); + writer.RenderEndTag(); + } + } +} diff --git a/src/DotNetOpenAuth.OpenId.RelyingParty.UI/OpenId/RelyingParty/SelectorProviderButton.cs b/src/DotNetOpenAuth.OpenId.RelyingParty.UI/OpenId/RelyingParty/SelectorProviderButton.cs new file mode 100644 index 0000000..efd6c50 --- /dev/null +++ b/src/DotNetOpenAuth.OpenId.RelyingParty.UI/OpenId/RelyingParty/SelectorProviderButton.cs @@ -0,0 +1,113 @@ +//----------------------------------------------------------------------- +// <copyright file="SelectorProviderButton.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.RelyingParty { + using System; + using System.ComponentModel; + using System.Diagnostics.Contracts; + using System.Drawing.Design; + using System.Web.UI; + using DotNetOpenAuth.ComponentModel; + using DotNetOpenAuth.Messaging; + + /// <summary> + /// A button that appears in the <see cref="OpenIdSelector"/> control that + /// provides one-click access to a popular OpenID Provider. + /// </summary> + public class SelectorProviderButton : SelectorButton { + /// <summary> + /// Initializes a new instance of the <see cref="SelectorProviderButton"/> class. + /// </summary> + public SelectorProviderButton() { + Reporting.RecordFeatureUse(this); + } + + /// <summary> + /// Initializes a new instance of the <see cref="SelectorProviderButton"/> class. + /// </summary> + /// <param name="providerIdentifier">The OP Identifier.</param> + /// <param name="imageUrl">The image to display on the button.</param> + public SelectorProviderButton(Identifier providerIdentifier, string imageUrl) + : this() { + Requires.NotNull(providerIdentifier, "providerIdentifier"); + Requires.NotNullOrEmpty(imageUrl, "imageUrl"); + + this.OPIdentifier = providerIdentifier; + this.Image = imageUrl; + } + + /// <summary> + /// Gets or sets the path to the image to display on the button's surface. + /// </summary> + /// <value>The virtual path to the image.</value> + [Editor("System.Web.UI.Design.ImageUrlEditor, System.Design, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", typeof(UITypeEditor))] + [UrlProperty] + public string Image { get; set; } + + /// <summary> + /// Gets or sets the OP Identifier represented by the button. + /// </summary> + /// <value> + /// The OP identifier, which may be provided in the easiest "user-supplied identifier" form, + /// but for security should be provided with a leading https:// if possible. + /// For example: "yahoo.com" or "https://me.yahoo.com/". + /// </value> + [TypeConverter(typeof(IdentifierConverter))] + public Identifier OPIdentifier { get; set; } + + /// <summary> + /// Gets or sets a value indicating whether this Provider doesn't handle + /// checkid_immediate messages correctly and background authentication + /// should not be attempted. + /// </summary> + public bool SkipBackgroundAuthentication { get; set; } + + /// <summary> + /// Ensures that this button has been initialized to a valid state. + /// </summary> + internal override void EnsureValid() { + Contract.Ensures(!string.IsNullOrEmpty(this.Image)); + Contract.Ensures(this.OPIdentifier != null); + + // Every button must have an image. + ErrorUtilities.VerifyOperation(!string.IsNullOrEmpty(this.Image), OpenIdStrings.PropertyNotSet, "SelectorButton.Image"); + + // Every button must have exactly one purpose. + ErrorUtilities.VerifyOperation(this.OPIdentifier != null, OpenIdStrings.PropertyNotSet, "SelectorButton.OPIdentifier"); + } + + /// <summary> + /// Renders the leading attributes for the LI tag. + /// </summary> + /// <param name="writer">The writer.</param> + protected internal override void RenderLeadingAttributes(HtmlTextWriter writer) { + writer.AddAttribute(HtmlTextWriterAttribute.Id, this.OPIdentifier); + + string style = "OPButton"; + if (this.SkipBackgroundAuthentication) { + style += " NoAsyncAuth"; + } + writer.AddAttribute(HtmlTextWriterAttribute.Class, style); + } + + /// <summary> + /// Renders the content of the button. + /// </summary> + /// <param name="writer">The writer.</param> + /// <param name="selector">The containing selector control.</param> + protected internal override void RenderButtonContent(HtmlTextWriter writer, OpenIdSelector selector) { + writer.AddAttribute(HtmlTextWriterAttribute.Src, selector.Page.ResolveUrl(this.Image)); + writer.RenderBeginTag(HtmlTextWriterTag.Img); + writer.RenderEndTag(); + + writer.AddAttribute(HtmlTextWriterAttribute.Src, selector.Page.ClientScript.GetWebResourceUrl(typeof(OpenIdAjaxTextBox), OpenIdAjaxTextBox.EmbeddedLoginSuccessResourceName)); + writer.AddAttribute(HtmlTextWriterAttribute.Class, "loginSuccess"); + writer.AddAttribute(HtmlTextWriterAttribute.Title, selector.AuthenticatedAsToolTip); + writer.RenderBeginTag(HtmlTextWriterTag.Img); + writer.RenderEndTag(); + } + } +} diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/login_failure.png b/src/DotNetOpenAuth.OpenId.RelyingParty.UI/OpenId/RelyingParty/login_failure.png Binary files differindex 8003700..8003700 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/login_failure.png +++ b/src/DotNetOpenAuth.OpenId.RelyingParty.UI/OpenId/RelyingParty/login_failure.png diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/login_success (lock).png b/src/DotNetOpenAuth.OpenId.RelyingParty.UI/OpenId/RelyingParty/login_success (lock).png Binary files differindex bc0c0c8..bc0c0c8 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/login_success (lock).png +++ b/src/DotNetOpenAuth.OpenId.RelyingParty.UI/OpenId/RelyingParty/login_success (lock).png diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/login_success.png b/src/DotNetOpenAuth.OpenId.RelyingParty.UI/OpenId/RelyingParty/login_success.png Binary files differindex 0ae1365..0ae1365 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/login_success.png +++ b/src/DotNetOpenAuth.OpenId.RelyingParty.UI/OpenId/RelyingParty/login_success.png diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/openid_login.png b/src/DotNetOpenAuth.OpenId.RelyingParty.UI/OpenId/RelyingParty/openid_login.png Binary files differindex caebd58..caebd58 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/openid_login.png +++ b/src/DotNetOpenAuth.OpenId.RelyingParty.UI/OpenId/RelyingParty/openid_login.png diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/spinner.gif b/src/DotNetOpenAuth.OpenId.RelyingParty.UI/OpenId/RelyingParty/spinner.gif Binary files differindex 9cb298e..9cb298e 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/spinner.gif +++ b/src/DotNetOpenAuth.OpenId.RelyingParty.UI/OpenId/RelyingParty/spinner.gif diff --git a/src/DotNetOpenAuth.OpenId.RelyingParty.UI/Properties/AssemblyInfo.cs b/src/DotNetOpenAuth.OpenId.RelyingParty.UI/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..2b8f823 --- /dev/null +++ b/src/DotNetOpenAuth.OpenId.RelyingParty.UI/Properties/AssemblyInfo.cs @@ -0,0 +1,60 @@ +//----------------------------------------------------------------------- +// <copyright file="AssemblyInfo.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +// We DON'T put an AssemblyVersionAttribute in here because it is generated in the build. + +using System; +using System.Diagnostics.Contracts; +using System.Net; +using System.Reflection; +using System.Resources; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Security; +using System.Security.Permissions; +using System.Web.UI; + +[assembly: TagPrefix("DotNetOpenAuth.OpenId.RelyingParty", "rp")] + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("DotNetOpenAuth OpenID")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("DotNetOpenAuth")] +[assembly: AssemblyCopyright("Copyright © 2008")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: NeutralResourcesLanguage("en-US")] +[assembly: CLSCompliant(true)] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("7d73990c-47c0-4256-9f20-a893add9e289")] + +[assembly: ContractVerification(true)] + +#if StrongNameSigned +// See comment at top of this file. We need this so that strong-naming doesn't +// keep this assembly from being useful to shared host (medium trust) web sites. +[assembly: AllowPartiallyTrustedCallers] + +[assembly: InternalsVisibleTo("DotNetOpenAuth.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100AD093C3765257C89A7010E853F2C7C741FF92FA8ACE06D7B8254702CAD5CF99104447F63AB05F8BB6F51CE0D81C8C93D2FCE8C20AAFF7042E721CBA16EAAE98778611DED11C0ABC8900DC5667F99B50A9DADEC24DBD8F2C91E3E8AD300EF64F1B4B9536CEB16FB440AF939F57624A9B486F867807C649AE4830EAB88C6C03998")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.OpenId.Provider, PublicKey=0024000004800000940000000602000000240000525341310004000001000100AD093C3765257C89A7010E853F2C7C741FF92FA8ACE06D7B8254702CAD5CF99104447F63AB05F8BB6F51CE0D81C8C93D2FCE8C20AAFF7042E721CBA16EAAE98778611DED11C0ABC8900DC5667F99B50A9DADEC24DBD8F2C91E3E8AD300EF64F1B4B9536CEB16FB440AF939F57624A9B486F867807C649AE4830EAB88C6C03998")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.OpenIdInfoCard.UI, PublicKey=0024000004800000940000000602000000240000525341310004000001000100AD093C3765257C89A7010E853F2C7C741FF92FA8ACE06D7B8254702CAD5CF99104447F63AB05F8BB6F51CE0D81C8C93D2FCE8C20AAFF7042E721CBA16EAAE98778611DED11C0ABC8900DC5667F99B50A9DADEC24DBD8F2C91E3E8AD300EF64F1B4B9536CEB16FB440AF939F57624A9B486F867807C649AE4830EAB88C6C03998")] +[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")] +#else +[assembly: InternalsVisibleTo("DotNetOpenAuth.Test")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.OpenId.Provider")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.OpenIdInfoCard.UI")] +[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] +#endif diff --git a/src/DotNetOpenAuth.OpenId.RelyingParty/DotNetOpenAuth.OpenId.RelyingParty.csproj b/src/DotNetOpenAuth.OpenId.RelyingParty/DotNetOpenAuth.OpenId.RelyingParty.csproj new file mode 100644 index 0000000..6fa3076 --- /dev/null +++ b/src/DotNetOpenAuth.OpenId.RelyingParty/DotNetOpenAuth.OpenId.RelyingParty.csproj @@ -0,0 +1,88 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), EnlistmentInfo.props))\EnlistmentInfo.props" Condition=" '$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), EnlistmentInfo.props))' != '' " /> + <PropertyGroup> + <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> + <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> + </PropertyGroup> + <Import Project="$(ProjectRoot)tools\DotNetOpenAuth.props" /> + <PropertyGroup> + <SchemaVersion>2.0</SchemaVersion> + <ProjectGuid>{F458AB60-BA1C-43D9-8CEF-EC01B50BE87B}</ProjectGuid> + <OutputType>Library</OutputType> + <AppDesignerFolder>Properties</AppDesignerFolder> + <RootNamespace>DotNetOpenAuth</RootNamespace> + <AssemblyName>DotNetOpenAuth.OpenId.RelyingParty</AssemblyName> + </PropertyGroup> + <Import Project="$(ProjectRoot)tools\DotNetOpenAuth.Product.props" /> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> + </PropertyGroup> + <ItemGroup> + <Compile Include="OpenId\RelyingParty\Behaviors\AXFetchAsSregTransform.cs" /> + <Compile Include="OpenId\RelyingParty\Behaviors\GsaIcamProfile.cs" /> + <Compile Include="OpenId\ChannelElements\ExtensionsBindingElementRelyingParty.cs" /> + <Compile Include="OpenId\ChannelElements\OpenIdRelyingPartyChannel.cs" /> + <Compile Include="OpenId\ChannelElements\OpenIdRelyingPartyMessageFactory.cs" /> + <Compile Include="OpenId\ChannelElements\RelyingPartySecurityOptions.cs" /> + <Compile Include="OpenId\ChannelElements\RelyingPartySigningBindingElement.cs" /> + <Compile Include="OpenId\ChannelElements\ReturnToNonceBindingElement.cs" /> + <Compile Include="OpenId\RelyingParty\Extensions\ExtensionsInteropHelper.cs" /> + <Compile Include="OpenId\HostMetaDiscoveryService.cs" /> + <Compile Include="OpenId\Interop\AuthenticationResponseShim.cs" /> + <Compile Include="OpenId\Interop\ClaimsResponseShim.cs" /> + <Compile Include="OpenId\Interop\OpenIdRelyingPartyShim.cs" /> + <Compile Include="OpenId\Messages\AssociateDiffieHellmanRelyingPartyResponse.cs" Condition=" '$(ExcludeDiffieHellman)' != 'true' " /> + <Compile Include="OpenId\Messages\AssociateRequestRelyingParty.cs" /> + <Compile Include="OpenId\Messages\AssociateSuccessfulResponseRelyingPartyContract.cs" /> + <Compile Include="OpenId\Messages\IAssociateSuccessfulResponseRelyingParty.cs" /> + <Compile Include="OpenId\Messages\AssociateUnencryptedResponseRelyingParty.cs" /> + <Compile Include="OpenId\RelyingParty\CryptoKeyStoreAsRelyingPartyAssociationStore.cs" /> + <Compile Include="OpenId\RelyingParty\Extensions\UIUtilities.cs" /> + <Compile Include="OpenId\RelyingParty\IRelyingPartyAssociationStore.cs" /> + <Compile Include="OpenId\RelyingParty\Associations.cs" /> + <Compile Include="OpenId\RelyingParty\AssociationManager.cs" /> + <Compile Include="OpenId\RelyingParty\AssociationPreference.cs" /> + <Compile Include="OpenId\RelyingParty\AuthenticationRequest.cs" /> + <Compile Include="OpenId\RelyingParty\DuplicateRequestedHostsComparer.cs" /> + <Compile Include="OpenId\RelyingParty\NegativeAuthenticationResponse.cs" /> + <Compile Include="OpenId\RelyingParty\PositiveAnonymousResponse.cs" /> + <Compile Include="OpenId\RelyingParty\PositiveAuthenticationResponse.cs" /> + <Compile Include="OpenId\RelyingParty\FailedAuthenticationResponse.cs" /> + <Compile Include="OpenId\RelyingParty\ISetupRequiredAuthenticationResponse.cs" /> + <Compile Include="OpenId\RelyingParty\OpenIdRelyingParty.cs" /> + <Compile Include="OpenId\RelyingParty\PositiveAuthenticationResponseSnapshot.cs" /> + <Compile Include="OpenId\RelyingParty\SimpleXrdsProviderEndpoint.cs" /> + <Compile Include="OpenId\RelyingParty\StandardRelyingPartyApplicationStore.cs" /> + <Compile Include="OpenId\RelyingParty\WellKnownProviders.cs" /> + <Compile Include="Properties\AssemblyInfo.cs" /> + </ItemGroup> + <ItemGroup> + <None Include="OpenId\RelyingParty\OpenIdRelyingParty.cd" /> + </ItemGroup> + <ItemGroup> + <ProjectReference Include="..\DotNetOpenAuth.Core\DotNetOpenAuth.Core.csproj"> + <Project>{60426312-6AE5-4835-8667-37EDEA670222}</Project> + <Name>DotNetOpenAuth.Core</Name> + </ProjectReference> + <ProjectReference Include="..\DotNetOpenAuth.OpenId\DotNetOpenAuth.OpenId.csproj"> + <Project>{3896A32A-E876-4C23-B9B8-78E17D134CD3}</Project> + <Name>DotNetOpenAuth.OpenId</Name> + </ProjectReference> + <ProjectReference Include="..\Org.Mentalis.Security.Cryptography\Org.Mentalis.Security.Cryptography.csproj" Condition=" '$(ExcludeDiffieHellman)' != 'true' "> + <Project>{26DC877F-5987-48DD-9DDB-E62F2DE0E150}</Project> + <Name>Org.Mentalis.Security.Cryptography</Name> + </ProjectReference> + </ItemGroup> + <ItemGroup> + <Reference Include="System" /> + </ItemGroup> + <ItemGroup> + <Folder Include="ComponentModel\" /> + <Folder Include="Configuration\" /> + </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))' != '' " /> +</Project>
\ No newline at end of file diff --git a/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/ChannelElements/ExtensionsBindingElementRelyingParty.cs b/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/ChannelElements/ExtensionsBindingElementRelyingParty.cs new file mode 100644 index 0000000..099573d --- /dev/null +++ b/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/ChannelElements/ExtensionsBindingElementRelyingParty.cs @@ -0,0 +1,38 @@ +//----------------------------------------------------------------------- +// <copyright file="ExtensionsBindingElementRelyingParty.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.ChannelElements { + using System; + using System.Collections.Generic; + using System.Diagnostics.Contracts; + using System.Linq; + using System.Text; + using DotNetOpenAuth.OpenId.RelyingParty; + + /// <summary> + /// The OpenID binding element responsible for reading/writing OpenID extensions + /// at the Relying Party. + /// </summary> + internal class ExtensionsBindingElementRelyingParty : ExtensionsBindingElement { + /// <summary> + /// The security settings that apply to this relying party, if it is a relying party. + /// </summary> + private readonly RelyingPartySecuritySettings relyingPartySecuritySettings; + + /// <summary> + /// Initializes a new instance of the <see cref="ExtensionsBindingElementRelyingParty"/> class. + /// </summary> + /// <param name="extensionFactory">The extension factory.</param> + /// <param name="securitySettings">The security settings.</param> + internal ExtensionsBindingElementRelyingParty(IOpenIdExtensionFactory extensionFactory, RelyingPartySecuritySettings securitySettings) + : base(extensionFactory, securitySettings, !securitySettings.IgnoreUnsignedExtensions) { + Requires.NotNull(extensionFactory, "extensionFactory"); + Requires.NotNull(securitySettings, "securitySettings"); + + this.relyingPartySecuritySettings = securitySettings; + } + } +} diff --git a/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/ChannelElements/OpenIdRelyingPartyChannel.cs b/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/ChannelElements/OpenIdRelyingPartyChannel.cs new file mode 100644 index 0000000..4739d84 --- /dev/null +++ b/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/ChannelElements/OpenIdRelyingPartyChannel.cs @@ -0,0 +1,120 @@ +//----------------------------------------------------------------------- +// <copyright file="OpenIdRelyingPartyChannel.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.ChannelElements { + using System; + using System.Collections.Generic; + using System.Diagnostics.Contracts; + using System.Linq; + using System.Text; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.Messaging.Bindings; + using DotNetOpenAuth.OpenId.Extensions; + using DotNetOpenAuth.OpenId.RelyingParty; + + /// <summary> + /// The messaging channel for OpenID relying parties. + /// </summary> + internal class OpenIdRelyingPartyChannel : OpenIdChannel { + /// <summary> + /// Initializes a new instance of the <see cref="OpenIdRelyingPartyChannel"/> class. + /// </summary> + /// <param name="cryptoKeyStore">The association store to use.</param> + /// <param name="nonceStore">The nonce store to use.</param> + /// <param name="securitySettings">The security settings to apply.</param> + internal OpenIdRelyingPartyChannel(ICryptoKeyStore cryptoKeyStore, INonceStore nonceStore, RelyingPartySecuritySettings securitySettings) + : this(cryptoKeyStore, nonceStore, new OpenIdRelyingPartyMessageFactory(), securitySettings, false) { + Requires.NotNull(securitySettings, "securitySettings"); + } + + /// <summary> + /// Initializes a new instance of the <see cref="OpenIdRelyingPartyChannel"/> class. + /// </summary> + /// <param name="cryptoKeyStore">The association store to use.</param> + /// <param name="nonceStore">The nonce store to use.</param> + /// <param name="messageTypeProvider">An object that knows how to distinguish the various OpenID message types for deserialization purposes.</param> + /// <param name="securitySettings">The security settings to apply.</param> + /// <param name="nonVerifying">A value indicating whether the channel is set up with no functional security binding elements.</param> + private OpenIdRelyingPartyChannel(ICryptoKeyStore cryptoKeyStore, INonceStore nonceStore, IMessageFactory messageTypeProvider, RelyingPartySecuritySettings securitySettings, bool nonVerifying) : + base(messageTypeProvider, InitializeBindingElements(cryptoKeyStore, nonceStore, securitySettings, nonVerifying)) { + Requires.NotNull(messageTypeProvider, "messageTypeProvider"); + Requires.NotNull(securitySettings, "securitySettings"); + Requires.True(!nonVerifying || securitySettings is RelyingPartySecuritySettings); + } + + /// <summary> + /// A value indicating whether the channel is set up + /// with no functional security binding elements. + /// </summary> + /// <returns>A new <see cref="OpenIdChannel"/> instance that will not perform verification on incoming messages or apply any security to outgoing messages.</returns> + /// <remarks> + /// <para>A value of <c>true</c> allows the relying party to preview incoming + /// messages without invalidating nonces or checking signatures.</para> + /// <para>Setting this to <c>true</c> poses a great security risk and is only + /// present to support the OpenIdAjaxTextBox which needs to preview + /// messages, and will validate them later.</para> + /// </remarks> + internal static OpenIdChannel CreateNonVerifyingChannel() { + Contract.Ensures(Contract.Result<OpenIdChannel>() != null); + + return new OpenIdRelyingPartyChannel(null, null, new OpenIdRelyingPartyMessageFactory(), new RelyingPartySecuritySettings(), true); + } + + /// <summary> + /// Initializes the binding elements. + /// </summary> + /// <param name="cryptoKeyStore">The crypto key store.</param> + /// <param name="nonceStore">The nonce store to use.</param> + /// <param name="securitySettings">The security settings to apply. Must be an instance of either <see cref="RelyingPartySecuritySettings"/> or ProviderSecuritySettings.</param> + /// <param name="nonVerifying">A value indicating whether the channel is set up with no functional security binding elements.</param> + /// <returns> + /// An array of binding elements which may be used to construct the channel. + /// </returns> + private static IChannelBindingElement[] InitializeBindingElements(ICryptoKeyStore cryptoKeyStore, INonceStore nonceStore, RelyingPartySecuritySettings securitySettings, bool nonVerifying) { + Requires.NotNull(securitySettings, "securitySettings"); + + SigningBindingElement signingElement; + signingElement = nonVerifying ? null : new RelyingPartySigningBindingElement(new CryptoKeyStoreAsRelyingPartyAssociationStore(cryptoKeyStore ?? new MemoryCryptoKeyStore())); + + var extensionFactory = OpenIdExtensionFactoryAggregator.LoadFromConfiguration(); + + List<IChannelBindingElement> elements = new List<IChannelBindingElement>(8); + elements.Add(new ExtensionsBindingElementRelyingParty(extensionFactory, securitySettings)); + elements.Add(new RelyingPartySecurityOptions(securitySettings)); + elements.Add(new BackwardCompatibilityBindingElement()); + ReturnToNonceBindingElement requestNonceElement = null; + + if (cryptoKeyStore != null) { + if (nonceStore != null) { + // There is no point in having a ReturnToNonceBindingElement without + // a ReturnToSignatureBindingElement because the nonce could be + // artificially changed without it. + requestNonceElement = new ReturnToNonceBindingElement(nonceStore, securitySettings); + elements.Add(requestNonceElement); + } + + // It is important that the return_to signing element comes last + // so that the nonce is included in the signature. + elements.Add(new ReturnToSignatureBindingElement(cryptoKeyStore)); + } + + ErrorUtilities.VerifyOperation(!securitySettings.RejectUnsolicitedAssertions || requestNonceElement != null, OpenIdStrings.UnsolicitedAssertionRejectionRequiresNonceStore); + + if (nonVerifying) { + elements.Add(new SkipSecurityBindingElement()); + } else { + if (nonceStore != null) { + elements.Add(new StandardReplayProtectionBindingElement(nonceStore, true)); + } + + elements.Add(new StandardExpirationBindingElement()); + elements.Add(signingElement); + } + + return elements.ToArray(); + } + } +} diff --git a/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/ChannelElements/OpenIdRelyingPartyMessageFactory.cs b/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/ChannelElements/OpenIdRelyingPartyMessageFactory.cs new file mode 100644 index 0000000..9ec6c53 --- /dev/null +++ b/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/ChannelElements/OpenIdRelyingPartyMessageFactory.cs @@ -0,0 +1,124 @@ +//----------------------------------------------------------------------- +// <copyright file="OpenIdRelyingPartyMessageFactory.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.ChannelElements { + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OpenId.Messages; + + /// <summary> + /// Message factory for OpenID Relying Parties. + /// </summary> + internal class OpenIdRelyingPartyMessageFactory : IMessageFactory { + /// <summary> + /// Analyzes an incoming request message payload to discover what kind of + /// message is embedded in it and returns the type, or null if no match is found. + /// </summary> + /// <param name="recipient">The intended or actual recipient of the request message.</param> + /// <param name="fields">The name/value pairs that make up the message payload.</param> + /// <returns> + /// A newly instantiated <see cref="IProtocolMessage"/>-derived object that this message can + /// deserialize to. Null if the request isn't recognized as a valid protocol message. + /// </returns> + public IDirectedProtocolMessage GetNewRequestMessage(MessageReceivingEndpoint recipient, IDictionary<string, string> fields) { + RequestBase message = null; + + // Discern the OpenID version of the message. + Protocol protocol = Protocol.V11; + string ns; + if (fields.TryGetValue(Protocol.V20.openid.ns, out ns)) { + ErrorUtilities.VerifyProtocol(string.Equals(ns, Protocol.OpenId2Namespace, StringComparison.Ordinal), MessagingStrings.UnexpectedMessagePartValue, Protocol.V20.openid.ns, ns); + protocol = Protocol.V20; + } + + string mode; + if (fields.TryGetValue(protocol.openid.mode, out mode)) { + if (string.Equals(mode, protocol.Args.Mode.cancel) || + (string.Equals(mode, protocol.Args.Mode.setup_needed) && (protocol.Version.Major >= 2 || fields.ContainsKey(protocol.openid.user_setup_url)))) { + message = new NegativeAssertionResponse(protocol.Version, recipient.Location, mode); + } else if (string.Equals(mode, protocol.Args.Mode.id_res)) { + if (fields.ContainsKey(protocol.openid.identity)) { + message = new PositiveAssertionResponse(protocol.Version, recipient.Location); + } else { + ErrorUtilities.VerifyProtocol(!fields.ContainsKey(protocol.openid.claimed_id), OpenIdStrings.IdentityAndClaimedIdentifierMustBeBothPresentOrAbsent); + message = new IndirectSignedResponse(protocol.Version, recipient.Location); + } + } else if (string.Equals(mode, protocol.Args.Mode.error)) { + message = new IndirectErrorResponse(protocol.Version, recipient.Location); + } else { + ErrorUtilities.ThrowProtocol(MessagingStrings.UnexpectedMessagePartValue, protocol.openid.mode, mode); + } + } + + if (message != null) { + message.SetAsIncoming(); + } + + return message; + } + + /// <summary> + /// Analyzes an incoming request message payload to discover what kind of + /// message is embedded in it and returns the type, or null if no match is found. + /// </summary> + /// <param name="request">The message that was sent as a request that resulted in the response.</param> + /// <param name="fields">The name/value pairs that make up the message payload.</param> + /// <returns> + /// A newly instantiated <see cref="IProtocolMessage"/>-derived object that this message can + /// deserialize to. Null if the request isn't recognized as a valid protocol message. + /// </returns> + public IDirectResponseProtocolMessage GetNewResponseMessage(IDirectedProtocolMessage request, IDictionary<string, string> fields) { + DirectResponseBase message = null; + + // Discern the OpenID version of the message. + Protocol protocol = Protocol.V11; + string ns; + if (fields.TryGetValue(Protocol.V20.openidnp.ns, out ns)) { + ErrorUtilities.VerifyProtocol(string.Equals(ns, Protocol.OpenId2Namespace, StringComparison.Ordinal), MessagingStrings.UnexpectedMessagePartValue, Protocol.V20.openidnp.ns, ns); + protocol = Protocol.V20; + } + + // Handle error messages generally. + if (fields.ContainsKey(protocol.openidnp.error)) { + message = new DirectErrorResponse(protocol.Version, request); + } + + var associateRequest = request as AssociateRequest; + if (associateRequest != null) { + if (protocol.Version.Major >= 2 && fields.ContainsKey(protocol.openidnp.error_code)) { + // This is a special recognized error case that we create a special message for. + message = new AssociateUnsuccessfulResponse(protocol.Version, associateRequest); + } else if (message == null) { +#if !ExcludeDiffieHellman + var associateDiffieHellmanRequest = request as AssociateDiffieHellmanRequest; + if (associateDiffieHellmanRequest != null) { + message = new AssociateDiffieHellmanRelyingPartyResponse(protocol.Version, associateDiffieHellmanRequest); + } +#endif + + var associateUnencryptedRequest = request as AssociateUnencryptedRequest; + if (associateUnencryptedRequest != null) { + message = new AssociateUnencryptedResponseRelyingParty(protocol.Version, associateUnencryptedRequest); + } + } + } + + var checkAuthenticationRequest = request as CheckAuthenticationRequest; + if (checkAuthenticationRequest != null && message == null) { + message = new CheckAuthenticationResponse(protocol.Version, checkAuthenticationRequest); + } + + if (message != null) { + message.SetAsIncoming(); + } + + return message; + } + } +} diff --git a/src/DotNetOpenAuth/OpenId/ChannelElements/RelyingPartySecurityOptions.cs b/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/ChannelElements/RelyingPartySecurityOptions.cs index d8fc103..d8fc103 100644 --- a/src/DotNetOpenAuth/OpenId/ChannelElements/RelyingPartySecurityOptions.cs +++ b/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/ChannelElements/RelyingPartySecurityOptions.cs diff --git a/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/ChannelElements/RelyingPartySigningBindingElement.cs b/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/ChannelElements/RelyingPartySigningBindingElement.cs new file mode 100644 index 0000000..4a3f5ee --- /dev/null +++ b/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/ChannelElements/RelyingPartySigningBindingElement.cs @@ -0,0 +1,110 @@ +//----------------------------------------------------------------------- +// <copyright file="RelyingPartySigningBindingElement.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.ChannelElements { + using System; + using System.Collections.Generic; + using System.Diagnostics.Contracts; + using System.Linq; + using System.Text; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.Messaging.Bindings; + using DotNetOpenAuth.OpenId.Messages; + using DotNetOpenAuth.OpenId.RelyingParty; + + /// <summary> + /// The signing binding element for OpenID Relying Parties. + /// </summary> + internal class RelyingPartySigningBindingElement : SigningBindingElement { + /// <summary> + /// The association store used by Relying Parties to look up the secrets needed for signing. + /// </summary> + private readonly IRelyingPartyAssociationStore rpAssociations; + + /// <summary> + /// Initializes a new instance of the <see cref="RelyingPartySigningBindingElement"/> class. + /// </summary> + /// <param name="associationStore">The association store used to look up the secrets needed for signing. May be null for dumb Relying Parties.</param> + internal RelyingPartySigningBindingElement(IRelyingPartyAssociationStore associationStore) { + this.rpAssociations = associationStore; + } + + /// <summary> + /// Gets a specific association referenced in a given message's association handle. + /// </summary> + /// <param name="signedMessage">The signed message whose association handle should be used to lookup the association to return.</param> + /// <returns> + /// The referenced association; or <c>null</c> if such an association cannot be found. + /// </returns> + protected override Association GetSpecificAssociation(ITamperResistantOpenIdMessage signedMessage) { + Association association = null; + + if (!string.IsNullOrEmpty(signedMessage.AssociationHandle)) { + IndirectSignedResponse indirectSignedMessage = signedMessage as IndirectSignedResponse; + if (this.rpAssociations != null) { // if on a smart RP + Uri providerEndpoint = indirectSignedMessage.ProviderEndpoint; + association = this.rpAssociations.GetAssociation(providerEndpoint, signedMessage.AssociationHandle); + } + } + + return association; + } + + /// <summary> + /// Gets the association to use to sign or verify a message. + /// </summary> + /// <param name="signedMessage">The message to sign or verify.</param> + /// <returns> + /// The association to use to sign or verify the message. + /// </returns> + protected override Association GetAssociation(ITamperResistantOpenIdMessage signedMessage) { + // We're on a Relying Party verifying a signature. + IDirectedProtocolMessage directedMessage = (IDirectedProtocolMessage)signedMessage; + if (this.rpAssociations != null) { + return this.rpAssociations.GetAssociation(directedMessage.Recipient, signedMessage.AssociationHandle); + } else { + return null; + } + } + + /// <summary> + /// Verifies the signature by unrecognized handle. + /// </summary> + /// <param name="message">The message.</param> + /// <param name="signedMessage">The signed message.</param> + /// <param name="protectionsApplied">The protections applied.</param> + /// <returns> + /// The applied protections. + /// </returns> + protected override MessageProtections VerifySignatureByUnrecognizedHandle(IProtocolMessage message, ITamperResistantOpenIdMessage signedMessage, MessageProtections protectionsApplied) { + // We did not recognize the association the provider used to sign the message. + // Ask the provider to check the signature then. + var indirectSignedResponse = (IndirectSignedResponse)signedMessage; + var checkSignatureRequest = new CheckAuthenticationRequest(indirectSignedResponse, this.Channel); + var checkSignatureResponse = this.Channel.Request<CheckAuthenticationResponse>(checkSignatureRequest); + if (!checkSignatureResponse.IsValid) { + Logger.Bindings.Error("Provider reports signature verification failed."); + throw new InvalidSignatureException(message); + } + + // If the OP confirms that a handle should be invalidated as well, do that. + if (!string.IsNullOrEmpty(checkSignatureResponse.InvalidateHandle)) { + if (this.rpAssociations != null) { + this.rpAssociations.RemoveAssociation(indirectSignedResponse.ProviderEndpoint, checkSignatureResponse.InvalidateHandle); + } + } + + // When we're in dumb mode we can't provide our own replay protection, + // but for OpenID 2.0 Providers we can rely on them providing it as part + // of signature verification. + if (message.Version.Major >= 2) { + protectionsApplied |= MessageProtections.ReplayProtection; + } + + return protectionsApplied; + } + } +} diff --git a/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/ChannelElements/ReturnToNonceBindingElement.cs b/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/ChannelElements/ReturnToNonceBindingElement.cs new file mode 100644 index 0000000..46cd103 --- /dev/null +++ b/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/ChannelElements/ReturnToNonceBindingElement.cs @@ -0,0 +1,291 @@ +//----------------------------------------------------------------------- +// <copyright file="ReturnToNonceBindingElement.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.ChannelElements { + using System; + using System.Collections.Generic; + using System.Diagnostics.Contracts; + using System.Linq; + using System.Text; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.Messaging.Bindings; + using DotNetOpenAuth.OpenId.Messages; + using DotNetOpenAuth.OpenId.RelyingParty; + + /// <summary> + /// This binding element adds a nonce to a Relying Party's outgoing + /// authentication request when working against an OpenID 1.0 Provider + /// in order to protect against replay attacks or on all authentication + /// requests to distinguish solicited from unsolicited assertions. + /// </summary> + /// <remarks> + /// <para>This nonce goes beyond the OpenID 1.x spec, but adds to security. + /// Since this library's Provider implementation also provides special nonce + /// protection for 1.0 messages, this security feature overlaps with that one. + /// This means that if an RP from this library were talking to an OP from this + /// library, but the Identifier being authenticated advertised the OP as a 1.x + /// OP, then both RP and OP might try to use a nonce for protecting the assertion. + /// There's no problem with that--it will still all work out. And it would be a + /// very rare combination of elements anyway. + /// </para> + /// <para> + /// This binding element deactivates itself for OpenID 2.0 (or later) messages + /// since they are automatically protected in the protocol by the Provider's + /// openid.response_nonce parameter. The exception to this is when + /// <see cref="RelyingPartySecuritySettings.RejectUnsolicitedAssertions"/> is + /// set to <c>true</c>, which will not only add a request nonce to every outgoing + /// authentication request but also require that it be present in positive + /// assertions, effectively disabling unsolicited assertions. + /// </para> + /// <para>In the messaging stack, this binding element looks like an ordinary + /// transform-type of binding element rather than a protection element, + /// due to its required order in the channel stack and that it exists + /// only on the RP side and only on some messages.</para> + /// </remarks> + internal class ReturnToNonceBindingElement : IChannelBindingElement { + /// <summary> + /// The parameter of the callback parameter we tack onto the return_to URL + /// to store the replay-detection nonce. + /// </summary> + internal const string NonceParameter = OpenIdUtilities.CustomParameterPrefix + "request_nonce"; + + /// <summary> + /// The context within which return_to nonces must be unique -- they all go into the same bucket. + /// </summary> + private const string ReturnToNonceContext = "https://localhost/dnoa/return_to_nonce"; + + /// <summary> + /// The length of the generated nonce's random part. + /// </summary> + private const int NonceByteLength = 128 / 8; // 128-bit nonce + + /// <summary> + /// The nonce store that will allow us to recall which nonces we've seen before. + /// </summary> + private INonceStore nonceStore; + + /// <summary> + /// The security settings at the RP. + /// </summary> + private RelyingPartySecuritySettings securitySettings; + + /// <summary> + /// Backing field for the <see cref="Channel"/> property. + /// </summary> + private Channel channel; + + /// <summary> + /// Initializes a new instance of the <see cref="ReturnToNonceBindingElement"/> class. + /// </summary> + /// <param name="nonceStore">The nonce store to use.</param> + /// <param name="securitySettings">The security settings of the RP.</param> + internal ReturnToNonceBindingElement(INonceStore nonceStore, RelyingPartySecuritySettings securitySettings) { + Requires.NotNull(nonceStore, "nonceStore"); + Requires.NotNull(securitySettings, "securitySettings"); + + this.nonceStore = nonceStore; + this.securitySettings = securitySettings; + } + + #region IChannelBindingElement Properties + + /// <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 { + return this.channel; + } + + set { + if (this.channel == value) { + return; + } + + this.channel = value; + } + } + + /// <summary> + /// Gets the protection offered (if any) by this binding element. + /// </summary> + public MessageProtections Protection { + get { return MessageProtections.ReplayProtection; } + } + + #endregion + + /// <summary> + /// Gets the maximum message age from the standard expiration binding element. + /// </summary> + private static TimeSpan MaximumMessageAge { + get { return StandardExpirationBindingElement.MaximumMessageAge; } + } + + #region IChannelBindingElement Methods + + /// <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 MessageProtections? ProcessOutgoingMessage(IProtocolMessage message) { + // We only add a nonce to some auth requests. + SignedResponseRequest request = message as SignedResponseRequest; + if (this.UseRequestNonce(request)) { + request.AddReturnToArguments(NonceParameter, CustomNonce.NewNonce().Serialize()); + request.SignReturnTo = true; // a nonce without a signature is completely pointless + + return MessageProtections.ReplayProtection; + } + + 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 MessageProtections? ProcessIncomingMessage(IProtocolMessage message) { + IndirectSignedResponse response = message as IndirectSignedResponse; + if (this.UseRequestNonce(response)) { + if (!response.ReturnToParametersSignatureValidated) { + Logger.OpenId.Error("Incoming message is expected to have a nonce, but the return_to parameter is not signed."); + } + + string nonceValue = response.GetReturnToArgument(NonceParameter); + ErrorUtilities.VerifyProtocol( + nonceValue != null && response.ReturnToParametersSignatureValidated, + this.securitySettings.RejectUnsolicitedAssertions ? OpenIdStrings.UnsolicitedAssertionsNotAllowed : OpenIdStrings.UnsolicitedAssertionsNotAllowedFrom1xOPs); + + CustomNonce nonce = CustomNonce.Deserialize(nonceValue); + DateTime expirationDate = nonce.CreationDateUtc + MaximumMessageAge; + if (expirationDate < DateTime.UtcNow) { + throw new ExpiredMessageException(expirationDate, message); + } + + IReplayProtectedProtocolMessage replayResponse = response; + if (!this.nonceStore.StoreNonce(ReturnToNonceContext, nonce.RandomPartAsString, nonce.CreationDateUtc)) { + Logger.OpenId.ErrorFormat("Replayed nonce detected ({0} {1}). Rejecting message.", replayResponse.Nonce, replayResponse.UtcCreationDate); + throw new ReplayedMessageException(message); + } + + return MessageProtections.ReplayProtection; + } + + return null; + } + + #endregion + + /// <summary> + /// Determines whether a request nonce should be applied the request + /// or should be expected in the response. + /// </summary> + /// <param name="message">The authentication request or the positive assertion response.</param> + /// <returns> + /// <c>true</c> if the message exchanged with an OpenID 1.x provider + /// or if unsolicited assertions should be rejected at the RP; otherwise <c>false</c>. + /// </returns> + private bool UseRequestNonce(IMessage message) { + return message != null && (this.securitySettings.RejectUnsolicitedAssertions || + (message.Version.Major < 2 && this.securitySettings.ProtectDownlevelReplayAttacks)); + } + + /// <summary> + /// A special DotNetOpenAuth-only nonce used by the RP when talking to 1.0 OPs in order + /// to protect against replay attacks. + /// </summary> + private class CustomNonce { + /// <summary> + /// The random bits generated for the nonce. + /// </summary> + private byte[] randomPart; + + /// <summary> + /// Initializes a new instance of the <see cref="CustomNonce"/> class. + /// </summary> + /// <param name="creationDate">The creation date of the nonce.</param> + /// <param name="randomPart">The random bits that help make the nonce unique.</param> + private CustomNonce(DateTime creationDate, byte[] randomPart) { + this.CreationDateUtc = creationDate; + this.randomPart = randomPart; + } + + /// <summary> + /// Gets the creation date. + /// </summary> + internal DateTime CreationDateUtc { get; private set; } + + /// <summary> + /// Gets the random part of the nonce as a base64 encoded string. + /// </summary> + internal string RandomPartAsString { + get { return Convert.ToBase64String(this.randomPart); } + } + + /// <summary> + /// Creates a new nonce. + /// </summary> + /// <returns>The newly instantiated instance.</returns> + internal static CustomNonce NewNonce() { + return new CustomNonce(DateTime.UtcNow, MessagingUtilities.GetCryptoRandomData(NonceByteLength)); + } + + /// <summary> + /// Deserializes a nonce from the return_to parameter. + /// </summary> + /// <param name="value">The base64-encoded value of the nonce.</param> + /// <returns>The instantiated and initialized nonce.</returns> + internal static CustomNonce Deserialize(string value) { + Requires.NotNullOrEmpty(value, "value"); + + byte[] nonce = MessagingUtilities.FromBase64WebSafeString(value); + Contract.Assume(nonce != null); + DateTime creationDateUtc = new DateTime(BitConverter.ToInt64(nonce, 0), DateTimeKind.Utc); + byte[] randomPart = new byte[NonceByteLength]; + Array.Copy(nonce, sizeof(long), randomPart, 0, NonceByteLength); + return new CustomNonce(creationDateUtc, randomPart); + } + + /// <summary> + /// Serializes the entire nonce for adding to the return_to URL. + /// </summary> + /// <returns>The base64-encoded string representing the nonce.</returns> + internal string Serialize() { + byte[] timestamp = BitConverter.GetBytes(this.CreationDateUtc.Ticks); + byte[] nonce = new byte[timestamp.Length + this.randomPart.Length]; + timestamp.CopyTo(nonce, 0); + this.randomPart.CopyTo(nonce, timestamp.Length); + string base64Nonce = MessagingUtilities.ConvertToBase64WebSafeString(nonce); + return base64Nonce; + } + } + } +} diff --git a/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/HostMetaDiscoveryService.cs b/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/HostMetaDiscoveryService.cs new file mode 100644 index 0000000..6d60030 --- /dev/null +++ b/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/HostMetaDiscoveryService.cs @@ -0,0 +1,516 @@ +//----------------------------------------------------------------------- +// <copyright file="HostMetaDiscoveryService.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId { + using System; + using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; + using System.Diagnostics.Contracts; + using System.Globalization; + using System.IO; + using System.Linq; + using System.Net; + using System.Security; + using System.Security.Cryptography; + using System.Security.Cryptography.X509Certificates; + using System.Security.Permissions; + using System.Text; + using System.Text.RegularExpressions; + using System.Xml; + using System.Xml.XPath; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OpenId.RelyingParty; + using DotNetOpenAuth.Xrds; + using DotNetOpenAuth.Yadis; + + /// <summary> + /// The discovery service to support host-meta based discovery, such as Google Apps for Domains. + /// </summary> + /// <remarks> + /// The spec for this discovery mechanism can be found at: + /// http://groups.google.com/group/google-federated-login-api/web/openid-discovery-for-hosted-domains + /// and the XMLDSig spec referenced in that spec can be found at: + /// http://wiki.oasis-open.org/xri/XrdOne/XmlDsigProfile + /// </remarks> + public class HostMetaDiscoveryService : IIdentifierDiscoveryService { + /// <summary> + /// The URI template for discovery host-meta on domains hosted by + /// Google Apps for Domains. + /// </summary> + private static readonly HostMetaProxy GoogleHostedHostMeta = new HostMetaProxy("https://www.google.com/accounts/o8/.well-known/host-meta?hd={0}", "hosted-id.google.com"); + + /// <summary> + /// Path to the well-known location of the host-meta document at a domain. + /// </summary> + private const string LocalHostMetaPath = "/.well-known/host-meta"; + + /// <summary> + /// The pattern within a host-meta file to look for to obtain the URI to the XRDS document. + /// </summary> + private static readonly Regex HostMetaLink = new Regex(@"^Link: <(?<location>.+?)>; rel=""describedby http://reltype.google.com/openid/xrd-op""; type=""application/xrds\+xml""$"); + + /// <summary> + /// Initializes a new instance of the <see cref="HostMetaDiscoveryService"/> class. + /// </summary> + public HostMetaDiscoveryService() { + this.TrustedHostMetaProxies = new List<HostMetaProxy>(); + } + + /// <summary> + /// Gets the set of URI templates to use to contact host-meta hosting proxies + /// for domain discovery. + /// </summary> + public IList<HostMetaProxy> TrustedHostMetaProxies { get; private set; } + + /// <summary> + /// Gets or sets a value indicating whether to trust Google to host domains' host-meta documents. + /// </summary> + /// <remarks> + /// This property is just a convenient mechanism for checking or changing the set of + /// trusted host-meta proxies in the <see cref="TrustedHostMetaProxies"/> property. + /// </remarks> + public bool UseGoogleHostedHostMeta { + get { + return this.TrustedHostMetaProxies.Contains(GoogleHostedHostMeta); + } + + set { + if (value != this.UseGoogleHostedHostMeta) { + if (value) { + this.TrustedHostMetaProxies.Add(GoogleHostedHostMeta); + } else { + this.TrustedHostMetaProxies.Remove(GoogleHostedHostMeta); + } + } + } + } + + #region IIdentifierDiscoveryService Members + + /// <summary> + /// Performs discovery on the specified identifier. + /// </summary> + /// <param name="identifier">The identifier to perform discovery on.</param> + /// <param name="requestHandler">The means to place outgoing HTTP requests.</param> + /// <param name="abortDiscoveryChain">if set to <c>true</c>, no further discovery services will be called for this identifier.</param> + /// <returns> + /// A sequence of service endpoints yielded by discovery. Must not be null, but may be empty. + /// </returns> + public IEnumerable<IdentifierDiscoveryResult> Discover(Identifier identifier, IDirectWebRequestHandler requestHandler, out bool abortDiscoveryChain) { + abortDiscoveryChain = false; + + // Google Apps are always URIs -- not XRIs. + var uriIdentifier = identifier as UriIdentifier; + if (uriIdentifier == null) { + return Enumerable.Empty<IdentifierDiscoveryResult>(); + } + + var results = new List<IdentifierDiscoveryResult>(); + string signingHost; + using (var response = GetXrdsResponse(uriIdentifier, requestHandler, out signingHost)) { + if (response != null) { + try { + var document = new XrdsDocument(XmlReader.Create(response.ResponseStream)); + ValidateXmlDSig(document, uriIdentifier, response, signingHost); + var xrds = GetXrdElements(document, uriIdentifier.Uri.Host); + + // Look for claimed identifier template URIs for an additional XRDS document. + results.AddRange(GetExternalServices(xrds, uriIdentifier, requestHandler)); + + // If we couldn't find any claimed identifiers, look for OP identifiers. + // Normally this would be the opposite (OP Identifiers take precedence over + // claimed identifiers, but for Google Apps, XRDS' always have OP Identifiers + // mixed in, which the OpenID spec mandate should eclipse Claimed Identifiers, + // which would break positive assertion checks). + if (results.Count == 0) { + results.AddRange(xrds.CreateServiceEndpoints(uriIdentifier, uriIdentifier)); + } + + abortDiscoveryChain = true; + } catch (XmlException ex) { + Logger.Yadis.ErrorFormat("Error while parsing XRDS document at {0} pointed to by host-meta: {1}", response.FinalUri, ex); + } + } + } + + return results; + } + + #endregion + + /// <summary> + /// Gets the XRD elements that have a given CanonicalID. + /// </summary> + /// <param name="document">The XRDS document.</param> + /// <param name="canonicalId">The CanonicalID to match on.</param> + /// <returns>A sequence of XRD elements.</returns> + private static IEnumerable<XrdElement> GetXrdElements(XrdsDocument document, string canonicalId) { + // filter to include only those XRD elements describing the host whose host-meta pointed us to this document. + return document.XrdElements.Where(xrd => string.Equals(xrd.CanonicalID, canonicalId, StringComparison.Ordinal)); + } + + /// <summary> + /// Gets the described-by services in XRD elements. + /// </summary> + /// <param name="xrds">The XRDs to search.</param> + /// <returns>A sequence of services.</returns> + private static IEnumerable<ServiceElement> GetDescribedByServices(IEnumerable<XrdElement> xrds) { + Requires.NotNull(xrds, "xrds"); + Contract.Ensures(Contract.Result<IEnumerable<ServiceElement>>() != null); + + var describedBy = from xrd in xrds + from service in xrd.SearchForServiceTypeUris(p => "http://www.iana.org/assignments/relation/describedby") + select service; + return describedBy; + } + + /// <summary> + /// Gets the services for an identifier that are described by an external XRDS document. + /// </summary> + /// <param name="xrds">The XRD elements to search for described-by services.</param> + /// <param name="identifier">The identifier under discovery.</param> + /// <param name="requestHandler">The request handler.</param> + /// <returns>The discovered services.</returns> + private static IEnumerable<IdentifierDiscoveryResult> GetExternalServices(IEnumerable<XrdElement> xrds, UriIdentifier identifier, IDirectWebRequestHandler requestHandler) { + Requires.NotNull(xrds, "xrds"); + Requires.NotNull(identifier, "identifier"); + Requires.NotNull(requestHandler, "requestHandler"); + Contract.Ensures(Contract.Result<IEnumerable<IdentifierDiscoveryResult>>() != null); + + var results = new List<IdentifierDiscoveryResult>(); + foreach (var serviceElement in GetDescribedByServices(xrds)) { + var templateNode = serviceElement.Node.SelectSingleNode("google:URITemplate", serviceElement.XmlNamespaceResolver); + var nextAuthorityNode = serviceElement.Node.SelectSingleNode("google:NextAuthority", serviceElement.XmlNamespaceResolver); + if (templateNode != null) { + Uri externalLocation = new Uri(templateNode.Value.Trim().Replace("{%uri}", Uri.EscapeDataString(identifier.Uri.AbsoluteUri))); + string nextAuthority = nextAuthorityNode != null ? nextAuthorityNode.Value.Trim() : identifier.Uri.Host; + try { + using (var externalXrdsResponse = GetXrdsResponse(identifier, requestHandler, externalLocation)) { + XrdsDocument externalXrds = new XrdsDocument(XmlReader.Create(externalXrdsResponse.ResponseStream)); + ValidateXmlDSig(externalXrds, identifier, externalXrdsResponse, nextAuthority); + results.AddRange(GetXrdElements(externalXrds, identifier).CreateServiceEndpoints(identifier, identifier)); + } + } catch (ProtocolException ex) { + Logger.Yadis.WarnFormat("HTTP GET error while retrieving described-by XRDS document {0}: {1}", externalLocation.AbsoluteUri, ex); + } catch (XmlException ex) { + Logger.Yadis.ErrorFormat("Error while parsing described-by XRDS document {0}: {1}", externalLocation.AbsoluteUri, ex); + } + } + } + + return results; + } + + /// <summary> + /// Validates the XML digital signature on an XRDS document. + /// </summary> + /// <param name="document">The XRDS document whose signature should be validated.</param> + /// <param name="identifier">The identifier under discovery.</param> + /// <param name="response">The response.</param> + /// <param name="signingHost">The host name on the certificate that should be used to verify the signature in the XRDS.</param> + /// <exception cref="ProtocolException">Thrown if the XRDS document has an invalid or a missing signature.</exception> + [SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "XmlDSig", Justification = "xml")] + private static void ValidateXmlDSig(XrdsDocument document, UriIdentifier identifier, IncomingWebResponse response, string signingHost) { + Requires.NotNull(document, "document"); + Requires.NotNull(identifier, "identifier"); + Requires.NotNull(response, "response"); + + var signatureNode = document.Node.SelectSingleNode("/xrds:XRDS/ds:Signature", document.XmlNamespaceResolver); + ErrorUtilities.VerifyProtocol(signatureNode != null, OpenIdStrings.MissingElement, "Signature"); + var signedInfoNode = signatureNode.SelectSingleNode("ds:SignedInfo", document.XmlNamespaceResolver); + ErrorUtilities.VerifyProtocol(signedInfoNode != null, OpenIdStrings.MissingElement, "SignedInfo"); + ErrorUtilities.VerifyProtocol( + signedInfoNode.SelectSingleNode("ds:CanonicalizationMethod[@Algorithm='http://docs.oasis-open.org/xri/xrd/2009/01#canonicalize-raw-octets']", document.XmlNamespaceResolver) != null, + OpenIdStrings.UnsupportedCanonicalizationMethod); + ErrorUtilities.VerifyProtocol( + signedInfoNode.SelectSingleNode("ds:SignatureMethod[@Algorithm='http://www.w3.org/2000/09/xmldsig#rsa-sha1']", document.XmlNamespaceResolver) != null, + OpenIdStrings.UnsupportedSignatureMethod); + var certNodes = signatureNode.Select("ds:KeyInfo/ds:X509Data/ds:X509Certificate", document.XmlNamespaceResolver); + ErrorUtilities.VerifyProtocol(certNodes.Count > 0, OpenIdStrings.MissingElement, "X509Certificate"); + var certs = certNodes.Cast<XPathNavigator>().Select(n => new X509Certificate2(Convert.FromBase64String(n.Value.Trim()))).ToList(); + + // Verify that we trust the signer of the certificates. + // Start by trying to validate just the certificate used to sign the XRDS document, + // since we can do that with partial trust. + Logger.OpenId.Debug("Verifying that we trust the certificate used to sign the discovery document."); + if (!certs[0].Verify()) { + // We couldn't verify just the signing certificate, so try to verify the whole certificate chain. + try { + Logger.OpenId.Debug("Verifying the whole certificate chain."); + VerifyCertChain(certs); + Logger.OpenId.Debug("Certificate chain verified."); + } catch (SecurityException) { + Logger.Yadis.Warn("Signing certificate verification failed and we have insufficient code access security permissions to perform certificate chain validation."); + ErrorUtilities.ThrowProtocol(OpenIdStrings.X509CertificateNotTrusted); + } + } + + // Verify that the certificate is issued to the host on whom we are performing discovery. + string hostName = certs[0].GetNameInfo(X509NameType.DnsName, false); + ErrorUtilities.VerifyProtocol(string.Equals(hostName, signingHost, StringComparison.OrdinalIgnoreCase), OpenIdStrings.MisdirectedSigningCertificate, hostName, signingHost); + + // Verify the signature itself + byte[] signature = Convert.FromBase64String(response.Headers["Signature"]); + var provider = (RSACryptoServiceProvider)certs.First().PublicKey.Key; + byte[] data = new byte[response.ResponseStream.Length]; + response.ResponseStream.Seek(0, SeekOrigin.Begin); + response.ResponseStream.Read(data, 0, data.Length); + ErrorUtilities.VerifyProtocol(provider.VerifyData(data, "SHA1", signature), OpenIdStrings.InvalidDSig); + } + + /// <summary> + /// Verifies the cert chain. + /// </summary> + /// <param name="certs">The certs.</param> + /// <remarks> + /// This must be in a method of its own because there is a LinkDemand on the <see cref="X509Chain.Build"/> + /// method. By being in a method of its own, the caller of this method may catch a + /// <see cref="SecurityException"/> that is thrown if we're not running with full trust and execute + /// an alternative plan. + /// </remarks> + /// <exception cref="ProtocolException">Thrown if the certificate chain is invalid or unverifiable.</exception> + [SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", MessageId = "DotNetOpenAuth.Messaging.ErrorUtilities.ThrowProtocol(System.String,System.Object[])", Justification = "The localized portion is a string resource already."), SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands", Justification = "By design")] + private static void VerifyCertChain(List<X509Certificate2> certs) { + var chain = new X509Chain(); + foreach (var cert in certs) { + chain.Build(cert); + } + + if (chain.ChainStatus.Length > 0) { + ErrorUtilities.ThrowProtocol( + string.Format( + CultureInfo.CurrentCulture, + OpenIdStrings.X509CertificateNotTrusted + " {0}", + string.Join(", ", chain.ChainStatus.Select(status => status.StatusInformation).ToArray()))); + } + } + + /// <summary> + /// Gets the XRDS HTTP response for a given identifier. + /// </summary> + /// <param name="identifier">The identifier.</param> + /// <param name="requestHandler">The request handler.</param> + /// <param name="xrdsLocation">The location of the XRDS document to retrieve.</param> + /// <returns> + /// A HTTP response carrying an XRDS document. + /// </returns> + /// <exception cref="ProtocolException">Thrown if the XRDS document could not be obtained.</exception> + private static IncomingWebResponse GetXrdsResponse(UriIdentifier identifier, IDirectWebRequestHandler requestHandler, Uri xrdsLocation) { + Requires.NotNull(identifier, "identifier"); + Requires.NotNull(requestHandler, "requestHandler"); + Requires.NotNull(xrdsLocation, "xrdsLocation"); + Contract.Ensures(Contract.Result<IncomingWebResponse>() != null); + + var request = (HttpWebRequest)WebRequest.Create(xrdsLocation); + request.CachePolicy = Yadis.IdentifierDiscoveryCachePolicy; + request.Accept = ContentTypes.Xrds; + var options = identifier.IsDiscoverySecureEndToEnd ? DirectWebRequestOptions.RequireSsl : DirectWebRequestOptions.None; + var response = requestHandler.GetResponse(request, options).GetSnapshot(Yadis.MaximumResultToScan); + if (!string.Equals(response.ContentType.MediaType, ContentTypes.Xrds, StringComparison.Ordinal)) { + Logger.Yadis.WarnFormat("Host-meta pointed to XRDS at {0}, but Content-Type at that URL was unexpected value '{1}'.", xrdsLocation, response.ContentType); + } + + return response; + } + + /// <summary> + /// Gets the XRDS HTTP response for a given identifier. + /// </summary> + /// <param name="identifier">The identifier.</param> + /// <param name="requestHandler">The request handler.</param> + /// <param name="signingHost">The host name on the certificate that should be used to verify the signature in the XRDS.</param> + /// <returns>A HTTP response carrying an XRDS document, or <c>null</c> if one could not be obtained.</returns> + /// <exception cref="ProtocolException">Thrown if the XRDS document could not be obtained.</exception> + private IncomingWebResponse GetXrdsResponse(UriIdentifier identifier, IDirectWebRequestHandler requestHandler, out string signingHost) { + Requires.NotNull(identifier, "identifier"); + Requires.NotNull(requestHandler, "requestHandler"); + Uri xrdsLocation = this.GetXrdsLocation(identifier, requestHandler, out signingHost); + if (xrdsLocation == null) { + return null; + } + + var response = GetXrdsResponse(identifier, requestHandler, xrdsLocation); + + return response; + } + + /// <summary> + /// Gets the location of the XRDS document that describes a given identifier. + /// </summary> + /// <param name="identifier">The identifier under discovery.</param> + /// <param name="requestHandler">The request handler.</param> + /// <param name="signingHost">The host name on the certificate that should be used to verify the signature in the XRDS.</param> + /// <returns>An absolute URI, or <c>null</c> if one could not be determined.</returns> + private Uri GetXrdsLocation(UriIdentifier identifier, IDirectWebRequestHandler requestHandler, out string signingHost) { + Requires.NotNull(identifier, "identifier"); + Requires.NotNull(requestHandler, "requestHandler"); + using (var hostMetaResponse = this.GetHostMeta(identifier, requestHandler, out signingHost)) { + if (hostMetaResponse == null) { + return null; + } + + using (var sr = hostMetaResponse.GetResponseReader()) { + string line = sr.ReadLine(); + Match m = HostMetaLink.Match(line); + if (m.Success) { + Uri location = new Uri(m.Groups["location"].Value); + Logger.Yadis.InfoFormat("Found link to XRDS at {0} in host-meta document {1}.", location, hostMetaResponse.FinalUri); + return location; + } + } + + Logger.Yadis.WarnFormat("Could not find link to XRDS in host-meta document: {0}", hostMetaResponse.FinalUri); + return null; + } + } + + /// <summary> + /// Gets the host-meta for a given identifier. + /// </summary> + /// <param name="identifier">The identifier.</param> + /// <param name="requestHandler">The request handler.</param> + /// <param name="signingHost">The host name on the certificate that should be used to verify the signature in the XRDS.</param> + /// <returns> + /// The host-meta response, or <c>null</c> if no host-meta document could be obtained. + /// </returns> + private IncomingWebResponse GetHostMeta(UriIdentifier identifier, IDirectWebRequestHandler requestHandler, out string signingHost) { + Requires.NotNull(identifier, "identifier"); + Requires.NotNull(requestHandler, "requestHandler"); + foreach (var hostMetaProxy in this.GetHostMetaLocations(identifier)) { + var hostMetaLocation = hostMetaProxy.GetProxy(identifier); + var request = (HttpWebRequest)WebRequest.Create(hostMetaLocation); + request.CachePolicy = Yadis.IdentifierDiscoveryCachePolicy; + var options = DirectWebRequestOptions.AcceptAllHttpResponses; + if (identifier.IsDiscoverySecureEndToEnd) { + options |= DirectWebRequestOptions.RequireSsl; + } + var response = requestHandler.GetResponse(request, options).GetSnapshot(Yadis.MaximumResultToScan); + try { + if (response.Status == HttpStatusCode.OK) { + Logger.Yadis.InfoFormat("Found host-meta for {0} at: {1}", identifier.Uri.Host, hostMetaLocation); + signingHost = hostMetaProxy.GetSigningHost(identifier); + return response; + } else { + Logger.Yadis.InfoFormat("Could not obtain host-meta for {0} from {1}", identifier.Uri.Host, hostMetaLocation); + response.Dispose(); + } + } catch { + response.Dispose(); + throw; + } + } + + signingHost = null; + return null; + } + + /// <summary> + /// Gets the URIs authorized to host host-meta documents on behalf of a given domain. + /// </summary> + /// <param name="identifier">The identifier.</param> + /// <returns>A sequence of URIs that MAY provide the host-meta for a given identifier.</returns> + private IEnumerable<HostMetaProxy> GetHostMetaLocations(UriIdentifier identifier) { + Requires.NotNull(identifier, "identifier"); + + // First try the proxies, as they are considered more "secure" than the local + // host-meta for a domain since the domain may be defaced. + IEnumerable<HostMetaProxy> result = this.TrustedHostMetaProxies; + + // Finally, look for the local host-meta. + UriBuilder localHostMetaBuilder = new UriBuilder(); + localHostMetaBuilder.Scheme = identifier.IsDiscoverySecureEndToEnd || identifier.Uri.IsTransportSecure() ? Uri.UriSchemeHttps : Uri.UriSchemeHttp; + localHostMetaBuilder.Host = identifier.Uri.Host; + localHostMetaBuilder.Path = LocalHostMetaPath; + result = result.Concat(new[] { new HostMetaProxy(localHostMetaBuilder.Uri.AbsoluteUri, identifier.Uri.Host) }); + + return result; + } + + /// <summary> + /// A description of a web server that hosts host-meta documents. + /// </summary> + [SuppressMessage("Microsoft.Design", "CA1034:NestedTypesShouldNotBeVisible", Justification = "By design")] + public class HostMetaProxy { + /// <summary> + /// Initializes a new instance of the <see cref="HostMetaProxy"/> class. + /// </summary> + /// <param name="proxyFormat">The proxy formatting string.</param> + /// <param name="signingHostFormat">The signing host formatting string.</param> + public HostMetaProxy(string proxyFormat, string signingHostFormat) { + Requires.NotNullOrEmpty(proxyFormat, "proxyFormat"); + Requires.NotNullOrEmpty(signingHostFormat, "signingHostFormat"); + this.ProxyFormat = proxyFormat; + this.SigningHostFormat = signingHostFormat; + } + + /// <summary> + /// Gets the URL of the host-meta proxy. + /// </summary> + /// <value>The absolute proxy URL, which may include {0} to be replaced with the host of the identifier to be discovered.</value> + public string ProxyFormat { get; private set; } + + /// <summary> + /// Gets the formatting string to determine the expected host name on the certificate + /// that is expected to be used to sign the XRDS document. + /// </summary> + /// <value> + /// Either a string literal, or a formatting string where these placeholders may exist: + /// {0} the host on the identifier discovery was originally performed on; + /// {1} the host on this proxy. + /// </value> + public string SigningHostFormat { get; private set; } + + /// <summary> + /// Gets the absolute proxy URI. + /// </summary> + /// <param name="identifier">The identifier being discovered.</param> + /// <returns>The an absolute URI.</returns> + public virtual Uri GetProxy(UriIdentifier identifier) { + Requires.NotNull(identifier, "identifier"); + return new Uri(string.Format(CultureInfo.InvariantCulture, this.ProxyFormat, Uri.EscapeDataString(identifier.Uri.Host))); + } + + /// <summary> + /// Gets the signing host URI. + /// </summary> + /// <param name="identifier">The identifier being discovered.</param> + /// <returns>A host name.</returns> + public virtual string GetSigningHost(UriIdentifier identifier) { + Requires.NotNull(identifier, "identifier"); + return string.Format(CultureInfo.InvariantCulture, this.SigningHostFormat, identifier.Uri.Host, this.GetProxy(identifier).Host); + } + + /// <summary> + /// Determines whether the specified <see cref="T:System.Object"/> is equal to the current <see cref="T:System.Object"/>. + /// </summary> + /// <param name="obj">The <see cref="T:System.Object"/> to compare with the current <see cref="T:System.Object"/>.</param> + /// <returns> + /// true if the specified <see cref="T:System.Object"/> is equal to the current <see cref="T:System.Object"/>; otherwise, false. + /// </returns> + /// <exception cref="T:System.NullReferenceException"> + /// The <paramref name="obj"/> parameter is null. + /// </exception> + public override bool Equals(object obj) { + var other = obj as HostMetaProxy; + if (other == null) { + return false; + } + + return this.ProxyFormat == other.ProxyFormat && this.SigningHostFormat == other.SigningHostFormat; + } + + /// <summary> + /// Serves as a hash function for a particular type. + /// </summary> + /// <returns> + /// A hash code for the current <see cref="T:System.Object"/>. + /// </returns> + public override int GetHashCode() { + return this.ProxyFormat.GetHashCode(); + } + } + } +} diff --git a/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/Interop/AuthenticationResponseShim.cs b/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/Interop/AuthenticationResponseShim.cs new file mode 100644 index 0000000..c0d2b35 --- /dev/null +++ b/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/Interop/AuthenticationResponseShim.cs @@ -0,0 +1,120 @@ +//----------------------------------------------------------------------- +// <copyright file="AuthenticationResponseShim.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.Interop { + using System; + using System.Diagnostics.CodeAnalysis; + using System.Diagnostics.Contracts; + using System.Runtime.InteropServices; + using System.Web; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OpenId.Extensions.SimpleRegistration; + using DotNetOpenAuth.OpenId.RelyingParty; + + /// <summary> + /// The COM type used to provide details of an authentication result to a relying party COM client. + /// </summary> + [SuppressMessage("Microsoft.Interoperability", "CA1409:ComVisibleTypesShouldBeCreatable", Justification = "It's only creatable on the inside. It must be ComVisible for ASP to see it.")] + [ComVisible(true), Obsolete("This class acts as a COM Server and should not be called directly from .NET code.")] + public sealed class AuthenticationResponseShim { + /// <summary> + /// The response read in by the Relying Party. + /// </summary> + private readonly IAuthenticationResponse response; + + /// <summary> + /// Initializes a new instance of the <see cref="AuthenticationResponseShim"/> class. + /// </summary> + /// <param name="response">The response.</param> + internal AuthenticationResponseShim(IAuthenticationResponse response) { + Requires.NotNull(response, "response"); + + this.response = response; + var claimsResponse = this.response.GetExtension<ClaimsResponse>(); + if (claimsResponse != null) { + this.ClaimsResponse = new ClaimsResponseShim(claimsResponse); + } + } + + /// <summary> + /// Gets an Identifier that the end user claims to own. For use with user database storage and lookup. + /// May be null for some failed authentications (i.e. failed directed identity authentications). + /// </summary> + /// <remarks> + /// <para> + /// This is the secure identifier that should be used for database storage and lookup. + /// It is not always friendly (i.e. =Arnott becomes =!9B72.7DD1.50A9.5CCD), but it protects + /// user identities against spoofing and other attacks. + /// </para> + /// <para> + /// For user-friendly identifiers to display, use the + /// <see cref="FriendlyIdentifierForDisplay"/> property. + /// </para> + /// </remarks> + public string ClaimedIdentifier { + get { return this.response.ClaimedIdentifier; } + } + + /// <summary> + /// Gets a user-friendly OpenID Identifier for display purposes ONLY. + /// </summary> + /// <remarks> + /// <para> + /// This <i>should</i> be put through <see cref="HttpUtility.HtmlEncode(string)"/> before + /// sending to a browser to secure against javascript injection attacks. + /// </para> + /// <para> + /// This property retains some aspects of the user-supplied identifier that get lost + /// in the <see cref="ClaimedIdentifier"/>. For example, XRIs used as user-supplied + /// identifiers (i.e. =Arnott) become unfriendly unique strings (i.e. =!9B72.7DD1.50A9.5CCD). + /// For display purposes, such as text on a web page that says "You're logged in as ...", + /// this property serves to provide the =Arnott string, or whatever else is the most friendly + /// string close to what the user originally typed in. + /// </para> + /// <para> + /// If the user-supplied identifier is a URI, this property will be the URI after all + /// redirects, and with the protocol and fragment trimmed off. + /// If the user-supplied identifier is an XRI, this property will be the original XRI. + /// If the user-supplied identifier is an OpenID Provider identifier (i.e. yahoo.com), + /// this property will be the Claimed Identifier, with the protocol stripped if it is a URI. + /// </para> + /// <para> + /// It is <b>very</b> important that this property <i>never</i> be used for database storage + /// or lookup to avoid identity spoofing and other security risks. For database storage + /// and lookup please use the <see cref="ClaimedIdentifier"/> property. + /// </para> + /// </remarks> + public string FriendlyIdentifierForDisplay { + get { return this.response.FriendlyIdentifierForDisplay; } + } + + /// <summary> + /// Gets the provider endpoint that sent the assertion. + /// </summary> + public string ProviderEndpoint { + get { return this.response.Provider != null ? this.response.Provider.Uri.AbsoluteUri : null; } + } + + /// <summary> + /// Gets a value indicating whether the authentication attempt succeeded. + /// </summary> + public bool Successful { + get { return this.response.Status == AuthenticationStatus.Authenticated; } + } + + /// <summary> + /// Gets the Simple Registration response. + /// </summary> + public ClaimsResponseShim ClaimsResponse { get; private set; } + + /// <summary> + /// Gets details regarding a failed authentication attempt, if available. + /// </summary> + public string ExceptionMessage { + get { return this.response.Exception != null ? this.response.Exception.Message : null; } + } + } +} diff --git a/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/Interop/ClaimsResponseShim.cs b/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/Interop/ClaimsResponseShim.cs new file mode 100644 index 0000000..fa3b874 --- /dev/null +++ b/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/Interop/ClaimsResponseShim.cs @@ -0,0 +1,108 @@ +//----------------------------------------------------------------------- +// <copyright file="ClaimsResponseShim.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.Interop { + using System; + using System.Diagnostics.CodeAnalysis; + using System.Diagnostics.Contracts; + using System.Runtime.InteropServices; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OpenId.Extensions.SimpleRegistration; + + /// <summary> + /// A struct storing Simple Registration field values describing an + /// authenticating user. + /// </summary> + [SuppressMessage("Microsoft.Interoperability", "CA1409:ComVisibleTypesShouldBeCreatable", Justification = "It's only creatable on the inside. It must be ComVisible for ASP to see it.")] + [ComVisible(true), Obsolete("This class acts as a COM Server and should not be called directly from .NET code.")] + [ContractVerification(true)] + public sealed class ClaimsResponseShim { + /// <summary> + /// The Simple Registration claims response message that this shim wraps. + /// </summary> + private readonly ClaimsResponse response; + + /// <summary> + /// Initializes a new instance of the <see cref="ClaimsResponseShim"/> class. + /// </summary> + /// <param name="response">The Simple Registration response to wrap.</param> + internal ClaimsResponseShim(ClaimsResponse response) + { + Requires.NotNull(response, "response"); + + this.response = response; + } + + /// <summary> + /// Gets the nickname the user goes by. + /// </summary> + public string Nickname { + get { return this.response.Nickname; } + } + + /// <summary> + /// Gets the user's email address. + /// </summary> + public string Email { + get { return this.response.Email; } + } + + /// <summary> + /// Gets the full name of a user as a single string. + /// </summary> + public string FullName { + get { return this.response.FullName; } + } + + /// <summary> + /// Gets the raw birth date string given by the extension. + /// </summary> + /// <value>A string in the format yyyy-MM-dd.</value> + public string BirthDate { + get { return this.response.BirthDateRaw; } + } + + /// <summary> + /// Gets the gender of the user. + /// </summary> + public string Gender { + get { + if (this.response.Gender.HasValue) { + return this.response.Gender.Value == Extensions.SimpleRegistration.Gender.Male ? Constants.Genders.Male : Constants.Genders.Female; + } + return null; + } + } + + /// <summary> + /// Gets the zip code / postal code of the user. + /// </summary> + public string PostalCode { + get { return this.response.PostalCode; } + } + + /// <summary> + /// Gets the country of the user. + /// </summary> + public string Country { + get { return this.response.Country; } + } + + /// <summary> + /// Gets the primary/preferred language of the user. + /// </summary> + public string Language { + get { return this.response.Language; } + } + + /// <summary> + /// Gets the user's timezone. + /// </summary> + public string TimeZone { + get { return this.response.TimeZone; } + } + } +}
\ No newline at end of file diff --git a/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/Interop/OpenIdRelyingPartyShim.cs b/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/Interop/OpenIdRelyingPartyShim.cs new file mode 100644 index 0000000..873aabe --- /dev/null +++ b/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/Interop/OpenIdRelyingPartyShim.cs @@ -0,0 +1,190 @@ +//----------------------------------------------------------------------- +// <copyright file="OpenIdRelyingPartyShim.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.Interop { + using System; + using System.Diagnostics.CodeAnalysis; + using System.IO; + using System.Runtime.InteropServices; + using System.Text; + using System.Web; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OpenId.Extensions.SimpleRegistration; + using DotNetOpenAuth.OpenId.RelyingParty; + + /// <summary> + /// The COM interface describing the DotNetOpenAuth functionality available to + /// COM client OpenID relying parties. + /// </summary> + [Guid("56BD3DB0-EE0D-4191-ADFC-1F3705CD2636")] + [InterfaceType(ComInterfaceType.InterfaceIsDual)] + public interface IOpenIdRelyingParty { + /// <summary> + /// Creates an authentication request to verify that a user controls + /// some given Identifier. + /// </summary> + /// <param name="userSuppliedIdentifier"> + /// The Identifier supplied by the user. This may be a URL, an XRI or i-name. + /// </param> + /// <param name="realm"> + /// The shorest URL that describes this relying party web site's address. + /// For example, if your login page is found at https://www.example.com/login.aspx, + /// your realm would typically be https://www.example.com/. + /// </param> + /// <param name="returnToUrl"> + /// The URL of the login page, or the page prepared to receive authentication + /// responses from the OpenID Provider. + /// </param> + /// <returns> + /// An authentication request object that describes the HTTP response to + /// send to the user agent to initiate the authentication. + /// </returns> + /// <exception cref="ProtocolException">Thrown if no OpenID endpoint could be found.</exception> + string CreateRequest(string userSuppliedIdentifier, string realm, string returnToUrl); + + /// <summary> + /// Creates an authentication request to verify that a user controls + /// some given Identifier. + /// </summary> + /// <param name="userSuppliedIdentifier">The Identifier supplied by the user. This may be a URL, an XRI or i-name.</param> + /// <param name="realm">The shorest URL that describes this relying party web site's address. + /// For example, if your login page is found at https://www.example.com/login.aspx, + /// your realm would typically be https://www.example.com/.</param> + /// <param name="returnToUrl">The URL of the login page, or the page prepared to receive authentication + /// responses from the OpenID Provider.</param> + /// <param name="optionalSreg">A comma-delimited list of simple registration fields to request as optional.</param> + /// <param name="requiredSreg">A comma-delimited list of simple registration fields to request as required.</param> + /// <returns> + /// An authentication request object that describes the HTTP response to + /// send to the user agent to initiate the authentication. + /// </returns> + /// <exception cref="ProtocolException">Thrown if no OpenID endpoint could be found.</exception> + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Sreg", Justification = "Accepted acronym")] + string CreateRequestWithSimpleRegistration(string userSuppliedIdentifier, string realm, string returnToUrl, string optionalSreg, string requiredSreg); + + /// <summary> + /// Gets the result of a user agent's visit to his OpenId provider in an + /// authentication attempt. Null if no response is available. + /// </summary> + /// <param name="url">The incoming request URL .</param> + /// <param name="form">The form data that may have been included in the case of a POST request.</param> + /// <returns>The Provider's response to a previous authentication request, or null if no response is present.</returns> +#pragma warning disable 0618 // we're using the COM type properly + AuthenticationResponseShim ProcessAuthentication(string url, string form); +#pragma warning restore 0618 + } + + /// <summary> + /// Implementation of <see cref="IOpenIdRelyingParty"/>, providing a subset of the + /// functionality available to .NET clients. + /// </summary> + [Guid("8F97A798-B4C5-4da5-9727-EE7DD96A8CD9")] + [ProgId("DotNetOpenAuth.OpenId.RelyingParty.OpenIdRelyingParty")] + [ComVisible(true), Obsolete("This class acts as a COM Server and should not be called directly from .NET code.", true)] + [ClassInterface(ClassInterfaceType.None)] + public sealed class OpenIdRelyingPartyShim : IOpenIdRelyingParty { + /// <summary> + /// The OpenIdRelyingParty instance to use for requests. + /// </summary> + private static OpenIdRelyingParty relyingParty; + + /// <summary> + /// Initializes static members of the <see cref="OpenIdRelyingPartyShim"/> class. + /// </summary> + static OpenIdRelyingPartyShim() { + relyingParty = new OpenIdRelyingParty(null); + relyingParty.Behaviors.Add(new RelyingParty.Behaviors.AXFetchAsSregTransform()); + } + + /// <summary> + /// Initializes a new instance of the <see cref="OpenIdRelyingPartyShim"/> class. + /// </summary> + public OpenIdRelyingPartyShim() { + Reporting.RecordFeatureUse(this); + } + + /// <summary> + /// Creates an authentication request to verify that a user controls + /// some given Identifier. + /// </summary> + /// <param name="userSuppliedIdentifier"> + /// The Identifier supplied by the user. This may be a URL, an XRI or i-name. + /// </param> + /// <param name="realm"> + /// The shorest URL that describes this relying party web site's address. + /// For example, if your login page is found at https://www.example.com/login.aspx, + /// your realm would typically be https://www.example.com/. + /// </param> + /// <param name="returnToUrl"> + /// The URL of the login page, or the page prepared to receive authentication + /// responses from the OpenID Provider. + /// </param> + /// <returns> + /// An authentication request object that describes the HTTP response to + /// send to the user agent to initiate the authentication. + /// </returns> + /// <exception cref="ProtocolException">Thrown if no OpenID endpoint could be found.</exception> + [SuppressMessage("Microsoft.Usage", "CA2234:PassSystemUriObjectsInsteadOfStrings", Justification = "COM requires primitive types")] + public string CreateRequest(string userSuppliedIdentifier, string realm, string returnToUrl) { + var request = relyingParty.CreateRequest(userSuppliedIdentifier, realm, new Uri(returnToUrl)); + return request.RedirectingResponse.GetDirectUriRequest(relyingParty.Channel).AbsoluteUri; + } + + /// <summary> + /// Creates an authentication request to verify that a user controls + /// some given Identifier. + /// </summary> + /// <param name="userSuppliedIdentifier">The Identifier supplied by the user. This may be a URL, an XRI or i-name.</param> + /// <param name="realm">The shorest URL that describes this relying party web site's address. + /// For example, if your login page is found at https://www.example.com/login.aspx, + /// your realm would typically be https://www.example.com/.</param> + /// <param name="returnToUrl">The URL of the login page, or the page prepared to receive authentication + /// responses from the OpenID Provider.</param> + /// <param name="optionalSreg">A comma-delimited list of simple registration fields to request as optional.</param> + /// <param name="requiredSreg">A comma-delimited list of simple registration fields to request as required.</param> + /// <returns> + /// An authentication request object that describes the HTTP response to + /// send to the user agent to initiate the authentication. + /// </returns> + /// <exception cref="ProtocolException">Thrown if no OpenID endpoint could be found.</exception> + [SuppressMessage("Microsoft.Usage", "CA2234:PassSystemUriObjectsInsteadOfStrings", Justification = "COM requires primitive types")] + public string CreateRequestWithSimpleRegistration(string userSuppliedIdentifier, string realm, string returnToUrl, string optionalSreg, string requiredSreg) { + var request = relyingParty.CreateRequest(userSuppliedIdentifier, realm, new Uri(returnToUrl)); + + ClaimsRequest sreg = new ClaimsRequest(); + if (!string.IsNullOrEmpty(optionalSreg)) { + sreg.SetProfileRequestFromList(optionalSreg.Split(','), DemandLevel.Request); + } + if (!string.IsNullOrEmpty(requiredSreg)) { + sreg.SetProfileRequestFromList(requiredSreg.Split(','), DemandLevel.Require); + } + request.AddExtension(sreg); + return request.RedirectingResponse.GetDirectUriRequest(relyingParty.Channel).AbsoluteUri; + } + + /// <summary> + /// Gets the result of a user agent's visit to his OpenId provider in an + /// authentication attempt. Null if no response is available. + /// </summary> + /// <param name="url">The incoming request URL.</param> + /// <param name="form">The form data that may have been included in the case of a POST request.</param> + /// <returns>The Provider's response to a previous authentication request, or null if no response is present.</returns> + public AuthenticationResponseShim ProcessAuthentication(string url, string form) { + HttpRequestInfo requestInfo = new HttpRequestInfo { UrlBeforeRewriting = new Uri(url) }; + if (!string.IsNullOrEmpty(form)) { + requestInfo.HttpMethod = "POST"; + requestInfo.InputStream = new MemoryStream(Encoding.Unicode.GetBytes(form)); + } + + var response = relyingParty.GetResponse(requestInfo); + if (response != null) { + return new AuthenticationResponseShim(response); + } + + return null; + } + } +} diff --git a/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/Messages/AssociateDiffieHellmanRelyingPartyResponse.cs b/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/Messages/AssociateDiffieHellmanRelyingPartyResponse.cs new file mode 100644 index 0000000..2112288 --- /dev/null +++ b/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/Messages/AssociateDiffieHellmanRelyingPartyResponse.cs @@ -0,0 +1,50 @@ +//----------------------------------------------------------------------- +// <copyright file="AssociateDiffieHellmanRelyingPartyResponse.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.Messages { + using System; + using System.Diagnostics.Contracts; + using System.Security.Cryptography; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.Messaging.Reflection; + using Org.Mentalis.Security.Cryptography; + + /// <summary> + /// The successful Diffie-Hellman association response message. + /// </summary> + /// <remarks> + /// Association response messages are described in OpenID 2.0 section 8.2. This type covers section 8.2.3. + /// </remarks> + internal class AssociateDiffieHellmanRelyingPartyResponse : AssociateDiffieHellmanResponse, IAssociateSuccessfulResponseRelyingParty { + /// <summary> + /// Initializes a new instance of the <see cref="AssociateDiffieHellmanRelyingPartyResponse"/> class. + /// </summary> + /// <param name="responseVersion">The OpenID version of the response message.</param> + /// <param name="originatingRequest">The originating request.</param> + internal AssociateDiffieHellmanRelyingPartyResponse(Version responseVersion, AssociateDiffieHellmanRequest originatingRequest) + : base(responseVersion, originatingRequest) { + } + + /// <summary> + /// Creates the association at relying party side after the association response has been received. + /// </summary> + /// <param name="request">The original association request that was already sent and responded to.</param> + /// <returns>The newly created association.</returns> + /// <remarks> + /// The resulting association is <i>not</i> added to the association store and must be done by the caller. + /// </remarks> + public Association CreateAssociationAtRelyingParty(AssociateRequest request) { + var diffieHellmanRequest = request as AssociateDiffieHellmanRequest; + ErrorUtilities.VerifyArgument(diffieHellmanRequest != null, OpenIdStrings.DiffieHellmanAssociationRequired); + + HashAlgorithm hasher = DiffieHellmanUtilities.Lookup(Protocol, this.SessionType); + byte[] associationSecret = DiffieHellmanUtilities.SHAHashXorSecret(hasher, diffieHellmanRequest.Algorithm, this.DiffieHellmanServerPublic, this.EncodedMacKey); + + Association association = HmacShaAssociation.Create(Protocol, this.AssociationType, this.AssociationHandle, associationSecret, TimeSpan.FromSeconds(this.ExpiresIn)); + return association; + } + } +} diff --git a/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/Messages/AssociateRequestRelyingParty.cs b/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/Messages/AssociateRequestRelyingParty.cs new file mode 100644 index 0000000..0e00963 --- /dev/null +++ b/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/Messages/AssociateRequestRelyingParty.cs @@ -0,0 +1,83 @@ +//----------------------------------------------------------------------- +// <copyright file="AssociateRequestRelyingParty.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.Messages { + using System; + using System.Collections.Generic; + using System.Diagnostics.Contracts; + using System.Linq; + using System.Text; + using DotNetOpenAuth.OpenId.RelyingParty; + + /// <summary> + /// Utility methods for requesting associations from the relying party. + /// </summary> + internal static class AssociateRequestRelyingParty { + /// <summary> + /// Creates an association request message that is appropriate for a given Provider. + /// </summary> + /// <param name="securityRequirements">The set of requirements the selected association type must comply to.</param> + /// <param name="provider">The provider to create an association with.</param> + /// <returns> + /// The message to send to the Provider to request an association. + /// Null if no association could be created that meet the security requirements + /// and the provider OpenID version. + /// </returns> + internal static AssociateRequest Create(SecuritySettings securityRequirements, IProviderEndpoint provider) { + Requires.NotNull(securityRequirements, "securityRequirements"); + Requires.NotNull(provider, "provider"); + + // Apply our knowledge of the endpoint's transport, OpenID version, and + // security requirements to decide the best association. + bool unencryptedAllowed = provider.Uri.IsTransportSecure(); + bool useDiffieHellman = !unencryptedAllowed; + string associationType, sessionType; + if (!HmacShaAssociation.TryFindBestAssociation(Protocol.Lookup(provider.Version), true, securityRequirements, useDiffieHellman, out associationType, out sessionType)) { + // There are no associations that meet all requirements. + Logger.OpenId.Warn("Security requirements and protocol combination knock out all possible association types. Dumb mode forced."); + return null; + } + + return Create(securityRequirements, provider, associationType, sessionType); + } + + /// <summary> + /// Creates an association request message that is appropriate for a given Provider. + /// </summary> + /// <param name="securityRequirements">The set of requirements the selected association type must comply to.</param> + /// <param name="provider">The provider to create an association with.</param> + /// <param name="associationType">Type of the association.</param> + /// <param name="sessionType">Type of the session.</param> + /// <returns> + /// The message to send to the Provider to request an association. + /// Null if no association could be created that meet the security requirements + /// and the provider OpenID version. + /// </returns> + internal static AssociateRequest Create(SecuritySettings securityRequirements, IProviderEndpoint provider, string associationType, string sessionType) { + Requires.NotNull(securityRequirements, "securityRequirements"); + Requires.NotNull(provider, "provider"); + Requires.NotNullOrEmpty(associationType, "associationType"); + Requires.NotNull(sessionType, "sessionType"); + + bool unencryptedAllowed = provider.Uri.IsTransportSecure(); + if (unencryptedAllowed) { + var associateRequest = new AssociateUnencryptedRequest(provider.Version, provider.Uri); + associateRequest.AssociationType = associationType; + return associateRequest; + } else { +#if !ExcludeDiffieHellman + var associateRequest = new AssociateDiffieHellmanRequest(provider.Version, provider.Uri); + associateRequest.AssociationType = associationType; + associateRequest.SessionType = sessionType; + associateRequest.InitializeRequest(); + return associateRequest; +#else + return null; +#endif + } + } + } +} diff --git a/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/Messages/AssociateSuccessfulResponseRelyingPartyContract.cs b/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/Messages/AssociateSuccessfulResponseRelyingPartyContract.cs new file mode 100644 index 0000000..8999a2a --- /dev/null +++ b/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/Messages/AssociateSuccessfulResponseRelyingPartyContract.cs @@ -0,0 +1,79 @@ +//----------------------------------------------------------------------- +// <copyright file="AssociateSuccessfulResponseRelyingPartyContract.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth { + using System; + using System.Collections.Generic; + using System.Diagnostics.Contracts; + using System.Linq; + using System.Text; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OpenId; + using DotNetOpenAuth.OpenId.Messages; + + /// <summary> + /// Code contract for the <see cref="IAssociateSuccessfulResponseRelyingParty"/> interface. + /// </summary> + [ContractClassFor(typeof(IAssociateSuccessfulResponseRelyingParty))] + internal abstract class IAssociateSuccessfulResponseRelyingPartyContract : IAssociateSuccessfulResponseRelyingParty { + #region IProtocolMessage Members + + /// <summary> + /// Gets the level of protection this message requires. + /// </summary> + Messaging.MessageProtections Messaging.IProtocolMessage.RequiredProtection { + get { throw new NotImplementedException(); } + } + + /// <summary> + /// Gets a value indicating whether this is a direct or indirect message. + /// </summary> + Messaging.MessageTransport Messaging.IProtocolMessage.Transport { + get { throw new NotImplementedException(); } + } + + #endregion + + #region IMessage members + + /// <summary> + /// Gets the version of the protocol or extension this message is prepared to implement. + /// </summary> + Version Messaging.IMessage.Version { + get { throw new NotImplementedException(); } + } + + /// <summary> + /// Gets the extra, non-standard Protocol parameters included in the message. + /// </summary> + IDictionary<string, string> Messaging.IMessage.ExtraData { + get { throw new NotImplementedException(); } + } + + /// <summary> + /// Checks the message state for conformity to the protocol specification + /// and throws an exception if the message is invalid. + /// </summary> + /// <exception cref="ProtocolException">Thrown if the message is invalid.</exception> + void Messaging.IMessage.EnsureValidMessage() { + throw new NotImplementedException(); + } + + #endregion + + /// <summary> + /// Called to create the Association based on a request previously given by the Relying Party. + /// </summary> + /// <param name="request">The prior request for an association.</param> + /// <returns> + /// The created association. + /// </returns> + Association IAssociateSuccessfulResponseRelyingParty.CreateAssociationAtRelyingParty(AssociateRequest request) { + Requires.NotNull(request, "request"); + throw new NotImplementedException(); + } + } +} diff --git a/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/Messages/AssociateUnencryptedResponseRelyingParty.cs b/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/Messages/AssociateUnencryptedResponseRelyingParty.cs new file mode 100644 index 0000000..b2a5e11 --- /dev/null +++ b/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/Messages/AssociateUnencryptedResponseRelyingParty.cs @@ -0,0 +1,36 @@ +//----------------------------------------------------------------------- +// <copyright file="AssociateUnencryptedResponseRelyingParty.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.Messages { + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + + /// <summary> + /// A response to an unencrypted assocation request, as it is received by the relying party. + /// </summary> + internal class AssociateUnencryptedResponseRelyingParty : AssociateUnencryptedResponse, IAssociateSuccessfulResponseRelyingParty { + /// <summary> + /// Initializes a new instance of the <see cref="AssociateUnencryptedResponseRelyingParty"/> class. + /// </summary> + /// <param name="version">The version.</param> + /// <param name="request">The request.</param> + internal AssociateUnencryptedResponseRelyingParty(Version version, AssociateUnencryptedRequest request) + : base(version, request) { + } + + /// <summary> + /// Called to create the Association based on a request previously given by the Relying Party. + /// </summary> + /// <param name="request">The prior request for an association.</param> + /// <returns>The created association.</returns> + public Association CreateAssociationAtRelyingParty(AssociateRequest request) { + Association association = HmacShaAssociation.Create(Protocol, this.AssociationType, this.AssociationHandle, this.MacKey, TimeSpan.FromSeconds(this.ExpiresIn)); + return association; + } + } +} diff --git a/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/Messages/IAssociateSuccessfulResponseRelyingParty.cs b/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/Messages/IAssociateSuccessfulResponseRelyingParty.cs new file mode 100644 index 0000000..a3eef44 --- /dev/null +++ b/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/Messages/IAssociateSuccessfulResponseRelyingParty.cs @@ -0,0 +1,27 @@ +//----------------------------------------------------------------------- +// <copyright file="IAssociateSuccessfulResponseRelyingParty.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.Messages { + using System; + using System.Collections.Generic; + using System.Diagnostics.Contracts; + using System.Linq; + using System.Text; + using DotNetOpenAuth.Messaging; + + /// <summary> + /// A successful association response as it is received by the relying party. + /// </summary> + [ContractClass(typeof(IAssociateSuccessfulResponseRelyingPartyContract))] + internal interface IAssociateSuccessfulResponseRelyingParty : IProtocolMessage { + /// <summary> + /// Called to create the Association based on a request previously given by the Relying Party. + /// </summary> + /// <param name="request">The prior request for an association.</param> + /// <returns>The created association.</returns> + Association CreateAssociationAtRelyingParty(AssociateRequest request); + } +} diff --git a/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/AssociationManager.cs b/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/AssociationManager.cs new file mode 100644 index 0000000..eb501c5 --- /dev/null +++ b/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/AssociationManager.cs @@ -0,0 +1,246 @@ +//----------------------------------------------------------------------- +// <copyright file="AssociationManager.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.RelyingParty { + using System; + using System.Collections.Generic; + using System.Diagnostics.Contracts; + using System.Linq; + using System.Net; + using System.Security; + using System.Text; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OpenId.ChannelElements; + using DotNetOpenAuth.OpenId.Messages; + + /// <summary> + /// Manages the establishment, storage and retrieval of associations at the relying party. + /// </summary> + internal class AssociationManager { + /// <summary> + /// The storage to use for saving and retrieving associations. May be null. + /// </summary> + private readonly IRelyingPartyAssociationStore associationStore; + + /// <summary> + /// Backing field for the <see cref="Channel"/> property. + /// </summary> + private Channel channel; + + /// <summary> + /// Backing field for the <see cref="SecuritySettings"/> property. + /// </summary> + private RelyingPartySecuritySettings securitySettings; + + /// <summary> + /// Initializes a new instance of the <see cref="AssociationManager"/> class. + /// </summary> + /// <param name="channel">The channel the relying party is using.</param> + /// <param name="associationStore">The association store. May be null for dumb mode relying parties.</param> + /// <param name="securitySettings">The security settings.</param> + internal AssociationManager(Channel channel, IRelyingPartyAssociationStore associationStore, RelyingPartySecuritySettings securitySettings) { + Requires.NotNull(channel, "channel"); + Requires.NotNull(securitySettings, "securitySettings"); + + this.channel = channel; + this.associationStore = associationStore; + this.securitySettings = securitySettings; + } + + /// <summary> + /// Gets or sets the channel to use for establishing associations. + /// </summary> + /// <value>The channel.</value> + internal Channel Channel { + get { + return this.channel; + } + + set { + Requires.NotNull(value, "value"); + this.channel = value; + } + } + + /// <summary> + /// Gets or sets the security settings to apply in choosing association types to support. + /// </summary> + internal RelyingPartySecuritySettings SecuritySettings { + get { + return this.securitySettings; + } + + set { + Requires.NotNull(value, "value"); + this.securitySettings = value; + } + } + + /// <summary> + /// Gets a value indicating whether this instance has an association store. + /// </summary> + /// <value> + /// <c>true</c> if the relying party can act in 'smart' mode; + /// <c>false</c> if the relying party must always act in 'dumb' mode. + /// </value> + internal bool HasAssociationStore { + get { return this.associationStore != null; } + } + + /// <summary> + /// Gets the storage to use for saving and retrieving associations. May be null. + /// </summary> + internal IRelyingPartyAssociationStore AssociationStoreTestHook { + get { return this.associationStore; } + } + + /// <summary> + /// Gets an association between this Relying Party and a given Provider + /// if it already exists in the association store. + /// </summary> + /// <param name="provider">The provider to create an association with.</param> + /// <returns>The association if one exists and has useful life remaining. Otherwise <c>null</c>.</returns> + internal Association GetExistingAssociation(IProviderEndpoint provider) { + Requires.NotNull(provider, "provider"); + + // If the RP has no application store for associations, there's no point in creating one. + if (this.associationStore == null) { + return null; + } + + Association association = this.associationStore.GetAssociation(provider.Uri, this.SecuritySettings); + + // If the returned association does not fulfill security requirements, ignore it. + if (association != null && !this.SecuritySettings.IsAssociationInPermittedRange(association)) { + association = null; + } + + if (association != null && !association.HasUsefulLifeRemaining) { + association = null; + } + + return association; + } + + /// <summary> + /// Gets an existing association with the specified Provider, or attempts to create + /// a new association of one does not already exist. + /// </summary> + /// <param name="provider">The provider to get an association for.</param> + /// <returns>The existing or new association; <c>null</c> if none existed and one could not be created.</returns> + internal Association GetOrCreateAssociation(IProviderEndpoint provider) { + return this.GetExistingAssociation(provider) ?? this.CreateNewAssociation(provider); + } + + /// <summary> + /// Creates a new association with a given Provider. + /// </summary> + /// <param name="provider">The provider to create an association with.</param> + /// <returns> + /// The newly created association, or null if no association can be created with + /// the given Provider given the current security settings. + /// </returns> + /// <remarks> + /// A new association is created and returned even if one already exists in the + /// association store. + /// Any new association is automatically added to the <see cref="associationStore"/>. + /// </remarks> + private Association CreateNewAssociation(IProviderEndpoint provider) { + Requires.NotNull(provider, "provider"); + + // If there is no association store, there is no point in creating an association. + if (this.associationStore == null) { + return null; + } + + try { + var associateRequest = AssociateRequestRelyingParty.Create(this.securitySettings, provider); + + const int RenegotiateRetries = 1; + return this.CreateNewAssociation(provider, associateRequest, RenegotiateRetries); + } catch (VerificationException ex) { + // See Trac ticket #163. In partial trust host environments, the + // Diffie-Hellman implementation we're using for HTTP OP endpoints + // sometimes causes the CLR to throw: + // "VerificationException: Operation could destabilize the runtime." + // Just give up and use dumb mode in this case. + Logger.OpenId.ErrorFormat("VerificationException occurred while trying to create an association with {0}. {1}", provider.Uri, ex); + return null; + } + } + + /// <summary> + /// Creates a new association with a given Provider. + /// </summary> + /// <param name="provider">The provider to create an association with.</param> + /// <param name="associateRequest">The associate request. May be <c>null</c>, which will always result in a <c>null</c> return value..</param> + /// <param name="retriesRemaining">The number of times to try the associate request again if the Provider suggests it.</param> + /// <returns> + /// The newly created association, or null if no association can be created with + /// the given Provider given the current security settings. + /// </returns> + private Association CreateNewAssociation(IProviderEndpoint provider, AssociateRequest associateRequest, int retriesRemaining) { + Requires.NotNull(provider, "provider"); + + if (associateRequest == null || retriesRemaining < 0) { + // this can happen if security requirements and protocol conflict + // to where there are no association types to choose from. + return null; + } + + try { + var associateResponse = this.channel.Request(associateRequest); + var associateSuccessfulResponse = associateResponse as IAssociateSuccessfulResponseRelyingParty; + var associateUnsuccessfulResponse = associateResponse as AssociateUnsuccessfulResponse; + if (associateSuccessfulResponse != null) { + Association association = associateSuccessfulResponse.CreateAssociationAtRelyingParty(associateRequest); + this.associationStore.StoreAssociation(provider.Uri, association); + return association; + } else if (associateUnsuccessfulResponse != null) { + if (string.IsNullOrEmpty(associateUnsuccessfulResponse.AssociationType)) { + Logger.OpenId.Debug("Provider rejected an association request and gave no suggestion as to an alternative association type. Giving up."); + return null; + } + + if (!this.securitySettings.IsAssociationInPermittedRange(Protocol.Lookup(provider.Version), associateUnsuccessfulResponse.AssociationType)) { + Logger.OpenId.DebugFormat("Provider rejected an association request and suggested '{0}' as an association to try, which this Relying Party does not support. Giving up.", associateUnsuccessfulResponse.AssociationType); + return null; + } + + if (retriesRemaining <= 0) { + Logger.OpenId.Debug("Unable to agree on an association type with the Provider in the allowed number of retries. Giving up."); + return null; + } + + // Make sure the Provider isn't suggesting an incompatible pair of association/session types. + Protocol protocol = Protocol.Lookup(provider.Version); + ErrorUtilities.VerifyProtocol( + HmacShaAssociation.IsDHSessionCompatible(protocol, associateUnsuccessfulResponse.AssociationType, associateUnsuccessfulResponse.SessionType), + OpenIdStrings.IncompatibleAssociationAndSessionTypes, + associateUnsuccessfulResponse.AssociationType, + associateUnsuccessfulResponse.SessionType); + + associateRequest = AssociateRequestRelyingParty.Create(this.securitySettings, provider, associateUnsuccessfulResponse.AssociationType, associateUnsuccessfulResponse.SessionType); + return this.CreateNewAssociation(provider, associateRequest, retriesRemaining - 1); + } else { + throw new ProtocolException(MessagingStrings.UnexpectedMessageReceivedOfMany); + } + } catch (ProtocolException ex) { + // If the association failed because the remote server can't handle Expect: 100 Continue headers, + // then our web request handler should have already accomodated for future calls. Go ahead and + // immediately make one of those future calls now to try to get the association to succeed. + if (StandardWebRequestHandler.IsExceptionFrom417ExpectationFailed(ex)) { + return this.CreateNewAssociation(provider, associateRequest, retriesRemaining - 1); + } + + // Since having associations with OPs is not totally critical, we'll log and eat + // the exception so that auth may continue in dumb mode. + Logger.OpenId.ErrorFormat("An error occurred while trying to create an association with {0}. {1}", provider.Uri, ex); + return null; + } + } + } +} diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/AssociationPreference.cs b/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/AssociationPreference.cs index 9f4a21f..9f4a21f 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/AssociationPreference.cs +++ b/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/AssociationPreference.cs diff --git a/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/Associations.cs b/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/Associations.cs new file mode 100644 index 0000000..e65750f --- /dev/null +++ b/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/Associations.cs @@ -0,0 +1,127 @@ +//----------------------------------------------------------------------- +// <copyright file="Associations.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.RelyingParty { + using System; + using System.Collections.Generic; + using System.Collections.ObjectModel; + using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; + using System.Diagnostics.Contracts; + using System.Linq; + using DotNetOpenAuth.Messaging; + + /// <summary> + /// A dictionary of handle/Association pairs. + /// </summary> + /// <remarks> + /// Each method is locked, even if it is only one line, so that they are thread safe + /// against each other, particularly the ones that enumerate over the list, since they + /// can break if the collection is changed by another thread during enumeration. + /// </remarks> + [DebuggerDisplay("Count = {assocs.Count}")] + [ContractVerification(true)] + internal class Associations { + /// <summary> + /// The lookup table where keys are the association handles and values are the associations themselves. + /// </summary> + [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] + private readonly KeyedCollection<string, Association> associations = new KeyedCollectionDelegate<string, Association>(assoc => assoc.Handle); + + /// <summary> + /// Initializes a new instance of the <see cref="Associations"/> class. + /// </summary> + public Associations() { + } + + /// <summary> + /// Gets the <see cref="Association"/>s ordered in order of descending issue date + /// (most recently issued comes first). An empty sequence if no valid associations exist. + /// </summary> + /// <remarks> + /// This property is used by relying parties that are initiating authentication requests. + /// It does not apply to Providers, which always need a specific association by handle. + /// </remarks> + public IEnumerable<Association> Best { + get { + Contract.Ensures(Contract.Result<IEnumerable<Association>>() != null); + + lock (this.associations) { + return this.associations.OrderByDescending(assoc => assoc.Issued); + } + } + } + + /// <summary> + /// Stores an <see cref="Association"/> in the collection. + /// </summary> + /// <param name="association">The association to add to the collection.</param> + public void Set(Association association) { + Requires.NotNull(association, "association"); + Contract.Ensures(this.Get(association.Handle) == association); + lock (this.associations) { + this.associations.Remove(association.Handle); // just in case one already exists. + this.associations.Add(association); + } + + Contract.Assume(this.Get(association.Handle) == association); + } + + /// <summary> + /// Returns the <see cref="Association"/> with the given handle. Null if not found. + /// </summary> + /// <param name="handle">The handle to the required association.</param> + /// <returns>The desired association, or null if none with the given handle could be found.</returns> + [Pure] + public Association Get(string handle) { + Requires.NotNullOrEmpty(handle, "handle"); + + lock (this.associations) { + if (this.associations.Contains(handle)) { + return this.associations[handle]; + } else { + return null; + } + } + } + + /// <summary> + /// Removes the <see cref="Association"/> with the given handle. + /// </summary> + /// <param name="handle">The handle to the required association.</param> + /// <returns>Whether an <see cref="Association"/> with the given handle was in the collection for removal.</returns> + public bool Remove(string handle) { + Requires.NotNullOrEmpty(handle, "handle"); + lock (this.associations) { + return this.associations.Remove(handle); + } + } + + /// <summary> + /// Removes all expired associations from the collection. + /// </summary> + public void ClearExpired() { + lock (this.associations) { + var expireds = this.associations.Where(assoc => assoc.IsExpired).ToList(); + foreach (Association assoc in expireds) { + this.associations.Remove(assoc.Handle); + } + } + } + +#if CONTRACTS_FULL + /// <summary> + /// Verifies conditions that should be true for any valid state of this object. + /// </summary> + [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Called by code contracts.")] + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called by code contracts.")] + [ContractInvariantMethod] + private void ObjectInvariant() { + Contract.Invariant(this.associations != null); + } +#endif + } +} diff --git a/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/AuthenticationRequest.cs b/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/AuthenticationRequest.cs new file mode 100644 index 0000000..c85c0c5 --- /dev/null +++ b/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/AuthenticationRequest.cs @@ -0,0 +1,594 @@ +//----------------------------------------------------------------------- +// <copyright file="AuthenticationRequest.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.RelyingParty { + using System; + using System.Collections.Generic; + using System.Collections.Specialized; + using System.Diagnostics.Contracts; + using System.Linq; + using System.Text; + using System.Threading; + using System.Web; + + using DotNetOpenAuth.Configuration; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OpenId.ChannelElements; + using DotNetOpenAuth.OpenId.Messages; + + /// <summary> + /// Facilitates customization and creation and an authentication request + /// that a Relying Party is preparing to send. + /// </summary> + internal class AuthenticationRequest : IAuthenticationRequest { + /// <summary> + /// The name of the internal callback parameter to use to store the user-supplied identifier. + /// </summary> + internal const string UserSuppliedIdentifierParameterName = OpenIdUtilities.CustomParameterPrefix + "userSuppliedIdentifier"; + + /// <summary> + /// The relying party that created this request object. + /// </summary> + private readonly OpenIdRelyingParty RelyingParty; + + /// <summary> + /// How an association may or should be created or used in the formulation of the + /// authentication request. + /// </summary> + private AssociationPreference associationPreference = AssociationPreference.IfPossible; + + /// <summary> + /// The extensions that have been added to this authentication request. + /// </summary> + private List<IOpenIdMessageExtension> extensions = new List<IOpenIdMessageExtension>(); + + /// <summary> + /// Arguments to add to the return_to part of the query string, so that + /// these values come back to the consumer when the user agent returns. + /// </summary> + private Dictionary<string, string> returnToArgs = new Dictionary<string, string>(); + + /// <summary> + /// A value indicating whether the return_to callback arguments must be signed. + /// </summary> + /// <remarks> + /// This field defaults to false, but is set to true as soon as the first callback argument + /// is added that indicates it must be signed. At which point, all arguments are signed + /// even if individual ones did not need to be. + /// </remarks> + private bool returnToArgsMustBeSigned; + + /// <summary> + /// Initializes a new instance of the <see cref="AuthenticationRequest"/> class. + /// </summary> + /// <param name="discoveryResult">The endpoint that describes the OpenID Identifier and Provider that will complete the authentication.</param> + /// <param name="realm">The realm, or root URL, of the host web site.</param> + /// <param name="returnToUrl">The base return_to URL that the Provider should return the user to to complete authentication. This should not include callback parameters as these should be added using the <see cref="AddCallbackArguments(string, string)"/> method.</param> + /// <param name="relyingParty">The relying party that created this instance.</param> + private AuthenticationRequest(IdentifierDiscoveryResult discoveryResult, Realm realm, Uri returnToUrl, OpenIdRelyingParty relyingParty) { + Requires.NotNull(discoveryResult, "discoveryResult"); + Requires.NotNull(realm, "realm"); + Requires.NotNull(returnToUrl, "returnToUrl"); + Requires.NotNull(relyingParty, "relyingParty"); + + this.DiscoveryResult = discoveryResult; + this.RelyingParty = relyingParty; + this.Realm = realm; + this.ReturnToUrl = returnToUrl; + + this.Mode = AuthenticationRequestMode.Setup; + } + + #region IAuthenticationRequest Members + + /// <summary> + /// Gets or sets the mode the Provider should use during authentication. + /// </summary> + /// <value></value> + public AuthenticationRequestMode Mode { get; set; } + + /// <summary> + /// Gets the HTTP response the relying party should send to the user agent + /// to redirect it to the OpenID Provider to start the OpenID authentication process. + /// </summary> + /// <value></value> + public OutgoingWebResponse RedirectingResponse { + get { + foreach (var behavior in this.RelyingParty.Behaviors) { + behavior.OnOutgoingAuthenticationRequest(this); + } + + return this.RelyingParty.Channel.PrepareResponse(this.CreateRequestMessage()); + } + } + + /// <summary> + /// Gets the URL that the user agent will return to after authentication + /// completes or fails at the Provider. + /// </summary> + /// <value></value> + public Uri ReturnToUrl { get; private set; } + + /// <summary> + /// Gets the URL that identifies this consumer web application that + /// the Provider will display to the end user. + /// </summary> + public Realm Realm { get; private set; } + + /// <summary> + /// Gets the Claimed Identifier that the User Supplied Identifier + /// resolved to. Null if the user provided an OP Identifier + /// (directed identity). + /// </summary> + /// <value></value> + /// <remarks> + /// Null is returned if the user is using the directed identity feature + /// of OpenID 2.0 to make it nearly impossible for a relying party site + /// to improperly store the reserved OpenID URL used for directed identity + /// as a user's own Identifier. + /// However, to test for the Directed Identity feature, please test the + /// <see cref="IsDirectedIdentity"/> property rather than testing this + /// property for a null value. + /// </remarks> + public Identifier ClaimedIdentifier { + get { return this.IsDirectedIdentity ? null : this.DiscoveryResult.ClaimedIdentifier; } + } + + /// <summary> + /// Gets a value indicating whether the authenticating user has chosen to let the Provider + /// determine and send the ClaimedIdentifier after authentication. + /// </summary> + public bool IsDirectedIdentity { + get { return this.DiscoveryResult.ClaimedIdentifier == this.DiscoveryResult.Protocol.ClaimedIdentifierForOPIdentifier; } + } + + /// <summary> + /// Gets or sets a value indicating whether this request only carries extensions + /// and is not a request to verify that the user controls some identifier. + /// </summary> + /// <value> + /// <c>true</c> if this request is merely a carrier of extensions and is not + /// about an OpenID identifier; otherwise, <c>false</c>. + /// </value> + public bool IsExtensionOnly { get; set; } + + /// <summary> + /// Gets information about the OpenId Provider, as advertised by the + /// OpenId discovery documents found at the <see cref="ClaimedIdentifier"/> + /// location. + /// </summary> + public IProviderEndpoint Provider { + get { return this.DiscoveryResult; } + } + + /// <summary> + /// Gets the discovery result leading to the formulation of this request. + /// </summary> + /// <value>The discovery result.</value> + public IdentifierDiscoveryResult DiscoveryResult { get; private set; } + + #endregion + + /// <summary> + /// Gets or sets how an association may or should be created or used + /// in the formulation of the authentication request. + /// </summary> + internal AssociationPreference AssociationPreference { + get { return this.associationPreference; } + set { this.associationPreference = value; } + } + + /// <summary> + /// Gets the extensions that have been added to the request. + /// </summary> + internal IEnumerable<IOpenIdMessageExtension> AppliedExtensions { + get { return this.extensions; } + } + + /// <summary> + /// Gets the list of extensions for this request. + /// </summary> + internal IList<IOpenIdMessageExtension> Extensions { + get { return this.extensions; } + } + + #region IAuthenticationRequest methods + + /// <summary> + /// Makes a dictionary of key/value pairs available when the authentication is completed. + /// </summary> + /// <param name="arguments">The arguments to add to the request's return_to URI.</param> + /// <remarks> + /// <para>Note that these values are NOT protected against eavesdropping in transit. No + /// privacy-sensitive data should be stored using this method.</para> + /// <para>The values stored here can be retrieved using + /// <see cref="IAuthenticationResponse.GetCallbackArguments"/>, which will only return the value + /// if it hasn't been tampered with in transit.</para> + /// <para>Since the data set here is sent in the querystring of the request and some + /// servers place limits on the size of a request URL, this data should be kept relatively + /// small to ensure successful authentication. About 1.5KB is about all that should be stored.</para> + /// </remarks> + public void AddCallbackArguments(IDictionary<string, string> arguments) { + ErrorUtilities.VerifyOperation(this.RelyingParty.CanSignCallbackArguments, OpenIdStrings.CallbackArgumentsRequireSecretStore, typeof(IRelyingPartyAssociationStore).Name, typeof(OpenIdRelyingParty).Name); + + this.returnToArgsMustBeSigned = true; + foreach (var pair in arguments) { + ErrorUtilities.VerifyArgument(!string.IsNullOrEmpty(pair.Key), MessagingStrings.UnexpectedNullOrEmptyKey); + ErrorUtilities.VerifyArgument(pair.Value != null, MessagingStrings.UnexpectedNullValue, pair.Key); + + this.returnToArgs.Add(pair.Key, pair.Value); + } + } + + /// <summary> + /// Makes a key/value pair available when the authentication is completed. + /// </summary> + /// <param name="key">The parameter name.</param> + /// <param name="value">The value of the argument.</param> + /// <remarks> + /// <para>Note that these values are NOT protected against eavesdropping in transit. No + /// privacy-sensitive data should be stored using this method.</para> + /// <para>The value stored here can be retrieved using + /// <see cref="IAuthenticationResponse.GetCallbackArgument"/>, which will only return the value + /// if it hasn't been tampered with in transit.</para> + /// <para>Since the data set here is sent in the querystring of the request and some + /// servers place limits on the size of a request URL, this data should be kept relatively + /// small to ensure successful authentication. About 1.5KB is about all that should be stored.</para> + /// </remarks> + public void AddCallbackArguments(string key, string value) { + ErrorUtilities.VerifyOperation(this.RelyingParty.CanSignCallbackArguments, OpenIdStrings.CallbackArgumentsRequireSecretStore, typeof(IRelyingPartyAssociationStore).Name, typeof(OpenIdRelyingParty).Name); + + this.returnToArgsMustBeSigned = true; + this.returnToArgs.Add(key, value); + } + + /// <summary> + /// Makes a key/value pair available when the authentication is completed. + /// </summary> + /// <param name="key">The parameter name.</param> + /// <param name="value">The value of the argument. Must not be null.</param> + /// <remarks> + /// <para>Note that these values are NOT protected against tampering in transit. No + /// security-sensitive data should be stored using this method.</para> + /// <para>The value stored here can be retrieved using + /// <see cref="IAuthenticationResponse.GetCallbackArgument"/>.</para> + /// <para>Since the data set here is sent in the querystring of the request and some + /// servers place limits on the size of a request URL, this data should be kept relatively + /// small to ensure successful authentication. About 1.5KB is about all that should be stored.</para> + /// </remarks> + public void SetCallbackArgument(string key, string value) { + ErrorUtilities.VerifyOperation(this.RelyingParty.CanSignCallbackArguments, OpenIdStrings.CallbackArgumentsRequireSecretStore, typeof(IRelyingPartyAssociationStore).Name, typeof(OpenIdRelyingParty).Name); + + this.returnToArgsMustBeSigned = true; + this.returnToArgs[key] = value; + } + + /// <summary> + /// Makes a key/value pair available when the authentication is completed without + /// requiring a return_to signature to protect against tampering of the callback argument. + /// </summary> + /// <param name="key">The parameter name.</param> + /// <param name="value">The value of the argument. Must not be null.</param> + /// <remarks> + /// <para>Note that these values are NOT protected against eavesdropping or tampering in transit. No + /// security-sensitive data should be stored using this method. </para> + /// <para>The value stored here can be retrieved using + /// <see cref="IAuthenticationResponse.GetCallbackArgument"/>.</para> + /// <para>Since the data set here is sent in the querystring of the request and some + /// servers place limits on the size of a request URL, this data should be kept relatively + /// small to ensure successful authentication. About 1.5KB is about all that should be stored.</para> + /// </remarks> + public void SetUntrustedCallbackArgument(string key, string value) { + this.returnToArgs[key] = value; + } + + /// <summary> + /// Adds an OpenID extension to the request directed at the OpenID provider. + /// </summary> + /// <param name="extension">The initialized extension to add to the request.</param> + public void AddExtension(IOpenIdMessageExtension extension) { + this.extensions.Add(extension); + } + + /// <summary> + /// Redirects the user agent to the provider for authentication. + /// </summary> + /// <remarks> + /// This method requires an ASP.NET HttpContext. + /// </remarks> + public void RedirectToProvider() { + this.RedirectingResponse.Send(); + } + + #endregion + + /// <summary> + /// Performs identifier discovery, creates associations and generates authentication requests + /// on-demand for as long as new ones can be generated based on the results of Identifier discovery. + /// </summary> + /// <param name="userSuppliedIdentifier">The user supplied identifier.</param> + /// <param name="relyingParty">The relying party.</param> + /// <param name="realm">The realm.</param> + /// <param name="returnToUrl">The return_to base URL.</param> + /// <param name="createNewAssociationsAsNeeded">if set to <c>true</c>, associations that do not exist between this Relying Party and the asserting Providers are created before the authentication request is created.</param> + /// <returns> + /// A sequence of authentication requests, any of which constitutes a valid identity assertion on the Claimed Identifier. + /// Never null, but may be empty. + /// </returns> + internal static IEnumerable<AuthenticationRequest> Create(Identifier userSuppliedIdentifier, OpenIdRelyingParty relyingParty, Realm realm, Uri returnToUrl, bool createNewAssociationsAsNeeded) { + Requires.NotNull(userSuppliedIdentifier, "userSuppliedIdentifier"); + Requires.NotNull(relyingParty, "relyingParty"); + Requires.NotNull(realm, "realm"); + Contract.Ensures(Contract.Result<IEnumerable<AuthenticationRequest>>() != null); + + // Normalize the portion of the return_to path that correlates to the realm for capitalization. + // (so that if a web app base path is /MyApp/, but the URL of this request happens to be + // /myapp/login.aspx, we bump up the return_to Url to use /MyApp/ so it matches the realm. + UriBuilder returnTo = new UriBuilder(returnToUrl); + if (returnTo.Path.StartsWith(realm.AbsolutePath, StringComparison.OrdinalIgnoreCase) && + !returnTo.Path.StartsWith(realm.AbsolutePath, StringComparison.Ordinal)) { + returnTo.Path = realm.AbsolutePath + returnTo.Path.Substring(realm.AbsolutePath.Length); + returnToUrl = returnTo.Uri; + } + + userSuppliedIdentifier = userSuppliedIdentifier.TrimFragment(); + if (relyingParty.SecuritySettings.RequireSsl) { + // Rather than check for successful SSL conversion at this stage, + // We'll wait for secure discovery to fail on the new identifier. + if (!userSuppliedIdentifier.TryRequireSsl(out userSuppliedIdentifier)) { + // But at least log the failure. + Logger.OpenId.WarnFormat("RequireSsl mode is on, so discovery on insecure identifier {0} will yield no results.", userSuppliedIdentifier); + } + } + + if (Logger.OpenId.IsWarnEnabled && returnToUrl.Query != null) { + NameValueCollection returnToArgs = HttpUtility.ParseQueryString(returnToUrl.Query); + foreach (string key in returnToArgs) { + if (OpenIdRelyingParty.IsOpenIdSupportingParameter(key)) { + Logger.OpenId.WarnFormat("OpenID argument \"{0}\" found in return_to URL. This can corrupt an OpenID response.", key); + } + } + } + + // Throw an exception now if the realm and the return_to URLs don't match + // as required by the provider. We could wait for the provider to test this and + // fail, but this will be faster and give us a better error message. + ErrorUtilities.VerifyProtocol(realm.Contains(returnToUrl), OpenIdStrings.ReturnToNotUnderRealm, returnToUrl, realm); + + // Perform discovery right now (not deferred). + IEnumerable<IdentifierDiscoveryResult> serviceEndpoints; + try { + var results = relyingParty.Discover(userSuppliedIdentifier).CacheGeneratedResults(); + + // If any OP Identifier service elements were found, we must not proceed + // to use any Claimed Identifier services, per OpenID 2.0 sections 7.3.2.2 and 11.2. + // For a discussion on this topic, see + // http://groups.google.com/group/dotnetopenid/browse_thread/thread/4b5a8c6b2210f387/5e25910e4d2252c8 + // Usually the Discover method we called will automatically filter this for us, but + // just to be sure, we'll do it here as well since the RP may be configured to allow + // these dual identifiers for assertion verification purposes. + var opIdentifiers = results.Where(result => result.ClaimedIdentifier == result.Protocol.ClaimedIdentifierForOPIdentifier).CacheGeneratedResults(); + var claimedIdentifiers = results.Where(result => result.ClaimedIdentifier != result.Protocol.ClaimedIdentifierForOPIdentifier); + serviceEndpoints = opIdentifiers.Any() ? opIdentifiers : claimedIdentifiers; + } catch (ProtocolException ex) { + Logger.Yadis.ErrorFormat("Error while performing discovery on: \"{0}\": {1}", userSuppliedIdentifier, ex); + serviceEndpoints = Enumerable.Empty<IdentifierDiscoveryResult>(); + } + + // Filter disallowed endpoints. + serviceEndpoints = relyingParty.SecuritySettings.FilterEndpoints(serviceEndpoints); + + // Call another method that defers request generation. + return CreateInternal(userSuppliedIdentifier, relyingParty, realm, returnToUrl, serviceEndpoints, createNewAssociationsAsNeeded); + } + + /// <summary> + /// Creates an instance of <see cref="AuthenticationRequest"/> FOR TESTING PURPOSES ONLY. + /// </summary> + /// <param name="discoveryResult">The discovery result.</param> + /// <param name="realm">The realm.</param> + /// <param name="returnTo">The return to.</param> + /// <param name="rp">The relying party.</param> + /// <returns>The instantiated <see cref="AuthenticationRequest"/>.</returns> + internal static AuthenticationRequest CreateForTest(IdentifierDiscoveryResult discoveryResult, Realm realm, Uri returnTo, OpenIdRelyingParty rp) { + return new AuthenticationRequest(discoveryResult, realm, returnTo, rp); + } + + /// <summary> + /// Creates the request message to send to the Provider, + /// based on the properties in this instance. + /// </summary> + /// <returns>The message to send to the Provider.</returns> + internal SignedResponseRequest CreateRequestMessageTestHook() + { + return this.CreateRequestMessage(); + } + + /// <summary> + /// Performs deferred request generation for the <see cref="Create"/> method. + /// </summary> + /// <param name="userSuppliedIdentifier">The user supplied identifier.</param> + /// <param name="relyingParty">The relying party.</param> + /// <param name="realm">The realm.</param> + /// <param name="returnToUrl">The return_to base URL.</param> + /// <param name="serviceEndpoints">The discovered service endpoints on the Claimed Identifier.</param> + /// <param name="createNewAssociationsAsNeeded">if set to <c>true</c>, associations that do not exist between this Relying Party and the asserting Providers are created before the authentication request is created.</param> + /// <returns> + /// A sequence of authentication requests, any of which constitutes a valid identity assertion on the Claimed Identifier. + /// Never null, but may be empty. + /// </returns> + /// <remarks> + /// All data validation and cleansing steps must have ALREADY taken place + /// before calling this method. + /// </remarks> + private static IEnumerable<AuthenticationRequest> CreateInternal(Identifier userSuppliedIdentifier, OpenIdRelyingParty relyingParty, Realm realm, Uri returnToUrl, IEnumerable<IdentifierDiscoveryResult> serviceEndpoints, bool createNewAssociationsAsNeeded) { + // DO NOT USE CODE CONTRACTS IN THIS METHOD, since it uses yield return + ErrorUtilities.VerifyArgumentNotNull(userSuppliedIdentifier, "userSuppliedIdentifier"); + ErrorUtilities.VerifyArgumentNotNull(relyingParty, "relyingParty"); + ErrorUtilities.VerifyArgumentNotNull(realm, "realm"); + ErrorUtilities.VerifyArgumentNotNull(serviceEndpoints, "serviceEndpoints"); + ////Contract.Ensures(Contract.Result<IEnumerable<AuthenticationRequest>>() != null); + + // If shared associations are required, then we had better have an association store. + ErrorUtilities.VerifyOperation(!relyingParty.SecuritySettings.RequireAssociation || relyingParty.AssociationManager.HasAssociationStore, OpenIdStrings.AssociationStoreRequired); + + Logger.Yadis.InfoFormat("Performing discovery on user-supplied identifier: {0}", userSuppliedIdentifier); + IEnumerable<IdentifierDiscoveryResult> endpoints = FilterAndSortEndpoints(serviceEndpoints, relyingParty); + + // Maintain a list of endpoints that we could not form an association with. + // We'll fallback to generating requests to these if the ones we CAN create + // an association with run out. + var failedAssociationEndpoints = new List<IdentifierDiscoveryResult>(0); + + foreach (var endpoint in endpoints) { + Logger.OpenId.DebugFormat("Creating authentication request for user supplied Identifier: {0}", userSuppliedIdentifier); + + // The strategy here is to prefer endpoints with whom we can create associations. + Association association = null; + if (relyingParty.AssociationManager.HasAssociationStore) { + // In some scenarios (like the AJAX control wanting ALL auth requests possible), + // we don't want to create associations with every Provider. But we'll use + // associations where they are already formed from previous authentications. + association = createNewAssociationsAsNeeded ? relyingParty.AssociationManager.GetOrCreateAssociation(endpoint) : relyingParty.AssociationManager.GetExistingAssociation(endpoint); + if (association == null && createNewAssociationsAsNeeded) { + Logger.OpenId.WarnFormat("Failed to create association with {0}. Skipping to next endpoint.", endpoint.ProviderEndpoint); + + // No association could be created. Add it to the list of failed association + // endpoints and skip to the next available endpoint. + failedAssociationEndpoints.Add(endpoint); + continue; + } + } + + yield return new AuthenticationRequest(endpoint, realm, returnToUrl, relyingParty); + } + + // Now that we've run out of endpoints that respond to association requests, + // since we apparently are still running, the caller must want another request. + // We'll go ahead and generate the requests to OPs that may be down -- + // unless associations are set as required in our security settings. + if (failedAssociationEndpoints.Count > 0) { + if (relyingParty.SecuritySettings.RequireAssociation) { + Logger.OpenId.Warn("Associations could not be formed with some Providers. Security settings require shared associations for authentication requests so these will be skipped."); + } else { + Logger.OpenId.Debug("Now generating requests for Provider endpoints that failed initial association attempts."); + + foreach (var endpoint in failedAssociationEndpoints) { + Logger.OpenId.DebugFormat("Creating authentication request for user supplied Identifier: {0} at endpoint: {1}", userSuppliedIdentifier, endpoint.ProviderEndpoint.AbsoluteUri); + + // Create the auth request, but prevent it from attempting to create an association + // because we've already tried. Let's not have it waste time trying again. + var authRequest = new AuthenticationRequest(endpoint, realm, returnToUrl, relyingParty); + authRequest.associationPreference = AssociationPreference.IfAlreadyEstablished; + yield return authRequest; + } + } + } + } + + /// <summary> + /// Returns a filtered and sorted list of the available OP endpoints for a discovered Identifier. + /// </summary> + /// <param name="endpoints">The endpoints.</param> + /// <param name="relyingParty">The relying party.</param> + /// <returns>A filtered and sorted list of endpoints; may be empty if the input was empty or the filter removed all endpoints.</returns> + private static List<IdentifierDiscoveryResult> FilterAndSortEndpoints(IEnumerable<IdentifierDiscoveryResult> endpoints, OpenIdRelyingParty relyingParty) { + Requires.NotNull(endpoints, "endpoints"); + Requires.NotNull(relyingParty, "relyingParty"); + + bool anyFilteredOut = false; + var filteredEndpoints = new List<IdentifierDiscoveryResult>(); + foreach (var endpoint in endpoints) { + if (relyingParty.FilterEndpoint(endpoint)) { + filteredEndpoints.Add(endpoint); + } else { + anyFilteredOut = true; + } + } + + // Sort endpoints so that the first one in the list is the most preferred one. + filteredEndpoints.OrderBy(ep => ep, relyingParty.EndpointOrder); + + var endpointList = new List<IdentifierDiscoveryResult>(filteredEndpoints.Count); + foreach (var endpoint in filteredEndpoints) { + endpointList.Add(endpoint); + } + + if (anyFilteredOut) { + Logger.Yadis.DebugFormat("Some endpoints were filtered out. Total endpoints remaining: {0}", filteredEndpoints.Count); + } + if (Logger.Yadis.IsDebugEnabled) { + if (MessagingUtilities.AreEquivalent(endpoints, endpointList)) { + Logger.Yadis.Debug("Filtering and sorting of endpoints did not affect the list."); + } else { + Logger.Yadis.Debug("After filtering and sorting service endpoints, this is the new prioritized list:"); + Logger.Yadis.Debug(Util.ToStringDeferred(filteredEndpoints, true)); + } + } + + return endpointList; + } + + /// <summary> + /// Creates the request message to send to the Provider, + /// based on the properties in this instance. + /// </summary> + /// <returns>The message to send to the Provider.</returns> + private SignedResponseRequest CreateRequestMessage() { + Association association = this.GetAssociation(); + + SignedResponseRequest request; + if (!this.IsExtensionOnly) { + CheckIdRequest authRequest = new CheckIdRequest(this.DiscoveryResult.Version, this.DiscoveryResult.ProviderEndpoint, this.Mode); + authRequest.ClaimedIdentifier = this.DiscoveryResult.ClaimedIdentifier; + authRequest.LocalIdentifier = this.DiscoveryResult.ProviderLocalIdentifier; + request = authRequest; + } else { + request = new SignedResponseRequest(this.DiscoveryResult.Version, this.DiscoveryResult.ProviderEndpoint, this.Mode); + } + request.Realm = this.Realm; + request.ReturnTo = this.ReturnToUrl; + request.AssociationHandle = association != null ? association.Handle : null; + request.SignReturnTo = this.returnToArgsMustBeSigned; + request.AddReturnToArguments(this.returnToArgs); + if (this.DiscoveryResult.UserSuppliedIdentifier != null && OpenIdElement.Configuration.RelyingParty.PreserveUserSuppliedIdentifier) { + request.AddReturnToArguments(UserSuppliedIdentifierParameterName, this.DiscoveryResult.UserSuppliedIdentifier.OriginalString); + } + foreach (IOpenIdMessageExtension extension in this.extensions) { + request.Extensions.Add(extension); + } + + return request; + } + + /// <summary> + /// Gets the association to use for this authentication request. + /// </summary> + /// <returns>The association to use; <c>null</c> to use 'dumb mode'.</returns> + private Association GetAssociation() { + Association association = null; + switch (this.associationPreference) { + case AssociationPreference.IfPossible: + association = this.RelyingParty.AssociationManager.GetOrCreateAssociation(this.DiscoveryResult); + if (association == null) { + // Avoid trying to create the association again if the redirecting response + // is generated again. + this.associationPreference = AssociationPreference.IfAlreadyEstablished; + } + break; + case AssociationPreference.IfAlreadyEstablished: + association = this.RelyingParty.AssociationManager.GetExistingAssociation(this.DiscoveryResult); + break; + case AssociationPreference.Never: + break; + default: + throw new InternalErrorException(); + } + + return association; + } + } +} diff --git a/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/Behaviors/AXFetchAsSregTransform.cs b/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/Behaviors/AXFetchAsSregTransform.cs new file mode 100644 index 0000000..b2794ec --- /dev/null +++ b/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/Behaviors/AXFetchAsSregTransform.cs @@ -0,0 +1,76 @@ +//----------------------------------------------------------------------- +// <copyright file="AXFetchAsSregTransform.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.RelyingParty.Behaviors { + using System; + using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; + using System.Linq; + using System.Text; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OpenId.Behaviors; + using DotNetOpenAuth.OpenId.Extensions; + using DotNetOpenAuth.OpenId.Extensions.AttributeExchange; + using DotNetOpenAuth.OpenId.Extensions.SimpleRegistration; + using DotNetOpenAuth.OpenId.RelyingParty; + using DotNetOpenAuth.OpenId.RelyingParty.Extensions; + + /// <summary> + /// An Attribute Exchange and Simple Registration filter to make all incoming attribute + /// requests look like Simple Registration requests, and to convert the response + /// to the originally requested extension and format. + /// </summary> + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Sreg", Justification = "Abbreviation")] + public sealed class AXFetchAsSregTransform : AXFetchAsSregTransformBase, IRelyingPartyBehavior { + /// <summary> + /// Initializes a new instance of the <see cref="AXFetchAsSregTransform"/> class. + /// </summary> + public AXFetchAsSregTransform() { + } + + #region IRelyingPartyBehavior Members + + /// <summary> + /// Applies a well known set of security requirements to a default set of security settings. + /// </summary> + /// <param name="securitySettings">The security settings to enhance with the requirements of this profile.</param> + /// <remarks> + /// Care should be taken to never decrease security when applying a profile. + /// Profiles should only enhance security requirements to avoid being + /// incompatible with each other. + /// </remarks> + void IRelyingPartyBehavior.ApplySecuritySettings(RelyingPartySecuritySettings securitySettings) { + } + + /// <summary> + /// Called when an authentication request is about to be sent. + /// </summary> + /// <param name="request">The request.</param> + /// <remarks> + /// Implementations should be prepared to be called multiple times on the same outgoing message + /// without malfunctioning. + /// </remarks> + void IRelyingPartyBehavior.OnOutgoingAuthenticationRequest(RelyingParty.IAuthenticationRequest request) { + // Don't create AX extensions for OpenID 1.x messages, since AX requires OpenID 2.0. + if (request.Provider.Version.Major >= 2) { + request.SpreadSregToAX(this.AXFormats); + } + } + + /// <summary> + /// Called when an incoming positive assertion is received. + /// </summary> + /// <param name="assertion">The positive assertion.</param> + void IRelyingPartyBehavior.OnIncomingPositiveAssertion(IAuthenticationResponse assertion) { + if (assertion.GetExtension<ClaimsResponse>() == null) { + ClaimsResponse sreg = assertion.UnifyExtensionsAsSreg(true); + ((PositiveAnonymousResponse)assertion).Response.Extensions.Add(sreg); + } + } + + #endregion + } +} diff --git a/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/Behaviors/GsaIcamProfile.cs b/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/Behaviors/GsaIcamProfile.cs new file mode 100644 index 0000000..5a1ddaa --- /dev/null +++ b/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/Behaviors/GsaIcamProfile.cs @@ -0,0 +1,124 @@ +//----------------------------------------------------------------------- +// <copyright file="GsaIcamProfile.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.RelyingParty.Behaviors { + using System; + using System.Diagnostics.CodeAnalysis; + using System.Diagnostics.Contracts; + using System.Linq; + using DotNetOpenAuth.Configuration; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OpenId.Behaviors; + using DotNetOpenAuth.OpenId.Extensions.AttributeExchange; + using DotNetOpenAuth.OpenId.Extensions.ProviderAuthenticationPolicy; + using DotNetOpenAuth.OpenId.Extensions.SimpleRegistration; + using DotNetOpenAuth.OpenId.RelyingParty; + + /// <summary> + /// Implements the Identity, Credential, & Access Management (ICAM) OpenID 2.0 Profile + /// for the General Services Administration (GSA). + /// </summary> + /// <remarks> + /// <para>Relying parties that include this profile are always held to the terms required by the profile, + /// but Providers are only affected by the special behaviors of the profile when the RP specifically + /// indicates that they want to use this profile. </para> + /// </remarks> + [Serializable] + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Icam", Justification = "Acronym")] + public sealed class GsaIcamProfile : GsaIcamProfileBase, IRelyingPartyBehavior { + /// <summary> + /// Initializes a new instance of the <see cref="GsaIcamProfile"/> class. + /// </summary> + public GsaIcamProfile() { + if (DisableSslRequirement) { + Logger.OpenId.Warn("GSA level 1 behavior has its RequireSsl requirement disabled."); + } + } + + #region IRelyingPartyBehavior Members + + /// <summary> + /// Applies a well known set of security requirements. + /// </summary> + /// <param name="securitySettings">The security settings to enhance with the requirements of this profile.</param> + /// <remarks> + /// Care should be taken to never decrease security when applying a profile. + /// Profiles should only enhance security requirements to avoid being + /// incompatible with each other. + /// </remarks> + void IRelyingPartyBehavior.ApplySecuritySettings(RelyingPartySecuritySettings securitySettings) { + if (securitySettings.MaximumHashBitLength < 256) { + securitySettings.MaximumHashBitLength = 256; + } + + securitySettings.RequireSsl = !DisableSslRequirement; + securitySettings.RequireDirectedIdentity = true; + securitySettings.RequireAssociation = true; + securitySettings.RejectDelegatingIdentifiers = true; + securitySettings.IgnoreUnsignedExtensions = true; + securitySettings.MinimumRequiredOpenIdVersion = ProtocolVersion.V20; + } + + /// <summary> + /// Called when an authentication request is about to be sent. + /// </summary> + /// <param name="request">The request.</param> + void IRelyingPartyBehavior.OnOutgoingAuthenticationRequest(RelyingParty.IAuthenticationRequest request) { + RelyingParty.AuthenticationRequest requestInternal = (RelyingParty.AuthenticationRequest)request; + ErrorUtilities.VerifyProtocol(string.Equals(request.Realm.Scheme, Uri.UriSchemeHttps, StringComparison.Ordinal) || DisableSslRequirement, BehaviorStrings.RealmMustBeHttps); + + var pape = requestInternal.AppliedExtensions.OfType<PolicyRequest>().SingleOrDefault(); + if (pape == null) { + request.AddExtension(pape = new PolicyRequest()); + } + + if (!pape.PreferredPolicies.Contains(AuthenticationPolicies.PrivatePersonalIdentifier)) { + pape.PreferredPolicies.Add(AuthenticationPolicies.PrivatePersonalIdentifier); + } + + if (!pape.PreferredPolicies.Contains(AuthenticationPolicies.USGovernmentTrustLevel1)) { + pape.PreferredPolicies.Add(AuthenticationPolicies.USGovernmentTrustLevel1); + } + + if (!AllowPersonallyIdentifiableInformation && !pape.PreferredPolicies.Contains(AuthenticationPolicies.NoPersonallyIdentifiableInformation)) { + pape.PreferredPolicies.Add(AuthenticationPolicies.NoPersonallyIdentifiableInformation); + } + + if (pape.PreferredPolicies.Contains(AuthenticationPolicies.NoPersonallyIdentifiableInformation)) { + ErrorUtilities.VerifyProtocol( + (!requestInternal.AppliedExtensions.OfType<ClaimsRequest>().Any() && + !requestInternal.AppliedExtensions.OfType<FetchRequest>().Any()), + BehaviorStrings.PiiIncludedWithNoPiiPolicy); + } + + Reporting.RecordEventOccurrence(this, "RP"); + } + + /// <summary> + /// Called when an incoming positive assertion is received. + /// </summary> + /// <param name="assertion">The positive assertion.</param> + void IRelyingPartyBehavior.OnIncomingPositiveAssertion(IAuthenticationResponse assertion) { + PolicyResponse pape = assertion.GetExtension<PolicyResponse>(); + ErrorUtilities.VerifyProtocol( + pape != null && + pape.ActualPolicies.Contains(AuthenticationPolicies.USGovernmentTrustLevel1) && + pape.ActualPolicies.Contains(AuthenticationPolicies.PrivatePersonalIdentifier), + BehaviorStrings.PapeResponseOrRequiredPoliciesMissing); + + ErrorUtilities.VerifyProtocol(AllowPersonallyIdentifiableInformation || pape.ActualPolicies.Contains(AuthenticationPolicies.NoPersonallyIdentifiableInformation), BehaviorStrings.PapeResponseOrRequiredPoliciesMissing); + + if (pape.ActualPolicies.Contains(AuthenticationPolicies.NoPersonallyIdentifiableInformation)) { + ErrorUtilities.VerifyProtocol( + assertion.GetExtension<ClaimsResponse>() == null && + assertion.GetExtension<FetchResponse>() == null, + BehaviorStrings.PiiIncludedWithNoPiiPolicy); + } + } + + #endregion + } +} diff --git a/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/CryptoKeyStoreAsRelyingPartyAssociationStore.cs b/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/CryptoKeyStoreAsRelyingPartyAssociationStore.cs new file mode 100644 index 0000000..1614145 --- /dev/null +++ b/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/CryptoKeyStoreAsRelyingPartyAssociationStore.cs @@ -0,0 +1,87 @@ +//----------------------------------------------------------------------- +// <copyright file="CryptoKeyStoreAsRelyingPartyAssociationStore.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.RelyingParty { + using System; + using System.Diagnostics.Contracts; + using System.Linq; + using DotNetOpenAuth.Messaging.Bindings; + + /// <summary> + /// Wraps a standard <see cref="ICryptoKeyStore"/> so that it behaves as an association store. + /// </summary> + internal class CryptoKeyStoreAsRelyingPartyAssociationStore : IRelyingPartyAssociationStore { + /// <summary> + /// The underlying key store. + /// </summary> + private readonly ICryptoKeyStore keyStore; + + /// <summary> + /// Initializes a new instance of the <see cref="CryptoKeyStoreAsRelyingPartyAssociationStore"/> class. + /// </summary> + /// <param name="keyStore">The key store.</param> + internal CryptoKeyStoreAsRelyingPartyAssociationStore(ICryptoKeyStore keyStore) { + Requires.NotNull(keyStore, "keyStore"); + Contract.Ensures(this.keyStore == keyStore); + this.keyStore = keyStore; + } + + /// <summary> + /// Saves an <see cref="Association"/> for later recall. + /// </summary> + /// <param name="providerEndpoint">The OP Endpoint with which the association is established.</param> + /// <param name="association">The association to store.</param> + public void StoreAssociation(Uri providerEndpoint, Association association) { + var cryptoKey = new CryptoKey(association.SerializePrivateData(), association.Expires); + this.keyStore.StoreKey(providerEndpoint.AbsoluteUri, association.Handle, cryptoKey); + } + + /// <summary> + /// Gets the best association (the one with the longest remaining life) for a given key. + /// </summary> + /// <param name="providerEndpoint">The OP Endpoint with which the association is established.</param> + /// <param name="securityRequirements">The security requirements that the returned association must meet.</param> + /// <returns> + /// The requested association, or null if no unexpired <see cref="Association"/>s exist for the given key. + /// </returns> + public Association GetAssociation(Uri providerEndpoint, SecuritySettings securityRequirements) { + var matches = from cryptoKey in this.keyStore.GetKeys(providerEndpoint.AbsoluteUri) + where cryptoKey.Value.ExpiresUtc > DateTime.UtcNow + orderby cryptoKey.Value.ExpiresUtc descending + let assoc = Association.Deserialize(cryptoKey.Key, cryptoKey.Value.ExpiresUtc, cryptoKey.Value.Key) + where assoc.HashBitLength >= securityRequirements.MinimumHashBitLength + where assoc.HashBitLength <= securityRequirements.MaximumHashBitLength + select assoc; + return matches.FirstOrDefault(); + } + + /// <summary> + /// Gets the association for a given key and handle. + /// </summary> + /// <param name="providerEndpoint">The OP Endpoint with which the association is established.</param> + /// <param name="handle">The handle of the specific association that must be recalled.</param> + /// <returns> + /// The requested association, or null if no unexpired <see cref="Association"/>s exist for the given key and handle. + /// </returns> + public Association GetAssociation(Uri providerEndpoint, string handle) { + var cryptoKey = this.keyStore.GetKey(providerEndpoint.AbsoluteUri, handle); + return cryptoKey != null ? Association.Deserialize(handle, cryptoKey.ExpiresUtc, cryptoKey.Key) : null; + } + + /// <summary> + /// Removes a specified handle that may exist in the store. + /// </summary> + /// <param name="providerEndpoint">The OP Endpoint with which the association is established.</param> + /// <param name="handle">The handle of the specific association that must be deleted.</param> + /// <returns> + /// True if the association existed in this store previous to this call. + /// </returns> + public bool RemoveAssociation(Uri providerEndpoint, string handle) { + this.keyStore.RemoveKey(providerEndpoint.AbsoluteUri, handle); + return true; // return value isn't used by DNOA. + } + } +} diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/DuplicateRequestedHostsComparer.cs b/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/DuplicateRequestedHostsComparer.cs index 94eb5ba..94eb5ba 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/DuplicateRequestedHostsComparer.cs +++ b/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/DuplicateRequestedHostsComparer.cs diff --git a/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/Extensions/ExtensionsInteropHelper.cs b/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/Extensions/ExtensionsInteropHelper.cs new file mode 100644 index 0000000..0f7778f --- /dev/null +++ b/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/Extensions/ExtensionsInteropHelper.cs @@ -0,0 +1,152 @@ +//----------------------------------------------------------------------- +// <copyright file="ExtensionsInteropHelper.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.RelyingParty.Extensions { + using System; + using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; + using System.Diagnostics.Contracts; + using System.Linq; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OpenId.Extensions; + using DotNetOpenAuth.OpenId.Extensions.AttributeExchange; + using DotNetOpenAuth.OpenId.Extensions.SimpleRegistration; + using DotNetOpenAuth.OpenId.Messages; + + /// <summary> + /// A set of methods designed to assist in improving interop across different + /// OpenID implementations and their extensions. + /// </summary> + public static class ExtensionsInteropHelper { + /// <summary> + /// Adds an Attribute Exchange (AX) extension to the authentication request + /// that asks for the same attributes as the Simple Registration (sreg) extension + /// that is already applied. + /// </summary> + /// <param name="request">The authentication request.</param> + /// <param name="attributeFormats">The attribute formats to use in the AX request.</param> + /// <remarks> + /// <para>If discovery on the user-supplied identifier yields hints regarding which + /// extensions and attribute formats the Provider supports, this method MAY ignore the + /// <paramref name="attributeFormats"/> argument and accomodate the Provider to minimize + /// the size of the request.</para> + /// <para>If the request does not carry an sreg extension, the method logs a warning but + /// otherwise quietly returns doing nothing.</para> + /// </remarks> + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Sreg", Justification = "Abbreviation")] + public static void SpreadSregToAX(this RelyingParty.IAuthenticationRequest request, AXAttributeFormats attributeFormats) { + Requires.NotNull(request, "request"); + + var req = (RelyingParty.AuthenticationRequest)request; + var sreg = req.AppliedExtensions.OfType<ClaimsRequest>().SingleOrDefault(); + if (sreg == null) { + Logger.OpenId.Debug("No Simple Registration (ClaimsRequest) extension present in the request to spread to AX."); + return; + } + + if (req.DiscoveryResult.IsExtensionSupported<ClaimsRequest>()) { + Logger.OpenId.Debug("Skipping generation of AX request because the Identifier advertises the Provider supports the Sreg extension."); + return; + } + + var ax = req.AppliedExtensions.OfType<FetchRequest>().SingleOrDefault(); + if (ax == null) { + ax = new FetchRequest(); + req.AddExtension(ax); + } + + // Try to use just one AX Type URI format if we can figure out which type the OP accepts. + AXAttributeFormats detectedFormat; + if (TryDetectOPAttributeFormat(request, out detectedFormat)) { + Logger.OpenId.Debug("Detected OP support for AX but not for Sreg. Removing Sreg extension request and using AX instead."); + attributeFormats = detectedFormat; + req.Extensions.Remove(sreg); + } else { + Logger.OpenId.Debug("Could not determine whether OP supported Sreg or AX. Using both extensions."); + } + + foreach (AXAttributeFormats format in OpenIdExtensionsInteropHelper.ForEachFormat(attributeFormats)) { + OpenIdExtensionsInteropHelper.FetchAttribute(ax, format, WellKnownAttributes.BirthDate.WholeBirthDate, sreg.BirthDate); + OpenIdExtensionsInteropHelper.FetchAttribute(ax, format, WellKnownAttributes.Contact.HomeAddress.Country, sreg.Country); + OpenIdExtensionsInteropHelper.FetchAttribute(ax, format, WellKnownAttributes.Contact.Email, sreg.Email); + OpenIdExtensionsInteropHelper.FetchAttribute(ax, format, WellKnownAttributes.Name.FullName, sreg.FullName); + OpenIdExtensionsInteropHelper.FetchAttribute(ax, format, WellKnownAttributes.Person.Gender, sreg.Gender); + OpenIdExtensionsInteropHelper.FetchAttribute(ax, format, WellKnownAttributes.Preferences.Language, sreg.Language); + OpenIdExtensionsInteropHelper.FetchAttribute(ax, format, WellKnownAttributes.Name.Alias, sreg.Nickname); + OpenIdExtensionsInteropHelper.FetchAttribute(ax, format, WellKnownAttributes.Contact.HomeAddress.PostalCode, sreg.PostalCode); + OpenIdExtensionsInteropHelper.FetchAttribute(ax, format, WellKnownAttributes.Preferences.TimeZone, sreg.TimeZone); + } + } + + /// <summary> + /// Looks for Simple Registration and Attribute Exchange (all known formats) + /// response extensions and returns them as a Simple Registration extension. + /// </summary> + /// <param name="response">The authentication response.</param> + /// <param name="allowUnsigned">if set to <c>true</c> unsigned extensions will be included in the search.</param> + /// <returns> + /// The Simple Registration response if found, + /// or a fabricated one based on the Attribute Exchange extension if found, + /// or just an empty <see cref="ClaimsResponse"/> if there was no data. + /// Never <c>null</c>.</returns> + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Sreg", Justification = "Abbreviation")] + public static ClaimsResponse UnifyExtensionsAsSreg(this RelyingParty.IAuthenticationResponse response, bool allowUnsigned) { + Requires.NotNull(response, "response"); + + var resp = (RelyingParty.IAuthenticationResponse)response; + var sreg = allowUnsigned ? resp.GetUntrustedExtension<ClaimsResponse>() : resp.GetExtension<ClaimsResponse>(); + if (sreg != null) { + return sreg; + } + + AXAttributeFormats formats = AXAttributeFormats.All; + sreg = new ClaimsResponse(); + var fetchResponse = allowUnsigned ? resp.GetUntrustedExtension<FetchResponse>() : resp.GetExtension<FetchResponse>(); + if (fetchResponse != null) { + ((IOpenIdMessageExtension)sreg).IsSignedByRemoteParty = fetchResponse.IsSignedByProvider; + sreg.BirthDateRaw = fetchResponse.GetAttributeValue(WellKnownAttributes.BirthDate.WholeBirthDate, formats); + sreg.Country = fetchResponse.GetAttributeValue(WellKnownAttributes.Contact.HomeAddress.Country, formats); + sreg.PostalCode = fetchResponse.GetAttributeValue(WellKnownAttributes.Contact.HomeAddress.PostalCode, formats); + sreg.Email = fetchResponse.GetAttributeValue(WellKnownAttributes.Contact.Email, formats); + sreg.FullName = fetchResponse.GetAttributeValue(WellKnownAttributes.Name.FullName, formats); + sreg.Language = fetchResponse.GetAttributeValue(WellKnownAttributes.Preferences.Language, formats); + sreg.Nickname = fetchResponse.GetAttributeValue(WellKnownAttributes.Name.Alias, formats); + sreg.TimeZone = fetchResponse.GetAttributeValue(WellKnownAttributes.Preferences.TimeZone, formats); + string gender = fetchResponse.GetAttributeValue(WellKnownAttributes.Person.Gender, formats); + if (gender != null) { + sreg.Gender = (Gender)OpenIdExtensionsInteropHelper.GenderEncoder.Decode(gender); + } + } + + return sreg; + } + + /// <summary> + /// Gets the attribute value if available. + /// </summary> + /// <param name="fetchResponse">The AX fetch response extension to look for the attribute value.</param> + /// <param name="typeUri">The type URI of the attribute, using the axschema.org format of <see cref="WellKnownAttributes"/>.</param> + /// <param name="formats">The AX type URI formats to search.</param> + /// <returns> + /// The first value of the attribute, if available. + /// </returns> + internal static string GetAttributeValue(this FetchResponse fetchResponse, string typeUri, AXAttributeFormats formats) { + return OpenIdExtensionsInteropHelper.ForEachFormat(formats).Select(format => fetchResponse.GetAttributeValue(OpenIdExtensionsInteropHelper.TransformAXFormat(typeUri, format))).FirstOrDefault(s => s != null); + } + + /// <summary> + /// Tries to find the exact format of AX attribute Type URI supported by the Provider. + /// </summary> + /// <param name="request">The authentication request.</param> + /// <param name="attributeFormat">The attribute formats the RP will try if this discovery fails.</param> + /// <returns>The AX format(s) to use based on the Provider's advertised AX support.</returns> + private static bool TryDetectOPAttributeFormat(RelyingParty.IAuthenticationRequest request, out AXAttributeFormats attributeFormat) { + Requires.NotNull(request, "request"); + attributeFormat = OpenIdExtensionsInteropHelper.DetectAXFormat(request.DiscoveryResult.Capabilities); + return attributeFormat != AXAttributeFormats.None; + } + } +} diff --git a/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/Extensions/UIUtilities.cs b/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/Extensions/UIUtilities.cs new file mode 100644 index 0000000..4606bf7 --- /dev/null +++ b/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/Extensions/UIUtilities.cs @@ -0,0 +1,42 @@ +//----------------------------------------------------------------------- +// <copyright file="UIUtilities.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.RelyingParty.Extensions.UI { + using System; + using System.Diagnostics.Contracts; + using System.Globalization; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OpenId.RelyingParty; + + /// <summary> + /// Constants used in implementing support for the UI extension. + /// </summary> + internal static class UIUtilities { + /// <summary> + /// Gets the <c>window.open</c> javascript snippet to use to open a popup window + /// compliant with the UI extension. + /// </summary> + /// <param name="relyingParty">The relying party.</param> + /// <param name="request">The authentication request to place in the window.</param> + /// <param name="windowName">The name to assign to the popup window.</param> + /// <returns>A string starting with 'window.open' and forming just that one method call.</returns> + internal static string GetWindowPopupScript(OpenIdRelyingParty relyingParty, IAuthenticationRequest request, string windowName) { + Requires.NotNull(relyingParty, "relyingParty"); + Requires.NotNull(request, "request"); + Requires.NotNullOrEmpty(windowName, "windowName"); + + Uri popupUrl = request.RedirectingResponse.GetDirectUriRequest(relyingParty.Channel); + + return string.Format( + CultureInfo.InvariantCulture, + "(window.showModalDialog ? window.showModalDialog({0}, {1}, 'status:0;resizable:1;scroll:1;center:1;dialogWidth:{2}px; dialogHeight:{3}') : window.open({0}, {1}, 'status=0,toolbar=0,location=1,resizable=1,scrollbars=1,left=' + ((screen.width - {2}) / 2) + ',top=' + ((screen.height - {3}) / 2) + ',width={2},height={3}'));", + MessagingUtilities.GetSafeJavascriptValue(popupUrl.AbsoluteUri), + MessagingUtilities.GetSafeJavascriptValue(windowName), + OpenId.Extensions.UI.UIUtilities.PopupWidth, + OpenId.Extensions.UI.UIUtilities.PopupHeight); + } + } +} diff --git a/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/FailedAuthenticationResponse.cs b/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/FailedAuthenticationResponse.cs new file mode 100644 index 0000000..b9266d3 --- /dev/null +++ b/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/FailedAuthenticationResponse.cs @@ -0,0 +1,299 @@ +//----------------------------------------------------------------------- +// <copyright file="FailedAuthenticationResponse.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.RelyingParty { + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Diagnostics.Contracts; + using System.Globalization; + using System.Text; + using System.Web; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OpenId; + using DotNetOpenAuth.OpenId.Messages; + using DotNetOpenAuth.OpenId.RelyingParty; + + /// <summary> + /// Wraps a failed authentication response in an <see cref="IAuthenticationResponse"/> instance + /// for public consumption by the host web site. + /// </summary> + [DebuggerDisplay("{Exception.Message}")] + internal class FailedAuthenticationResponse : IAuthenticationResponse { + /// <summary> + /// Initializes a new instance of the <see cref="FailedAuthenticationResponse"/> class. + /// </summary> + /// <param name="exception">The exception that resulted in the failed authentication.</param> + internal FailedAuthenticationResponse(Exception exception) { + Requires.NotNull(exception, "exception"); + + this.Exception = exception; + + string category = string.Empty; + if (Reporting.Enabled) { + var pe = exception as ProtocolException; + if (pe != null) { + var responseMessage = pe.FaultedMessage as IndirectSignedResponse; + if (responseMessage != null && responseMessage.ProviderEndpoint != null) { // check "required" parts because this is a failure after all + category = responseMessage.ProviderEndpoint.AbsoluteUri; + } + } + + Reporting.RecordEventOccurrence(this, category); + } + } + + #region IAuthenticationResponse Members + + /// <summary> + /// Gets the Identifier that the end user claims to own. For use with user database storage and lookup. + /// May be null for some failed authentications (i.e. failed directed identity authentications). + /// </summary> + /// <value></value> + /// <remarks> + /// <para> + /// This is the secure identifier that should be used for database storage and lookup. + /// It is not always friendly (i.e. =Arnott becomes =!9B72.7DD1.50A9.5CCD), but it protects + /// user identities against spoofing and other attacks. + /// </para> + /// <para> + /// For user-friendly identifiers to display, use the + /// <see cref="FriendlyIdentifierForDisplay"/> property. + /// </para> + /// </remarks> + public Identifier ClaimedIdentifier { + get { return null; } + } + + /// <summary> + /// Gets a user-friendly OpenID Identifier for display purposes ONLY. + /// </summary> + /// <value></value> + /// <remarks> + /// <para> + /// This <i>should</i> be put through <see cref="HttpUtility.HtmlEncode(string)"/> before + /// sending to a browser to secure against javascript injection attacks. + /// </para> + /// <para> + /// This property retains some aspects of the user-supplied identifier that get lost + /// in the <see cref="ClaimedIdentifier"/>. For example, XRIs used as user-supplied + /// identifiers (i.e. =Arnott) become unfriendly unique strings (i.e. =!9B72.7DD1.50A9.5CCD). + /// For display purposes, such as text on a web page that says "You're logged in as ...", + /// this property serves to provide the =Arnott string, or whatever else is the most friendly + /// string close to what the user originally typed in. + /// </para> + /// <para> + /// If the user-supplied identifier is a URI, this property will be the URI after all + /// redirects, and with the protocol and fragment trimmed off. + /// If the user-supplied identifier is an XRI, this property will be the original XRI. + /// If the user-supplied identifier is an OpenID Provider identifier (i.e. yahoo.com), + /// this property will be the Claimed Identifier, with the protocol stripped if it is a URI. + /// </para> + /// <para> + /// It is <b>very</b> important that this property <i>never</i> be used for database storage + /// or lookup to avoid identity spoofing and other security risks. For database storage + /// and lookup please use the <see cref="ClaimedIdentifier"/> property. + /// </para> + /// </remarks> + public string FriendlyIdentifierForDisplay { + get { return null; } + } + + /// <summary> + /// Gets the detailed success or failure status of the authentication attempt. + /// </summary> + /// <value></value> + public AuthenticationStatus Status { + get { return AuthenticationStatus.Failed; } + } + + /// <summary> + /// Gets information about the OpenId Provider, as advertised by the + /// OpenID discovery documents found at the <see cref="ClaimedIdentifier"/> + /// location. + /// </summary> + /// <value> + /// The Provider endpoint that issued the positive assertion; + /// or <c>null</c> if information about the Provider is unavailable. + /// </value> + public IProviderEndpoint Provider { + get { return null; } + } + + /// <summary> + /// Gets the details regarding a failed authentication attempt, if available. + /// This will be set if and only if <see cref="Status"/> is <see cref="AuthenticationStatus.Failed"/>. + /// </summary> + public Exception Exception { get; private set; } + + /// <summary> + /// Gets all the callback arguments that were previously added using + /// <see cref="IAuthenticationRequest.AddCallbackArguments(string, string)"/> or as a natural part + /// of the return_to URL. + /// </summary> + /// <returns>A name-value dictionary. Never null.</returns> + /// <remarks> + /// <para>This MAY return any argument on the querystring that came with the authentication response, + /// which may include parameters not explicitly added using + /// <see cref="IAuthenticationRequest.AddCallbackArguments(string, string)"/>.</para> + /// <para>Note that these values are NOT protected against tampering in transit.</para> + /// </remarks> + public IDictionary<string, string> GetCallbackArguments() { + return EmptyDictionary<string, string>.Instance; + } + + /// <summary> + /// Gets all the callback arguments that were previously added using + /// <see cref="IAuthenticationRequest.AddCallbackArguments(string, string)"/> or as a natural part + /// of the return_to URL. + /// </summary> + /// <returns>A name-value dictionary. Never null.</returns> + /// <remarks> + /// Callback parameters are only available even if the RP is in stateless mode, + /// or the callback parameters are otherwise unverifiable as untampered with. + /// Therefore, use this method only when the callback argument is not to be + /// used to make a security-sensitive decision. + /// </remarks> + public IDictionary<string, string> GetUntrustedCallbackArguments() { + return EmptyDictionary<string, string>.Instance; + } + + /// <summary> + /// Gets a callback argument's value that was previously added using + /// <see cref="IAuthenticationRequest.AddCallbackArguments(string, string)"/>. + /// </summary> + /// <param name="key">The name of the parameter whose value is sought.</param> + /// <returns> + /// The value of the argument, or null if the named parameter could not be found. + /// </returns> + /// <remarks> + /// <para>This may return any argument on the querystring that came with the authentication response, + /// which may include parameters not explicitly added using + /// <see cref="IAuthenticationRequest.AddCallbackArguments(string, string)"/>.</para> + /// <para>Note that these values are NOT protected against tampering in transit.</para> + /// </remarks> + public string GetCallbackArgument(string key) { + return null; + } + + /// <summary> + /// Gets a callback argument's value that was previously added using + /// <see cref="IAuthenticationRequest.AddCallbackArguments(string, string)"/>. + /// </summary> + /// <param name="key">The name of the parameter whose value is sought.</param> + /// <returns> + /// The value of the argument, or null if the named parameter could not be found. + /// </returns> + /// <remarks> + /// Callback parameters are only available even if the RP is in stateless mode, + /// or the callback parameters are otherwise unverifiable as untampered with. + /// Therefore, use this method only when the callback argument is not to be + /// used to make a security-sensitive decision. + /// </remarks> + public string GetUntrustedCallbackArgument(string key) { + return null; + } + + /// <summary> + /// Tries to get an OpenID extension that may be present in the response. + /// </summary> + /// <typeparam name="T">The type of extension to look for in the response message.</typeparam> + /// <returns> + /// The extension, if it is found. Null otherwise. + /// </returns> + /// <remarks> + /// <para>Extensions are returned only if the Provider signed them. + /// Relying parties that do not care if the values were modified in + /// transit should use the <see cref="GetUntrustedExtension<T>"/> method + /// in order to allow the Provider to not sign the extension. </para> + /// <para>Unsigned extensions are completely unreliable and should be + /// used only to prefill user forms since the user or any other third + /// party may have tampered with the data carried by the extension.</para> + /// <para>Signed extensions are only reliable if the relying party + /// trusts the OpenID Provider that signed them. Signing does not mean + /// the relying party can trust the values -- it only means that the values + /// have not been tampered with since the Provider sent the message.</para> + /// </remarks> + public T GetExtension<T>() where T : IOpenIdMessageExtension { + return default(T); + } + + /// <summary> + /// Tries to get an OpenID extension that may be present in the response. + /// </summary> + /// <param name="extensionType">Type of the extension to look for in the response.</param> + /// <returns> + /// The extension, if it is found. Null otherwise. + /// </returns> + /// <remarks> + /// <para>Extensions are returned only if the Provider signed them. + /// Relying parties that do not care if the values were modified in + /// transit should use the <see cref="GetUntrustedExtension"/> method + /// in order to allow the Provider to not sign the extension. </para> + /// <para>Unsigned extensions are completely unreliable and should be + /// used only to prefill user forms since the user or any other third + /// party may have tampered with the data carried by the extension.</para> + /// <para>Signed extensions are only reliable if the relying party + /// trusts the OpenID Provider that signed them. Signing does not mean + /// the relying party can trust the values -- it only means that the values + /// have not been tampered with since the Provider sent the message.</para> + /// </remarks> + public IOpenIdMessageExtension GetExtension(Type extensionType) { + return null; + } + + /// <summary> + /// Tries to get an OpenID extension that may be present in the response, without + /// requiring it to be signed by the Provider. + /// </summary> + /// <typeparam name="T">The type of extension to look for in the response message.</typeparam> + /// <returns> + /// The extension, if it is found. Null otherwise. + /// </returns> + /// <remarks> + /// <para>Extensions are returned whether they are signed or not. + /// Use the <see cref="GetExtension<T>"/> method to retrieve + /// extension responses only if they are signed by the Provider to + /// protect against tampering. </para> + /// <para>Unsigned extensions are completely unreliable and should be + /// used only to prefill user forms since the user or any other third + /// party may have tampered with the data carried by the extension.</para> + /// <para>Signed extensions are only reliable if the relying party + /// trusts the OpenID Provider that signed them. Signing does not mean + /// the relying party can trust the values -- it only means that the values + /// have not been tampered with since the Provider sent the message.</para> + /// </remarks> + public T GetUntrustedExtension<T>() where T : IOpenIdMessageExtension { + return default(T); + } + + /// <summary> + /// Tries to get an OpenID extension that may be present in the response. + /// </summary> + /// <param name="extensionType">Type of the extension to look for in the response.</param> + /// <returns> + /// The extension, if it is found. Null otherwise. + /// </returns> + /// <remarks> + /// <para>Extensions are returned whether they are signed or not. + /// Use the <see cref="GetExtension"/> method to retrieve + /// extension responses only if they are signed by the Provider to + /// protect against tampering. </para> + /// <para>Unsigned extensions are completely unreliable and should be + /// used only to prefill user forms since the user or any other third + /// party may have tampered with the data carried by the extension.</para> + /// <para>Signed extensions are only reliable if the relying party + /// trusts the OpenID Provider that signed them. Signing does not mean + /// the relying party can trust the values -- it only means that the values + /// have not been tampered with since the Provider sent the message.</para> + /// </remarks> + public IOpenIdMessageExtension GetUntrustedExtension(Type extensionType) { + return null; + } + + #endregion + } +} diff --git a/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/IRelyingPartyAssociationStore.cs b/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/IRelyingPartyAssociationStore.cs new file mode 100644 index 0000000..52e828c --- /dev/null +++ b/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/IRelyingPartyAssociationStore.cs @@ -0,0 +1,153 @@ +//----------------------------------------------------------------------- +// <copyright file="IRelyingPartyAssociationStore.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.RelyingParty { + using System; + using System.Diagnostics.Contracts; + + /// <summary> + /// Stores <see cref="Association"/>s for lookup by their handle, keeping + /// associations separated by a given OP Endpoint. + /// </summary> + /// <remarks> + /// Expired associations should be periodically cleared out of an association store. + /// This should be done frequently enough to avoid a memory leak, but sparingly enough + /// to not be a performance drain. Because this balance can vary by host, it is the + /// responsibility of the host to initiate this cleaning. + /// </remarks> + [ContractClass(typeof(IRelyingPartyAssociationStoreContract))] + public interface IRelyingPartyAssociationStore { + /// <summary> + /// Saves an <see cref="Association"/> for later recall. + /// </summary> + /// <param name="providerEndpoint">The OP Endpoint with which the association is established.</param> + /// <param name="association">The association to store.</param> + /// <remarks> + /// If the new association conflicts (in OP endpoint and association handle) with an existing association, + /// (which should never happen by the way) implementations may overwrite the previously saved association. + /// </remarks> + void StoreAssociation(Uri providerEndpoint, Association association); + + /// <summary> + /// Gets the best association (the one with the longest remaining life) for a given key. + /// </summary> + /// <param name="providerEndpoint">The OP Endpoint with which the association is established.</param> + /// <param name="securityRequirements">The security requirements that the returned association must meet.</param> + /// <returns> + /// The requested association, or null if no unexpired <see cref="Association"/>s exist for the given key. + /// </returns> + /// <remarks> + /// In the event that multiple associations exist for the given + /// <paramref name="providerEndpoint"/>, it is important for the + /// implementation for this method to use the <paramref name="securityRequirements"/> + /// to pick the best (highest grade or longest living as the host's policy may dictate) + /// association that fits the security requirements. + /// Associations that are returned that do not meet the security requirements will be + /// ignored and a new association created. + /// </remarks> + Association GetAssociation(Uri providerEndpoint, SecuritySettings securityRequirements); + + /// <summary> + /// Gets the association for a given key and handle. + /// </summary> + /// <param name="providerEndpoint">The OP Endpoint with which the association is established.</param> + /// <param name="handle">The handle of the specific association that must be recalled.</param> + /// <returns>The requested association, or null if no unexpired <see cref="Association"/>s exist for the given key and handle.</returns> + Association GetAssociation(Uri providerEndpoint, string handle); + + /// <summary>Removes a specified handle that may exist in the store.</summary> + /// <param name="providerEndpoint">The OP Endpoint with which the association is established.</param> + /// <param name="handle">The handle of the specific association that must be deleted.</param> + /// <returns> + /// Deprecated. The return value is insignificant. + /// Previously: True if the association existed in this store previous to this call. + /// </returns> + /// <remarks> + /// No exception should be thrown if the association does not exist in the store + /// before this call. + /// </remarks> + bool RemoveAssociation(Uri providerEndpoint, string handle); + } + + /// <summary> + /// Code Contract for the <see cref="IRelyingPartyAssociationStore"/> class. + /// </summary> + [ContractClassFor(typeof(IRelyingPartyAssociationStore))] + internal abstract class IRelyingPartyAssociationStoreContract : IRelyingPartyAssociationStore { + #region IAssociationStore Members + + /// <summary> + /// Saves an <see cref="Association"/> for later recall. + /// </summary> + /// <param name="providerEndpoint">The Uri (for relying parties) or Smart/Dumb (for providers).</param> + /// <param name="association">The association to store.</param> + /// <remarks> + /// TODO: what should implementations do on association handle conflict? + /// </remarks> + void IRelyingPartyAssociationStore.StoreAssociation(Uri providerEndpoint, Association association) { + Requires.NotNull(providerEndpoint, "providerEndpoint"); + Requires.NotNull(association, "association"); + throw new NotImplementedException(); + } + + /// <summary> + /// Gets the best association (the one with the longest remaining life) for a given key. + /// </summary> + /// <param name="providerEndpoint">The Uri (for relying parties) or Smart/Dumb (for Providers).</param> + /// <param name="securityRequirements">The security requirements that the returned association must meet.</param> + /// <returns> + /// The requested association, or null if no unexpired <see cref="Association"/>s exist for the given key. + /// </returns> + /// <remarks> + /// In the event that multiple associations exist for the given + /// <paramref name="providerEndpoint"/>, it is important for the + /// implementation for this method to use the <paramref name="securityRequirements"/> + /// to pick the best (highest grade or longest living as the host's policy may dictate) + /// association that fits the security requirements. + /// Associations that are returned that do not meet the security requirements will be + /// ignored and a new association created. + /// </remarks> + Association IRelyingPartyAssociationStore.GetAssociation(Uri providerEndpoint, SecuritySettings securityRequirements) { + Requires.NotNull(providerEndpoint, "providerEndpoint"); + Requires.NotNull(securityRequirements, "securityRequirements"); + throw new NotImplementedException(); + } + + /// <summary> + /// Gets the association for a given key and handle. + /// </summary> + /// <param name="providerEndpoint">The Uri (for relying parties) or Smart/Dumb (for Providers).</param> + /// <param name="handle">The handle of the specific association that must be recalled.</param> + /// <returns> + /// The requested association, or null if no unexpired <see cref="Association"/>s exist for the given key and handle. + /// </returns> + Association IRelyingPartyAssociationStore.GetAssociation(Uri providerEndpoint, string handle) { + Requires.NotNull(providerEndpoint, "providerEndpoint"); + Contract.Requires(!String.IsNullOrEmpty(handle)); + throw new NotImplementedException(); + } + + /// <summary> + /// Removes a specified handle that may exist in the store. + /// </summary> + /// <param name="providerEndpoint">The Uri (for relying parties) or Smart/Dumb (for Providers).</param> + /// <param name="handle">The handle of the specific association that must be deleted.</param> + /// <returns> + /// True if the association existed in this store previous to this call. + /// </returns> + /// <remarks> + /// No exception should be thrown if the association does not exist in the store + /// before this call. + /// </remarks> + bool IRelyingPartyAssociationStore.RemoveAssociation(Uri providerEndpoint, string handle) { + Requires.NotNull(providerEndpoint, "providerEndpoint"); + Contract.Requires(!String.IsNullOrEmpty(handle)); + throw new NotImplementedException(); + } + + #endregion + } +} diff --git a/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/ISetupRequiredAuthenticationResponse.cs b/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/ISetupRequiredAuthenticationResponse.cs new file mode 100644 index 0000000..2942c9d --- /dev/null +++ b/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/ISetupRequiredAuthenticationResponse.cs @@ -0,0 +1,51 @@ +//----------------------------------------------------------------------- +// <copyright file="ISetupRequiredAuthenticationResponse.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.RelyingParty { + using System; + using System.Diagnostics.Contracts; + + /// <summary> + /// An interface to expose useful properties and functionality for handling + /// authentication responses that are returned from Immediate authentication + /// requests that require a subsequent request to be made in non-immediate mode. + /// </summary> + [ContractClass(typeof(ISetupRequiredAuthenticationResponseContract))] + public interface ISetupRequiredAuthenticationResponse { + /// <summary> + /// Gets the <see cref="Identifier"/> to pass to <see cref="OpenIdRelyingParty.CreateRequest(Identifier)"/> + /// in a subsequent authentication attempt. + /// </summary> + Identifier UserSuppliedIdentifier { get; } + } + + /// <summary> + /// Code contract class for the <see cref="ISetupRequiredAuthenticationResponse"/> type. + /// </summary> + [ContractClassFor(typeof(ISetupRequiredAuthenticationResponse))] + internal abstract class ISetupRequiredAuthenticationResponseContract : ISetupRequiredAuthenticationResponse { + /// <summary> + /// Initializes a new instance of the <see cref="ISetupRequiredAuthenticationResponseContract"/> class. + /// </summary> + protected ISetupRequiredAuthenticationResponseContract() { + } + + #region ISetupRequiredAuthenticationResponse Members + + /// <summary> + /// Gets the <see cref="Identifier"/> to pass to <see cref="OpenIdRelyingParty.CreateRequest(Identifier)"/> + /// in a subsequent authentication attempt. + /// </summary> + Identifier ISetupRequiredAuthenticationResponse.UserSuppliedIdentifier { + get { + Requires.ValidState(((IAuthenticationResponse)this).Status == AuthenticationStatus.SetupRequired, OpenIdStrings.OperationOnlyValidForSetupRequiredState); + throw new System.NotImplementedException(); + } + } + + #endregion + } +} diff --git a/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/NegativeAuthenticationResponse.cs b/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/NegativeAuthenticationResponse.cs new file mode 100644 index 0000000..4b21fea --- /dev/null +++ b/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/NegativeAuthenticationResponse.cs @@ -0,0 +1,313 @@ +//----------------------------------------------------------------------- +// <copyright file="NegativeAuthenticationResponse.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.RelyingParty { + using System; + using System.Collections.Generic; + using System.Diagnostics.Contracts; + using System.Linq; + using System.Web; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OpenId.Messages; + + /// <summary> + /// Wraps a negative assertion response in an <see cref="IAuthenticationResponse"/> instance + /// for public consumption by the host web site. + /// </summary> + internal class NegativeAuthenticationResponse : IAuthenticationResponse, ISetupRequiredAuthenticationResponse { + /// <summary> + /// The negative assertion message that was received by the RP that was used + /// to create this instance. + /// </summary> + private readonly NegativeAssertionResponse response; + + /// <summary> + /// Initializes a new instance of the <see cref="NegativeAuthenticationResponse"/> class. + /// </summary> + /// <param name="response">The negative assertion response received by the Relying Party.</param> + internal NegativeAuthenticationResponse(NegativeAssertionResponse response) { + Requires.NotNull(response, "response"); + this.response = response; + + Reporting.RecordEventOccurrence(this, string.Empty); + } + + #region IAuthenticationResponse Properties + + /// <summary> + /// Gets the Identifier that the end user claims to own. For use with user database storage and lookup. + /// May be null for some failed authentications (i.e. failed directed identity authentications). + /// </summary> + /// <value></value> + /// <remarks> + /// <para> + /// This is the secure identifier that should be used for database storage and lookup. + /// It is not always friendly (i.e. =Arnott becomes =!9B72.7DD1.50A9.5CCD), but it protects + /// user identities against spoofing and other attacks. + /// </para> + /// <para> + /// For user-friendly identifiers to display, use the + /// <see cref="FriendlyIdentifierForDisplay"/> property. + /// </para> + /// </remarks> + public Identifier ClaimedIdentifier { + get { return null; } + } + + /// <summary> + /// Gets a user-friendly OpenID Identifier for display purposes ONLY. + /// </summary> + /// <value></value> + /// <remarks> + /// <para> + /// This <i>should</i> be put through <see cref="HttpUtility.HtmlEncode(string)"/> before + /// sending to a browser to secure against javascript injection attacks. + /// </para> + /// <para> + /// This property retains some aspects of the user-supplied identifier that get lost + /// in the <see cref="ClaimedIdentifier"/>. For example, XRIs used as user-supplied + /// identifiers (i.e. =Arnott) become unfriendly unique strings (i.e. =!9B72.7DD1.50A9.5CCD). + /// For display purposes, such as text on a web page that says "You're logged in as ...", + /// this property serves to provide the =Arnott string, or whatever else is the most friendly + /// string close to what the user originally typed in. + /// </para> + /// <para> + /// If the user-supplied identifier is a URI, this property will be the URI after all + /// redirects, and with the protocol and fragment trimmed off. + /// If the user-supplied identifier is an XRI, this property will be the original XRI. + /// If the user-supplied identifier is an OpenID Provider identifier (i.e. yahoo.com), + /// this property will be the Claimed Identifier, with the protocol stripped if it is a URI. + /// </para> + /// <para> + /// It is <b>very</b> important that this property <i>never</i> be used for database storage + /// or lookup to avoid identity spoofing and other security risks. For database storage + /// and lookup please use the <see cref="ClaimedIdentifier"/> property. + /// </para> + /// </remarks> + public string FriendlyIdentifierForDisplay { + get { return null; } + } + + /// <summary> + /// Gets the detailed success or failure status of the authentication attempt. + /// </summary> + /// <value></value> + public AuthenticationStatus Status { + get { return this.response.Immediate ? AuthenticationStatus.SetupRequired : AuthenticationStatus.Canceled; } + } + + /// <summary> + /// Gets information about the OpenId Provider, as advertised by the + /// OpenID discovery documents found at the <see cref="ClaimedIdentifier"/> + /// location. + /// </summary> + /// <value> + /// The Provider endpoint that issued the positive assertion; + /// or <c>null</c> if information about the Provider is unavailable. + /// </value> + public IProviderEndpoint Provider { + get { return null; } + } + + /// <summary> + /// Gets the details regarding a failed authentication attempt, if available. + /// This will be set if and only if <see cref="Status"/> is <see cref="AuthenticationStatus.Failed"/>. + /// </summary> + /// <value></value> + public Exception Exception { + get { return null; } + } + + #endregion + + #region ISetupRequiredAuthenticationResponse Members + + /// <summary> + /// Gets the <see cref="Identifier"/> to pass to <see cref="OpenIdRelyingParty.CreateRequest(Identifier)"/> + /// in a subsequent authentication attempt. + /// </summary> + /// <value></value> + public Identifier UserSuppliedIdentifier { + get { + ErrorUtilities.VerifyOperation(((IAuthenticationResponse)this).Status == AuthenticationStatus.SetupRequired, OpenIdStrings.OperationOnlyValidForSetupRequiredState); + string userSuppliedIdentifier; + this.response.ExtraData.TryGetValue(AuthenticationRequest.UserSuppliedIdentifierParameterName, out userSuppliedIdentifier); + return userSuppliedIdentifier; + } + } + + #endregion + + #region IAuthenticationResponse Methods + + /// <summary> + /// Gets a callback argument's value that was previously added using + /// <see cref="IAuthenticationRequest.AddCallbackArguments(string, string)"/>. + /// </summary> + /// <param name="key">The name of the parameter whose value is sought.</param> + /// <returns> + /// The value of the argument, or null if the named parameter could not be found. + /// </returns> + /// <remarks> + /// <para>This may return any argument on the querystring that came with the authentication response, + /// which may include parameters not explicitly added using + /// <see cref="IAuthenticationRequest.AddCallbackArguments(string, string)"/>.</para> + /// <para>Note that these values are NOT protected against tampering in transit.</para> + /// </remarks> + public string GetCallbackArgument(string key) { + return null; + } + + /// <summary> + /// Gets a callback argument's value that was previously added using + /// <see cref="IAuthenticationRequest.AddCallbackArguments(string, string)"/>. + /// </summary> + /// <param name="key">The name of the parameter whose value is sought.</param> + /// <returns> + /// The value of the argument, or null if the named parameter could not be found. + /// </returns> + /// <remarks> + /// Callback parameters are only available even if the RP is in stateless mode, + /// or the callback parameters are otherwise unverifiable as untampered with. + /// Therefore, use this method only when the callback argument is not to be + /// used to make a security-sensitive decision. + /// </remarks> + public string GetUntrustedCallbackArgument(string key) { + return null; + } + + /// <summary> + /// Gets all the callback arguments that were previously added using + /// <see cref="IAuthenticationRequest.AddCallbackArguments(string, string)"/> or as a natural part + /// of the return_to URL. + /// </summary> + /// <returns>A name-value dictionary. Never null.</returns> + /// <remarks> + /// <para>This MAY return any argument on the querystring that came with the authentication response, + /// which may include parameters not explicitly added using + /// <see cref="IAuthenticationRequest.AddCallbackArguments(string, string)"/>.</para> + /// <para>Note that these values are NOT protected against tampering in transit.</para> + /// </remarks> + public IDictionary<string, string> GetCallbackArguments() { + return EmptyDictionary<string, string>.Instance; + } + + /// <summary> + /// Gets all the callback arguments that were previously added using + /// <see cref="IAuthenticationRequest.AddCallbackArguments(string, string)"/> or as a natural part + /// of the return_to URL. + /// </summary> + /// <returns>A name-value dictionary. Never null.</returns> + /// <remarks> + /// Callback parameters are only available even if the RP is in stateless mode, + /// or the callback parameters are otherwise unverifiable as untampered with. + /// Therefore, use this method only when the callback argument is not to be + /// used to make a security-sensitive decision. + /// </remarks> + public IDictionary<string, string> GetUntrustedCallbackArguments() { + return EmptyDictionary<string, string>.Instance; + } + + /// <summary> + /// Tries to get an OpenID extension that may be present in the response. + /// </summary> + /// <typeparam name="T">The type of extension to look for in the response message.</typeparam> + /// <returns> + /// The extension, if it is found. Null otherwise. + /// </returns> + /// <remarks> + /// <para>Extensions are returned only if the Provider signed them. + /// Relying parties that do not care if the values were modified in + /// transit should use the <see cref="GetUntrustedExtension<T>"/> method + /// in order to allow the Provider to not sign the extension. </para> + /// <para>Unsigned extensions are completely unreliable and should be + /// used only to prefill user forms since the user or any other third + /// party may have tampered with the data carried by the extension.</para> + /// <para>Signed extensions are only reliable if the relying party + /// trusts the OpenID Provider that signed them. Signing does not mean + /// the relying party can trust the values -- it only means that the values + /// have not been tampered with since the Provider sent the message.</para> + /// </remarks> + public T GetExtension<T>() where T : IOpenIdMessageExtension { + return default(T); + } + + /// <summary> + /// Tries to get an OpenID extension that may be present in the response. + /// </summary> + /// <param name="extensionType">Type of the extension to look for in the response.</param> + /// <returns> + /// The extension, if it is found. Null otherwise. + /// </returns> + /// <remarks> + /// <para>Extensions are returned only if the Provider signed them. + /// Relying parties that do not care if the values were modified in + /// transit should use the <see cref="GetUntrustedExtension"/> method + /// in order to allow the Provider to not sign the extension. </para> + /// <para>Unsigned extensions are completely unreliable and should be + /// used only to prefill user forms since the user or any other third + /// party may have tampered with the data carried by the extension.</para> + /// <para>Signed extensions are only reliable if the relying party + /// trusts the OpenID Provider that signed them. Signing does not mean + /// the relying party can trust the values -- it only means that the values + /// have not been tampered with since the Provider sent the message.</para> + /// </remarks> + public IOpenIdMessageExtension GetExtension(Type extensionType) { + return null; + } + + /// <summary> + /// Tries to get an OpenID extension that may be present in the response, without + /// requiring it to be signed by the Provider. + /// </summary> + /// <typeparam name="T">The type of extension to look for in the response message.</typeparam> + /// <returns> + /// The extension, if it is found. Null otherwise. + /// </returns> + /// <remarks> + /// <para>Extensions are returned whether they are signed or not. + /// Use the <see cref="GetExtension<T>"/> method to retrieve + /// extension responses only if they are signed by the Provider to + /// protect against tampering. </para> + /// <para>Unsigned extensions are completely unreliable and should be + /// used only to prefill user forms since the user or any other third + /// party may have tampered with the data carried by the extension.</para> + /// <para>Signed extensions are only reliable if the relying party + /// trusts the OpenID Provider that signed them. Signing does not mean + /// the relying party can trust the values -- it only means that the values + /// have not been tampered with since the Provider sent the message.</para> + /// </remarks> + public T GetUntrustedExtension<T>() where T : IOpenIdMessageExtension { + return this.response.Extensions.OfType<T>().FirstOrDefault(); + } + + /// <summary> + /// Tries to get an OpenID extension that may be present in the response. + /// </summary> + /// <param name="extensionType">Type of the extension to look for in the response.</param> + /// <returns> + /// The extension, if it is found. Null otherwise. + /// </returns> + /// <remarks> + /// <para>Extensions are returned whether they are signed or not. + /// Use the <see cref="GetExtension"/> method to retrieve + /// extension responses only if they are signed by the Provider to + /// protect against tampering. </para> + /// <para>Unsigned extensions are completely unreliable and should be + /// used only to prefill user forms since the user or any other third + /// party may have tampered with the data carried by the extension.</para> + /// <para>Signed extensions are only reliable if the relying party + /// trusts the OpenID Provider that signed them. Signing does not mean + /// the relying party can trust the values -- it only means that the values + /// have not been tampered with since the Provider sent the message.</para> + /// </remarks> + public IOpenIdMessageExtension GetUntrustedExtension(Type extensionType) { + return this.response.Extensions.OfType<IOpenIdMessageExtension>().Where(ext => extensionType.IsInstanceOfType(ext)).FirstOrDefault(); + } + + #endregion + } +} diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingParty.cd b/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/OpenIdRelyingParty.cd index 0519ecb..0519ecb 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingParty.cd +++ b/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/OpenIdRelyingParty.cd diff --git a/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/OpenIdRelyingParty.cs b/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/OpenIdRelyingParty.cs new file mode 100644 index 0000000..509e319 --- /dev/null +++ b/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/OpenIdRelyingParty.cs @@ -0,0 +1,903 @@ +//----------------------------------------------------------------------- +// <copyright file="OpenIdRelyingParty.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.RelyingParty { + using System; + using System.Collections.Generic; + using System.Collections.ObjectModel; + using System.Collections.Specialized; + using System.ComponentModel; + using System.Diagnostics.CodeAnalysis; + using System.Diagnostics.Contracts; + using System.Globalization; + using System.Linq; + using System.Net; + using System.Net.Mime; + using System.Text; + using System.Web; + using DotNetOpenAuth.Configuration; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.Messaging.Bindings; + using DotNetOpenAuth.OpenId.ChannelElements; + using DotNetOpenAuth.OpenId.Extensions; + using DotNetOpenAuth.OpenId.Messages; + + /// <summary> + /// A delegate that decides whether a given OpenID Provider endpoint may be + /// considered for authenticating a user. + /// </summary> + /// <param name="endpoint">The endpoint for consideration.</param> + /// <returns> + /// <c>True</c> if the endpoint should be considered. + /// <c>False</c> to remove it from the pool of acceptable providers. + /// </returns> + public delegate bool EndpointSelector(IProviderEndpoint endpoint); + + /// <summary> + /// Provides the programmatic facilities to act as an OpenID relying party. + /// </summary> + [SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Justification = "Unavoidable")] + [ContractVerification(true)] + public class OpenIdRelyingParty : IDisposable { + /// <summary> + /// The name of the key to use in the HttpApplication cache to store the + /// instance of <see cref="StandardRelyingPartyApplicationStore"/> to use. + /// </summary> + private const string ApplicationStoreKey = "DotNetOpenAuth.OpenId.RelyingParty.OpenIdRelyingParty.HttpApplicationStore"; + + /// <summary> + /// Backing store for the <see cref="Behaviors"/> property. + /// </summary> + private readonly ObservableCollection<IRelyingPartyBehavior> behaviors = new ObservableCollection<IRelyingPartyBehavior>(); + + /// <summary> + /// Backing field for the <see cref="DiscoveryServices"/> property. + /// </summary> + private readonly IList<IIdentifierDiscoveryService> discoveryServices = new List<IIdentifierDiscoveryService>(2); + + /// <summary> + /// A type initializer that ensures that another type initializer runs in order to guarantee that + /// types are serializable. + /// </summary> + private static Identifier dummyIdentifierToInvokeStaticCtor = "http://localhost/"; + + /// <summary> + /// A type initializer that ensures that another type initializer runs in order to guarantee that + /// types are serializable. + /// </summary> + private static Realm dummyRealmToInvokeStaticCtor = "http://localhost/"; + + /// <summary> + /// Backing field for the <see cref="NonVerifyingRelyingParty"/> property. + /// </summary> + private OpenIdRelyingParty nonVerifyingRelyingParty; + + /// <summary> + /// The lock to obtain when initializing the <see cref="nonVerifyingRelyingParty"/> member. + /// </summary> + private object nonVerifyingRelyingPartyInitLock = new object(); + + /// <summary> + /// A dictionary of extension response types and the javascript member + /// name to map them to on the user agent. + /// </summary> + private Dictionary<Type, string> clientScriptExtensions = new Dictionary<Type, string>(); + + /// <summary> + /// Backing field for the <see cref="SecuritySettings"/> property. + /// </summary> + private RelyingPartySecuritySettings securitySettings; + + /// <summary> + /// Backing store for the <see cref="EndpointOrder"/> property. + /// </summary> + private Comparison<IdentifierDiscoveryResult> endpointOrder = DefaultEndpointOrder; + + /// <summary> + /// Backing field for the <see cref="Channel"/> property. + /// </summary> + private Channel channel; + + /// <summary> + /// Initializes a new instance of the <see cref="OpenIdRelyingParty"/> class. + /// </summary> + public OpenIdRelyingParty() + : this(OpenIdElement.Configuration.RelyingParty.ApplicationStore.CreateInstance(HttpApplicationStore)) { + } + + /// <summary> + /// Initializes a new instance of the <see cref="OpenIdRelyingParty"/> class. + /// </summary> + /// <param name="applicationStore">The application store. If <c>null</c>, the relying party will always operate in "dumb mode".</param> + public OpenIdRelyingParty(IOpenIdApplicationStore applicationStore) + : this(applicationStore, applicationStore) { + } + + /// <summary> + /// Initializes a new instance of the <see cref="OpenIdRelyingParty"/> class. + /// </summary> + /// <param name="cryptoKeyStore">The association store. If null, the relying party will always operate in "dumb mode".</param> + /// <param name="nonceStore">The nonce store to use. If null, the relying party will always operate in "dumb mode".</param> + [SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Justification = "Unavoidable")] + private OpenIdRelyingParty(ICryptoKeyStore cryptoKeyStore, INonceStore nonceStore) { + // If we are a smart-mode RP (supporting associations), then we MUST also be + // capable of storing nonces to prevent replay attacks. + // If we're a dumb-mode RP, then 2.0 OPs are responsible for preventing replays. + Requires.True(cryptoKeyStore == null || nonceStore != null, null, OpenIdStrings.AssociationStoreRequiresNonceStore); + + this.securitySettings = OpenIdElement.Configuration.RelyingParty.SecuritySettings.CreateSecuritySettings(); + + foreach (var discoveryService in OpenIdElement.Configuration.RelyingParty.DiscoveryServices.CreateInstances(true)) { + this.discoveryServices.Add(discoveryService); + } + + this.behaviors.CollectionChanged += this.OnBehaviorsChanged; + foreach (var behavior in OpenIdElement.Configuration.RelyingParty.Behaviors.CreateInstances(false)) { + this.behaviors.Add(behavior); + } + + // Without a nonce store, we must rely on the Provider to protect against + // replay attacks. But only 2.0+ Providers can be expected to provide + // replay protection. + if (nonceStore == null && + this.SecuritySettings.ProtectDownlevelReplayAttacks && + this.SecuritySettings.MinimumRequiredOpenIdVersion < ProtocolVersion.V20) { + Logger.OpenId.Warn("Raising minimum OpenID version requirement for Providers to 2.0 to protect this stateless RP from replay attacks."); + this.SecuritySettings.MinimumRequiredOpenIdVersion = ProtocolVersion.V20; + } + + if (cryptoKeyStore == null) { + cryptoKeyStore = new MemoryCryptoKeyStore(); + } + + this.channel = new OpenIdRelyingPartyChannel(cryptoKeyStore, nonceStore, this.SecuritySettings); + this.AssociationManager = new AssociationManager(this.Channel, new CryptoKeyStoreAsRelyingPartyAssociationStore(cryptoKeyStore), this.SecuritySettings); + + Reporting.RecordFeatureAndDependencyUse(this, cryptoKeyStore, nonceStore); + } + + /// <summary> + /// Gets an XRDS sorting routine that uses the XRDS Service/@Priority + /// attribute to determine order. + /// </summary> + /// <remarks> + /// Endpoints lacking any priority value are sorted to the end of the list. + /// </remarks> + [EditorBrowsable(EditorBrowsableState.Advanced)] + public static Comparison<IdentifierDiscoveryResult> DefaultEndpointOrder { + get { return IdentifierDiscoveryResult.EndpointOrder; } + } + + /// <summary> + /// Gets the standard state storage mechanism that uses ASP.NET's + /// HttpApplication state dictionary to store associations and nonces. + /// </summary> + [EditorBrowsable(EditorBrowsableState.Advanced)] + public static IOpenIdApplicationStore HttpApplicationStore { + get { + Contract.Ensures(Contract.Result<IOpenIdApplicationStore>() != null); + + HttpContext context = HttpContext.Current; + ErrorUtilities.VerifyOperation(context != null, Strings.StoreRequiredWhenNoHttpContextAvailable, typeof(IOpenIdApplicationStore).Name); + var store = (IOpenIdApplicationStore)context.Application[ApplicationStoreKey]; + if (store == null) { + context.Application.Lock(); + try { + if ((store = (IOpenIdApplicationStore)context.Application[ApplicationStoreKey]) == null) { + context.Application[ApplicationStoreKey] = store = new StandardRelyingPartyApplicationStore(); + } + } finally { + context.Application.UnLock(); + } + } + + return store; + } + } + + /// <summary> + /// Gets or sets the channel to use for sending/receiving messages. + /// </summary> + public Channel Channel { + get { + return this.channel; + } + + set { + Requires.NotNull(value, "value"); + this.channel = value; + this.AssociationManager.Channel = value; + } + } + + /// <summary> + /// Gets the security settings used by this Relying Party. + /// </summary> + public RelyingPartySecuritySettings SecuritySettings { + get { + Contract.Ensures(Contract.Result<RelyingPartySecuritySettings>() != null); + return this.securitySettings; + } + + internal set { + Requires.NotNull(value, "value"); + this.securitySettings = value; + this.AssociationManager.SecuritySettings = value; + } + } + + /// <summary> + /// Gets or sets the optional Provider Endpoint filter to use. + /// </summary> + /// <remarks> + /// Provides a way to optionally filter the providers that may be used in authenticating a user. + /// If provided, the delegate should return true to accept an endpoint, and false to reject it. + /// If null, all identity providers will be accepted. This is the default. + /// </remarks> + [EditorBrowsable(EditorBrowsableState.Advanced)] + public EndpointSelector EndpointFilter { get; set; } + + /// <summary> + /// Gets or sets the ordering routine that will determine which XRDS + /// Service element to try first + /// </summary> + /// <value>Default is <see cref="DefaultEndpointOrder"/>.</value> + /// <remarks> + /// This may never be null. To reset to default behavior this property + /// can be set to the value of <see cref="DefaultEndpointOrder"/>. + /// </remarks> + [EditorBrowsable(EditorBrowsableState.Advanced)] + public Comparison<IdentifierDiscoveryResult> EndpointOrder { + get { + return this.endpointOrder; + } + + set { + Requires.NotNull(value, "value"); + this.endpointOrder = value; + } + } + + /// <summary> + /// Gets the extension factories. + /// </summary> + public IList<IOpenIdExtensionFactory> ExtensionFactories { + get { return this.Channel.GetExtensionFactories(); } + } + + /// <summary> + /// Gets a list of custom behaviors to apply to OpenID actions. + /// </summary> + /// <remarks> + /// Adding behaviors can impact the security settings of this <see cref="OpenIdRelyingParty"/> + /// instance in ways that subsequently removing the behaviors will not reverse. + /// </remarks> + public ICollection<IRelyingPartyBehavior> Behaviors { + get { return this.behaviors; } + } + + /// <summary> + /// Gets the list of services that can perform discovery on identifiers given to this relying party. + /// </summary> + public IList<IIdentifierDiscoveryService> DiscoveryServices { + get { return this.discoveryServices; } + } + + /// <summary> + /// Gets a value indicating whether this Relying Party can sign its return_to + /// parameter in outgoing authentication requests. + /// </summary> + internal bool CanSignCallbackArguments { + get { return this.Channel.BindingElements.OfType<ReturnToSignatureBindingElement>().Any(); } + } + + /// <summary> + /// Gets the web request handler to use for discovery and the part of + /// authentication where direct messages are sent to an untrusted remote party. + /// </summary> + internal IDirectWebRequestHandler WebRequestHandler { + get { return this.Channel.WebRequestHandler; } + } + + /// <summary> + /// Gets the association manager. + /// </summary> + internal AssociationManager AssociationManager { get; private set; } + + /// <summary> + /// Gets the <see cref="OpenIdRelyingParty"/> instance used to process authentication responses + /// without verifying the assertion or consuming nonces. + /// </summary> + protected OpenIdRelyingParty NonVerifyingRelyingParty { + get { + if (this.nonVerifyingRelyingParty == null) { + lock (this.nonVerifyingRelyingPartyInitLock) { + if (this.nonVerifyingRelyingParty == null) { + this.nonVerifyingRelyingParty = OpenIdRelyingParty.CreateNonVerifying(); + } + } + } + + return this.nonVerifyingRelyingParty; + } + } + + /// <summary> + /// Creates an authentication request to verify that a user controls + /// some given Identifier. + /// </summary> + /// <param name="userSuppliedIdentifier"> + /// The Identifier supplied by the user. This may be a URL, an XRI or i-name. + /// </param> + /// <param name="realm"> + /// The shorest URL that describes this relying party web site's address. + /// For example, if your login page is found at https://www.example.com/login.aspx, + /// your realm would typically be https://www.example.com/. + /// </param> + /// <param name="returnToUrl"> + /// The URL of the login page, or the page prepared to receive authentication + /// responses from the OpenID Provider. + /// </param> + /// <returns> + /// An authentication request object to customize the request and generate + /// an object to send to the user agent to initiate the authentication. + /// </returns> + /// <exception cref="ProtocolException">Thrown if no OpenID endpoint could be found.</exception> + public IAuthenticationRequest CreateRequest(Identifier userSuppliedIdentifier, Realm realm, Uri returnToUrl) { + Requires.NotNull(userSuppliedIdentifier, "userSuppliedIdentifier"); + Requires.NotNull(realm, "realm"); + Requires.NotNull(returnToUrl, "returnToUrl"); + Contract.Ensures(Contract.Result<IAuthenticationRequest>() != null); + try { + return this.CreateRequests(userSuppliedIdentifier, realm, returnToUrl).First(); + } catch (InvalidOperationException ex) { + throw ErrorUtilities.Wrap(ex, OpenIdStrings.OpenIdEndpointNotFound); + } + } + + /// <summary> + /// Creates an authentication request to verify that a user controls + /// some given Identifier. + /// </summary> + /// <param name="userSuppliedIdentifier"> + /// The Identifier supplied by the user. This may be a URL, an XRI or i-name. + /// </param> + /// <param name="realm"> + /// The shorest URL that describes this relying party web site's address. + /// For example, if your login page is found at https://www.example.com/login.aspx, + /// your realm would typically be https://www.example.com/. + /// </param> + /// <returns> + /// An authentication request object that describes the HTTP response to + /// send to the user agent to initiate the authentication. + /// </returns> + /// <remarks> + /// <para>Requires an <see cref="HttpContext.Current">HttpContext.Current</see> context.</para> + /// </remarks> + /// <exception cref="ProtocolException">Thrown if no OpenID endpoint could be found.</exception> + /// <exception cref="InvalidOperationException">Thrown if <see cref="HttpContext.Current">HttpContext.Current</see> == <c>null</c>.</exception> + public IAuthenticationRequest CreateRequest(Identifier userSuppliedIdentifier, Realm realm) { + Requires.NotNull(userSuppliedIdentifier, "userSuppliedIdentifier"); + Requires.NotNull(realm, "realm"); + Contract.Ensures(Contract.Result<IAuthenticationRequest>() != null); + try { + var result = this.CreateRequests(userSuppliedIdentifier, realm).First(); + Contract.Assume(result != null); + return result; + } catch (InvalidOperationException ex) { + throw ErrorUtilities.Wrap(ex, OpenIdStrings.OpenIdEndpointNotFound); + } + } + + /// <summary> + /// Creates an authentication request to verify that a user controls + /// some given Identifier. + /// </summary> + /// <param name="userSuppliedIdentifier"> + /// The Identifier supplied by the user. This may be a URL, an XRI or i-name. + /// </param> + /// <returns> + /// An authentication request object that describes the HTTP response to + /// send to the user agent to initiate the authentication. + /// </returns> + /// <remarks> + /// <para>Requires an <see cref="HttpContext.Current">HttpContext.Current</see> context.</para> + /// </remarks> + /// <exception cref="ProtocolException">Thrown if no OpenID endpoint could be found.</exception> + /// <exception cref="InvalidOperationException">Thrown if <see cref="HttpContext.Current">HttpContext.Current</see> == <c>null</c>.</exception> + public IAuthenticationRequest CreateRequest(Identifier userSuppliedIdentifier) { + Requires.NotNull(userSuppliedIdentifier, "userSuppliedIdentifier"); + Contract.Ensures(Contract.Result<IAuthenticationRequest>() != null); + try { + return this.CreateRequests(userSuppliedIdentifier).First(); + } catch (InvalidOperationException ex) { + throw ErrorUtilities.Wrap(ex, OpenIdStrings.OpenIdEndpointNotFound); + } + } + + /// <summary> + /// Generates the authentication requests that can satisfy the requirements of some OpenID Identifier. + /// </summary> + /// <param name="userSuppliedIdentifier"> + /// The Identifier supplied by the user. This may be a URL, an XRI or i-name. + /// </param> + /// <param name="realm"> + /// The shorest URL that describes this relying party web site's address. + /// For example, if your login page is found at https://www.example.com/login.aspx, + /// your realm would typically be https://www.example.com/. + /// </param> + /// <param name="returnToUrl"> + /// The URL of the login page, or the page prepared to receive authentication + /// responses from the OpenID Provider. + /// </param> + /// <returns> + /// A sequence of authentication requests, any of which constitutes a valid identity assertion on the Claimed Identifier. + /// Never null, but may be empty. + /// </returns> + /// <remarks> + /// <para>Any individual generated request can satisfy the authentication. + /// The generated requests are sorted in preferred order. + /// Each request is generated as it is enumerated to. Associations are created only as + /// <see cref="IAuthenticationRequest.RedirectingResponse"/> is called.</para> + /// <para>No exception is thrown if no OpenID endpoints were discovered. + /// An empty enumerable is returned instead.</para> + /// </remarks> + public virtual IEnumerable<IAuthenticationRequest> CreateRequests(Identifier userSuppliedIdentifier, Realm realm, Uri returnToUrl) { + Requires.NotNull(userSuppliedIdentifier, "userSuppliedIdentifier"); + Requires.NotNull(realm, "realm"); + Requires.NotNull(returnToUrl, "returnToUrl"); + Contract.Ensures(Contract.Result<IEnumerable<IAuthenticationRequest>>() != null); + + return AuthenticationRequest.Create(userSuppliedIdentifier, this, realm, returnToUrl, true).Cast<IAuthenticationRequest>().CacheGeneratedResults(); + } + + /// <summary> + /// Generates the authentication requests that can satisfy the requirements of some OpenID Identifier. + /// </summary> + /// <param name="userSuppliedIdentifier"> + /// The Identifier supplied by the user. This may be a URL, an XRI or i-name. + /// </param> + /// <param name="realm"> + /// The shorest URL that describes this relying party web site's address. + /// For example, if your login page is found at https://www.example.com/login.aspx, + /// your realm would typically be https://www.example.com/. + /// </param> + /// <returns> + /// A sequence of authentication requests, any of which constitutes a valid identity assertion on the Claimed Identifier. + /// Never null, but may be empty. + /// </returns> + /// <remarks> + /// <para>Any individual generated request can satisfy the authentication. + /// The generated requests are sorted in preferred order. + /// Each request is generated as it is enumerated to. Associations are created only as + /// <see cref="IAuthenticationRequest.RedirectingResponse"/> is called.</para> + /// <para>No exception is thrown if no OpenID endpoints were discovered. + /// An empty enumerable is returned instead.</para> + /// <para>Requires an <see cref="HttpContext.Current">HttpContext.Current</see> context.</para> + /// </remarks> + /// <exception cref="InvalidOperationException">Thrown if <see cref="HttpContext.Current">HttpContext.Current</see> == <c>null</c>.</exception> + public IEnumerable<IAuthenticationRequest> CreateRequests(Identifier userSuppliedIdentifier, Realm realm) { + Requires.ValidState(HttpContext.Current != null && HttpContext.Current.Request != null, MessagingStrings.HttpContextRequired); + Requires.NotNull(userSuppliedIdentifier, "userSuppliedIdentifier"); + Requires.NotNull(realm, "realm"); + Contract.Ensures(Contract.Result<IEnumerable<IAuthenticationRequest>>() != null); + + // This next code contract is a BAD idea, because it causes each authentication request to be generated + // at least an extra time. + ////Contract.Ensures(Contract.ForAll(Contract.Result<IEnumerable<IAuthenticationRequest>>(), el => el != null)); + + // Build the return_to URL + UriBuilder returnTo = new UriBuilder(this.Channel.GetRequestFromContext().UrlBeforeRewriting); + + // Trim off any parameters with an "openid." prefix, and a few known others + // to avoid carrying state from a prior login attempt. + returnTo.Query = string.Empty; + NameValueCollection queryParams = this.Channel.GetRequestFromContext().QueryStringBeforeRewriting; + var returnToParams = new Dictionary<string, string>(queryParams.Count); + foreach (string key in queryParams) { + if (!IsOpenIdSupportingParameter(key) && key != null) { + returnToParams.Add(key, queryParams[key]); + } + } + returnTo.AppendQueryArgs(returnToParams); + + return this.CreateRequests(userSuppliedIdentifier, realm, returnTo.Uri); + } + + /// <summary> + /// Generates the authentication requests that can satisfy the requirements of some OpenID Identifier. + /// </summary> + /// <param name="userSuppliedIdentifier"> + /// The Identifier supplied by the user. This may be a URL, an XRI or i-name. + /// </param> + /// <returns> + /// A sequence of authentication requests, any of which constitutes a valid identity assertion on the Claimed Identifier. + /// Never null, but may be empty. + /// </returns> + /// <remarks> + /// <para>Any individual generated request can satisfy the authentication. + /// The generated requests are sorted in preferred order. + /// Each request is generated as it is enumerated to. Associations are created only as + /// <see cref="IAuthenticationRequest.RedirectingResponse"/> is called.</para> + /// <para>No exception is thrown if no OpenID endpoints were discovered. + /// An empty enumerable is returned instead.</para> + /// <para>Requires an <see cref="HttpContext.Current">HttpContext.Current</see> context.</para> + /// </remarks> + /// <exception cref="InvalidOperationException">Thrown if <see cref="HttpContext.Current">HttpContext.Current</see> == <c>null</c>.</exception> + public IEnumerable<IAuthenticationRequest> CreateRequests(Identifier userSuppliedIdentifier) { + Requires.NotNull(userSuppliedIdentifier, "userSuppliedIdentifier"); + Requires.ValidState(HttpContext.Current != null && HttpContext.Current.Request != null, MessagingStrings.HttpContextRequired); + Contract.Ensures(Contract.Result<IEnumerable<IAuthenticationRequest>>() != null); + + return this.CreateRequests(userSuppliedIdentifier, Realm.AutoDetect); + } + + /// <summary> + /// Gets an authentication response from a Provider. + /// </summary> + /// <returns>The processed authentication response if there is any; <c>null</c> otherwise.</returns> + /// <remarks> + /// <para>Requires an <see cref="HttpContext.Current">HttpContext.Current</see> context.</para> + /// </remarks> + public IAuthenticationResponse GetResponse() { + Requires.ValidState(HttpContext.Current != null && HttpContext.Current.Request != null, MessagingStrings.HttpContextRequired); + return this.GetResponse(this.Channel.GetRequestFromContext()); + } + + /// <summary> + /// Gets an authentication response from a Provider. + /// </summary> + /// <param name="httpRequestInfo">The HTTP request that may be carrying an authentication response from the Provider.</param> + /// <returns>The processed authentication response if there is any; <c>null</c> otherwise.</returns> + public IAuthenticationResponse GetResponse(HttpRequestInfo httpRequestInfo) { + Requires.NotNull(httpRequestInfo, "httpRequestInfo"); + try { + var message = this.Channel.ReadFromRequest(httpRequestInfo); + PositiveAssertionResponse positiveAssertion; + NegativeAssertionResponse negativeAssertion; + IndirectSignedResponse positiveExtensionOnly; + if ((positiveAssertion = message as PositiveAssertionResponse) != null) { + // We need to make sure that this assertion is coming from an endpoint + // that the host deems acceptable. + var providerEndpoint = new SimpleXrdsProviderEndpoint(positiveAssertion); + ErrorUtilities.VerifyProtocol( + this.FilterEndpoint(providerEndpoint), + OpenIdStrings.PositiveAssertionFromNonQualifiedProvider, + providerEndpoint.Uri); + + var response = new PositiveAuthenticationResponse(positiveAssertion, this); + foreach (var behavior in this.Behaviors) { + behavior.OnIncomingPositiveAssertion(response); + } + + return response; + } else if ((positiveExtensionOnly = message as IndirectSignedResponse) != null) { + return new PositiveAnonymousResponse(positiveExtensionOnly); + } else if ((negativeAssertion = message as NegativeAssertionResponse) != null) { + return new NegativeAuthenticationResponse(negativeAssertion); + } else if (message != null) { + Logger.OpenId.WarnFormat("Received unexpected message type {0} when expecting an assertion message.", message.GetType().Name); + } + + return null; + } catch (ProtocolException ex) { + return new FailedAuthenticationResponse(ex); + } + } + + /// <summary> + /// Processes the response received in a popup window or iframe to an AJAX-directed OpenID authentication. + /// </summary> + /// <returns>The HTTP response to send to this HTTP request.</returns> + /// <remarks> + /// <para>Requires an <see cref="HttpContext.Current">HttpContext.Current</see> context.</para> + /// </remarks> + public OutgoingWebResponse ProcessResponseFromPopup() { + Requires.ValidState(HttpContext.Current != null && HttpContext.Current.Request != null, MessagingStrings.HttpContextRequired); + Contract.Ensures(Contract.Result<OutgoingWebResponse>() != null); + + return this.ProcessResponseFromPopup(this.Channel.GetRequestFromContext()); + } + + /// <summary> + /// Processes the response received in a popup window or iframe to an AJAX-directed OpenID authentication. + /// </summary> + /// <param name="request">The incoming HTTP request that is expected to carry an OpenID authentication response.</param> + /// <returns>The HTTP response to send to this HTTP request.</returns> + public OutgoingWebResponse ProcessResponseFromPopup(HttpRequestInfo request) { + Requires.NotNull(request, "request"); + Contract.Ensures(Contract.Result<OutgoingWebResponse>() != null); + + return this.ProcessResponseFromPopup(request, null); + } + + /// <summary> + /// Allows an OpenID extension to read data out of an unverified positive authentication assertion + /// and send it down to the client browser so that Javascript running on the page can perform + /// some preprocessing on the extension data. + /// </summary> + /// <typeparam name="T">The extension <i>response</i> type that will read data from the assertion.</typeparam> + /// <param name="propertyName">The property name on the openid_identifier input box object that will be used to store the extension data. For example: sreg</param> + /// <remarks> + /// This method should be called before <see cref="ProcessResponseFromPopup()"/>. + /// </remarks> + [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "By design")] + public void RegisterClientScriptExtension<T>(string propertyName) where T : IClientScriptExtensionResponse { + Requires.NotNullOrEmpty(propertyName, "propertyName"); + ErrorUtilities.VerifyArgumentNamed(!this.clientScriptExtensions.ContainsValue(propertyName), "propertyName", OpenIdStrings.ClientScriptExtensionPropertyNameCollision, propertyName); + foreach (var ext in this.clientScriptExtensions.Keys) { + ErrorUtilities.VerifyArgument(ext != typeof(T), OpenIdStrings.ClientScriptExtensionTypeCollision, typeof(T).FullName); + } + this.clientScriptExtensions.Add(typeof(T), propertyName); + } + + #region IDisposable Members + + /// <summary> + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// </summary> + public void Dispose() { + this.Dispose(true); + GC.SuppressFinalize(this); + } + + #endregion + + /// <summary> + /// Determines whether some parameter name belongs to OpenID or this library + /// as a protocol or internal parameter name. + /// </summary> + /// <param name="parameterName">Name of the parameter.</param> + /// <returns> + /// <c>true</c> if the named parameter is a library- or protocol-specific parameter; otherwise, <c>false</c>. + /// </returns> + internal static bool IsOpenIdSupportingParameter(string parameterName) { + // Yes, it is possible with some query strings to have a null or empty parameter name + if (string.IsNullOrEmpty(parameterName)) { + return false; + } + + Protocol protocol = Protocol.Default; + return parameterName.StartsWith(protocol.openid.Prefix, StringComparison.OrdinalIgnoreCase) + || parameterName.StartsWith(OpenIdUtilities.CustomParameterPrefix, StringComparison.Ordinal); + } + + /// <summary> + /// Creates a relying party that does not verify incoming messages against + /// nonce or association stores. + /// </summary> + /// <returns>The instantiated <see cref="OpenIdRelyingParty"/>.</returns> + /// <remarks> + /// Useful for previewing messages while + /// allowing them to be fully processed and verified later. + /// </remarks> + internal static OpenIdRelyingParty CreateNonVerifying() { + OpenIdRelyingParty rp = new OpenIdRelyingParty(); + try { + rp.Channel = OpenIdRelyingPartyChannel.CreateNonVerifyingChannel(); + return rp; + } catch { + rp.Dispose(); + throw; + } + } + + /// <summary> + /// Processes the response received in a popup window or iframe to an AJAX-directed OpenID authentication. + /// </summary> + /// <param name="request">The incoming HTTP request that is expected to carry an OpenID authentication response.</param> + /// <param name="callback">The callback fired after the response status has been determined but before the Javascript response is formulated.</param> + /// <returns> + /// The HTTP response to send to this HTTP request. + /// </returns> + [SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "OpenID", Justification = "real word"), SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "iframe", Justification = "Code contracts")] + internal OutgoingWebResponse ProcessResponseFromPopup(HttpRequestInfo request, Action<AuthenticationStatus> callback) { + Requires.NotNull(request, "request"); + Contract.Ensures(Contract.Result<OutgoingWebResponse>() != null); + + string extensionsJson = null; + var authResponse = this.NonVerifyingRelyingParty.GetResponse(); + ErrorUtilities.VerifyProtocol(authResponse != null, OpenIdStrings.PopupRedirectMissingResponse); + + // Give the caller a chance to notify the hosting page and fill up the clientScriptExtensions collection. + if (callback != null) { + callback(authResponse.Status); + } + + Logger.OpenId.DebugFormat("Popup or iframe callback from OP: {0}", request.Url); + Logger.Controls.DebugFormat( + "An authentication response was found in a popup window or iframe using a non-verifying RP with status: {0}", + authResponse.Status); + if (authResponse.Status == AuthenticationStatus.Authenticated) { + var extensionsDictionary = new Dictionary<string, string>(); + foreach (var pair in this.clientScriptExtensions) { + IClientScriptExtensionResponse extension = (IClientScriptExtensionResponse)authResponse.GetExtension(pair.Key); + if (extension == null) { + continue; + } + var positiveResponse = (PositiveAuthenticationResponse)authResponse; + string js = extension.InitializeJavaScriptData(positiveResponse.Response); + if (!string.IsNullOrEmpty(js)) { + extensionsDictionary[pair.Value] = js; + } + } + + extensionsJson = MessagingUtilities.CreateJsonObject(extensionsDictionary, true); + } + + string payload = "document.URL"; + if (request.HttpMethod == "POST") { + // Promote all form variables to the query string, but since it won't be passed + // to any server (this is a javascript window-to-window transfer) the length of + // it can be arbitrarily long, whereas it was POSTed here probably because it + // was too long for HTTP transit. + UriBuilder payloadUri = new UriBuilder(request.Url); + payloadUri.AppendQueryArgs(request.Form.ToDictionary()); + payload = MessagingUtilities.GetSafeJavascriptValue(payloadUri.Uri.AbsoluteUri); + } + + if (!string.IsNullOrEmpty(extensionsJson)) { + payload += ", " + extensionsJson; + } + + return InvokeParentPageScript("dnoa_internal.processAuthorizationResult(" + payload + ")"); + } + + /// <summary> + /// Performs discovery on the specified identifier. + /// </summary> + /// <param name="identifier">The identifier to discover services for.</param> + /// <returns>A non-null sequence of services discovered for the identifier.</returns> + internal IEnumerable<IdentifierDiscoveryResult> Discover(Identifier identifier) { + Requires.NotNull(identifier, "identifier"); + Contract.Ensures(Contract.Result<IEnumerable<IdentifierDiscoveryResult>>() != null); + + IEnumerable<IdentifierDiscoveryResult> results = Enumerable.Empty<IdentifierDiscoveryResult>(); + foreach (var discoverer in this.DiscoveryServices) { + bool abortDiscoveryChain; + var discoveryResults = discoverer.Discover(identifier, this.WebRequestHandler, out abortDiscoveryChain).CacheGeneratedResults(); + results = results.Concat(discoveryResults); + if (abortDiscoveryChain) { + Logger.OpenId.InfoFormat("Further discovery on '{0}' was stopped by the {1} discovery service.", identifier, discoverer.GetType().Name); + break; + } + } + + // If any OP Identifier service elements were found, we must not proceed + // to use any Claimed Identifier services, per OpenID 2.0 sections 7.3.2.2 and 11.2. + // For a discussion on this topic, see + // http://groups.google.com/group/dotnetopenid/browse_thread/thread/4b5a8c6b2210f387/5e25910e4d2252c8 + // Sometimes the IIdentifierDiscoveryService will automatically filter this for us, but + // just to be sure, we'll do it here as well. + if (!this.SecuritySettings.AllowDualPurposeIdentifiers) { + results = results.CacheGeneratedResults(); // avoid performing discovery repeatedly + var opIdentifiers = results.Where(result => result.ClaimedIdentifier == result.Protocol.ClaimedIdentifierForOPIdentifier); + var claimedIdentifiers = results.Where(result => result.ClaimedIdentifier != result.Protocol.ClaimedIdentifierForOPIdentifier); + results = opIdentifiers.Any() ? opIdentifiers : claimedIdentifiers; + } + + return results; + } + + /// <summary> + /// Checks whether a given OP Endpoint is permitted by the host relying party. + /// </summary> + /// <param name="endpoint">The OP endpoint.</param> + /// <returns><c>true</c> if the OP Endpoint is allowed; <c>false</c> otherwise.</returns> + protected internal bool FilterEndpoint(IProviderEndpoint endpoint) { + if (this.SecuritySettings.RejectAssertionsFromUntrustedProviders) { + if (!this.SecuritySettings.TrustedProviderEndpoints.Contains(endpoint.Uri)) { + Logger.OpenId.InfoFormat("Filtering out OP endpoint {0} because it is not on the exclusive trusted provider whitelist.", endpoint.Uri.AbsoluteUri); + return false; + } + } + + if (endpoint.Version < Protocol.Lookup(this.SecuritySettings.MinimumRequiredOpenIdVersion).Version) { + Logger.OpenId.InfoFormat( + "Filtering out OP endpoint {0} because it implements OpenID {1} but this relying party requires OpenID {2} or later.", + endpoint.Uri.AbsoluteUri, + endpoint.Version, + Protocol.Lookup(this.SecuritySettings.MinimumRequiredOpenIdVersion).Version); + return false; + } + + if (this.EndpointFilter != null) { + if (!this.EndpointFilter(endpoint)) { + Logger.OpenId.InfoFormat("Filtering out OP endpoint {0} because the host rejected it.", endpoint.Uri.AbsoluteUri); + return false; + } + } + + return true; + } + + /// <summary> + /// Releases unmanaged and - optionally - managed resources + /// </summary> + /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param> + protected virtual void Dispose(bool disposing) { + if (disposing) { + if (this.nonVerifyingRelyingParty != null) { + this.nonVerifyingRelyingParty.Dispose(); + this.nonVerifyingRelyingParty = null; + } + + // Tear off the instance member as a local variable for thread safety. + IDisposable disposableChannel = this.channel as IDisposable; + if (disposableChannel != null) { + disposableChannel.Dispose(); + } + } + } + + /// <summary> + /// Invokes a method on a parent frame or window and closes the calling popup window if applicable. + /// </summary> + /// <param name="methodCall">The method to call on the parent window, including + /// parameters. (i.e. "callback('arg1', 2)"). No escaping is done by this method.</param> + /// <returns>The entire HTTP response to send to the popup window or iframe to perform the invocation.</returns> + private static OutgoingWebResponse InvokeParentPageScript(string methodCall) { + Requires.NotNullOrEmpty(methodCall, "methodCall"); + + Logger.OpenId.DebugFormat("Sending Javascript callback: {0}", methodCall); + StringBuilder builder = new StringBuilder(); + builder.AppendLine("<html><body><script type='text/javascript' language='javascript'><!--"); + builder.AppendLine("//<![CDATA["); + builder.Append(@" var inPopup = !window.frameElement; + var objSrc = inPopup ? window.opener : window.frameElement; +"); + + // Something about calling objSrc.{0} can somehow cause FireFox to forget about the inPopup variable, + // so we have to actually put the test for it ABOVE the call to objSrc.{0} so that it already + // whether to call window.self.close() after the call. + string htmlFormat = @" if (inPopup) {{ + try {{ + objSrc.{0}; + }} catch (ex) {{ + alert(ex); + }} finally {{ + window.self.close(); + }} + }} else {{ + objSrc.{0}; + }}"; + builder.AppendFormat(CultureInfo.InvariantCulture, htmlFormat, methodCall); + builder.AppendLine("//]]>--></script>"); + builder.AppendLine("</body></html>"); + + var response = new OutgoingWebResponse(); + response.Body = builder.ToString(); + response.Headers.Add(HttpResponseHeader.ContentType, new ContentType("text/html").ToString()); + return response; + } + + /// <summary> + /// Called by derived classes when behaviors are added or removed. + /// </summary> + /// <param name="sender">The collection being modified.</param> + /// <param name="e">The <see cref="System.Collections.Specialized.NotifyCollectionChangedEventArgs"/> instance containing the event data.</param> + private void OnBehaviorsChanged(object sender, NotifyCollectionChangedEventArgs e) { + foreach (IRelyingPartyBehavior profile in e.NewItems) { + profile.ApplySecuritySettings(this.SecuritySettings); + Reporting.RecordFeatureUse(profile); + } + } + +#if CONTRACTS_FULL + /// <summary> + /// Verifies conditions that should be true for any valid state of this object. + /// </summary> + [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Called by code contracts.")] + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called by code contracts.")] + [ContractInvariantMethod] + private void ObjectInvariant() { + Contract.Invariant(this.SecuritySettings != null); + Contract.Invariant(this.Channel != null); + Contract.Invariant(this.EndpointOrder != null); + } +#endif + } +} diff --git a/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/PositiveAnonymousResponse.cs b/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/PositiveAnonymousResponse.cs new file mode 100644 index 0000000..de82bed --- /dev/null +++ b/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/PositiveAnonymousResponse.cs @@ -0,0 +1,347 @@ +//----------------------------------------------------------------------- +// <copyright file="PositiveAnonymousResponse.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.RelyingParty { + using System; + using System.Collections.Generic; + using System.Diagnostics.Contracts; + using System.Linq; + using System.Web; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OpenId.Messages; + + /// <summary> + /// Wraps an extension-only response from the OP in an <see cref="IAuthenticationResponse"/> instance + /// for public consumption by the host web site. + /// </summary> + internal class PositiveAnonymousResponse : IAuthenticationResponse { + /// <summary> + /// Backin field for the <see cref="Response"/> property. + /// </summary> + private readonly IndirectSignedResponse response; + + /// <summary> + /// Information about the OP endpoint that issued this assertion. + /// </summary> + private readonly IProviderEndpoint provider; + + /// <summary> + /// Initializes a new instance of the <see cref="PositiveAnonymousResponse"/> class. + /// </summary> + /// <param name="response">The response message.</param> + protected internal PositiveAnonymousResponse(IndirectSignedResponse response) { + Requires.NotNull(response, "response"); + + this.response = response; + if (response.ProviderEndpoint != null && response.Version != null) { + this.provider = new ProviderEndpointDescription(response.ProviderEndpoint, response.Version); + } + + // Derived types of this are responsible to log an appropriate message for themselves. + if (Logger.OpenId.IsInfoEnabled && this.GetType() == typeof(PositiveAnonymousResponse)) { + Logger.OpenId.Info("Received anonymous (identity-less) positive assertion."); + } + + if (response.ProviderEndpoint != null) { + Reporting.RecordEventOccurrence(this, response.ProviderEndpoint.AbsoluteUri); + } + } + + #region IAuthenticationResponse Properties + + /// <summary> + /// Gets the Identifier that the end user claims to own. For use with user database storage and lookup. + /// May be null for some failed authentications (i.e. failed directed identity authentications). + /// </summary> + /// <remarks> + /// <para> + /// This is the secure identifier that should be used for database storage and lookup. + /// It is not always friendly (i.e. =Arnott becomes =!9B72.7DD1.50A9.5CCD), but it protects + /// user identities against spoofing and other attacks. + /// </para> + /// <para> + /// For user-friendly identifiers to display, use the + /// <see cref="FriendlyIdentifierForDisplay"/> property. + /// </para> + /// </remarks> + public virtual Identifier ClaimedIdentifier { + get { return null; } + } + + /// <summary> + /// Gets a user-friendly OpenID Identifier for display purposes ONLY. + /// </summary> + /// <value></value> + /// <remarks> + /// <para> + /// This <i>should</i> be put through <see cref="HttpUtility.HtmlEncode(string)"/> before + /// sending to a browser to secure against javascript injection attacks. + /// </para> + /// <para> + /// This property retains some aspects of the user-supplied identifier that get lost + /// in the <see cref="ClaimedIdentifier"/>. For example, XRIs used as user-supplied + /// identifiers (i.e. =Arnott) become unfriendly unique strings (i.e. =!9B72.7DD1.50A9.5CCD). + /// For display purposes, such as text on a web page that says "You're logged in as ...", + /// this property serves to provide the =Arnott string, or whatever else is the most friendly + /// string close to what the user originally typed in. + /// </para> + /// <para> + /// If the user-supplied identifier is a URI, this property will be the URI after all + /// redirects, and with the protocol and fragment trimmed off. + /// If the user-supplied identifier is an XRI, this property will be the original XRI. + /// If the user-supplied identifier is an OpenID Provider identifier (i.e. yahoo.com), + /// this property will be the Claimed Identifier, with the protocol stripped if it is a URI. + /// </para> + /// <para> + /// It is <b>very</b> important that this property <i>never</i> be used for database storage + /// or lookup to avoid identity spoofing and other security risks. For database storage + /// and lookup please use the <see cref="ClaimedIdentifier"/> property. + /// </para> + /// </remarks> + public virtual string FriendlyIdentifierForDisplay { + get { return null; } + } + + /// <summary> + /// Gets the detailed success or failure status of the authentication attempt. + /// </summary> + public virtual AuthenticationStatus Status { + get { return AuthenticationStatus.ExtensionsOnly; } + } + + /// <summary> + /// Gets information about the OpenId Provider, as advertised by the + /// OpenID discovery documents found at the <see cref="ClaimedIdentifier"/> + /// location. + /// </summary> + /// <value> + /// The Provider endpoint that issued the positive assertion; + /// or <c>null</c> if information about the Provider is unavailable. + /// </value> + public IProviderEndpoint Provider { + get { return this.provider; } + } + + /// <summary> + /// Gets the details regarding a failed authentication attempt, if available. + /// This will be set if and only if <see cref="Status"/> is <see cref="AuthenticationStatus.Failed"/>. + /// </summary> + /// <value></value> + public Exception Exception { + get { return null; } + } + + #endregion + + /// <summary> + /// Gets a value indicating whether trusted callback arguments are available. + /// </summary> + /// <remarks> + /// We use this internally to avoid logging a warning during a standard snapshot creation. + /// </remarks> + internal bool TrustedCallbackArgumentsAvailable { + get { return this.response.ReturnToParametersSignatureValidated; } + } + + /// <summary> + /// Gets the positive extension-only message the Relying Party received that this instance wraps. + /// </summary> + protected internal IndirectSignedResponse Response { + get { return this.response; } + } + + #region IAuthenticationResponse methods + + /// <summary> + /// Gets a callback argument's value that was previously added using + /// <see cref="IAuthenticationRequest.AddCallbackArguments(string, string)"/>. + /// </summary> + /// <param name="key">The name of the parameter whose value is sought.</param> + /// <returns> + /// The value of the argument, or null if the named parameter could not be found. + /// </returns> + /// <remarks> + /// Callback parameters are only available if they are complete and untampered with + /// since the original request message (as proven by a signature). + /// If the relying party is operating in stateless mode <c>null</c> is always + /// returned since the callback arguments could not be signed to protect against + /// tampering. + /// </remarks> + public string GetCallbackArgument(string key) { + if (this.response.ReturnToParametersSignatureValidated) { + return this.GetUntrustedCallbackArgument(key); + } else { + Logger.OpenId.WarnFormat(OpenIdStrings.CallbackArgumentsRequireSecretStore, typeof(IRelyingPartyAssociationStore).Name, typeof(OpenIdRelyingParty).Name); + return null; + } + } + + /// <summary> + /// Gets a callback argument's value that was previously added using + /// <see cref="IAuthenticationRequest.AddCallbackArguments(string, string)"/>. + /// </summary> + /// <param name="key">The name of the parameter whose value is sought.</param> + /// <returns> + /// The value of the argument, or null if the named parameter could not be found. + /// </returns> + /// <remarks> + /// Callback parameters are only available even if the RP is in stateless mode, + /// or the callback parameters are otherwise unverifiable as untampered with. + /// Therefore, use this method only when the callback argument is not to be + /// used to make a security-sensitive decision. + /// </remarks> + public string GetUntrustedCallbackArgument(string key) { + return this.response.GetReturnToArgument(key); + } + + /// <summary> + /// Gets all the callback arguments that were previously added using + /// <see cref="IAuthenticationRequest.AddCallbackArguments(string, string)"/> or as a natural part + /// of the return_to URL. + /// </summary> + /// <returns>A name-value dictionary. Never null.</returns> + /// <remarks> + /// Callback parameters are only available if they are complete and untampered with + /// since the original request message (as proven by a signature). + /// If the relying party is operating in stateless mode an empty dictionary is always + /// returned since the callback arguments could not be signed to protect against + /// tampering. + /// </remarks> + public IDictionary<string, string> GetCallbackArguments() { + if (this.response.ReturnToParametersSignatureValidated) { + return this.GetUntrustedCallbackArguments(); + } else { + Logger.OpenId.WarnFormat(OpenIdStrings.CallbackArgumentsRequireSecretStore, typeof(IRelyingPartyAssociationStore).Name, typeof(OpenIdRelyingParty).Name); + return EmptyDictionary<string, string>.Instance; + } + } + + /// <summary> + /// Gets all the callback arguments that were previously added using + /// <see cref="IAuthenticationRequest.AddCallbackArguments(string, string)"/> or as a natural part + /// of the return_to URL. + /// </summary> + /// <returns>A name-value dictionary. Never null.</returns> + /// <remarks> + /// Callback parameters are only available if they are complete and untampered with + /// since the original request message (as proven by a signature). + /// If the relying party is operating in stateless mode an empty dictionary is always + /// returned since the callback arguments could not be signed to protect against + /// tampering. + /// </remarks> + public IDictionary<string, string> GetUntrustedCallbackArguments() { + var args = new Dictionary<string, string>(); + + // Return all the return_to arguments, except for the OpenID-supporting ones. + // The only arguments that should be returned here are the ones that the host + // web site adds explicitly. + foreach (string key in this.response.GetReturnToParameterNames().Where(key => !OpenIdRelyingParty.IsOpenIdSupportingParameter(key))) { + args[key] = this.response.GetReturnToArgument(key); + } + + return args; + } + + /// <summary> + /// Tries to get an OpenID extension that may be present in the response. + /// </summary> + /// <typeparam name="T">The type of extension to look for in the response message.</typeparam> + /// <returns> + /// The extension, if it is found. Null otherwise. + /// </returns> + /// <remarks> + /// <para>Extensions are returned only if the Provider signed them. + /// Relying parties that do not care if the values were modified in + /// transit should use the <see cref="GetUntrustedExtension<T>"/> method + /// in order to allow the Provider to not sign the extension. </para> + /// <para>Unsigned extensions are completely unreliable and should be + /// used only to prefill user forms since the user or any other third + /// party may have tampered with the data carried by the extension.</para> + /// <para>Signed extensions are only reliable if the relying party + /// trusts the OpenID Provider that signed them. Signing does not mean + /// the relying party can trust the values -- it only means that the values + /// have not been tampered with since the Provider sent the message.</para> + /// </remarks> + public T GetExtension<T>() where T : IOpenIdMessageExtension { + return this.response.SignedExtensions.OfType<T>().FirstOrDefault(); + } + + /// <summary> + /// Tries to get an OpenID extension that may be present in the response. + /// </summary> + /// <param name="extensionType">Type of the extension to look for in the response.</param> + /// <returns> + /// The extension, if it is found. Null otherwise. + /// </returns> + /// <remarks> + /// <para>Extensions are returned only if the Provider signed them. + /// Relying parties that do not care if the values were modified in + /// transit should use the <see cref="GetUntrustedExtension"/> method + /// in order to allow the Provider to not sign the extension. </para> + /// <para>Unsigned extensions are completely unreliable and should be + /// used only to prefill user forms since the user or any other third + /// party may have tampered with the data carried by the extension.</para> + /// <para>Signed extensions are only reliable if the relying party + /// trusts the OpenID Provider that signed them. Signing does not mean + /// the relying party can trust the values -- it only means that the values + /// have not been tampered with since the Provider sent the message.</para> + /// </remarks> + public IOpenIdMessageExtension GetExtension(Type extensionType) { + return this.response.SignedExtensions.OfType<IOpenIdMessageExtension>().Where(ext => extensionType.IsInstanceOfType(ext)).FirstOrDefault(); + } + + /// <summary> + /// Tries to get an OpenID extension that may be present in the response, without + /// requiring it to be signed by the Provider. + /// </summary> + /// <typeparam name="T">The type of extension to look for in the response message.</typeparam> + /// <returns> + /// The extension, if it is found. Null otherwise. + /// </returns> + /// <remarks> + /// <para>Extensions are returned whether they are signed or not. + /// Use the <see cref="GetExtension<T>"/> method to retrieve + /// extension responses only if they are signed by the Provider to + /// protect against tampering. </para> + /// <para>Unsigned extensions are completely unreliable and should be + /// used only to prefill user forms since the user or any other third + /// party may have tampered with the data carried by the extension.</para> + /// <para>Signed extensions are only reliable if the relying party + /// trusts the OpenID Provider that signed them. Signing does not mean + /// the relying party can trust the values -- it only means that the values + /// have not been tampered with since the Provider sent the message.</para> + /// </remarks> + public T GetUntrustedExtension<T>() where T : IOpenIdMessageExtension { + return this.response.Extensions.OfType<T>().FirstOrDefault(); + } + + /// <summary> + /// Tries to get an OpenID extension that may be present in the response. + /// </summary> + /// <param name="extensionType">Type of the extension to look for in the response.</param> + /// <returns> + /// The extension, if it is found. Null otherwise. + /// </returns> + /// <remarks> + /// <para>Extensions are returned whether they are signed or not. + /// Use the <see cref="GetExtension"/> method to retrieve + /// extension responses only if they are signed by the Provider to + /// protect against tampering. </para> + /// <para>Unsigned extensions are completely unreliable and should be + /// used only to prefill user forms since the user or any other third + /// party may have tampered with the data carried by the extension.</para> + /// <para>Signed extensions are only reliable if the relying party + /// trusts the OpenID Provider that signed them. Signing does not mean + /// the relying party can trust the values -- it only means that the values + /// have not been tampered with since the Provider sent the message.</para> + /// </remarks> + public IOpenIdMessageExtension GetUntrustedExtension(Type extensionType) { + return this.response.Extensions.OfType<IOpenIdMessageExtension>().Where(ext => extensionType.IsInstanceOfType(ext)).FirstOrDefault(); + } + + #endregion + } +} diff --git a/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/PositiveAuthenticationResponse.cs b/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/PositiveAuthenticationResponse.cs new file mode 100644 index 0000000..1123912 --- /dev/null +++ b/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/PositiveAuthenticationResponse.cs @@ -0,0 +1,174 @@ +//----------------------------------------------------------------------- +// <copyright file="PositiveAuthenticationResponse.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.RelyingParty { + using System; + using System.Diagnostics; + using System.Diagnostics.Contracts; + using System.Linq; + using System.Web; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OpenId.Messages; + + /// <summary> + /// Wraps a positive assertion response in an <see cref="IAuthenticationResponse"/> instance + /// for public consumption by the host web site. + /// </summary> + [DebuggerDisplay("Status: {Status}, ClaimedIdentifier: {ClaimedIdentifier}")] + internal class PositiveAuthenticationResponse : PositiveAnonymousResponse { + /// <summary> + /// Initializes a new instance of the <see cref="PositiveAuthenticationResponse"/> class. + /// </summary> + /// <param name="response">The positive assertion response that was just received by the Relying Party.</param> + /// <param name="relyingParty">The relying party.</param> + internal PositiveAuthenticationResponse(PositiveAssertionResponse response, OpenIdRelyingParty relyingParty) + : base(response) { + Requires.NotNull(relyingParty, "relyingParty"); + + this.Endpoint = IdentifierDiscoveryResult.CreateForClaimedIdentifier( + this.Response.ClaimedIdentifier, + this.Response.GetReturnToArgument(AuthenticationRequest.UserSuppliedIdentifierParameterName), + this.Response.LocalIdentifier, + new ProviderEndpointDescription(this.Response.ProviderEndpoint, this.Response.Version), + null, + null); + + this.VerifyDiscoveryMatchesAssertion(relyingParty); + + Logger.OpenId.InfoFormat("Received identity assertion for {0} via {1}.", this.Response.ClaimedIdentifier, this.Provider.Uri); + } + + #region IAuthenticationResponse Properties + + /// <summary> + /// Gets the Identifier that the end user claims to own. For use with user database storage and lookup. + /// May be null for some failed authentications (i.e. failed directed identity authentications). + /// </summary> + /// <value></value> + /// <remarks> + /// <para> + /// This is the secure identifier that should be used for database storage and lookup. + /// It is not always friendly (i.e. =Arnott becomes =!9B72.7DD1.50A9.5CCD), but it protects + /// user identities against spoofing and other attacks. + /// </para> + /// <para> + /// For user-friendly identifiers to display, use the + /// <see cref="FriendlyIdentifierForDisplay"/> property. + /// </para> + /// </remarks> + public override Identifier ClaimedIdentifier { + get { return this.Endpoint.ClaimedIdentifier; } + } + + /// <summary> + /// Gets a user-friendly OpenID Identifier for display purposes ONLY. + /// </summary> + /// <remarks> + /// <para> + /// This <i>should</i> be put through <see cref="HttpUtility.HtmlEncode(string)"/> before + /// sending to a browser to secure against javascript injection attacks. + /// </para> + /// <para> + /// This property retains some aspects of the user-supplied identifier that get lost + /// in the <see cref="ClaimedIdentifier"/>. For example, XRIs used as user-supplied + /// identifiers (i.e. =Arnott) become unfriendly unique strings (i.e. =!9B72.7DD1.50A9.5CCD). + /// For display purposes, such as text on a web page that says "You're logged in as ...", + /// this property serves to provide the =Arnott string, or whatever else is the most friendly + /// string close to what the user originally typed in. + /// </para> + /// <para> + /// If the user-supplied identifier is a URI, this property will be the URI after all + /// redirects, and with the protocol and fragment trimmed off. + /// If the user-supplied identifier is an XRI, this property will be the original XRI. + /// If the user-supplied identifier is an OpenID Provider identifier (i.e. yahoo.com), + /// this property will be the Claimed Identifier, with the protocol stripped if it is a URI. + /// </para> + /// <para> + /// It is <b>very</b> important that this property <i>never</i> be used for database storage + /// or lookup to avoid identity spoofing and other security risks. For database storage + /// and lookup please use the <see cref="ClaimedIdentifier"/> property. + /// </para> + /// </remarks> + public override string FriendlyIdentifierForDisplay { + get { return this.Endpoint.FriendlyIdentifierForDisplay; } + } + + /// <summary> + /// Gets the detailed success or failure status of the authentication attempt. + /// </summary> + public override AuthenticationStatus Status { + get { return AuthenticationStatus.Authenticated; } + } + + #endregion + + /// <summary> + /// Gets the OpenID service endpoint reconstructed from the assertion message. + /// </summary> + /// <remarks> + /// This information is straight from the Provider, and therefore must not + /// be trusted until verified as matching the discovery information for + /// the claimed identifier to avoid a Provider asserting an Identifier + /// for which it has no authority. + /// </remarks> + internal IdentifierDiscoveryResult Endpoint { get; private set; } + + /// <summary> + /// Gets the positive assertion response message. + /// </summary> + protected internal new PositiveAssertionResponse Response { + get { return (PositiveAssertionResponse)base.Response; } + } + + /// <summary> + /// Verifies that the positive assertion data matches the results of + /// discovery on the Claimed Identifier. + /// </summary> + /// <param name="relyingParty">The relying party.</param> + /// <exception cref="ProtocolException"> + /// Thrown when the Provider is asserting that a user controls an Identifier + /// when discovery on that Identifier contradicts what the Provider says. + /// This would be an indication of either a misconfigured Provider or + /// an attempt by someone to spoof another user's identity with a rogue Provider. + /// </exception> + private void VerifyDiscoveryMatchesAssertion(OpenIdRelyingParty relyingParty) { + Logger.OpenId.Debug("Verifying assertion matches identifier discovery results..."); + + // Ensure that we abide by the RP's rules regarding RequireSsl for this discovery step. + Identifier claimedId = this.Response.ClaimedIdentifier; + if (relyingParty.SecuritySettings.RequireSsl) { + if (!claimedId.TryRequireSsl(out claimedId)) { + Logger.OpenId.ErrorFormat("This site is configured to accept only SSL-protected OpenIDs, but {0} was asserted and must be rejected.", this.Response.ClaimedIdentifier); + ErrorUtilities.ThrowProtocol(OpenIdStrings.RequireSslNotSatisfiedByAssertedClaimedId, this.Response.ClaimedIdentifier); + } + } + + // Check whether this particular identifier presents a problem with HTTP discovery + // due to limitations in the .NET Uri class. + UriIdentifier claimedIdUri = claimedId as UriIdentifier; + if (claimedIdUri != null && claimedIdUri.ProblematicNormalization) { + ErrorUtilities.VerifyProtocol(relyingParty.SecuritySettings.AllowApproximateIdentifierDiscovery, OpenIdStrings.ClaimedIdentifierDefiesDotNetNormalization); + Logger.OpenId.WarnFormat("Positive assertion for claimed identifier {0} cannot be precisely verified under partial trust hosting due to .NET limitation. An approximate verification will be attempted.", claimedId); + } + + // While it LOOKS like we're performing discovery over HTTP again + // Yadis.IdentifierDiscoveryCachePolicy is set to HttpRequestCacheLevel.CacheIfAvailable + // which means that the .NET runtime is caching our discoveries for us. This turns out + // to be very fast and keeps our code clean and easily verifiable as correct and secure. + // CAUTION: if this discovery is ever made to be skipped based on previous discovery + // data that was saved to the return_to URL, be careful to verify that that information + // is signed by the RP before it's considered reliable. In 1.x stateless mode, this RP + // doesn't (and can't) sign its own return_to URL, so its cached discovery information + // is merely a hint that must be verified by performing discovery again here. + var discoveryResults = relyingParty.Discover(claimedId); + ErrorUtilities.VerifyProtocol( + discoveryResults.Contains(this.Endpoint), + OpenIdStrings.IssuedAssertionFailsIdentifierDiscovery, + this.Endpoint, + discoveryResults.ToStringDeferred(true)); + } + } +} diff --git a/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/PositiveAuthenticationResponseSnapshot.cs b/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/PositiveAuthenticationResponseSnapshot.cs new file mode 100644 index 0000000..141b4f7 --- /dev/null +++ b/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/PositiveAuthenticationResponseSnapshot.cs @@ -0,0 +1,304 @@ +//----------------------------------------------------------------------- +// <copyright file="PositiveAuthenticationResponseSnapshot.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.RelyingParty { + using System; + using System.Collections.Generic; + using System.Diagnostics.Contracts; + using System.Text; + using System.Web; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OpenId.Messages; + + /// <summary> + /// A serializable snapshot of a verified authentication message. + /// </summary> + [Serializable] + internal class PositiveAuthenticationResponseSnapshot : IAuthenticationResponse { + /// <summary> + /// The callback arguments that came with the authentication response. + /// </summary> + private IDictionary<string, string> callbackArguments; + + /// <summary> + /// The untrusted callback arguments that came with the authentication response. + /// </summary> + private IDictionary<string, string> untrustedCallbackArguments; + + /// <summary> + /// Initializes a new instance of the <see cref="PositiveAuthenticationResponseSnapshot"/> class. + /// </summary> + /// <param name="copyFrom">The authentication response to copy from.</param> + internal PositiveAuthenticationResponseSnapshot(IAuthenticationResponse copyFrom) { + Requires.NotNull(copyFrom, "copyFrom"); + + this.ClaimedIdentifier = copyFrom.ClaimedIdentifier; + this.FriendlyIdentifierForDisplay = copyFrom.FriendlyIdentifierForDisplay; + this.Status = copyFrom.Status; + this.Provider = copyFrom.Provider; + this.untrustedCallbackArguments = copyFrom.GetUntrustedCallbackArguments(); + + // Do this special check to avoid logging a warning for trying to clone a dictionary. + var anonResponse = copyFrom as PositiveAnonymousResponse; + if (anonResponse == null || anonResponse.TrustedCallbackArgumentsAvailable) { + this.callbackArguments = copyFrom.GetCallbackArguments(); + } else { + this.callbackArguments = EmptyDictionary<string, string>.Instance; + } + } + + #region IAuthenticationResponse Members + + /// <summary> + /// Gets the Identifier that the end user claims to own. For use with user database storage and lookup. + /// May be null for some failed authentications (i.e. failed directed identity authentications). + /// </summary> + /// <value></value> + /// <remarks> + /// <para> + /// This is the secure identifier that should be used for database storage and lookup. + /// It is not always friendly (i.e. =Arnott becomes =!9B72.7DD1.50A9.5CCD), but it protects + /// user identities against spoofing and other attacks. + /// </para> + /// <para> + /// For user-friendly identifiers to display, use the + /// <see cref="FriendlyIdentifierForDisplay"/> property. + /// </para> + /// </remarks> + public Identifier ClaimedIdentifier { get; private set; } + + /// <summary> + /// Gets a user-friendly OpenID Identifier for display purposes ONLY. + /// </summary> + /// <value></value> + /// <remarks> + /// <para> + /// This <i>should</i> be put through <see cref="HttpUtility.HtmlEncode(string)"/> before + /// sending to a browser to secure against javascript injection attacks. + /// </para> + /// <para> + /// This property retains some aspects of the user-supplied identifier that get lost + /// in the <see cref="ClaimedIdentifier"/>. For example, XRIs used as user-supplied + /// identifiers (i.e. =Arnott) become unfriendly unique strings (i.e. =!9B72.7DD1.50A9.5CCD). + /// For display purposes, such as text on a web page that says "You're logged in as ...", + /// this property serves to provide the =Arnott string, or whatever else is the most friendly + /// string close to what the user originally typed in. + /// </para> + /// <para> + /// If the user-supplied identifier is a URI, this property will be the URI after all + /// redirects, and with the protocol and fragment trimmed off. + /// If the user-supplied identifier is an XRI, this property will be the original XRI. + /// If the user-supplied identifier is an OpenID Provider identifier (i.e. yahoo.com), + /// this property will be the Claimed Identifier, with the protocol stripped if it is a URI. + /// </para> + /// <para> + /// It is <b>very</b> important that this property <i>never</i> be used for database storage + /// or lookup to avoid identity spoofing and other security risks. For database storage + /// and lookup please use the <see cref="ClaimedIdentifier"/> property. + /// </para> + /// </remarks> + public string FriendlyIdentifierForDisplay { get; private set; } + + /// <summary> + /// Gets the detailed success or failure status of the authentication attempt. + /// </summary> + /// <value></value> + public AuthenticationStatus Status { get; private set; } + + /// <summary> + /// Gets information about the OpenId Provider, as advertised by the + /// OpenID discovery documents found at the <see cref="ClaimedIdentifier"/> + /// location. + /// </summary> + /// <value> + /// The Provider endpoint that issued the positive assertion; + /// or <c>null</c> if information about the Provider is unavailable. + /// </value> + public IProviderEndpoint Provider { get; private set; } + + /// <summary> + /// Gets the details regarding a failed authentication attempt, if available. + /// This will be set if and only if <see cref="Status"/> is <see cref="AuthenticationStatus.Failed"/>. + /// </summary> + /// <value></value> + public Exception Exception { + get { throw new NotSupportedException(OpenIdStrings.NotSupportedByAuthenticationSnapshot); } + } + + /// <summary> + /// Tries to get an OpenID extension that may be present in the response. + /// </summary> + /// <typeparam name="T">The type of extension to look for in the response message.</typeparam> + /// <returns> + /// The extension, if it is found. Null otherwise. + /// </returns> + /// <remarks> + /// <para>Extensions are returned only if the Provider signed them. + /// Relying parties that do not care if the values were modified in + /// transit should use the <see cref="GetUntrustedExtension<T>"/> method + /// in order to allow the Provider to not sign the extension. </para> + /// <para>Unsigned extensions are completely unreliable and should be + /// used only to prefill user forms since the user or any other third + /// party may have tampered with the data carried by the extension.</para> + /// <para>Signed extensions are only reliable if the relying party + /// trusts the OpenID Provider that signed them. Signing does not mean + /// the relying party can trust the values -- it only means that the values + /// have not been tampered with since the Provider sent the message.</para> + /// </remarks> + public T GetExtension<T>() where T : IOpenIdMessageExtension { + throw new NotSupportedException(OpenIdStrings.NotSupportedByAuthenticationSnapshot); + } + + /// <summary> + /// Tries to get an OpenID extension that may be present in the response. + /// </summary> + /// <param name="extensionType">Type of the extension to look for in the response.</param> + /// <returns> + /// The extension, if it is found. Null otherwise. + /// </returns> + /// <remarks> + /// <para>Extensions are returned only if the Provider signed them. + /// Relying parties that do not care if the values were modified in + /// transit should use the <see cref="GetUntrustedExtension"/> method + /// in order to allow the Provider to not sign the extension. </para> + /// <para>Unsigned extensions are completely unreliable and should be + /// used only to prefill user forms since the user or any other third + /// party may have tampered with the data carried by the extension.</para> + /// <para>Signed extensions are only reliable if the relying party + /// trusts the OpenID Provider that signed them. Signing does not mean + /// the relying party can trust the values -- it only means that the values + /// have not been tampered with since the Provider sent the message.</para> + /// </remarks> + public IOpenIdMessageExtension GetExtension(Type extensionType) { + throw new NotSupportedException(OpenIdStrings.NotSupportedByAuthenticationSnapshot); + } + + /// <summary> + /// Tries to get an OpenID extension that may be present in the response, without + /// requiring it to be signed by the Provider. + /// </summary> + /// <typeparam name="T">The type of extension to look for in the response message.</typeparam> + /// <returns> + /// The extension, if it is found. Null otherwise. + /// </returns> + /// <remarks> + /// <para>Extensions are returned whether they are signed or not. + /// Use the <see cref="GetExtension<T>"/> method to retrieve + /// extension responses only if they are signed by the Provider to + /// protect against tampering. </para> + /// <para>Unsigned extensions are completely unreliable and should be + /// used only to prefill user forms since the user or any other third + /// party may have tampered with the data carried by the extension.</para> + /// <para>Signed extensions are only reliable if the relying party + /// trusts the OpenID Provider that signed them. Signing does not mean + /// the relying party can trust the values -- it only means that the values + /// have not been tampered with since the Provider sent the message.</para> + /// </remarks> + public T GetUntrustedExtension<T>() where T : IOpenIdMessageExtension { + throw new NotSupportedException(OpenIdStrings.NotSupportedByAuthenticationSnapshot); + } + + /// <summary> + /// Tries to get an OpenID extension that may be present in the response. + /// </summary> + /// <param name="extensionType">Type of the extension to look for in the response.</param> + /// <returns> + /// The extension, if it is found. Null otherwise. + /// </returns> + /// <remarks> + /// <para>Extensions are returned whether they are signed or not. + /// Use the <see cref="GetExtension"/> method to retrieve + /// extension responses only if they are signed by the Provider to + /// protect against tampering. </para> + /// <para>Unsigned extensions are completely unreliable and should be + /// used only to prefill user forms since the user or any other third + /// party may have tampered with the data carried by the extension.</para> + /// <para>Signed extensions are only reliable if the relying party + /// trusts the OpenID Provider that signed them. Signing does not mean + /// the relying party can trust the values -- it only means that the values + /// have not been tampered with since the Provider sent the message.</para> + /// </remarks> + public IOpenIdMessageExtension GetUntrustedExtension(Type extensionType) { + throw new NotSupportedException(OpenIdStrings.NotSupportedByAuthenticationSnapshot); + } + + /// <summary> + /// Gets all the callback arguments that were previously added using + /// <see cref="IAuthenticationRequest.AddCallbackArguments(string, string)"/> or as a natural part + /// of the return_to URL. + /// </summary> + /// <returns>A name-value dictionary. Never null.</returns> + /// <remarks> + /// <para>This MAY return any argument on the querystring that came with the authentication response, + /// which may include parameters not explicitly added using + /// <see cref="IAuthenticationRequest.AddCallbackArguments(string, string)"/>.</para> + /// <para>Note that these values are NOT protected against tampering in transit.</para> + /// </remarks> + public IDictionary<string, string> GetCallbackArguments() { + // Return a copy so that the caller cannot change the contents. + return new Dictionary<string, string>(this.callbackArguments); + } + + /// <summary> + /// Gets all the callback arguments that were previously added using + /// <see cref="IAuthenticationRequest.AddCallbackArguments(string, string)"/> or as a natural part + /// of the return_to URL. + /// </summary> + /// <returns>A name-value dictionary. Never null.</returns> + /// <remarks> + /// Callback parameters are only available even if the RP is in stateless mode, + /// or the callback parameters are otherwise unverifiable as untampered with. + /// Therefore, use this method only when the callback argument is not to be + /// used to make a security-sensitive decision. + /// </remarks> + public IDictionary<string, string> GetUntrustedCallbackArguments() { + // Return a copy so that the caller cannot change the contents. + return new Dictionary<string, string>(this.untrustedCallbackArguments); + } + + /// <summary> + /// Gets a callback argument's value that was previously added using + /// <see cref="IAuthenticationRequest.AddCallbackArguments(string, string)"/>. + /// </summary> + /// <param name="key">The name of the parameter whose value is sought.</param> + /// <returns> + /// The value of the argument, or null if the named parameter could not be found. + /// </returns> + /// <remarks> + /// <para>This may return any argument on the querystring that came with the authentication response, + /// which may include parameters not explicitly added using + /// <see cref="IAuthenticationRequest.AddCallbackArguments(string, string)"/>.</para> + /// <para>Note that these values are NOT protected against tampering in transit.</para> + /// </remarks> + public string GetCallbackArgument(string key) { + string value; + this.callbackArguments.TryGetValue(key, out value); + return value; + } + + /// <summary> + /// Gets a callback argument's value that was previously added using + /// <see cref="IAuthenticationRequest.AddCallbackArguments(string, string)"/>. + /// </summary> + /// <param name="key">The name of the parameter whose value is sought.</param> + /// <returns> + /// The value of the argument, or null if the named parameter could not be found. + /// </returns> + /// <remarks> + /// Callback parameters are only available even if the RP is in stateless mode, + /// or the callback parameters are otherwise unverifiable as untampered with. + /// Therefore, use this method only when the callback argument is not to be + /// used to make a security-sensitive decision. + /// </remarks> + public string GetUntrustedCallbackArgument(string key) { + string value; + this.untrustedCallbackArguments.TryGetValue(key, out value); + return value; + } + + #endregion + } +} diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/SimpleXrdsProviderEndpoint.cs b/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/SimpleXrdsProviderEndpoint.cs index 678f69a..678f69a 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/SimpleXrdsProviderEndpoint.cs +++ b/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/SimpleXrdsProviderEndpoint.cs diff --git a/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/StandardRelyingPartyApplicationStore.cs b/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/StandardRelyingPartyApplicationStore.cs new file mode 100644 index 0000000..a14b55d --- /dev/null +++ b/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/StandardRelyingPartyApplicationStore.cs @@ -0,0 +1,110 @@ +//----------------------------------------------------------------------- +// <copyright file="StandardRelyingPartyApplicationStore.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.RelyingParty { + using System; + using System.Collections.Generic; + using DotNetOpenAuth.Configuration; + using DotNetOpenAuth.Messaging.Bindings; + using DotNetOpenAuth.OpenId.ChannelElements; + + /// <summary> + /// An in-memory store for Relying Parties, suitable for single server, single process + /// ASP.NET web sites. + /// </summary> + public class StandardRelyingPartyApplicationStore : IOpenIdApplicationStore { + /// <summary> + /// The nonce store to use. + /// </summary> + private readonly INonceStore nonceStore; + + /// <summary> + /// The association store to use. + /// </summary> + private readonly ICryptoKeyStore keyStore; + + /// <summary> + /// Initializes a new instance of the <see cref="StandardRelyingPartyApplicationStore"/> class. + /// </summary> + public StandardRelyingPartyApplicationStore() { + this.nonceStore = new NonceMemoryStore(OpenIdElement.Configuration.MaxAuthenticationTime); + this.keyStore = new MemoryCryptoKeyStore(); + } + + #region ICryptoKeyStore Members + + /// <summary> + /// Gets the key in a given bucket and handle. + /// </summary> + /// <param name="bucket">The bucket name. Case sensitive.</param> + /// <param name="handle">The key handle. Case sensitive.</param> + /// <returns> + /// The cryptographic key, or <c>null</c> if no matching key was found. + /// </returns> + public CryptoKey GetKey(string bucket, string handle) { + return this.keyStore.GetKey(bucket, handle); + } + + /// <summary> + /// Gets a sequence of existing keys within a given bucket. + /// </summary> + /// <param name="bucket">The bucket name. Case sensitive.</param> + /// <returns> + /// A sequence of handles and keys, ordered by descending <see cref="CryptoKey.ExpiresUtc"/>. + /// </returns> + public IEnumerable<KeyValuePair<string, CryptoKey>> GetKeys(string bucket) { + return this.keyStore.GetKeys(bucket); + } + + /// <summary> + /// Stores a cryptographic key. + /// </summary> + /// <param name="bucket">The name of the bucket to store the key in. Case sensitive.</param> + /// <param name="handle">The handle to the key, unique within the bucket. Case sensitive.</param> + /// <param name="key">The key to store.</param> + /// <exception cref="CryptoKeyCollisionException">Thrown in the event of a conflict with an existing key in the same bucket and with the same handle.</exception> + public void StoreKey(string bucket, string handle, CryptoKey key) { + this.keyStore.StoreKey(bucket, handle, key); + } + + /// <summary> + /// Removes the key. + /// </summary> + /// <param name="bucket">The bucket name. Case sensitive.</param> + /// <param name="handle">The key handle. Case sensitive.</param> + public void RemoveKey(string bucket, string handle) { + this.keyStore.RemoveKey(bucket, handle); + } + + #endregion + + #region INonceStore Members + + /// <summary> + /// Stores a given nonce and timestamp. + /// </summary> + /// <param name="context">The context, or namespace, within which the <paramref name="nonce"/> must be unique.</param> + /// <param name="nonce">A series of random characters.</param> + /// <param name="timestampUtc">The timestamp that together with the nonce string make it unique. + /// The timestamp may also be used by the data store to clear out old nonces.</param> + /// <returns> + /// True if the nonce+timestamp (combination) was not previously in the database. + /// False if the nonce was stored previously with the same timestamp. + /// </returns> + /// <remarks> + /// The nonce must be stored for no less than the maximum time window a message may + /// be processed within before being discarded as an expired message. + /// If the binding element is applicable to your channel, this expiration window + /// is retrieved or set using the + /// <see cref="StandardExpirationBindingElement.MaximumMessageAge"/> property. + /// </remarks> + public bool StoreNonce(string context, string nonce, DateTime timestampUtc) { + return this.nonceStore.StoreNonce(context, nonce, timestampUtc); + } + + #endregion + } +} diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/WellKnownProviders.cs b/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/WellKnownProviders.cs index ad1a11a..ad1a11a 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/WellKnownProviders.cs +++ b/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/WellKnownProviders.cs diff --git a/src/DotNetOpenAuth.OpenId.RelyingParty/Properties/AssemblyInfo.cs b/src/DotNetOpenAuth.OpenId.RelyingParty/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..e425143 --- /dev/null +++ b/src/DotNetOpenAuth.OpenId.RelyingParty/Properties/AssemblyInfo.cs @@ -0,0 +1,58 @@ +//----------------------------------------------------------------------- +// <copyright file="AssemblyInfo.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +// We DON'T put an AssemblyVersionAttribute in here because it is generated in the build. + +using System; +using System.Diagnostics.Contracts; +using System.Net; +using System.Reflection; +using System.Resources; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Security; +using System.Security.Permissions; +using System.Web.UI; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("DotNetOpenAuth OpenID")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("DotNetOpenAuth")] +[assembly: AssemblyCopyright("Copyright © 2008")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: NeutralResourcesLanguage("en-US")] +[assembly: CLSCompliant(true)] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("7d73990c-47c0-4256-9f20-a893add9e289")] + +[assembly: ContractVerification(true)] + +#if StrongNameSigned +// See comment at top of this file. We need this so that strong-naming doesn't +// keep this assembly from being useful to shared host (medium trust) web sites. +[assembly: AllowPartiallyTrustedCallers] + +[assembly: InternalsVisibleTo("DotNetOpenAuth.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100AD093C3765257C89A7010E853F2C7C741FF92FA8ACE06D7B8254702CAD5CF99104447F63AB05F8BB6F51CE0D81C8C93D2FCE8C20AAFF7042E721CBA16EAAE98778611DED11C0ABC8900DC5667F99B50A9DADEC24DBD8F2C91E3E8AD300EF64F1B4B9536CEB16FB440AF939F57624A9B486F867807C649AE4830EAB88C6C03998")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.OpenId.Provider, PublicKey=0024000004800000940000000602000000240000525341310004000001000100AD093C3765257C89A7010E853F2C7C741FF92FA8ACE06D7B8254702CAD5CF99104447F63AB05F8BB6F51CE0D81C8C93D2FCE8C20AAFF7042E721CBA16EAAE98778611DED11C0ABC8900DC5667F99B50A9DADEC24DBD8F2C91E3E8AD300EF64F1B4B9536CEB16FB440AF939F57624A9B486F867807C649AE4830EAB88C6C03998")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.OpenId.RelyingParty.UI, PublicKey=0024000004800000940000000602000000240000525341310004000001000100AD093C3765257C89A7010E853F2C7C741FF92FA8ACE06D7B8254702CAD5CF99104447F63AB05F8BB6F51CE0D81C8C93D2FCE8C20AAFF7042E721CBA16EAAE98778611DED11C0ABC8900DC5667F99B50A9DADEC24DBD8F2C91E3E8AD300EF64F1B4B9536CEB16FB440AF939F57624A9B486F867807C649AE4830EAB88C6C03998")] +[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")] +#else +[assembly: InternalsVisibleTo("DotNetOpenAuth.Test")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.OpenId.Provider")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.OpenId.RelyingParty.UI")] +[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] +#endif diff --git a/src/DotNetOpenAuth.OpenId.UI/DotNetOpenAuth.OpenId.UI.csproj b/src/DotNetOpenAuth.OpenId.UI/DotNetOpenAuth.OpenId.UI.csproj new file mode 100644 index 0000000..f2a1cf5 --- /dev/null +++ b/src/DotNetOpenAuth.OpenId.UI/DotNetOpenAuth.OpenId.UI.csproj @@ -0,0 +1,43 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), EnlistmentInfo.props))\EnlistmentInfo.props" Condition=" '$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), EnlistmentInfo.props))' != '' " /> + <PropertyGroup> + <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> + <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> + </PropertyGroup> + <Import Project="$(ProjectRoot)tools\DotNetOpenAuth.props" /> + <PropertyGroup> + <SchemaVersion>2.0</SchemaVersion> + <ProjectGuid>{75E13AAE-7D51-4421-ABFD-3F3DC91F576E}</ProjectGuid> + <OutputType>Library</OutputType> + <AppDesignerFolder>Properties</AppDesignerFolder> + <RootNamespace>DotNetOpenAuth</RootNamespace> + <AssemblyName>DotNetOpenAuth.OpenId.UI</AssemblyName> + </PropertyGroup> + <Import Project="$(ProjectRoot)tools\DotNetOpenAuth.Product.props" /> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> + </PropertyGroup> + <ItemGroup> + <Compile Include="Properties\AssemblyInfo.cs" /> + <Compile Include="XrdsPublisher.cs" /> + </ItemGroup> + <ItemGroup> + <ProjectReference Include="..\DotNetOpenAuth.Core\DotNetOpenAuth.Core.csproj"> + <Project>{60426312-6AE5-4835-8667-37EDEA670222}</Project> + <Name>DotNetOpenAuth.Core</Name> + </ProjectReference> + <ProjectReference Include="..\DotNetOpenAuth.OpenId\DotNetOpenAuth.OpenId.csproj"> + <Project>{3896A32A-E876-4C23-B9B8-78E17D134CD3}</Project> + <Name>DotNetOpenAuth.OpenId</Name> + </ProjectReference> + <ProjectReference Include="..\Org.Mentalis.Security.Cryptography\Org.Mentalis.Security.Cryptography.csproj"> + <Project>{26DC877F-5987-48DD-9DDB-E62F2DE0E150}</Project> + <Name>Org.Mentalis.Security.Cryptography</Name> + </ProjectReference> + </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))' != '' " /> +</Project>
\ No newline at end of file diff --git a/src/DotNetOpenAuth.OpenId.UI/Properties/AssemblyInfo.cs b/src/DotNetOpenAuth.OpenId.UI/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..d1eef8c --- /dev/null +++ b/src/DotNetOpenAuth.OpenId.UI/Properties/AssemblyInfo.cs @@ -0,0 +1,66 @@ +//----------------------------------------------------------------------- +// <copyright file="AssemblyInfo.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +// We DON'T put an AssemblyVersionAttribute in here because it is generated in the build. + +using System; +using System.Diagnostics.Contracts; +using System.Net; +using System.Reflection; +using System.Resources; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Security; +using System.Security.Permissions; +using System.Web.UI; + +[assembly: TagPrefix("DotNetOpenAuth.OpenId", "openid")] + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("DotNetOpenAuth OpenID")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("DotNetOpenAuth")] +[assembly: AssemblyCopyright("Copyright © 2008")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: NeutralResourcesLanguage("en-US")] +[assembly: CLSCompliant(true)] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("7d73990c-47c0-4256-9f20-a893add9e289")] + +[assembly: ContractVerification(true)] + +#if StrongNameSigned +// See comment at top of this file. We need this so that strong-naming doesn't +// keep this assembly from being useful to shared host (medium trust) web sites. +[assembly: AllowPartiallyTrustedCallers] + +[assembly: InternalsVisibleTo("DotNetOpenAuth.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100AD093C3765257C89A7010E853F2C7C741FF92FA8ACE06D7B8254702CAD5CF99104447F63AB05F8BB6F51CE0D81C8C93D2FCE8C20AAFF7042E721CBA16EAAE98778611DED11C0ABC8900DC5667F99B50A9DADEC24DBD8F2C91E3E8AD300EF64F1B4B9536CEB16FB440AF939F57624A9B486F867807C649AE4830EAB88C6C03998")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.OAuth, PublicKey=0024000004800000940000000602000000240000525341310004000001000100AD093C3765257C89A7010E853F2C7C741FF92FA8ACE06D7B8254702CAD5CF99104447F63AB05F8BB6F51CE0D81C8C93D2FCE8C20AAFF7042E721CBA16EAAE98778611DED11C0ABC8900DC5667F99B50A9DADEC24DBD8F2C91E3E8AD300EF64F1B4B9536CEB16FB440AF939F57624A9B486F867807C649AE4830EAB88C6C03998")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.OpenId.RelyingParty, PublicKey=0024000004800000940000000602000000240000525341310004000001000100AD093C3765257C89A7010E853F2C7C741FF92FA8ACE06D7B8254702CAD5CF99104447F63AB05F8BB6F51CE0D81C8C93D2FCE8C20AAFF7042E721CBA16EAAE98778611DED11C0ABC8900DC5667F99B50A9DADEC24DBD8F2C91E3E8AD300EF64F1B4B9536CEB16FB440AF939F57624A9B486F867807C649AE4830EAB88C6C03998")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.OpenId.RelyingParty.UI, PublicKey=0024000004800000940000000602000000240000525341310004000001000100AD093C3765257C89A7010E853F2C7C741FF92FA8ACE06D7B8254702CAD5CF99104447F63AB05F8BB6F51CE0D81C8C93D2FCE8C20AAFF7042E721CBA16EAAE98778611DED11C0ABC8900DC5667F99B50A9DADEC24DBD8F2C91E3E8AD300EF64F1B4B9536CEB16FB440AF939F57624A9B486F867807C649AE4830EAB88C6C03998")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.OpenId.Provider, PublicKey=0024000004800000940000000602000000240000525341310004000001000100AD093C3765257C89A7010E853F2C7C741FF92FA8ACE06D7B8254702CAD5CF99104447F63AB05F8BB6F51CE0D81C8C93D2FCE8C20AAFF7042E721CBA16EAAE98778611DED11C0ABC8900DC5667F99B50A9DADEC24DBD8F2C91E3E8AD300EF64F1B4B9536CEB16FB440AF939F57624A9B486F867807C649AE4830EAB88C6C03998")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.OpenId.Provider.UI, PublicKey=0024000004800000940000000602000000240000525341310004000001000100AD093C3765257C89A7010E853F2C7C741FF92FA8ACE06D7B8254702CAD5CF99104447F63AB05F8BB6F51CE0D81C8C93D2FCE8C20AAFF7042E721CBA16EAAE98778611DED11C0ABC8900DC5667F99B50A9DADEC24DBD8F2C91E3E8AD300EF64F1B4B9536CEB16FB440AF939F57624A9B486F867807C649AE4830EAB88C6C03998")] +[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")] +#else +[assembly: InternalsVisibleTo("DotNetOpenAuth.Test")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.OAuth")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.OpenId.RelyingParty")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.OpenId.RelyingParty.UI")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.OpenId.Provider")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.OpenId.Provider.UI")] +[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] +#endif diff --git a/src/DotNetOpenAuth/XrdsPublisher.cs b/src/DotNetOpenAuth.OpenId.UI/XrdsPublisher.cs index 03c32c1..03c32c1 100644 --- a/src/DotNetOpenAuth/XrdsPublisher.cs +++ b/src/DotNetOpenAuth.OpenId.UI/XrdsPublisher.cs diff --git a/src/DotNetOpenAuth/Configuration/AssociationTypeCollection.cs b/src/DotNetOpenAuth.OpenId/Configuration/AssociationTypeCollection.cs index 881fcdb..881fcdb 100644 --- a/src/DotNetOpenAuth/Configuration/AssociationTypeCollection.cs +++ b/src/DotNetOpenAuth.OpenId/Configuration/AssociationTypeCollection.cs diff --git a/src/DotNetOpenAuth/Configuration/AssociationTypeElement.cs b/src/DotNetOpenAuth.OpenId/Configuration/AssociationTypeElement.cs index 0eaea0e..0eaea0e 100644 --- a/src/DotNetOpenAuth/Configuration/AssociationTypeElement.cs +++ b/src/DotNetOpenAuth.OpenId/Configuration/AssociationTypeElement.cs diff --git a/src/DotNetOpenAuth.OpenId/Configuration/OpenIdElement.cs b/src/DotNetOpenAuth.OpenId/Configuration/OpenIdElement.cs new file mode 100644 index 0000000..e7b856e --- /dev/null +++ b/src/DotNetOpenAuth.OpenId/Configuration/OpenIdElement.cs @@ -0,0 +1,150 @@ +//----------------------------------------------------------------------- +// <copyright file="OpenIdElement.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Configuration { + using System; + using System.Collections.Generic; + using System.Configuration; + using System.Diagnostics.Contracts; + using DotNetOpenAuth.OpenId.ChannelElements; + using DotNetOpenAuth.OpenId.Messages; + + /// <summary> + /// Represents the <openid> element in the host's .config file. + /// </summary> + [ContractVerification(true)] + internal class OpenIdElement : ConfigurationSection { + /// <summary> + /// The name of the section under which this library's settings must be found. + /// </summary> + private const string SectionName = DotNetOpenAuthSection.SectionName + "/openid"; + + /// <summary> + /// The name of the <relyingParty> sub-element. + /// </summary> + private const string RelyingPartyElementName = "relyingParty"; + + /// <summary> + /// The name of the <provider> sub-element. + /// </summary> + private const string ProviderElementName = "provider"; + + /// <summary> + /// The name of the <extensions> sub-element. + /// </summary> + private const string ExtensionFactoriesElementName = "extensionFactories"; + + /// <summary> + /// The name of the <xriResolver> sub-element. + /// </summary> + private const string XriResolverElementName = "xriResolver"; + + /// <summary> + /// The name of the @maxAuthenticationTime attribute. + /// </summary> + private const string MaxAuthenticationTimePropertyName = "maxAuthenticationTime"; + + /// <summary> + /// The name of the @cacheDiscovery attribute. + /// </summary> + private const string CacheDiscoveryPropertyName = "cacheDiscovery"; + + /// <summary> + /// Initializes a new instance of the <see cref="OpenIdElement"/> class. + /// </summary> + internal OpenIdElement() { + } + + /// <summary> + /// Gets the configuration section from the .config file. + /// </summary> + public static OpenIdElement Configuration { + get { + Contract.Ensures(Contract.Result<OpenIdElement>() != null); + return (OpenIdElement)ConfigurationManager.GetSection(SectionName) ?? new OpenIdElement(); + } + } + + /// <summary> + /// Gets or sets the maximum time a user can take to complete authentication. + /// </summary> + /// <remarks> + /// This time limit allows the library to decide how long to cache certain values + /// necessary to complete authentication. The lower the time, the less demand on + /// the server. But too short a time can frustrate the user. + /// </remarks> + [ConfigurationProperty(MaxAuthenticationTimePropertyName, DefaultValue = "0:05")] // 5 minutes + [PositiveTimeSpanValidator] + internal TimeSpan MaxAuthenticationTime { + get { + Contract.Ensures(Contract.Result<TimeSpan>() > TimeSpan.Zero); + TimeSpan result = (TimeSpan)this[MaxAuthenticationTimePropertyName]; + Contract.Assume(result > TimeSpan.Zero); // our PositiveTimeSpanValidator should take care of this + return result; + } + + set { + Requires.InRange(value > TimeSpan.Zero, "value"); + this[MaxAuthenticationTimePropertyName] = value; + } + } + + /// <summary> + /// Gets or sets a value indicating whether the results of Identifier discovery + /// should be cached. + /// </summary> + /// <value> + /// Use <c>true</c> to allow identifier discovery to immediately return cached results when available; + /// otherwise, use <c>false</c>.to force fresh results every time at the cost of slightly slower logins. + /// The default value is <c>true</c>. + /// </value> + /// <remarks> + /// When enabled, caching is done according to HTTP standards. + /// </remarks> + [ConfigurationProperty(CacheDiscoveryPropertyName, DefaultValue = true)] + internal bool CacheDiscovery { + get { return (bool)this[CacheDiscoveryPropertyName]; } + set { this[CacheDiscoveryPropertyName] = value; } + } + + /// <summary> + /// Gets or sets the configuration specific for Relying Parties. + /// </summary> + [ConfigurationProperty(RelyingPartyElementName)] + internal OpenIdRelyingPartyElement RelyingParty { + get { return (OpenIdRelyingPartyElement)this[RelyingPartyElementName] ?? new OpenIdRelyingPartyElement(); } + set { this[RelyingPartyElementName] = value; } + } + + /// <summary> + /// Gets or sets the configuration specific for Providers. + /// </summary> + [ConfigurationProperty(ProviderElementName)] + internal OpenIdProviderElement Provider { + get { return (OpenIdProviderElement)this[ProviderElementName] ?? new OpenIdProviderElement(); } + set { this[ProviderElementName] = value; } + } + + /// <summary> + /// Gets or sets the registered OpenID extension factories. + /// </summary> + [ConfigurationProperty(ExtensionFactoriesElementName, IsDefaultCollection = false)] + [ConfigurationCollection(typeof(TypeConfigurationCollection<IOpenIdExtensionFactory>))] + internal TypeConfigurationCollection<IOpenIdExtensionFactory> ExtensionFactories { + get { return (TypeConfigurationCollection<IOpenIdExtensionFactory>)this[ExtensionFactoriesElementName] ?? new TypeConfigurationCollection<IOpenIdExtensionFactory>(); } + set { this[ExtensionFactoriesElementName] = value; } + } + + /// <summary> + /// Gets or sets the configuration for the XRI resolver. + /// </summary> + [ConfigurationProperty(XriResolverElementName)] + internal XriResolverElement XriResolver { + get { return (XriResolverElement)this[XriResolverElementName] ?? new XriResolverElement(); } + set { this[XriResolverElementName] = value; } + } + } +} diff --git a/src/DotNetOpenAuth.OpenId/Configuration/OpenIdProviderElement.cs b/src/DotNetOpenAuth.OpenId/Configuration/OpenIdProviderElement.cs new file mode 100644 index 0000000..6f5a043 --- /dev/null +++ b/src/DotNetOpenAuth.OpenId/Configuration/OpenIdProviderElement.cs @@ -0,0 +1,72 @@ +//----------------------------------------------------------------------- +// <copyright file="OpenIdProviderElement.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Configuration { + using System.Configuration; + using System.Diagnostics.Contracts; + using DotNetOpenAuth.OpenId; + using DotNetOpenAuth.OpenId.Provider; + + /// <summary> + /// The section in the .config file that allows customization of OpenID Provider behaviors. + /// </summary> + [ContractVerification(true)] + internal class OpenIdProviderElement : ConfigurationElement { + /// <summary> + /// The name of the <provider> sub-element. + /// </summary> + private const string ProviderElementName = "provider"; + + /// <summary> + /// The name of the security sub-element. + /// </summary> + private const string SecuritySettingsConfigName = "security"; + + /// <summary> + /// Gets the name of the <behaviors> sub-element. + /// </summary> + private const string BehaviorsElementName = "behaviors"; + + /// <summary> + /// The name of the custom store sub-element. + /// </summary> + private const string StoreConfigName = "store"; + + /// <summary> + /// Initializes a new instance of the <see cref="OpenIdProviderElement"/> class. + /// </summary> + public OpenIdProviderElement() { + } + + /// <summary> + /// Gets or sets the security settings. + /// </summary> + [ConfigurationProperty(SecuritySettingsConfigName)] + public OpenIdProviderSecuritySettingsElement SecuritySettings { + get { return (OpenIdProviderSecuritySettingsElement)this[SecuritySettingsConfigName] ?? new OpenIdProviderSecuritySettingsElement(); } + set { this[SecuritySettingsConfigName] = value; } + } + + /// <summary> + /// Gets or sets the special behaviors to apply. + /// </summary> + [ConfigurationProperty(BehaviorsElementName, IsDefaultCollection = false)] + [ConfigurationCollection(typeof(TypeConfigurationCollection<IProviderBehavior>))] + public TypeConfigurationCollection<IProviderBehavior> Behaviors { + get { return (TypeConfigurationCollection<IProviderBehavior>)this[BehaviorsElementName] ?? new TypeConfigurationCollection<IProviderBehavior>(); } + set { this[BehaviorsElementName] = value; } + } + + /// <summary> + /// Gets or sets the type to use for storing application state. + /// </summary> + [ConfigurationProperty(StoreConfigName)] + public TypeConfigurationElement<IOpenIdApplicationStore> ApplicationStore { + get { return (TypeConfigurationElement<IOpenIdApplicationStore>)this[StoreConfigName] ?? new TypeConfigurationElement<IOpenIdApplicationStore>(); } + set { this[StoreConfigName] = value; } + } + } +} diff --git a/src/DotNetOpenAuth/Configuration/OpenIdProviderSecuritySettingsElement.cs b/src/DotNetOpenAuth.OpenId/Configuration/OpenIdProviderSecuritySettingsElement.cs index 0d8e8b4..0d8e8b4 100644 --- a/src/DotNetOpenAuth/Configuration/OpenIdProviderSecuritySettingsElement.cs +++ b/src/DotNetOpenAuth.OpenId/Configuration/OpenIdProviderSecuritySettingsElement.cs diff --git a/src/DotNetOpenAuth.OpenId/Configuration/OpenIdRelyingPartyElement.cs b/src/DotNetOpenAuth.OpenId/Configuration/OpenIdRelyingPartyElement.cs new file mode 100644 index 0000000..c80141a --- /dev/null +++ b/src/DotNetOpenAuth.OpenId/Configuration/OpenIdRelyingPartyElement.cs @@ -0,0 +1,120 @@ +//----------------------------------------------------------------------- +// <copyright file="OpenIdRelyingPartyElement.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Configuration { + using System; + using System.Configuration; + using System.Diagnostics.Contracts; + using DotNetOpenAuth.OpenId; + using DotNetOpenAuth.OpenId.RelyingParty; + + /// <summary> + /// The section in the .config file that allows customization of OpenID Relying Party behaviors. + /// </summary> + [ContractVerification(true)] + internal class OpenIdRelyingPartyElement : ConfigurationElement { + /// <summary> + /// The name of the custom store sub-element. + /// </summary> + private const string StoreConfigName = "store"; + + /// <summary> + /// The name of the <relyingParty> sub-element. + /// </summary> + private const string RelyingPartyElementName = "relyingParty"; + + /// <summary> + /// The name of the attribute that specifies whether dnoa.userSuppliedIdentifier is tacked onto the openid.return_to URL. + /// </summary> + private const string PreserveUserSuppliedIdentifierConfigName = "preserveUserSuppliedIdentifier"; + + /// <summary> + /// Gets the name of the security sub-element. + /// </summary> + private const string SecuritySettingsConfigName = "security"; + + /// <summary> + /// The name of the <behaviors> sub-element. + /// </summary> + private const string BehaviorsElementName = "behaviors"; + + /// <summary> + /// The name of the <discoveryServices> sub-element. + /// </summary> + private const string DiscoveryServicesElementName = "discoveryServices"; + + /// <summary> + /// The built-in set of identifier discovery services. + /// </summary> + private static readonly TypeConfigurationCollection<IIdentifierDiscoveryService> defaultDiscoveryServices = new TypeConfigurationCollection<IIdentifierDiscoveryService>(new Type[] { typeof(UriDiscoveryService), typeof(XriDiscoveryProxyService) }); + + /// <summary> + /// Initializes a new instance of the <see cref="OpenIdRelyingPartyElement"/> class. + /// </summary> + public OpenIdRelyingPartyElement() { + } + + /// <summary> + /// Gets or sets a value indicating whether "dnoa.userSuppliedIdentifier" is tacked onto the openid.return_to URL in order to preserve what the user typed into the OpenID box. + /// </summary> + /// <value> + /// The default value is <c>true</c>. + /// </value> + [ConfigurationProperty(PreserveUserSuppliedIdentifierConfigName, DefaultValue = true)] + public bool PreserveUserSuppliedIdentifier { + get { return (bool)this[PreserveUserSuppliedIdentifierConfigName]; } + set { this[PreserveUserSuppliedIdentifierConfigName] = value; } + } + + /// <summary> + /// Gets or sets the security settings. + /// </summary> + [ConfigurationProperty(SecuritySettingsConfigName)] + public OpenIdRelyingPartySecuritySettingsElement SecuritySettings { + get { return (OpenIdRelyingPartySecuritySettingsElement)this[SecuritySettingsConfigName] ?? new OpenIdRelyingPartySecuritySettingsElement(); } + set { this[SecuritySettingsConfigName] = value; } + } + + /// <summary> + /// Gets or sets the special behaviors to apply. + /// </summary> + [ConfigurationProperty(BehaviorsElementName, IsDefaultCollection = false)] + [ConfigurationCollection(typeof(TypeConfigurationCollection<IRelyingPartyBehavior>))] + public TypeConfigurationCollection<IRelyingPartyBehavior> Behaviors { + get { return (TypeConfigurationCollection<IRelyingPartyBehavior>)this[BehaviorsElementName] ?? new TypeConfigurationCollection<IRelyingPartyBehavior>(); } + set { this[BehaviorsElementName] = value; } + } + + /// <summary> + /// Gets or sets the type to use for storing application state. + /// </summary> + [ConfigurationProperty(StoreConfigName)] + public TypeConfigurationElement<IOpenIdApplicationStore> ApplicationStore { + get { return (TypeConfigurationElement<IOpenIdApplicationStore>)this[StoreConfigName] ?? new TypeConfigurationElement<IOpenIdApplicationStore>(); } + set { this[StoreConfigName] = value; } + } + + /// <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(DiscoveryServicesElementName, IsDefaultCollection = false)] + [ConfigurationCollection(typeof(TypeConfigurationCollection<IIdentifierDiscoveryService>))] + internal TypeConfigurationCollection<IIdentifierDiscoveryService> DiscoveryServices { + get { + var configResult = (TypeConfigurationCollection<IIdentifierDiscoveryService>)this[DiscoveryServicesElementName]; + return configResult != null && configResult.Count > 0 ? configResult : defaultDiscoveryServices; + } + + set { + this[DiscoveryServicesElementName] = value; + } + } + } +} diff --git a/src/DotNetOpenAuth.OpenId/Configuration/OpenIdRelyingPartySecuritySettingsElement.cs b/src/DotNetOpenAuth.OpenId/Configuration/OpenIdRelyingPartySecuritySettingsElement.cs new file mode 100644 index 0000000..225b1e7 --- /dev/null +++ b/src/DotNetOpenAuth.OpenId/Configuration/OpenIdRelyingPartySecuritySettingsElement.cs @@ -0,0 +1,266 @@ +//----------------------------------------------------------------------- +// <copyright file="OpenIdRelyingPartySecuritySettingsElement.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Configuration { + using System; + using System.Configuration; + using System.Diagnostics.Contracts; + using DotNetOpenAuth.OpenId; + using DotNetOpenAuth.OpenId.RelyingParty; + + /// <summary> + /// Represents the .config file element that allows for setting the security policies of the Relying Party. + /// </summary> + internal class OpenIdRelyingPartySecuritySettingsElement : ConfigurationElement { + /// <summary> + /// Gets the name of the @minimumRequiredOpenIdVersion attribute. + /// </summary> + private const string MinimumRequiredOpenIdVersionConfigName = "minimumRequiredOpenIdVersion"; + + /// <summary> + /// Gets the name of the @minimumHashBitLength attribute. + /// </summary> + private const string MinimumHashBitLengthConfigName = "minimumHashBitLength"; + + /// <summary> + /// Gets the name of the @maximumHashBitLength attribute. + /// </summary> + private const string MaximumHashBitLengthConfigName = "maximumHashBitLength"; + + /// <summary> + /// Gets the name of the @requireSsl attribute. + /// </summary> + private const string RequireSslConfigName = "requireSsl"; + + /// <summary> + /// Gets the name of the @requireDirectedIdentity attribute. + /// </summary> + private const string RequireDirectedIdentityConfigName = "requireDirectedIdentity"; + + /// <summary> + /// Gets the name of the @requireAssociation attribute. + /// </summary> + private const string RequireAssociationConfigName = "requireAssociation"; + + /// <summary> + /// Gets the name of the @rejectUnsolicitedAssertions attribute. + /// </summary> + private const string RejectUnsolicitedAssertionsConfigName = "rejectUnsolicitedAssertions"; + + /// <summary> + /// Gets the name of the @rejectDelegatedIdentifiers attribute. + /// </summary> + private const string RejectDelegatingIdentifiersConfigName = "rejectDelegatingIdentifiers"; + + /// <summary> + /// Gets the name of the @ignoreUnsignedExtensions attribute. + /// </summary> + private const string IgnoreUnsignedExtensionsConfigName = "ignoreUnsignedExtensions"; + + /// <summary> + /// Gets the name of the @allowDualPurposeIdentifiers attribute. + /// </summary> + private const string AllowDualPurposeIdentifiersConfigName = "allowDualPurposeIdentifiers"; + + /// <summary> + /// Gets the name of the @allowApproximateIdentifierDiscovery attribute. + /// </summary> + private const string AllowApproximateIdentifierDiscoveryConfigName = "allowApproximateIdentifierDiscovery"; + + /// <summary> + /// Gets the name of the @protectDownlevelReplayAttacks attribute. + /// </summary> + private const string ProtectDownlevelReplayAttacksConfigName = "protectDownlevelReplayAttacks"; + + /// <summary> + /// The name of the <trustedProviders> sub-element. + /// </summary> + private const string TrustedProvidersElementName = "trustedProviders"; + + /// <summary> + /// Initializes a new instance of the <see cref="OpenIdRelyingPartySecuritySettingsElement"/> class. + /// </summary> + public OpenIdRelyingPartySecuritySettingsElement() { + } + + /// <summary> + /// Gets or sets a value indicating whether all discovery and authentication should require SSL security. + /// </summary> + [ConfigurationProperty(RequireSslConfigName, DefaultValue = false)] + public bool RequireSsl { + get { return (bool)this[RequireSslConfigName]; } + set { this[RequireSslConfigName] = value; } + } + + /// <summary> + /// Gets or sets a value indicating whether only OP Identifiers will be discoverable + /// when creating authentication requests. + /// </summary> + [ConfigurationProperty(RequireDirectedIdentityConfigName, DefaultValue = false)] + public bool RequireDirectedIdentity { + get { return (bool)this[RequireDirectedIdentityConfigName]; } + set { this[RequireDirectedIdentityConfigName] = value; } + } + + /// <summary> + /// Gets or sets a value indicating whether authentication requests + /// will only be created where an association with the Provider can be established. + /// </summary> + [ConfigurationProperty(RequireAssociationConfigName, DefaultValue = false)] + public bool RequireAssociation { + get { return (bool)this[RequireAssociationConfigName]; } + set { this[RequireAssociationConfigName] = value; } + } + + /// <summary> + /// Gets or sets the minimum OpenID version a Provider is required to support in order for this library to interoperate with it. + /// </summary> + /// <remarks> + /// Although the earliest versions of OpenID are supported, for security reasons it may be desirable to require the + /// remote party to support a later version of OpenID. + /// </remarks> + [ConfigurationProperty(MinimumRequiredOpenIdVersionConfigName, DefaultValue = "V10")] + public ProtocolVersion MinimumRequiredOpenIdVersion { + get { return (ProtocolVersion)this[MinimumRequiredOpenIdVersionConfigName]; } + set { this[MinimumRequiredOpenIdVersionConfigName] = value; } + } + + /// <summary> + /// Gets or sets the minimum length of the hash that protects the protocol from hijackers. + /// </summary> + [ConfigurationProperty(MinimumHashBitLengthConfigName, DefaultValue = SecuritySettings.MinimumHashBitLengthDefault)] + public int MinimumHashBitLength { + get { return (int)this[MinimumHashBitLengthConfigName]; } + set { this[MinimumHashBitLengthConfigName] = value; } + } + + /// <summary> + /// Gets or sets the maximum length of the hash that protects the protocol from hijackers. + /// </summary> + [ConfigurationProperty(MaximumHashBitLengthConfigName, DefaultValue = SecuritySettings.MaximumHashBitLengthRPDefault)] + public int MaximumHashBitLength { + get { return (int)this[MaximumHashBitLengthConfigName]; } + set { this[MaximumHashBitLengthConfigName] = value; } + } + + /// <summary> + /// Gets or sets a value indicating whether all unsolicited assertions should be ignored. + /// </summary> + /// <value>The default value is <c>false</c>.</value> + [ConfigurationProperty(RejectUnsolicitedAssertionsConfigName, DefaultValue = false)] + public bool RejectUnsolicitedAssertions { + get { return (bool)this[RejectUnsolicitedAssertionsConfigName]; } + set { this[RejectUnsolicitedAssertionsConfigName] = value; } + } + + /// <summary> + /// Gets or sets a value indicating whether delegating identifiers are refused for authentication. + /// </summary> + /// <value>The default value is <c>false</c>.</value> + /// <remarks> + /// When set to <c>true</c>, login attempts that start at the RP or arrive via unsolicited + /// assertions will be rejected if discovery on the identifier shows that OpenID delegation + /// is used for the identifier. This is useful for an RP that should only accept identifiers + /// directly issued by the Provider that is sending the assertion. + /// </remarks> + [ConfigurationProperty(RejectDelegatingIdentifiersConfigName, DefaultValue = false)] + public bool RejectDelegatingIdentifiers { + get { return (bool)this[RejectDelegatingIdentifiersConfigName]; } + set { this[RejectDelegatingIdentifiersConfigName] = value; } + } + + /// <summary> + /// Gets or sets a value indicating whether unsigned extensions in authentication responses should be ignored. + /// </summary> + /// <value>The default value is <c>false</c>.</value> + /// <remarks> + /// When set to true, the <see cref="IAuthenticationResponse.GetUntrustedExtension"/> methods + /// will not return any extension that was not signed by the Provider. + /// </remarks> + [ConfigurationProperty(IgnoreUnsignedExtensionsConfigName, DefaultValue = false)] + public bool IgnoreUnsignedExtensions { + get { return (bool)this[IgnoreUnsignedExtensionsConfigName]; } + set { this[IgnoreUnsignedExtensionsConfigName] = value; } + } + + /// <summary> + /// Gets or sets a value indicating whether identifiers that are both OP Identifiers and Claimed Identifiers + /// should ever be recognized as claimed identifiers. + /// </summary> + /// <value> + /// The default value is <c>false</c>, per the OpenID 2.0 spec. + /// </value> + [ConfigurationProperty(AllowDualPurposeIdentifiersConfigName, DefaultValue = false)] + public bool AllowDualPurposeIdentifiers { + get { return (bool)this[AllowDualPurposeIdentifiersConfigName]; } + set { this[AllowDualPurposeIdentifiersConfigName] = value; } + } + + /// <summary> + /// Gets or sets a value indicating whether certain Claimed Identifiers that exploit + /// features that .NET does not have the ability to send exact HTTP requests for will + /// still be allowed by using an approximate HTTP request. + /// </summary> + /// <value> + /// The default value is <c>true</c>. + /// </value> + [ConfigurationProperty(AllowApproximateIdentifierDiscoveryConfigName, DefaultValue = true)] + public bool AllowApproximateIdentifierDiscovery { + get { return (bool)this[AllowApproximateIdentifierDiscoveryConfigName]; } + set { this[AllowApproximateIdentifierDiscoveryConfigName] = value; } + } + + /// <summary> + /// Gets or sets a value indicating whether the Relying Party should take special care + /// to protect users against replay attacks when interoperating with OpenID 1.1 Providers. + /// </summary> + [ConfigurationProperty(ProtectDownlevelReplayAttacksConfigName, DefaultValue = RelyingPartySecuritySettings.ProtectDownlevelReplayAttacksDefault)] + public bool ProtectDownlevelReplayAttacks { + get { return (bool)this[ProtectDownlevelReplayAttacksConfigName]; } + set { this[ProtectDownlevelReplayAttacksConfigName] = value; } + } + + /// <summary> + /// Gets or sets the set of trusted OpenID Provider Endpoints. + /// </summary> + [ConfigurationProperty(TrustedProvidersElementName, IsDefaultCollection = false)] + [ConfigurationCollection(typeof(TrustedProviderConfigurationCollection))] + public TrustedProviderConfigurationCollection TrustedProviders { + get { return (TrustedProviderConfigurationCollection)this[TrustedProvidersElementName] ?? new TrustedProviderConfigurationCollection(); } + set { this[TrustedProvidersElementName] = value; } + } + + /// <summary> + /// Initializes a programmatically manipulatable bag of these security settings with the settings from the config file. + /// </summary> + /// <returns>The newly created security settings object.</returns> + public RelyingPartySecuritySettings CreateSecuritySettings() { + Contract.Ensures(Contract.Result<RelyingPartySecuritySettings>() != null); + + RelyingPartySecuritySettings settings = new RelyingPartySecuritySettings(); + settings.RequireSsl = this.RequireSsl; + settings.RequireDirectedIdentity = this.RequireDirectedIdentity; + settings.RequireAssociation = this.RequireAssociation; + settings.MinimumRequiredOpenIdVersion = this.MinimumRequiredOpenIdVersion; + settings.MinimumHashBitLength = this.MinimumHashBitLength; + settings.MaximumHashBitLength = this.MaximumHashBitLength; + settings.PrivateSecretMaximumAge = DotNetOpenAuthSection.Messaging.PrivateSecretMaximumAge; + settings.RejectUnsolicitedAssertions = this.RejectUnsolicitedAssertions; + settings.RejectDelegatingIdentifiers = this.RejectDelegatingIdentifiers; + settings.IgnoreUnsignedExtensions = this.IgnoreUnsignedExtensions; + settings.AllowDualPurposeIdentifiers = this.AllowDualPurposeIdentifiers; + settings.AllowApproximateIdentifierDiscovery = this.AllowApproximateIdentifierDiscovery; + settings.ProtectDownlevelReplayAttacks = this.ProtectDownlevelReplayAttacks; + + settings.RejectAssertionsFromUntrustedProviders = this.TrustedProviders.RejectAssertionsFromUntrustedProviders; + foreach (TrustedProviderEndpointConfigurationElement opEndpoint in this.TrustedProviders) { + settings.TrustedProviderEndpoints.Add(opEndpoint.ProviderEndpoint); + } + + return settings; + } + } +} diff --git a/src/DotNetOpenAuth/Configuration/XriResolverElement.cs b/src/DotNetOpenAuth.OpenId/Configuration/XriResolverElement.cs index fdb5b29..fdb5b29 100644 --- a/src/DotNetOpenAuth/Configuration/XriResolverElement.cs +++ b/src/DotNetOpenAuth.OpenId/Configuration/XriResolverElement.cs diff --git a/src/DotNetOpenAuth.OpenId/DotNetOpenAuth.OpenId.csproj b/src/DotNetOpenAuth.OpenId/DotNetOpenAuth.OpenId.csproj new file mode 100644 index 0000000..b45247b --- /dev/null +++ b/src/DotNetOpenAuth.OpenId/DotNetOpenAuth.OpenId.csproj @@ -0,0 +1,188 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), EnlistmentInfo.props))\EnlistmentInfo.props" Condition=" '$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), EnlistmentInfo.props))' != '' " /> + <PropertyGroup> + <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> + <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> + </PropertyGroup> + <Import Project="$(ProjectRoot)tools\DotNetOpenAuth.props" /> + <PropertyGroup> + <SchemaVersion>2.0</SchemaVersion> + <ProjectGuid>{3896A32A-E876-4C23-B9B8-78E17D134CD3}</ProjectGuid> + <OutputType>Library</OutputType> + <AppDesignerFolder>Properties</AppDesignerFolder> + <RootNamespace>DotNetOpenAuth</RootNamespace> + <AssemblyName>DotNetOpenAuth.OpenId</AssemblyName> + </PropertyGroup> + <Import Project="$(ProjectRoot)tools\DotNetOpenAuth.Product.props" /> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> + </PropertyGroup> + <ItemGroup> + <Compile Include="Configuration\AssociationTypeCollection.cs" /> + <Compile Include="Configuration\AssociationTypeElement.cs" /> + <Compile Include="Configuration\OpenIdElement.cs" /> + <Compile Include="Configuration\OpenIdProviderElement.cs" /> + <Compile Include="Configuration\OpenIdProviderSecuritySettingsElement.cs" /> + <Compile Include="Configuration\OpenIdRelyingPartyElement.cs" /> + <Compile Include="Configuration\OpenIdRelyingPartySecuritySettingsElement.cs" /> + <Compile Include="Configuration\XriResolverElement.cs" /> + <Compile Include="OpenIdXrdsHelper.cs" /> + <Compile Include="OpenId\Association.cs" /> + <Compile Include="OpenId\AuthenticationRequestMode.cs" /> + <Compile Include="OpenId\Behaviors\AXFetchAsSregTransformBase.cs" /> + <Compile Include="OpenId\Behaviors\BehaviorStrings.Designer.cs"> + <AutoGen>True</AutoGen> + <DesignTime>True</DesignTime> + <DependentUpon>BehaviorStrings.resx</DependentUpon> + </Compile> + <Compile Include="OpenId\Behaviors\GsaIcamProfileBase.cs" /> + <Compile Include="OpenId\ChannelElements\BackwardCompatibilityBindingElement.cs" /> + <Compile Include="OpenId\ChannelElements\SigningBindingElementContract.cs" /> + <Compile Include="OpenId\ChannelElements\ExtensionsBindingElement.cs" /> + <Compile Include="OpenId\ChannelElements\IOpenIdExtensionFactory.cs" /> + <Compile Include="OpenId\ChannelElements\ITamperResistantOpenIdMessage.cs" /> + <Compile Include="OpenId\ChannelElements\OriginalStringUriEncoder.cs" /> + <Compile Include="OpenId\ChannelElements\SigningBindingElement.cs" /> + <Compile Include="OpenId\ChannelElements\KeyValueFormEncoding.cs" /> + <Compile Include="OpenId\ChannelElements\OpenIdChannel.cs" /> + <Compile Include="OpenId\ChannelElements\ReturnToSignatureBindingElement.cs" /> + <Compile Include="OpenId\ChannelElements\SkipSecurityBindingElement.cs" /> + <Compile Include="OpenId\AssociationContract.cs" /> + <Compile Include="OpenId\Extensions\AliasManager.cs" /> + <Compile Include="OpenId\Extensions\AttributeExchange\AttributeRequest.cs" /> + <Compile Include="OpenId\Extensions\AttributeExchange\AttributeValues.cs" /> + <Compile Include="OpenId\Extensions\AttributeExchange\AXAttributeFormats.cs" /> + <Compile Include="OpenId\Extensions\AttributeExchange\AXUtilities.cs" /> + <Compile Include="OpenId\Extensions\AttributeExchange\Constants.cs" /> + <Compile Include="OpenId\Extensions\AttributeExchange\FetchRequest.cs" /> + <Compile Include="OpenId\Extensions\AttributeExchange\FetchResponse.cs" /> + <Compile Include="OpenId\Extensions\AttributeExchange\StoreRequest.cs" /> + <Compile Include="OpenId\Extensions\AttributeExchange\StoreResponse.cs" /> + <Compile Include="OpenId\Extensions\AttributeExchange\WellKnownAttributes.cs" /> + <Compile Include="OpenId\Extensions\ExtensionBase.cs" /> + <Compile Include="OpenId\Extensions\ExtensionArgumentsManager.cs" /> + <Compile Include="OpenId\Extensions\IClientScriptExtensionResponse.cs" /> + <Compile Include="OpenId\Extensions\OAuth\AuthorizationRequest.cs" /> + <Compile Include="OpenId\Extensions\OAuth\AuthorizationApprovedResponse.cs" /> + <Compile Include="OpenId\Extensions\OAuth\Constants.cs" /> + <Compile Include="OpenId\Extensions\OAuth\AuthorizationDeclinedResponse.cs" /> + <Compile Include="OpenId\Extensions\OpenIdExtensionFactoryAggregator.cs" /> + <Compile Include="OpenId\Extensions\StandardOpenIdExtensionFactory.cs" /> + <Compile Include="OpenId\Extensions\ProviderAuthenticationPolicy\AuthenticationPolicies.cs" /> + <Compile Include="OpenId\Extensions\ProviderAuthenticationPolicy\Constants.cs" /> + <Compile Include="OpenId\Extensions\ProviderAuthenticationPolicy\DateTimeEncoder.cs" /> + <Compile Include="OpenId\Extensions\ProviderAuthenticationPolicy\NistAssuranceLevel.cs" /> + <Compile Include="OpenId\Extensions\ProviderAuthenticationPolicy\PapeUtilities.cs" /> + <Compile Include="OpenId\Extensions\ProviderAuthenticationPolicy\PolicyRequest.cs" /> + <Compile Include="OpenId\Extensions\ProviderAuthenticationPolicy\PolicyResponse.cs" /> + <Compile Include="OpenId\Extensions\SimpleRegistration\ClaimsRequest.cs" /> + <Compile Include="OpenId\Extensions\SimpleRegistration\ClaimsResponse.cs" /> + <Compile Include="OpenId\Extensions\SimpleRegistration\Constants.cs" /> + <Compile Include="OpenId\Extensions\SimpleRegistration\DemandLevel.cs" /> + <Compile Include="OpenId\Extensions\SimpleRegistration\Gender.cs" /> + <Compile Include="OpenId\Extensions\UI\UIConstants.cs" /> + <Compile Include="OpenId\Extensions\UI\UIModes.cs" /> + <Compile Include="OpenId\Extensions\UI\UIRequest.cs" /> + <Compile Include="OpenId\Extensions\UI\UIUtilities.cs" /> + <Compile Include="OpenId\Identifier.cs" /> + <Compile Include="OpenId\IdentifierContract.cs" /> + <Compile Include="OpenId\Extensions\OpenIdExtensionsInteropHelper.cs" /> + <Compile Include="OpenId\IdentifierDiscoveryResult.cs" /> + <Compile Include="OpenId\IIdentifierDiscoveryService.cs" /> + <Compile Include="OpenId\IProviderEndpoint.cs" /> + <Compile Include="OpenId\Provider\IAuthenticationRequest.cs" /> + <Compile Include="OpenId\Provider\IHostProcessedRequest.cs" /> + <Compile Include="OpenId\Provider\IProviderBehavior.cs" /> + <Compile Include="OpenId\Provider\IRequest.cs" /> + <Compile Include="OpenId\Provider\ProviderSecuritySettings.cs" /> + <Compile Include="OpenId\Provider\RelyingPartyDiscoveryResult.cs" /> + <Compile Include="OpenId\RelyingParty\AuthenticationStatus.cs" /> + <Compile Include="OpenId\RelyingParty\IAuthenticationRequest.cs" /> + <Compile Include="OpenId\RelyingParty\IAuthenticationRequestContract.cs" /> + <Compile Include="OpenId\RelyingParty\IAuthenticationResponse.cs" /> + <Compile Include="OpenId\RelyingParty\IRelyingPartyBehavior.cs" /> + <Compile Include="OpenId\Messages\CheckAuthenticationRequest.cs" /> + <Compile Include="OpenId\Messages\CheckAuthenticationResponse.cs" /> + <Compile Include="OpenId\Messages\CheckIdRequest.cs" /> + <Compile Include="OpenId\Messages\AssociateSuccessfulResponseContract.cs" /> + <Compile Include="OpenId\Messages\IErrorMessage.cs" /> + <Compile Include="OpenId\Messages\IndirectResponseBase.cs" /> + <Compile Include="OpenId\Messages\IndirectSignedResponse.cs" /> + <Compile Include="OpenId\Messages\IOpenIdMessageExtension.cs" /> + <Compile Include="OpenId\Messages\NegativeAssertionResponse.cs" /> + <Compile Include="OpenId\Messages\PositiveAssertionResponse.cs" /> + <Compile Include="OpenId\Messages\SignedResponseRequest.cs" /> + <Compile Include="OpenId\NoDiscoveryIdentifier.cs" /> + <Compile Include="OpenId\OpenIdUtilities.cs" /> + <Compile Include="OpenId\OpenIdXrdsHelper.cs" /> + <Compile Include="OpenId\ProviderEndpointDescription.cs" /> + <Compile Include="OpenId\Realm.cs" /> + <Compile Include="OpenId\RelyingPartyDescription.cs" /> + <Compile Include="OpenId\DiffieHellmanUtilities.cs" Condition=" '$(ExcludeDiffieHellman)' != 'true' " /> + <Compile Include="OpenId\HmacShaAssociation.cs" /> + <Compile Include="OpenId\Messages\AssociateUnencryptedRequest.cs" /> + <Compile Include="OpenId\Messages\AssociateDiffieHellmanRequest.cs" /> + <Compile Include="OpenId\Messages\AssociateDiffieHellmanResponse.cs" Condition=" '$(ExcludeDiffieHellman)' != 'true' " /> + <Compile Include="OpenId\Messages\AssociateRequest.cs" /> + <Compile Include="OpenId\Messages\AssociateSuccessfulResponse.cs" /> + <Compile Include="OpenId\Messages\AssociateUnencryptedResponse.cs" /> + <Compile Include="OpenId\Messages\AssociateUnsuccessfulResponse.cs" /> + <Compile Include="OpenId\Messages\IndirectErrorResponse.cs" /> + <Compile Include="OpenId\Messages\DirectErrorResponse.cs" /> + <Compile Include="OpenId\Messages\RequestBase.cs" /> + <Compile Include="OpenId\Messages\DirectResponseBase.cs" /> + <Compile Include="OpenId\OpenIdStrings.Designer.cs"> + <DependentUpon>OpenIdStrings.resx</DependentUpon> + <DesignTime>True</DesignTime> + <AutoGen>True</AutoGen> + </Compile> + <Compile Include="OpenId\Protocol.cs" /> + <Compile Include="OpenId\IOpenIdApplicationStore.cs" /> + <Compile Include="OpenId\RelyingParty\RelyingPartySecuritySettings.cs" /> + <Compile Include="OpenId\UriDiscoveryService.cs" /> + <Compile Include="OpenId\XriDiscoveryProxyService.cs" /> + <Compile Include="OpenId\SecuritySettings.cs" /> + <Compile Include="OpenId\UriIdentifier.cs" /> + <Compile Include="OpenId\XriIdentifier.cs" /> + <Compile Include="Properties\AssemblyInfo.cs" /> + <Compile Include="Xrds\XrdsStrings.Designer.cs"> + <AutoGen>True</AutoGen> + <DesignTime>True</DesignTime> + <DependentUpon>XrdsStrings.resx</DependentUpon> + </Compile> + <Compile Include="Yadis\ContentTypes.cs" /> + <Compile Include="Yadis\DiscoveryResult.cs" /> + <Compile Include="Yadis\HtmlParser.cs" /> + <Compile Include="Xrds\ServiceElement.cs" /> + <Compile Include="Xrds\TypeElement.cs" /> + <Compile Include="Xrds\UriElement.cs" /> + <Compile Include="Xrds\XrdElement.cs" /> + <Compile Include="Xrds\XrdsDocument.cs" /> + <Compile Include="Xrds\XrdsNode.cs" /> + <Compile Include="Yadis\Yadis.cs" /> + <EmbeddedResource Include="OpenId\Behaviors\BehaviorStrings.resx" /> + <EmbeddedResource Include="OpenId\Behaviors\BehaviorStrings.sr.resx" /> + <EmbeddedResource Include="OpenId\OpenIdStrings.resx" /> + <EmbeddedResource Include="OpenId\OpenIdStrings.sr.resx" /> + <EmbeddedResource Include="Xrds\XrdsStrings.resx"> + <Generator>ResXFileCodeGenerator</Generator> + <LastGenOutput>XrdsStrings.Designer.cs</LastGenOutput> + </EmbeddedResource> + <EmbeddedResource Include="Xrds\XrdsStrings.sr.resx" /> + </ItemGroup> + <ItemGroup> + <ProjectReference Include="..\DotNetOpenAuth.Core\DotNetOpenAuth.Core.csproj"> + <Project>{60426312-6AE5-4835-8667-37EDEA670222}</Project> + <Name>DotNetOpenAuth.Core</Name> + </ProjectReference> + <ProjectReference Include="..\Org.Mentalis.Security.Cryptography\Org.Mentalis.Security.Cryptography.csproj" Condition=" '$(ExcludeDiffieHellman)' != 'true' "> + <Project>{26DC877F-5987-48DD-9DDB-E62F2DE0E150}</Project> + <Name>Org.Mentalis.Security.Cryptography</Name> + </ProjectReference> + </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))' != '' " /> +</Project>
\ No newline at end of file diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Association.cs b/src/DotNetOpenAuth.OpenId/OpenId/Association.cs new file mode 100644 index 0000000..ff30a36 --- /dev/null +++ b/src/DotNetOpenAuth.OpenId/OpenId/Association.cs @@ -0,0 +1,307 @@ +//----------------------------------------------------------------------- +// <copyright file="Association.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId { + using System; + using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; + using System.Diagnostics.Contracts; + using System.IO; + using System.Security.Cryptography; + using System.Text; + using DotNetOpenAuth.Configuration; + using DotNetOpenAuth.Messaging; + + /// <summary> + /// Stores a secret used in signing and verifying messages. + /// </summary> + /// <remarks> + /// OpenID associations may be shared between Provider and Relying Party (smart + /// associations), or be a way for a Provider to recall its own secret for later + /// (dumb associations). + /// </remarks> + [DebuggerDisplay("Handle = {Handle}, Expires = {Expires}")] + [ContractVerification(true)] + [ContractClass(typeof(AssociationContract))] + public abstract class Association { + /// <summary> + /// Initializes a new instance of the <see cref="Association"/> class. + /// </summary> + /// <param name="handle">The handle.</param> + /// <param name="secret">The secret.</param> + /// <param name="totalLifeLength">How long the association will be useful.</param> + /// <param name="issued">The UTC time of when this association was originally issued by the Provider.</param> + protected Association(string handle, byte[] secret, TimeSpan totalLifeLength, DateTime issued) { + Requires.NotNullOrEmpty(handle, "handle"); + Requires.NotNull(secret, "secret"); + Requires.InRange(totalLifeLength > TimeSpan.Zero, "totalLifeLength"); + Requires.True(issued.Kind == DateTimeKind.Utc, "issued"); + Requires.InRange(issued <= DateTime.UtcNow, "issued"); + Contract.Ensures(this.TotalLifeLength == totalLifeLength); + + this.Handle = handle; + this.SecretKey = secret; + this.TotalLifeLength = totalLifeLength; + this.Issued = OpenIdUtilities.CutToSecond(issued); + } + + /// <summary> + /// Gets a unique handle by which this <see cref="Association"/> may be stored or retrieved. + /// </summary> + public string Handle { get; internal set; } + + /// <summary> + /// Gets the UTC time when this <see cref="Association"/> will expire. + /// </summary> + public DateTime Expires { + get { return this.Issued + this.TotalLifeLength; } + } + + /// <summary> + /// Gets a value indicating whether this <see cref="Association"/> has already expired. + /// </summary> + public bool IsExpired { + get { return this.Expires < DateTime.UtcNow; } + } + + /// <summary> + /// Gets the length (in bits) of the hash this association creates when signing. + /// </summary> + public abstract int HashBitLength { get; } + + /// <summary> + /// Gets a value indicating whether this instance has useful life remaining. + /// </summary> + /// <value> + /// <c>true</c> if this instance has useful life remaining; otherwise, <c>false</c>. + /// </value> + internal bool HasUsefulLifeRemaining { + get { return this.TimeTillExpiration >= MinimumUsefulAssociationLifetime; } + } + + /// <summary> + /// Gets or sets the UTC time that this <see cref="Association"/> was first created. + /// </summary> + [MessagePart] + internal DateTime Issued { get; set; } + + /// <summary> + /// Gets the duration a secret key used for signing dumb client requests will be good for. + /// </summary> + protected internal static TimeSpan DumbSecretLifetime { + get { + Contract.Ensures(Contract.Result<TimeSpan>() > TimeSpan.Zero); + return OpenIdElement.Configuration.MaxAuthenticationTime; + } + } + + /// <summary> + /// Gets the number of seconds until this <see cref="Association"/> expires. + /// Never negative (counter runs to zero). + /// </summary> + protected internal long SecondsTillExpiration { + get { + Contract.Ensures(Contract.Result<long>() >= 0); + return Math.Max(0, (long)this.TimeTillExpiration.TotalSeconds); + } + } + + /// <summary> + /// Gets the shared secret key between the consumer and provider. + /// </summary> + [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification = "It is a buffer.")] + [MessagePart("key")] + protected internal byte[] SecretKey { get; private set; } + + /// <summary> + /// Gets the lifetime the OpenID provider permits this <see cref="Association"/>. + /// </summary> + [MessagePart("ttl")] + protected TimeSpan TotalLifeLength { get; private set; } + + /// <summary> + /// Gets the minimum lifetime an association must still be good for in order for it to be used for a future authentication. + /// </summary> + /// <remarks> + /// Associations that are not likely to last the duration of a user login are not worth using at all. + /// </remarks> + private static TimeSpan MinimumUsefulAssociationLifetime { + get { + Contract.Ensures(Contract.Result<TimeSpan>() > TimeSpan.Zero); + return OpenIdElement.Configuration.MaxAuthenticationTime; + } + } + + /// <summary> + /// Gets the TimeSpan till this association expires. + /// </summary> + private TimeSpan TimeTillExpiration { + get { return this.Expires - DateTime.UtcNow; } + } + + /// <summary> + /// Re-instantiates an <see cref="Association"/> previously persisted in a database or some + /// other shared store. + /// </summary> + /// <param name="handle"> + /// The <see cref="Handle"/> property of the previous <see cref="Association"/> instance. + /// </param> + /// <param name="expiresUtc"> + /// The UTC value of the <see cref="Expires"/> property of the previous <see cref="Association"/> instance. + /// </param> + /// <param name="privateData"> + /// The byte array returned by a call to <see cref="SerializePrivateData"/> on the previous + /// <see cref="Association"/> instance. + /// </param> + /// <returns> + /// The newly dehydrated <see cref="Association"/>, which can be returned + /// from a custom association store's + /// IRelyingPartyAssociationStore.GetAssociation method. + /// </returns> + public static Association Deserialize(string handle, DateTime expiresUtc, byte[] privateData) { + Requires.NotNullOrEmpty(handle, "handle"); + Requires.NotNull(privateData, "privateData"); + Contract.Ensures(Contract.Result<Association>() != null); + + expiresUtc = expiresUtc.ToUniversalTimeSafe(); + TimeSpan remainingLifeLength = expiresUtc - DateTime.UtcNow; + byte[] secret = privateData; // the whole of privateData is the secret key for now. + // We figure out what derived type to instantiate based on the length of the secret. + try { + return HmacShaAssociation.Create(handle, secret, remainingLifeLength); + } catch (ArgumentException ex) { + throw new ArgumentException(OpenIdStrings.BadAssociationPrivateData, "privateData", ex); + } + } + + /// <summary> + /// Returns private data required to persist this <see cref="Association"/> in + /// permanent storage (a shared database for example) for deserialization later. + /// </summary> + /// <returns> + /// An opaque byte array that must be stored and returned exactly as it is provided here. + /// The byte array may vary in length depending on the specific type of <see cref="Association"/>, + /// but in current versions are no larger than 256 bytes. + /// </returns> + /// <remarks> + /// Values of public properties on the base class <see cref="Association"/> are not included + /// in this byte array, as they are useful for fast database lookup and are persisted separately. + /// </remarks> + public byte[] SerializePrivateData() { + Contract.Ensures(Contract.Result<byte[]>() != null); + + // We may want to encrypt this secret using the machine.config private key, + // and add data regarding which Association derivative will need to be + // re-instantiated on deserialization. + // For now, we just send out the secret key. We can derive the type from the length later. + byte[] secretKeyCopy = new byte[this.SecretKey.Length]; + if (this.SecretKey.Length > 0) { + this.SecretKey.CopyTo(secretKeyCopy, 0); + } + return secretKeyCopy; + } + + /// <summary> + /// Tests equality of two <see cref="Association"/> objects. + /// </summary> + /// <param name="obj">The <see cref="T:System.Object"/> to compare with the current <see cref="T:System.Object"/>.</param> + /// <returns> + /// true if the specified <see cref="T:System.Object"/> is equal to the current <see cref="T:System.Object"/>; otherwise, false. + /// </returns> + public override bool Equals(object obj) { + Association a = obj as Association; + if (a == null) { + return false; + } + if (a.GetType() != GetType()) { + return false; + } + + if (a.Handle != this.Handle || + a.Issued != this.Issued || + !MessagingUtilities.Equals(a.TotalLifeLength, this.TotalLifeLength, TimeSpan.FromSeconds(1))) { + return false; + } + + if (!MessagingUtilities.AreEquivalent(a.SecretKey, this.SecretKey)) { + return false; + } + + return true; + } + + /// <summary> + /// Returns the hash code. + /// </summary> + /// <returns> + /// A hash code for the current <see cref="T:System.Object"/>. + /// </returns> + public override int GetHashCode() { + HMACSHA1 hmac = new HMACSHA1(this.SecretKey); + try { + CryptoStream cs = new CryptoStream(Stream.Null, hmac, CryptoStreamMode.Write); + + byte[] hbytes = ASCIIEncoding.ASCII.GetBytes(this.Handle); + + cs.Write(hbytes, 0, hbytes.Length); + cs.Close(); + + byte[] hash = hmac.Hash; + hmac.Clear(); + + long val = 0; + for (int i = 0; i < hash.Length; i++) { + val = val ^ (long)hash[i]; + } + + val = val ^ this.Expires.ToFileTimeUtc(); + + return (int)val; + } finally { + ((IDisposable)hmac).Dispose(); + } + } + + /// <summary> + /// The string to pass as the assoc_type value in the OpenID protocol. + /// </summary> + /// <param name="protocol">The protocol version of the message that the assoc_type value will be included in.</param> + /// <returns>The value that should be used for the openid.assoc_type parameter.</returns> + internal abstract string GetAssociationType(Protocol protocol); + + /// <summary> + /// Generates a signature from a given blob of data. + /// </summary> + /// <param name="data">The data to sign. This data will not be changed (the signature is the return value).</param> + /// <returns>The calculated signature of the data.</returns> + protected internal byte[] Sign(byte[] data) { + Requires.NotNull(data, "data"); + using (HashAlgorithm hasher = this.CreateHasher()) { + return hasher.ComputeHash(data); + } + } + + /// <summary> + /// Returns the specific hash algorithm used for message signing. + /// </summary> + /// <returns>The hash algorithm used for message signing.</returns> + protected abstract HashAlgorithm CreateHasher(); + +#if CONTRACTS_FULL + /// <summary> + /// Verifies conditions that should be true for any valid state of this object. + /// </summary> + [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Called by code contracts.")] + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called by code contracts.")] + [ContractInvariantMethod] + private void ObjectInvariant() { + Contract.Invariant(!string.IsNullOrEmpty(this.Handle)); + Contract.Invariant(this.TotalLifeLength > TimeSpan.Zero); + Contract.Invariant(this.SecretKey != null); + } +#endif + } +} diff --git a/src/DotNetOpenAuth.OpenId/OpenId/AssociationContract.cs b/src/DotNetOpenAuth.OpenId/OpenId/AssociationContract.cs new file mode 100644 index 0000000..e36028e --- /dev/null +++ b/src/DotNetOpenAuth.OpenId/OpenId/AssociationContract.cs @@ -0,0 +1,65 @@ +//----------------------------------------------------------------------- +// <copyright file="AssociationContract.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId { + using System; + using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; + using System.Diagnostics.Contracts; + using System.IO; + using System.Security.Cryptography; + using System.Text; + using DotNetOpenAuth.Configuration; + using DotNetOpenAuth.Messaging; + + /// <summary> + /// Code contract for the <see cref="Association"/> class. + /// </summary> + [ContractClassFor(typeof(Association))] + internal abstract class AssociationContract : Association { + /// <summary> + /// Prevents a default instance of the <see cref="AssociationContract"/> class from being created. + /// </summary> + private AssociationContract() + : base(null, null, TimeSpan.Zero, DateTime.Now) { + } + + /// <summary> + /// Gets the length (in bits) of the hash this association creates when signing. + /// </summary> + public override int HashBitLength { + get { + Contract.Ensures(Contract.Result<int>() > 0); + throw new NotImplementedException(); + } + } + + /// <summary> + /// The string to pass as the assoc_type value in the OpenID protocol. + /// </summary> + /// <param name="protocol">The protocol version of the message that the assoc_type value will be included in.</param> + /// <returns> + /// The value that should be used for the openid.assoc_type parameter. + /// </returns> + [Pure] + internal override string GetAssociationType(Protocol protocol) { + Requires.NotNull(protocol, "protocol"); + throw new NotImplementedException(); + } + + /// <summary> + /// Returns the specific hash algorithm used for message signing. + /// </summary> + /// <returns> + /// The hash algorithm used for message signing. + /// </returns> + [Pure] + protected override HashAlgorithm CreateHasher() { + Contract.Ensures(Contract.Result<HashAlgorithm>() != null); + throw new NotImplementedException(); + } + } +} diff --git a/src/DotNetOpenAuth.OpenId/OpenId/AuthenticationRequestMode.cs b/src/DotNetOpenAuth.OpenId/OpenId/AuthenticationRequestMode.cs new file mode 100644 index 0000000..dfe7b06 --- /dev/null +++ b/src/DotNetOpenAuth.OpenId/OpenId/AuthenticationRequestMode.cs @@ -0,0 +1,26 @@ +//----------------------------------------------------------------------- +// <copyright file="AuthenticationRequestMode.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId { + /// <summary> + /// Indicates the mode the Provider should use while authenticating the end user. + /// </summary> + public enum AuthenticationRequestMode { + /// <summary> + /// The Provider should use whatever credentials are immediately available + /// to determine whether the end user owns the Identifier. If sufficient + /// credentials (i.e. cookies) are not immediately available, the Provider + /// should fail rather than prompt the user. + /// </summary> + Immediate, + + /// <summary> + /// The Provider should determine whether the end user owns the Identifier, + /// displaying a web page to the user to login etc., if necessary. + /// </summary> + Setup, + } +} diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Behaviors/AXFetchAsSregTransformBase.cs b/src/DotNetOpenAuth.OpenId/OpenId/Behaviors/AXFetchAsSregTransformBase.cs new file mode 100644 index 0000000..41e6b8e --- /dev/null +++ b/src/DotNetOpenAuth.OpenId/OpenId/Behaviors/AXFetchAsSregTransformBase.cs @@ -0,0 +1,37 @@ +//----------------------------------------------------------------------- +// <copyright file="AXFetchAsSregTransformBase.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.Behaviors { + using System; + using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; + using System.Linq; + using System.Text; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OpenId.Extensions; + using DotNetOpenAuth.OpenId.Extensions.AttributeExchange; + using DotNetOpenAuth.OpenId.Extensions.SimpleRegistration; + + /// <summary> + /// An Attribute Exchange and Simple Registration filter to make all incoming attribute + /// requests look like Simple Registration requests, and to convert the response + /// to the originally requested extension and format. + /// </summary> + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Sreg", Justification = "Abbreviation")] + public abstract class AXFetchAsSregTransformBase { + /// <summary> + /// Initializes a new instance of the <see cref="AXFetchAsSregTransformBase"/> class. + /// </summary> + protected AXFetchAsSregTransformBase() { + this.AXFormats = AXAttributeFormats.Common; + } + + /// <summary> + /// Gets or sets the AX attribute type URI formats this transform is willing to work with. + /// </summary> + public AXAttributeFormats AXFormats { get; set; } + } +} diff --git a/src/DotNetOpenAuth/OpenId/Behaviors/BehaviorStrings.Designer.cs b/src/DotNetOpenAuth.OpenId/OpenId/Behaviors/BehaviorStrings.Designer.cs index 8c952ab..8c952ab 100644 --- a/src/DotNetOpenAuth/OpenId/Behaviors/BehaviorStrings.Designer.cs +++ b/src/DotNetOpenAuth.OpenId/OpenId/Behaviors/BehaviorStrings.Designer.cs diff --git a/src/DotNetOpenAuth/OpenId/Behaviors/BehaviorStrings.resx b/src/DotNetOpenAuth.OpenId/OpenId/Behaviors/BehaviorStrings.resx index a8bf2d6..a8bf2d6 100644 --- a/src/DotNetOpenAuth/OpenId/Behaviors/BehaviorStrings.resx +++ b/src/DotNetOpenAuth.OpenId/OpenId/Behaviors/BehaviorStrings.resx diff --git a/src/DotNetOpenAuth/OpenId/Behaviors/BehaviorStrings.sr.resx b/src/DotNetOpenAuth.OpenId/OpenId/Behaviors/BehaviorStrings.sr.resx index 2b1b911..2b1b911 100644 --- a/src/DotNetOpenAuth/OpenId/Behaviors/BehaviorStrings.sr.resx +++ b/src/DotNetOpenAuth.OpenId/OpenId/Behaviors/BehaviorStrings.sr.resx diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Behaviors/GsaIcamProfileBase.cs b/src/DotNetOpenAuth.OpenId/OpenId/Behaviors/GsaIcamProfileBase.cs new file mode 100644 index 0000000..25a4e59 --- /dev/null +++ b/src/DotNetOpenAuth.OpenId/OpenId/Behaviors/GsaIcamProfileBase.cs @@ -0,0 +1,58 @@ +//----------------------------------------------------------------------- +// <copyright file="GsaIcamProfileBase.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.Behaviors { + using System; + using System.Diagnostics.CodeAnalysis; + using System.Diagnostics.Contracts; + using System.Linq; + using DotNetOpenAuth.Configuration; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OpenId.Extensions.AttributeExchange; + using DotNetOpenAuth.OpenId.Extensions.ProviderAuthenticationPolicy; + using DotNetOpenAuth.OpenId.Extensions.SimpleRegistration; + + /// <summary> + /// Implements the Identity, Credential, & Access Management (ICAM) OpenID 2.0 Profile + /// for the General Services Administration (GSA). + /// </summary> + /// <remarks> + /// <para>Relying parties that include this profile are always held to the terms required by the profile, + /// but Providers are only affected by the special behaviors of the profile when the RP specifically + /// indicates that they want to use this profile. </para> + /// </remarks> + [Serializable] + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Icam", Justification = "Acronym")] + public abstract class GsaIcamProfileBase { + /// <summary> + /// Backing field for the <see cref="DisableSslRequirement"/> static property. + /// </summary> + private static bool disableSslRequirement = DotNetOpenAuthSection.Messaging.RelaxSslRequirements; + + /// <summary> + /// Initializes a new instance of the <see cref="GsaIcamProfileBase"/> class. + /// </summary> + public GsaIcamProfileBase() { + if (DisableSslRequirement) { + Logger.OpenId.Warn("GSA level 1 behavior has its RequireSsl requirement disabled."); + } + } + + /// <summary> + /// Gets or sets a value indicating whether PII is allowed to be requested or received via OpenID. + /// </summary> + /// <value>The default value is <c>false</c>.</value> + public static bool AllowPersonallyIdentifiableInformation { get; set; } + + /// <summary> + /// Gets or sets a value indicating whether to ignore the SSL requirement (for testing purposes only). + /// </summary> + public static bool DisableSslRequirement { // not an auto-property because it has a default value, and FxCop doesn't want us using static constructors. + get { return disableSslRequirement; } + set { disableSslRequirement = value; } + } + } +} diff --git a/src/DotNetOpenAuth/OpenId/ChannelElements/BackwardCompatibilityBindingElement.cs b/src/DotNetOpenAuth.OpenId/OpenId/ChannelElements/BackwardCompatibilityBindingElement.cs index b730b1f..b730b1f 100644 --- a/src/DotNetOpenAuth/OpenId/ChannelElements/BackwardCompatibilityBindingElement.cs +++ b/src/DotNetOpenAuth.OpenId/OpenId/ChannelElements/BackwardCompatibilityBindingElement.cs diff --git a/src/DotNetOpenAuth.OpenId/OpenId/ChannelElements/ExtensionsBindingElement.cs b/src/DotNetOpenAuth.OpenId/OpenId/ChannelElements/ExtensionsBindingElement.cs new file mode 100644 index 0000000..705f737 --- /dev/null +++ b/src/DotNetOpenAuth.OpenId/OpenId/ChannelElements/ExtensionsBindingElement.cs @@ -0,0 +1,251 @@ +//----------------------------------------------------------------------- +// <copyright file="ExtensionsBindingElement.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.ChannelElements { + using System; + using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; + using System.Diagnostics.Contracts; + using System.Linq; + using System.Text; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.Messaging.Reflection; + using DotNetOpenAuth.OpenId.Extensions; + using DotNetOpenAuth.OpenId.Messages; + + /// <summary> + /// The binding element that serializes/deserializes OpenID extensions to/from + /// their carrying OpenID messages. + /// </summary> + internal class ExtensionsBindingElement : IChannelBindingElement { + /// <summary> + /// False if unsigned extensions should be dropped. Must always be true on Providers, since RPs never sign extensions. + /// </summary> + private readonly bool receiveUnsignedExtensions; + + /// <summary> + /// Initializes a new instance of the <see cref="ExtensionsBindingElement"/> class. + /// </summary> + /// <param name="extensionFactory">The extension factory.</param> + /// <param name="securitySettings">The security settings.</param> + /// <param name="receiveUnsignedExtensions">Security setting for relying parties. Should be true for Providers.</param> + internal ExtensionsBindingElement(IOpenIdExtensionFactory extensionFactory, SecuritySettings securitySettings, bool receiveUnsignedExtensions) { + Requires.NotNull(extensionFactory, "extensionFactory"); + Requires.NotNull(securitySettings, "securitySettings"); + + this.ExtensionFactory = extensionFactory; + this.receiveUnsignedExtensions = receiveUnsignedExtensions; + } + + #region IChannelBindingElement Members + + /// <summary> + /// Gets or sets the channel that this binding element belongs to. + /// </summary> + /// <value></value> + /// <remarks> + /// This property is set by the channel when it is first constructed. + /// </remarks> + public Channel Channel { get; set; } + + /// <summary> + /// Gets the extension factory. + /// </summary> + public IOpenIdExtensionFactory ExtensionFactory { get; private set; } + + /// <summary> + /// Gets the protection offered (if any) by this binding element. + /// </summary> + /// <value><see cref="MessageProtections.None"/></value> + public 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> + [SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Justification = "It doesn't look too bad to me. :)")] + public MessageProtections? ProcessOutgoingMessage(IProtocolMessage message) { + var extendableMessage = message as IProtocolMessageWithExtensions; + if (extendableMessage != null) { + Protocol protocol = Protocol.Lookup(message.Version); + MessageDictionary baseMessageDictionary = this.Channel.MessageDescriptions.GetAccessor(message); + + // We have a helper class that will do all the heavy-lifting of organizing + // all the extensions, their aliases, and their parameters. + var extensionManager = ExtensionArgumentsManager.CreateOutgoingExtensions(protocol); + foreach (IExtensionMessage protocolExtension in extendableMessage.Extensions) { + var extension = protocolExtension as IOpenIdMessageExtension; + if (extension != null) { + Reporting.RecordFeatureUse(protocolExtension); + + // Give extensions that require custom serialization a chance to do their work. + var customSerializingExtension = extension as IMessageWithEvents; + if (customSerializingExtension != null) { + customSerializingExtension.OnSending(); + } + + // OpenID 2.0 Section 12 forbids two extensions with the same TypeURI in the same message. + ErrorUtilities.VerifyProtocol(!extensionManager.ContainsExtension(extension.TypeUri), OpenIdStrings.ExtensionAlreadyAddedWithSameTypeURI, extension.TypeUri); + + // Ensure that we're sending out a valid extension. + var extensionDescription = this.Channel.MessageDescriptions.Get(extension); + var extensionDictionary = extensionDescription.GetDictionary(extension).Serialize(); + extensionDescription.EnsureMessagePartsPassBasicValidation(extensionDictionary); + + // Add the extension to the outgoing message payload. + extensionManager.AddExtensionArguments(extension.TypeUri, extensionDictionary); + } else { + Logger.OpenId.WarnFormat("Unexpected extension type {0} did not implement {1}.", protocolExtension.GetType(), typeof(IOpenIdMessageExtension).Name); + } + } + + // We use a cheap trick (for now at least) to determine whether the 'openid.' prefix + // belongs on the parameters by just looking at what other parameters do. + // Technically, direct message responses from Provider to Relying Party are the only + // messages that leave off the 'openid.' prefix. + bool includeOpenIdPrefix = baseMessageDictionary.Keys.Any(key => key.StartsWith(protocol.openid.Prefix, StringComparison.Ordinal)); + + // Add the extension parameters to the base message for transmission. + baseMessageDictionary.AddExtraParameters(extensionManager.GetArgumentsToSend(includeOpenIdPrefix)); + return MessageProtections.None; + } + + 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 MessageProtections? ProcessIncomingMessage(IProtocolMessage message) { + var extendableMessage = message as IProtocolMessageWithExtensions; + if (extendableMessage != null) { + // First add the extensions that are signed by the Provider. + foreach (IOpenIdMessageExtension signedExtension in this.GetExtensions(extendableMessage, true, null)) { + Reporting.RecordFeatureUse(signedExtension); + signedExtension.IsSignedByRemoteParty = true; + extendableMessage.Extensions.Add(signedExtension); + } + + // Now search again, considering ALL extensions whether they are signed or not, + // skipping the signed ones and adding the new ones as unsigned extensions. + if (this.receiveUnsignedExtensions) { + Func<string, bool> isNotSigned = typeUri => !extendableMessage.Extensions.Cast<IOpenIdMessageExtension>().Any(ext => ext.TypeUri == typeUri); + foreach (IOpenIdMessageExtension unsignedExtension in this.GetExtensions(extendableMessage, false, isNotSigned)) { + Reporting.RecordFeatureUse(unsignedExtension); + unsignedExtension.IsSignedByRemoteParty = false; + extendableMessage.Extensions.Add(unsignedExtension); + } + } + + return MessageProtections.None; + } + + return null; + } + + #endregion + + /// <summary> + /// Gets the extensions on a message. + /// </summary> + /// <param name="message">The carrier of the extensions.</param> + /// <param name="ignoreUnsigned">If set to <c>true</c> only signed extensions will be available.</param> + /// <param name="extensionFilter">A optional filter that takes an extension type URI and + /// returns a value indicating whether that extension should be deserialized and + /// returned in the sequence. May be null.</param> + /// <returns>A sequence of extensions in the message.</returns> + private IEnumerable<IOpenIdMessageExtension> GetExtensions(IProtocolMessageWithExtensions message, bool ignoreUnsigned, Func<string, bool> extensionFilter) { + bool isAtProvider = message is SignedResponseRequest; + + // We have a helper class that will do all the heavy-lifting of organizing + // all the extensions, their aliases, and their parameters. + var extensionManager = ExtensionArgumentsManager.CreateIncomingExtensions(this.GetExtensionsDictionary(message, ignoreUnsigned)); + foreach (string typeUri in extensionManager.GetExtensionTypeUris()) { + // Our caller may have already obtained a signed version of this extension, + // so skip it if they don't want this one. + if (extensionFilter != null && !extensionFilter(typeUri)) { + continue; + } + + var extensionData = extensionManager.GetExtensionArguments(typeUri); + + // Initialize this particular extension. + IOpenIdMessageExtension extension = this.ExtensionFactory.Create(typeUri, extensionData, message, isAtProvider); + if (extension != null) { + try { + // Make sure the extension fulfills spec requirements before deserializing it. + MessageDescription messageDescription = this.Channel.MessageDescriptions.Get(extension); + messageDescription.EnsureMessagePartsPassBasicValidation(extensionData); + + // Deserialize the extension. + MessageDictionary extensionDictionary = messageDescription.GetDictionary(extension); + foreach (var pair in extensionData) { + extensionDictionary[pair.Key] = pair.Value; + } + + // Give extensions that require custom serialization a chance to do their work. + var customSerializingExtension = extension as IMessageWithEvents; + if (customSerializingExtension != null) { + customSerializingExtension.OnReceiving(); + } + } catch (ProtocolException ex) { + Logger.OpenId.ErrorFormat(OpenIdStrings.BadExtension, extension.GetType(), ex); + extension = null; + } + + if (extension != null) { + yield return extension; + } + } else { + Logger.OpenId.DebugFormat("Extension with type URI '{0}' ignored because it is not a recognized extension.", typeUri); + } + } + } + + /// <summary> + /// Gets the dictionary of message parts that should be deserialized into extensions. + /// </summary> + /// <param name="message">The message.</param> + /// <param name="ignoreUnsigned">If set to <c>true</c> only signed extensions will be available.</param> + /// <returns> + /// A dictionary of message parts, including only signed parts when appropriate. + /// </returns> + private IDictionary<string, string> GetExtensionsDictionary(IProtocolMessage message, bool ignoreUnsigned) { + Requires.ValidState(this.Channel != null); + + IndirectSignedResponse signedResponse = message as IndirectSignedResponse; + if (signedResponse != null && ignoreUnsigned) { + return signedResponse.GetSignedMessageParts(this.Channel); + } else { + return this.Channel.MessageDescriptions.GetAccessor(message); + } + } + } +} diff --git a/src/DotNetOpenAuth.OpenId/OpenId/ChannelElements/IOpenIdExtensionFactory.cs b/src/DotNetOpenAuth.OpenId/OpenId/ChannelElements/IOpenIdExtensionFactory.cs new file mode 100644 index 0000000..0c8d95e --- /dev/null +++ b/src/DotNetOpenAuth.OpenId/OpenId/ChannelElements/IOpenIdExtensionFactory.cs @@ -0,0 +1,48 @@ +//----------------------------------------------------------------------- +// <copyright file="IOpenIdExtensionFactory.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.ChannelElements { + using System.Collections.Generic; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OpenId.Messages; + + /// <summary> + /// OpenID extension factory class for creating extensions based on received Type URIs. + /// </summary> + /// <remarks> + /// OpenID extension factories must be registered with the library. This can be + /// done by adding a factory to OpenIdRelyingParty.ExtensionFactories + /// or OpenIdProvider.ExtensionFactories, or by adding a snippet + /// such as the following to your web.config file: + /// <example> + /// <dotNetOpenAuth> + /// <openid> + /// <extensionFactories> + /// <add type="DotNetOpenAuth.ApplicationBlock.CustomExtensions.Acme, DotNetOpenAuth.ApplicationBlock" /> + /// </extensionFactories> + /// </openid> + /// </dotNetOpenAuth> + /// </example> + /// </remarks> + public interface IOpenIdExtensionFactory { + /// <summary> + /// Creates a new instance of some extension based on the received extension parameters. + /// </summary> + /// <param name="typeUri">The type URI of the extension.</param> + /// <param name="data">The parameters associated specifically with this extension.</param> + /// <param name="baseMessage">The OpenID message carrying this extension.</param> + /// <param name="isProviderRole">A value indicating whether this extension is being received at the OpenID Provider.</param> + /// <returns> + /// An instance of <see cref="IOpenIdMessageExtension"/> if the factory recognizes + /// the extension described in the input parameters; <c>null</c> otherwise. + /// </returns> + /// <remarks> + /// This factory method need only initialize properties in the instantiated extension object + /// that are not bound using <see cref="MessagePartAttribute"/>. + /// </remarks> + IOpenIdMessageExtension Create(string typeUri, IDictionary<string, string> data, IProtocolMessageWithExtensions baseMessage, bool isProviderRole); + } +} diff --git a/src/DotNetOpenAuth/OpenId/ChannelElements/ITamperResistantOpenIdMessage.cs b/src/DotNetOpenAuth.OpenId/OpenId/ChannelElements/ITamperResistantOpenIdMessage.cs index 533e818..533e818 100644 --- a/src/DotNetOpenAuth/OpenId/ChannelElements/ITamperResistantOpenIdMessage.cs +++ b/src/DotNetOpenAuth.OpenId/OpenId/ChannelElements/ITamperResistantOpenIdMessage.cs diff --git a/src/DotNetOpenAuth.OpenId/OpenId/ChannelElements/KeyValueFormEncoding.cs b/src/DotNetOpenAuth.OpenId/OpenId/ChannelElements/KeyValueFormEncoding.cs new file mode 100644 index 0000000..1993cb4 --- /dev/null +++ b/src/DotNetOpenAuth.OpenId/OpenId/ChannelElements/KeyValueFormEncoding.cs @@ -0,0 +1,169 @@ +//----------------------------------------------------------------------- +// <copyright file="KeyValueFormEncoding.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.ChannelElements { + using System; + using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; + using System.Diagnostics.Contracts; + using System.Globalization; + using System.IO; + using System.Text; + using DotNetOpenAuth.Messaging; + + /// <summary> + /// Indicates the level of strictness to require when decoding a + /// Key-Value Form encoded dictionary. + /// </summary> + public enum KeyValueFormConformanceLevel { + /// <summary> + /// Be as forgiving as possible to errors made while encoding. + /// </summary> + Loose, + + /// <summary> + /// Allow for certain errors in encoding attributable to ambiguities + /// in the OpenID 1.1 spec's description of the encoding. + /// </summary> + OpenId11, + + /// <summary> + /// The strictest mode. The decoder requires the encoded dictionary + /// to be in strict compliance with OpenID 2.0's description of + /// the encoding. + /// </summary> + OpenId20, + } + + /// <summary> + /// Performs conversion to and from the Key-Value Form Encoding defined by + /// OpenID Authentication 2.0 section 4.1.1. + /// http://openid.net/specs/openid-authentication-2_0.html#anchor4 + /// </summary> + /// <remarks> + /// This class is thread safe and immutable. + /// </remarks> + internal class KeyValueFormEncoding { + /// <summary> + /// Characters that must not appear in parameter names. + /// </summary> + private static readonly char[] IllegalKeyCharacters = { '\n', ':' }; + + /// <summary> + /// Characters that must not appaer in parameter values. + /// </summary> + private static readonly char[] IllegalValueCharacters = { '\n' }; + + /// <summary> + /// The newline character sequence to use. + /// </summary> + private const string NewLineCharacters = "\n"; + + /// <summary> + /// The character encoding to use. + /// </summary> + private static readonly Encoding textEncoding = new UTF8Encoding(false); + + /// <summary> + /// Initializes a new instance of the <see cref="KeyValueFormEncoding"/> class. + /// </summary> + public KeyValueFormEncoding() { + this.ConformanceLevel = KeyValueFormConformanceLevel.Loose; + } + + /// <summary> + /// Initializes a new instance of the <see cref="KeyValueFormEncoding"/> class. + /// </summary> + /// <param name="conformanceLevel">How strictly an incoming Key-Value Form message will be held to the spec.</param> + public KeyValueFormEncoding(KeyValueFormConformanceLevel conformanceLevel) { + this.ConformanceLevel = conformanceLevel; + } + + /// <summary> + /// Gets a value controlling how strictly an incoming Key-Value Form message will be held to the spec. + /// </summary> + public KeyValueFormConformanceLevel ConformanceLevel { get; private set; } + + /// <summary> + /// Encodes key/value pairs to Key-Value Form. + /// </summary> + /// <param name="keysAndValues"> + /// The dictionary of key/value pairs to convert to a byte stream. + /// </param> + /// <returns>The UTF8 byte array.</returns> + /// <remarks> + /// Enumerating a Dictionary<TKey, TValue> has undeterministic ordering. + /// If ordering of the key=value pairs is important, a deterministic enumerator must + /// be used. + /// </remarks> + [SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times", Justification = "Not a problem for this type.")] + public static byte[] GetBytes(IEnumerable<KeyValuePair<string, string>> keysAndValues) { + Requires.NotNull(keysAndValues, "keysAndValues"); + + using (MemoryStream ms = new MemoryStream()) { + using (StreamWriter sw = new StreamWriter(ms, textEncoding)) { + sw.NewLine = NewLineCharacters; + foreach (var pair in keysAndValues) { + if (pair.Key.IndexOfAny(IllegalKeyCharacters) >= 0) { + throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, OpenIdStrings.InvalidCharacterInKeyValueFormInput, pair.Key)); + } + if (pair.Value.IndexOfAny(IllegalValueCharacters) >= 0) { + throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, OpenIdStrings.InvalidCharacterInKeyValueFormInput, pair.Value)); + } + + sw.Write(pair.Key); + sw.Write(':'); + sw.Write(pair.Value); + sw.WriteLine(); + } + } + + return ms.ToArray(); + } + } + + /// <summary> + /// Decodes bytes in Key-Value Form to key/value pairs. + /// </summary> + /// <param name="data">The stream of Key-Value Form encoded bytes.</param> + /// <returns>The deserialized dictionary.</returns> + /// <exception cref="FormatException">Thrown when the data is not in the expected format.</exception> + public IDictionary<string, string> GetDictionary(Stream data) { + using (StreamReader reader = new StreamReader(data, textEncoding)) { + var dict = new Dictionary<string, string>(); + int line_num = 0; + string line; + while ((line = reader.ReadLine()) != null) { + line_num++; + if (this.ConformanceLevel == KeyValueFormConformanceLevel.Loose) { + line = line.Trim(); + if (line.Length == 0) { + continue; + } + } + string[] parts = line.Split(new[] { ':' }, 2); + ErrorUtilities.VerifyFormat(parts.Length == 2, OpenIdStrings.InvalidKeyValueFormCharacterMissing, ':', line_num, line); + if (this.ConformanceLevel > KeyValueFormConformanceLevel.Loose) { + ErrorUtilities.VerifyFormat(!(char.IsWhiteSpace(parts[0], parts[0].Length - 1) || char.IsWhiteSpace(parts[1], 0)), OpenIdStrings.InvalidCharacterInKeyValueFormInput, ' ', line_num, line); + } + if (this.ConformanceLevel < KeyValueFormConformanceLevel.OpenId20) { + parts[0] = parts[0].Trim(); + parts[1] = parts[1].Trim(); + } + + // calling Add method will throw if a key is encountered twice, + // which we should do. + dict.Add(parts[0], parts[1]); + } + if (this.ConformanceLevel > KeyValueFormConformanceLevel.Loose) { + reader.BaseStream.Seek(-1, SeekOrigin.End); + ErrorUtilities.VerifyFormat(reader.BaseStream.ReadByte() == '\n', OpenIdStrings.InvalidKeyValueFormCharacterMissing, "\\n", line_num, line); + } + return dict; + } + } + } +} diff --git a/src/DotNetOpenAuth.OpenId/OpenId/ChannelElements/OpenIdChannel.cs b/src/DotNetOpenAuth.OpenId/OpenId/ChannelElements/OpenIdChannel.cs new file mode 100644 index 0000000..a2a5c88 --- /dev/null +++ b/src/DotNetOpenAuth.OpenId/OpenId/ChannelElements/OpenIdChannel.cs @@ -0,0 +1,228 @@ +//----------------------------------------------------------------------- +// <copyright file="OpenIdChannel.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.ChannelElements { + using System; + using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; + using System.Diagnostics.Contracts; + using System.Globalization; + using System.IO; + using System.Linq; + using System.Net; + using System.Text; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.Messaging.Bindings; + using DotNetOpenAuth.OpenId.Extensions; + using DotNetOpenAuth.OpenId.Messages; + + /// <summary> + /// A channel that knows how to send and receive OpenID messages. + /// </summary> + [ContractVerification(true)] + internal class OpenIdChannel : Channel { + /// <summary> + /// The HTTP Content-Type to use in Key-Value Form responses. + /// </summary> + /// <remarks> + /// OpenID 2.0 section 5.1.2 says this SHOULD be text/plain. But this value + /// does not prevent free hosters like GoDaddy from tacking on their ads + /// to the end of the direct response, corrupting the data. So we deviate + /// from the spec a bit here to improve the story for free Providers. + /// </remarks> + internal const string KeyValueFormContentType = "application/x-openid-kvf"; + + /// <summary> + /// The encoder that understands how to read and write Key-Value Form. + /// </summary> + private KeyValueFormEncoding keyValueForm = new KeyValueFormEncoding(); + + /// <summary> + /// Initializes a new instance of the <see cref="OpenIdChannel"/> class. + /// </summary> + /// <param name="messageTypeProvider">A class prepared to analyze incoming messages and indicate what concrete + /// message types can deserialize from it.</param> + /// <param name="bindingElements">The binding elements to use in sending and receiving messages.</param> + protected OpenIdChannel(IMessageFactory messageTypeProvider, IChannelBindingElement[] bindingElements) + : base(messageTypeProvider, bindingElements) { + Requires.NotNull(messageTypeProvider, "messageTypeProvider"); + + // Customize the binding element order, since we play some tricks for higher + // security and backward compatibility with older OpenID versions. + var outgoingBindingElements = new List<IChannelBindingElement>(bindingElements); + var incomingBindingElements = new List<IChannelBindingElement>(bindingElements); + incomingBindingElements.Reverse(); + + // Customize the order of the incoming elements by moving the return_to elements in front. + var backwardCompatibility = incomingBindingElements.OfType<BackwardCompatibilityBindingElement>().SingleOrDefault(); + var returnToSign = incomingBindingElements.OfType<ReturnToSignatureBindingElement>().SingleOrDefault(); + if (backwardCompatibility != null) { + incomingBindingElements.MoveTo(0, backwardCompatibility); + } + if (returnToSign != null) { + // Yes, this is intentionally, shifting the backward compatibility + // binding element to second position. + incomingBindingElements.MoveTo(0, returnToSign); + } + + this.CustomizeBindingElementOrder(outgoingBindingElements, incomingBindingElements); + + // Change out the standard web request handler to reflect the standard + // OpenID pattern that outgoing web requests are to unknown and untrusted + // servers on the Internet. + this.WebRequestHandler = new UntrustedWebRequestHandler(); + } + + /// <summary> + /// Verifies the integrity and applicability of an incoming message. + /// </summary> + /// <param name="message">The message just received.</param> + /// <exception cref="ProtocolException"> + /// Thrown when the message is somehow invalid, except for check_authentication messages. + /// This can be due to tampering, replay attack or expiration, among other things. + /// </exception> + protected override void ProcessIncomingMessage(IProtocolMessage message) { + var checkAuthRequest = message as CheckAuthenticationRequest; + if (checkAuthRequest != null) { + IndirectSignedResponse originalResponse = new IndirectSignedResponse(checkAuthRequest, this); + try { + base.ProcessIncomingMessage(originalResponse); + checkAuthRequest.IsValid = true; + } catch (ProtocolException) { + checkAuthRequest.IsValid = false; + } + } else { + base.ProcessIncomingMessage(message); + } + + // Convert an OpenID indirect error message, which we never expect + // between two good OpenID implementations, into an exception. + // We don't process DirectErrorResponse because associate negotiations + // commonly get a derivative of that message type and handle it. + var errorMessage = message as IndirectErrorResponse; + if (errorMessage != null) { + string exceptionMessage = string.Format( + CultureInfo.CurrentCulture, + OpenIdStrings.IndirectErrorFormattedMessage, + errorMessage.ErrorMessage, + errorMessage.Contact, + errorMessage.Reference); + throw new ProtocolException(exceptionMessage, message); + } + } + + /// <summary> + /// Prepares an HTTP request that carries a given message. + /// </summary> + /// <param name="request">The message to send.</param> + /// <returns> + /// The <see cref="HttpWebRequest"/> prepared to send the request. + /// </returns> + protected override HttpWebRequest CreateHttpRequest(IDirectedProtocolMessage request) { + return this.InitializeRequestAsPost(request); + } + + /// <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) { + try { + return this.keyValueForm.GetDictionary(response.ResponseStream); + } catch (FormatException ex) { + throw ErrorUtilities.Wrap(ex, ex.Message); + } + } + + /// <summary> + /// Called when receiving a direct response message, before deserialization begins. + /// </summary> + /// <param name="response">The HTTP direct response.</param> + /// <param name="message">The newly instantiated message, prior to deserialization.</param> + protected override void OnReceivingDirectResponse(IncomingWebResponse response, IDirectResponseProtocolMessage message) { + base.OnReceivingDirectResponse(response, message); + + // Verify that the expected HTTP status code was used for the message, + // per OpenID 2.0 section 5.1.2.2. + // Note: The v1.1 spec doesn't require 400 responses for some error messages + if (message.Version.Major >= 2) { + var httpDirectResponse = message as IHttpDirectResponse; + if (httpDirectResponse != null) { + ErrorUtilities.VerifyProtocol( + httpDirectResponse.HttpStatusCode == response.Status, + MessagingStrings.UnexpectedHttpStatusCode, + (int)httpDirectResponse.HttpStatusCode, + (int)response.Status); + } + } + } + + /// <summary> + /// Queues a message for sending in the response stream where the fields + /// are sent in the response stream in querystring style. + /// </summary> + /// <param name="response">The message to send as a response.</param> + /// <returns> + /// The pending user agent redirect based message to be sent as an HttpResponse. + /// </returns> + /// <remarks> + /// This method implements spec V1.0 section 5.3. + /// </remarks> + protected override OutgoingWebResponse PrepareDirectResponse(IProtocolMessage response) { + var messageAccessor = this.MessageDescriptions.GetAccessor(response); + var fields = messageAccessor.Serialize(); + byte[] keyValueEncoding = KeyValueFormEncoding.GetBytes(fields); + + OutgoingWebResponse preparedResponse = new OutgoingWebResponse(); + preparedResponse.Headers.Add(HttpResponseHeader.ContentType, KeyValueFormContentType); + preparedResponse.OriginalMessage = response; + preparedResponse.ResponseStream = new MemoryStream(keyValueEncoding); + + IHttpDirectResponse httpMessage = response as IHttpDirectResponse; + if (httpMessage != null) { + preparedResponse.Status = httpMessage.HttpStatusCode; + } + + return preparedResponse; + } + + /// <summary> + /// Gets the direct response of a direct HTTP request. + /// </summary> + /// <param name="webRequest">The web request.</param> + /// <returns>The response to the web request.</returns> + /// <exception cref="ProtocolException">Thrown on network or protocol errors.</exception> + protected override IncomingWebResponse GetDirectResponse(HttpWebRequest webRequest) { + IncomingWebResponse response = this.WebRequestHandler.GetResponse(webRequest, DirectWebRequestOptions.AcceptAllHttpResponses); + + // Filter the responses to the allowable set of HTTP status codes. + if (response.Status != HttpStatusCode.OK && response.Status != HttpStatusCode.BadRequest) { + if (Logger.Channel.IsErrorEnabled) { + using (var reader = new StreamReader(response.ResponseStream)) { + Logger.Channel.ErrorFormat( + "Unexpected HTTP status code {0} {1} received in direct response:{2}{3}", + (int)response.Status, + response.Status, + Environment.NewLine, + reader.ReadToEnd()); + } + } + + // Call dispose before throwing since we're not including the response in the + // exception we're throwing. + response.Dispose(); + + ErrorUtilities.ThrowProtocol(OpenIdStrings.UnexpectedHttpStatusCode, (int)response.Status, response.Status); + } + + return response; + } + } +} diff --git a/src/DotNetOpenAuth/OpenId/ChannelElements/OriginalStringUriEncoder.cs b/src/DotNetOpenAuth.OpenId/OpenId/ChannelElements/OriginalStringUriEncoder.cs index 75b01e1..75b01e1 100644 --- a/src/DotNetOpenAuth/OpenId/ChannelElements/OriginalStringUriEncoder.cs +++ b/src/DotNetOpenAuth.OpenId/OpenId/ChannelElements/OriginalStringUriEncoder.cs diff --git a/src/DotNetOpenAuth.OpenId/OpenId/ChannelElements/ReturnToSignatureBindingElement.cs b/src/DotNetOpenAuth.OpenId/OpenId/ChannelElements/ReturnToSignatureBindingElement.cs new file mode 100644 index 0000000..25a29bb --- /dev/null +++ b/src/DotNetOpenAuth.OpenId/OpenId/ChannelElements/ReturnToSignatureBindingElement.cs @@ -0,0 +1,210 @@ +//----------------------------------------------------------------------- +// <copyright file="ReturnToSignatureBindingElement.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.ChannelElements { + using System; + using System.Collections.Generic; + using System.Collections.Specialized; + using System.Diagnostics.Contracts; + using System.Security.Cryptography; + using System.Web; + using DotNetOpenAuth.Configuration; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.Messaging.Bindings; + using DotNetOpenAuth.OpenId.Messages; + + /// <summary> + /// This binding element signs a Relying Party's openid.return_to parameter + /// so that upon return, it can verify that it hasn't been tampered with. + /// </summary> + /// <remarks> + /// <para>Since Providers can send unsolicited assertions, not all openid.return_to + /// values will be signed. But those that are signed will be validated, and + /// any invalid or missing signatures will cause this library to not trust + /// the parameters in the return_to URL.</para> + /// <para>In the messaging stack, this binding element looks like an ordinary + /// transform-type of binding element rather than a protection element, + /// due to its required order in the channel stack and that it doesn't sign + /// anything except a particular message part.</para> + /// </remarks> + internal class ReturnToSignatureBindingElement : IChannelBindingElement { + /// <summary> + /// The name of the callback parameter we'll tack onto the return_to value + /// to store our signature on the return_to parameter. + /// </summary> + private const string ReturnToSignatureParameterName = OpenIdUtilities.CustomParameterPrefix + "return_to_sig"; + + /// <summary> + /// The name of the callback parameter we'll tack onto the return_to value + /// to store the handle of the association we use to sign the return_to parameter. + /// </summary> + private const string ReturnToSignatureHandleParameterName = OpenIdUtilities.CustomParameterPrefix + "return_to_sig_handle"; + + /// <summary> + /// The URI to use for private associations at this RP. + /// </summary> + private static readonly Uri SecretUri = new Uri("https://localhost/dnoa/secret"); + + /// <summary> + /// The key store used to generate the private signature on the return_to parameter. + /// </summary> + private ICryptoKeyStore cryptoKeyStore; + + /// <summary> + /// Initializes a new instance of the <see cref="ReturnToSignatureBindingElement"/> class. + /// </summary> + /// <param name="cryptoKeyStore">The crypto key store.</param> + internal ReturnToSignatureBindingElement(ICryptoKeyStore cryptoKeyStore) { + Requires.NotNull(cryptoKeyStore, "cryptoKeyStore"); + + this.cryptoKeyStore = cryptoKeyStore; + } + + #region IChannelBindingElement Members + + /// <summary> + /// Gets or sets the channel that this binding element belongs to. + /// </summary> + /// <value></value> + /// <remarks> + /// This property is set by the channel when it is first constructed. + /// </remarks> + public Channel Channel { get; set; } + + /// <summary> + /// Gets the protection offered (if any) by this binding element. + /// </summary> + /// <value><see cref="MessageProtections.None"/></value> + /// <remarks> + /// No message protection is reported because this binding element + /// does not protect the entire message -- only a part. + /// </remarks> + public 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 MessageProtections? ProcessOutgoingMessage(IProtocolMessage message) { + SignedResponseRequest request = message as SignedResponseRequest; + if (request != null && request.ReturnTo != null && request.SignReturnTo) { + var cryptoKeyPair = this.cryptoKeyStore.GetCurrentKey(SecretUri.AbsoluteUri, OpenIdElement.Configuration.MaxAuthenticationTime); + request.AddReturnToArguments(ReturnToSignatureHandleParameterName, cryptoKeyPair.Key); + string signature = Convert.ToBase64String(this.GetReturnToSignature(request.ReturnTo, cryptoKeyPair.Value)); + request.AddReturnToArguments(ReturnToSignatureParameterName, signature); + + // We return none because we are not signing the entire message (only a part). + return MessageProtections.None; + } + + 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 MessageProtections? ProcessIncomingMessage(IProtocolMessage message) { + IndirectSignedResponse response = message as IndirectSignedResponse; + + if (response != null) { + // We can't use response.GetReturnToArgument(string) because that relies + // on us already having validated this signature. + NameValueCollection returnToParameters = HttpUtility.ParseQueryString(response.ReturnTo.Query); + + // Only check the return_to signature if one is present. + if (returnToParameters[ReturnToSignatureHandleParameterName] != null) { + // Set the safety flag showing whether the return_to url had a valid signature. + byte[] expectedBytes = this.GetReturnToSignature(response.ReturnTo); + string actual = returnToParameters[ReturnToSignatureParameterName]; + actual = OpenIdUtilities.FixDoublyUriDecodedBase64String(actual); + byte[] actualBytes = Convert.FromBase64String(actual); + response.ReturnToParametersSignatureValidated = MessagingUtilities.AreEquivalentConstantTime(actualBytes, expectedBytes); + if (!response.ReturnToParametersSignatureValidated) { + Logger.Bindings.WarnFormat("The return_to signature failed verification."); + } + + return MessageProtections.None; + } + } + + return null; + } + + #endregion + + /// <summary> + /// Gets the return to signature. + /// </summary> + /// <param name="returnTo">The return to.</param> + /// <param name="cryptoKey">The crypto key.</param> + /// <returns> + /// The generated signature. + /// </returns> + /// <remarks> + /// Only the parameters in the return_to URI are signed, rather than the base URI + /// itself, in order that OPs that might change the return_to's implicit port :80 part + /// or other minor changes do not invalidate the signature. + /// </remarks> + private byte[] GetReturnToSignature(Uri returnTo, CryptoKey cryptoKey = null) { + Requires.NotNull(returnTo, "returnTo"); + + // Assemble the dictionary to sign, taking care to remove the signature itself + // in order to accurately reproduce the original signature (which of course didn't include + // the signature). + // Also we need to sort the dictionary's keys so that we sign in the same order as we did + // the last time. + var returnToParameters = HttpUtility.ParseQueryString(returnTo.Query); + returnToParameters.Remove(ReturnToSignatureParameterName); + var sortedReturnToParameters = new SortedDictionary<string, string>(StringComparer.OrdinalIgnoreCase); + foreach (string key in returnToParameters) { + sortedReturnToParameters.Add(key, returnToParameters[key]); + } + + Logger.Bindings.DebugFormat("ReturnTo signed data: {0}{1}", Environment.NewLine, sortedReturnToParameters.ToStringDeferred()); + + // Sign the parameters. + byte[] bytesToSign = KeyValueFormEncoding.GetBytes(sortedReturnToParameters); + byte[] signature; + try { + if (cryptoKey == null) { + cryptoKey = this.cryptoKeyStore.GetKey(SecretUri.AbsoluteUri, returnToParameters[ReturnToSignatureHandleParameterName]); + } + + using (var signer = new HMACSHA256(cryptoKey.Key)) { + signature = signer.ComputeHash(bytesToSign); + } + } catch (ProtocolException ex) { + throw ErrorUtilities.Wrap(ex, OpenIdStrings.MaximumAuthenticationTimeExpired); + } + + return signature; + } + } +} diff --git a/src/DotNetOpenAuth.OpenId/OpenId/ChannelElements/SigningBindingElement.cs b/src/DotNetOpenAuth.OpenId/OpenId/ChannelElements/SigningBindingElement.cs new file mode 100644 index 0000000..d14ca8a --- /dev/null +++ b/src/DotNetOpenAuth.OpenId/OpenId/ChannelElements/SigningBindingElement.cs @@ -0,0 +1,199 @@ +//----------------------------------------------------------------------- +// <copyright file="SigningBindingElement.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.ChannelElements { + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Diagnostics.Contracts; + using System.Globalization; + using System.Linq; + using System.Net.Security; + using System.Web; + using DotNetOpenAuth.Loggers; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.Messaging.Bindings; + using DotNetOpenAuth.Messaging.Reflection; + using DotNetOpenAuth.OpenId.Messages; + + /// <summary> + /// Signs and verifies authentication assertions. + /// </summary> + [ContractClass(typeof(SigningBindingElementContract))] + internal abstract class SigningBindingElement : IChannelBindingElement { + #region IChannelBindingElement Properties + + /// <summary> + /// Gets the protection offered (if any) by this binding element. + /// </summary> + /// <value><see cref="MessageProtections.TamperProtection"/></value> + public MessageProtections Protection { + get { return MessageProtections.TamperProtection; } + } + + /// <summary> + /// Gets or sets the channel that this binding element belongs to. + /// </summary> + public Channel Channel { get; set; } + + #endregion + + /// <summary> + /// Gets a value indicating whether this binding element is on a Provider channel. + /// </summary> + protected virtual bool IsOnProvider { + get { return false; } + } + + #region IChannelBindingElement Methods + + /// <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> + public virtual MessageProtections? ProcessOutgoingMessage(IProtocolMessage message) { + 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> + public MessageProtections? ProcessIncomingMessage(IProtocolMessage message) { + var signedMessage = message as ITamperResistantOpenIdMessage; + if (signedMessage != null) { + Logger.Bindings.DebugFormat("Verifying incoming {0} message signature of: {1}", message.GetType().Name, signedMessage.Signature); + MessageProtections protectionsApplied = MessageProtections.TamperProtection; + + this.EnsureParametersRequiringSignatureAreSigned(signedMessage); + + Association association = this.GetSpecificAssociation(signedMessage); + if (association != null) { + string signature = this.GetSignature(signedMessage, association); + if (!MessagingUtilities.EqualsConstantTime(signedMessage.Signature, signature)) { + Logger.Bindings.Error("Signature verification failed."); + throw new InvalidSignatureException(message); + } + } else { + ErrorUtilities.VerifyInternal(this.Channel != null, "Cannot verify private association signature because we don't have a channel."); + + protectionsApplied = this.VerifySignatureByUnrecognizedHandle(message, signedMessage, protectionsApplied); + } + + return protectionsApplied; + } + + return null; + } + + /// <summary> + /// Verifies the signature by unrecognized handle. + /// </summary> + /// <param name="message">The message.</param> + /// <param name="signedMessage">The signed message.</param> + /// <param name="protectionsApplied">The protections applied.</param> + /// <returns>The applied protections.</returns> + protected abstract MessageProtections VerifySignatureByUnrecognizedHandle(IProtocolMessage message, ITamperResistantOpenIdMessage signedMessage, MessageProtections protectionsApplied); + + #endregion + + /// <summary> + /// Calculates the signature for a given message. + /// </summary> + /// <param name="signedMessage">The message to sign or verify.</param> + /// <param name="association">The association to use to sign the message.</param> + /// <returns>The calculated signature of the method.</returns> + protected string GetSignature(ITamperResistantOpenIdMessage signedMessage, Association association) { + Requires.NotNull(signedMessage, "signedMessage"); + Requires.True(!String.IsNullOrEmpty(signedMessage.SignedParameterOrder), "signedMessage"); + Requires.NotNull(association, "association"); + + // Prepare the parts to sign, taking care to replace an openid.mode value + // of check_authentication with its original id_res so the signature matches. + MessageDictionary dictionary = this.Channel.MessageDescriptions.GetAccessor(signedMessage); + var parametersToSign = from name in signedMessage.SignedParameterOrder.Split(',') + let prefixedName = Protocol.V20.openid.Prefix + name + select new KeyValuePair<string, string>(name, dictionary.GetValueOrThrow(prefixedName, signedMessage)); + + byte[] dataToSign = KeyValueFormEncoding.GetBytes(parametersToSign); + string signature = Convert.ToBase64String(association.Sign(dataToSign)); + + if (Logger.Signatures.IsDebugEnabled) { + Logger.Signatures.DebugFormat( + "Signing these message parts: {0}{1}{0}Base64 representation of signed data: {2}{0}Signature: {3}", + Environment.NewLine, + parametersToSign.ToStringDeferred(), + Convert.ToBase64String(dataToSign), + signature); + } + + return signature; + } + + /// <summary> + /// Gets the association to use to sign or verify a message. + /// </summary> + /// <param name="signedMessage">The message to sign or verify.</param> + /// <returns>The association to use to sign or verify the message.</returns> + protected abstract Association GetAssociation(ITamperResistantOpenIdMessage signedMessage); + + /// <summary> + /// Gets a specific association referenced in a given message's association handle. + /// </summary> + /// <param name="signedMessage">The signed message whose association handle should be used to lookup the association to return.</param> + /// <returns>The referenced association; or <c>null</c> if such an association cannot be found.</returns> + /// <remarks> + /// If the association handle set in the message does not match any valid association, + /// the association handle property is cleared, and the + /// <see cref="ITamperResistantOpenIdMessage.InvalidateHandle"/> property is set to the + /// handle that could not be found. + /// </remarks> + protected abstract Association GetSpecificAssociation(ITamperResistantOpenIdMessage signedMessage); + + /// <summary> + /// Gets a private Provider association used for signing messages in "dumb" mode. + /// </summary> + /// <returns>An existing or newly created association.</returns> + protected virtual Association GetDumbAssociationForSigning() { + throw new NotImplementedException(); + } + + /// <summary> + /// Ensures that all message parameters that must be signed are in fact included + /// in the signature. + /// </summary> + /// <param name="signedMessage">The signed message.</param> + private void EnsureParametersRequiringSignatureAreSigned(ITamperResistantOpenIdMessage signedMessage) { + // Verify that the signed parameter order includes the mandated fields. + // We do this in such a way that derived classes that add mandated fields automatically + // get included in the list of checked parameters. + Protocol protocol = Protocol.Lookup(signedMessage.Version); + var partsRequiringProtection = from part in this.Channel.MessageDescriptions.Get(signedMessage).Mapping.Values + where part.RequiredProtection != ProtectionLevel.None + where part.IsRequired || part.IsNondefaultValueSet(signedMessage) + select part.Name; + ErrorUtilities.VerifyInternal(partsRequiringProtection.All(name => name.StartsWith(protocol.openid.Prefix, StringComparison.Ordinal)), "Signing only works when the parameters start with the 'openid.' prefix."); + string[] signedParts = signedMessage.SignedParameterOrder.Split(','); + var unsignedParts = from partName in partsRequiringProtection + where !signedParts.Contains(partName.Substring(protocol.openid.Prefix.Length)) + select partName; + ErrorUtilities.VerifyProtocol(!unsignedParts.Any(), OpenIdStrings.SignatureDoesNotIncludeMandatoryParts, string.Join(", ", unsignedParts.ToArray())); + } + } +} diff --git a/src/DotNetOpenAuth.OpenId/OpenId/ChannelElements/SigningBindingElementContract.cs b/src/DotNetOpenAuth.OpenId/OpenId/ChannelElements/SigningBindingElementContract.cs new file mode 100644 index 0000000..bf8b18d --- /dev/null +++ b/src/DotNetOpenAuth.OpenId/OpenId/ChannelElements/SigningBindingElementContract.cs @@ -0,0 +1,64 @@ +//----------------------------------------------------------------------- +// <copyright file="SigningBindingElementContract.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.ChannelElements { + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Diagnostics.Contracts; + using System.Globalization; + using System.Linq; + using System.Net.Security; + using System.Web; + using DotNetOpenAuth.Loggers; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.Messaging.Bindings; + using DotNetOpenAuth.Messaging.Reflection; + using DotNetOpenAuth.OpenId.Messages; + + /// <summary> + /// Code contract for the <see cref="SigningBindingElement"/> class. + /// </summary> + [ContractClassFor(typeof(SigningBindingElement))] + internal abstract class SigningBindingElementContract : SigningBindingElement { + /// <summary> + /// Verifies the signature by unrecognized handle. + /// </summary> + /// <param name="message">The message.</param> + /// <param name="signedMessage">The signed message.</param> + /// <param name="protectionsApplied">The protections applied.</param> + /// <returns> + /// The applied protections. + /// </returns> + protected override MessageProtections VerifySignatureByUnrecognizedHandle(IProtocolMessage message, ITamperResistantOpenIdMessage signedMessage, MessageProtections protectionsApplied) { + throw new NotImplementedException(); + } + + /// <summary> + /// Gets the association to use to sign or verify a message. + /// </summary> + /// <param name="signedMessage">The message to sign or verify.</param> + /// <returns> + /// The association to use to sign or verify the message. + /// </returns> + protected override Association GetAssociation(ITamperResistantOpenIdMessage signedMessage) { + Requires.NotNull(signedMessage, "signedMessage"); + throw new NotImplementedException(); + } + + /// <summary> + /// Gets a specific association referenced in a given message's association handle. + /// </summary> + /// <param name="signedMessage">The signed message whose association handle should be used to lookup the association to return.</param> + /// <returns> + /// The referenced association; or <c>null</c> if such an association cannot be found. + /// </returns> + protected override Association GetSpecificAssociation(ITamperResistantOpenIdMessage signedMessage) { + Requires.NotNull(signedMessage, "signedMessage"); + throw new NotImplementedException(); + } + } +} diff --git a/src/DotNetOpenAuth/OpenId/ChannelElements/SkipSecurityBindingElement.cs b/src/DotNetOpenAuth.OpenId/OpenId/ChannelElements/SkipSecurityBindingElement.cs index ad65a83..ad65a83 100644 --- a/src/DotNetOpenAuth/OpenId/ChannelElements/SkipSecurityBindingElement.cs +++ b/src/DotNetOpenAuth.OpenId/OpenId/ChannelElements/SkipSecurityBindingElement.cs diff --git a/src/DotNetOpenAuth.OpenId/OpenId/DiffieHellmanUtilities.cs b/src/DotNetOpenAuth.OpenId/OpenId/DiffieHellmanUtilities.cs new file mode 100644 index 0000000..e7e1bf8 --- /dev/null +++ b/src/DotNetOpenAuth.OpenId/OpenId/DiffieHellmanUtilities.cs @@ -0,0 +1,149 @@ +//----------------------------------------------------------------------- +// <copyright file="DiffieHellmanUtilities.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId { + using System; + using System.Collections.Generic; + using System.Diagnostics.Contracts; + using System.Globalization; + using System.Linq; + using System.Security.Cryptography; + using System.Text; + using DotNetOpenAuth.Messaging; + using Org.Mentalis.Security.Cryptography; + + /// <summary> + /// Diffie-Hellman encryption methods used by both the relying party and provider. + /// </summary> + internal class DiffieHellmanUtilities { + /// <summary> + /// An array of known Diffie Hellman sessions, sorted by decreasing hash size. + /// </summary> + private static DHSha[] diffieHellmanSessionTypes = new List<DHSha> { + new DHSha(SHA512.Create(), protocol => protocol.Args.SessionType.DH_SHA512), + new DHSha(SHA384.Create(), protocol => protocol.Args.SessionType.DH_SHA384), + new DHSha(SHA256.Create(), protocol => protocol.Args.SessionType.DH_SHA256), + new DHSha(SHA1.Create(), protocol => protocol.Args.SessionType.DH_SHA1), + } .ToArray(); + + /// <summary> + /// Finds the hashing algorithm to use given an openid.session_type value. + /// </summary> + /// <param name="protocol">The protocol version of the message that named the session_type to be used.</param> + /// <param name="sessionType">The value of the openid.session_type parameter.</param> + /// <returns>The hashing algorithm to use.</returns> + /// <exception cref="ProtocolException">Thrown if no match could be found for the given <paramref name="sessionType"/>.</exception> + public static HashAlgorithm Lookup(Protocol protocol, string sessionType) { + Requires.NotNull(protocol, "protocol"); + Requires.NotNull(sessionType, "sessionType"); + + // We COULD use just First instead of FirstOrDefault, but we want to throw ProtocolException instead of InvalidOperationException. + DHSha match = diffieHellmanSessionTypes.FirstOrDefault(dhsha => String.Equals(dhsha.GetName(protocol), sessionType, StringComparison.Ordinal)); + ErrorUtilities.VerifyProtocol(match != null, OpenIdStrings.NoSessionTypeFound, sessionType, protocol.Version); + return match.Algorithm; + } + + /// <summary> + /// Looks up the value to be used for the openid.session_type parameter. + /// </summary> + /// <param name="protocol">The protocol version that is to be used.</param> + /// <param name="hashSizeInBits">The hash size (in bits) that the DH session must have.</param> + /// <returns>The value to be used for the openid.session_type parameter, or null if no match was found.</returns> + internal static string GetNameForSize(Protocol protocol, int hashSizeInBits) { + Requires.NotNull(protocol, "protocol"); + DHSha match = diffieHellmanSessionTypes.FirstOrDefault(dhsha => dhsha.Algorithm.HashSize == hashSizeInBits); + return match != null ? match.GetName(protocol) : null; + } + + /// <summary> + /// Encrypts/decrypts a shared secret. + /// </summary> + /// <param name="hasher">The hashing algorithm that is agreed by both parties to use as part of the secret exchange.</param> + /// <param name="dh"> + /// If the secret is being encrypted, this is the new Diffie Hellman object to use. + /// If the secret is being decrypted, this must be the same Diffie Hellman object used to send the original request message. + /// </param> + /// <param name="remotePublicKey">The public key of the remote party.</param> + /// <param name="plainOrEncryptedSecret">The secret to encode, or the encoded secret. Whichever one is given will generate the opposite in the return value.</param> + /// <returns> + /// The encrypted version of the secret if the secret itself was given in <paramref name="remotePublicKey"/>. + /// The secret itself if the encrypted version of the secret was given in <paramref name="remotePublicKey"/>. + /// </returns> + internal static byte[] SHAHashXorSecret(HashAlgorithm hasher, DiffieHellman dh, byte[] remotePublicKey, byte[] plainOrEncryptedSecret) { + Requires.NotNull(hasher, "hasher"); + Requires.NotNull(dh, "dh"); + Requires.NotNull(remotePublicKey, "remotePublicKey"); + Requires.NotNull(plainOrEncryptedSecret, "plainOrEncryptedSecret"); + + byte[] sharedBlock = dh.DecryptKeyExchange(remotePublicKey); + byte[] sharedBlockHash = hasher.ComputeHash(EnsurePositive(sharedBlock)); + ErrorUtilities.VerifyProtocol(sharedBlockHash.Length == plainOrEncryptedSecret.Length, OpenIdStrings.AssociationSecretHashLengthMismatch, plainOrEncryptedSecret.Length, sharedBlockHash.Length); + + byte[] secret = new byte[plainOrEncryptedSecret.Length]; + for (int i = 0; i < plainOrEncryptedSecret.Length; i++) { + secret[i] = (byte)(plainOrEncryptedSecret[i] ^ sharedBlockHash[i]); + } + return secret; + } + + /// <summary> + /// Ensures that the big integer represented by a given series of bytes + /// is a positive integer. + /// </summary> + /// <param name="inputBytes">The bytes that make up the big integer.</param> + /// <returns> + /// A byte array (possibly new if a change was required) whose + /// integer is guaranteed to be positive. + /// </returns> + /// <remarks> + /// This is to be consistent with OpenID spec section 4.2. + /// </remarks> + internal static byte[] EnsurePositive(byte[] inputBytes) { + Requires.NotNull(inputBytes, "inputBytes"); + if (inputBytes.Length == 0) { + throw new ArgumentException(MessagingStrings.UnexpectedEmptyArray, "inputBytes"); + } + + int i = (int)inputBytes[0]; + if (i > 127) { + byte[] nowPositive = new byte[inputBytes.Length + 1]; + nowPositive[0] = 0; + inputBytes.CopyTo(nowPositive, 1); + return nowPositive; + } + + return inputBytes; + } + + /// <summary> + /// Provides access to a Diffie-Hellman session algorithm and its name. + /// </summary> + private class DHSha { + /// <summary> + /// Initializes a new instance of the <see cref="DHSha"/> class. + /// </summary> + /// <param name="algorithm">The hashing algorithm used in this particular Diffie-Hellman session type.</param> + /// <param name="getName">A function that will return the value of the openid.session_type parameter for a given version of OpenID.</param> + public DHSha(HashAlgorithm algorithm, Func<Protocol, string> getName) { + Requires.NotNull(algorithm, "algorithm"); + Requires.NotNull(getName, "getName"); + + this.GetName = getName; + this.Algorithm = algorithm; + } + + /// <summary> + /// Gets the function that will return the value of the openid.session_type parameter for a given version of OpenID. + /// </summary> + internal Func<Protocol, string> GetName { get; private set; } + + /// <summary> + /// Gets the hashing algorithm used in this particular Diffie-Hellman session type + /// </summary> + internal HashAlgorithm Algorithm { get; private set; } + } + } +} diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Extensions/AliasManager.cs b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/AliasManager.cs new file mode 100644 index 0000000..f6878ec --- /dev/null +++ b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/AliasManager.cs @@ -0,0 +1,187 @@ +//----------------------------------------------------------------------- +// <copyright file="AliasManager.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.Extensions { + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Diagnostics.Contracts; + using System.Globalization; + using System.Text; + using DotNetOpenAuth.Messaging; + + /// <summary> + /// Manages a fast, two-way mapping between type URIs and their aliases. + /// </summary> + internal class AliasManager { + /// <summary> + /// The format of auto-generated aliases. + /// </summary> + private const string AliasFormat = "alias{0}"; + + /// <summary> + /// Tracks extension Type URIs and aliases assigned to them. + /// </summary> + private Dictionary<string, string> typeUriToAliasMap = new Dictionary<string, string>(); + + /// <summary> + /// Tracks extension aliases and Type URIs assigned to them. + /// </summary> + private Dictionary<string, string> aliasToTypeUriMap = new Dictionary<string, string>(); + + /// <summary> + /// Gets the aliases that have been set. + /// </summary> + public IEnumerable<string> Aliases { + get { return this.aliasToTypeUriMap.Keys; } + } + + /// <summary> + /// Gets an alias assigned for a given Type URI. A new alias is assigned if necessary. + /// </summary> + /// <param name="typeUri">The type URI.</param> + /// <returns>The alias assigned to this type URI. Never null.</returns> + public string GetAlias(string typeUri) { + Requires.NotNullOrEmpty(typeUri, "typeUri"); + string alias; + return this.typeUriToAliasMap.TryGetValue(typeUri, out alias) ? alias : this.AssignNewAlias(typeUri); + } + + /// <summary> + /// Sets an alias and the value that will be returned by <see cref="ResolveAlias"/>. + /// </summary> + /// <param name="alias">The alias.</param> + /// <param name="typeUri">The type URI.</param> + public void SetAlias(string alias, string typeUri) { + Requires.NotNullOrEmpty(alias, "alias"); + Requires.NotNullOrEmpty(typeUri, "typeUri"); + this.aliasToTypeUriMap.Add(alias, typeUri); + this.typeUriToAliasMap.Add(typeUri, alias); + } + + /// <summary> + /// Takes a sequence of type URIs and assigns aliases for all of them. + /// </summary> + /// <param name="typeUris">The type URIs to create aliases for.</param> + /// <param name="preferredTypeUriToAliases">An optional dictionary of URI/alias pairs that suggest preferred aliases to use if available for certain type URIs.</param> + public void AssignAliases(IEnumerable<string> typeUris, IDictionary<string, string> preferredTypeUriToAliases) { + Requires.NotNull(typeUris, "typeUris"); + + // First go through the actually used type URIs and see which ones have matching preferred aliases. + if (preferredTypeUriToAliases != null) { + foreach (string typeUri in typeUris) { + if (this.typeUriToAliasMap.ContainsKey(typeUri)) { + // this Type URI is already mapped to an alias. + continue; + } + + string preferredAlias; + if (preferredTypeUriToAliases.TryGetValue(typeUri, out preferredAlias) && !this.IsAliasUsed(preferredAlias)) { + this.SetAlias(preferredAlias, typeUri); + } + } + } + + // Now go through the whole list again and assign whatever is left now that the preferred ones + // have gotten their picks where available. + foreach (string typeUri in typeUris) { + if (this.typeUriToAliasMap.ContainsKey(typeUri)) { + // this Type URI is already mapped to an alias. + continue; + } + + this.AssignNewAlias(typeUri); + } + } + + /// <summary> + /// Sets up aliases for any Type URIs in a dictionary that do not yet have aliases defined, + /// and where the given preferred alias is still available. + /// </summary> + /// <param name="preferredTypeUriToAliases">A dictionary of type URI keys and alias values.</param> + public void SetPreferredAliasesWhereNotSet(IDictionary<string, string> preferredTypeUriToAliases) { + Requires.NotNull(preferredTypeUriToAliases, "preferredTypeUriToAliases"); + + foreach (var pair in preferredTypeUriToAliases) { + if (this.typeUriToAliasMap.ContainsKey(pair.Key)) { + // type URI is already mapped + continue; + } + + if (this.aliasToTypeUriMap.ContainsKey(pair.Value)) { + // alias is already mapped + continue; + } + + // The type URI and alias are as yet unset, so go ahead and assign them. + this.SetAlias(pair.Value, pair.Key); + } + } + + /// <summary> + /// Gets the Type Uri encoded by a given alias. + /// </summary> + /// <param name="alias">The alias.</param> + /// <returns>The Type URI.</returns> + /// <exception cref="ArgumentOutOfRangeException">Thrown if the given alias does not have a matching TypeURI.</exception> + public string ResolveAlias(string alias) { + Requires.NotNullOrEmpty(alias, "alias"); + string typeUri = this.TryResolveAlias(alias); + if (typeUri == null) { + throw new ArgumentOutOfRangeException("alias"); + } + return typeUri; + } + + /// <summary> + /// Gets the Type Uri encoded by a given alias. + /// </summary> + /// <param name="alias">The alias.</param> + /// <returns>The Type URI for the given alias, or null if none for that alias exist.</returns> + public string TryResolveAlias(string alias) { + Requires.NotNullOrEmpty(alias, "alias"); + string typeUri = null; + this.aliasToTypeUriMap.TryGetValue(alias, out typeUri); + return typeUri; + } + + /// <summary> + /// Returns a value indicating whether an alias has already been assigned to a type URI. + /// </summary> + /// <param name="alias">The alias in question.</param> + /// <returns>True if the alias has already been assigned. False otherwise.</returns> + public bool IsAliasUsed(string alias) { + Requires.NotNullOrEmpty(alias, "alias"); + return this.aliasToTypeUriMap.ContainsKey(alias); + } + + /// <summary> + /// Determines whether a given TypeURI has an associated alias assigned to it. + /// </summary> + /// <param name="typeUri">The type URI.</param> + /// <returns> + /// <c>true</c> if the given type URI already has an alias assigned; <c>false</c> otherwise. + /// </returns> + public bool IsAliasAssignedTo(string typeUri) { + Requires.NotNull(typeUri, "typeUri"); + return this.typeUriToAliasMap.ContainsKey(typeUri); + } + + /// <summary> + /// Assigns a new alias to a given Type URI. + /// </summary> + /// <param name="typeUri">The type URI to assign a new alias to.</param> + /// <returns>The newly generated alias.</returns> + private string AssignNewAlias(string typeUri) { + Requires.NotNullOrEmpty(typeUri, "typeUri"); + ErrorUtilities.VerifyInternal(!this.typeUriToAliasMap.ContainsKey(typeUri), "Oops! This type URI already has an alias!"); + string alias = string.Format(CultureInfo.InvariantCulture, AliasFormat, this.typeUriToAliasMap.Count + 1); + this.typeUriToAliasMap.Add(typeUri, alias); + this.aliasToTypeUriMap.Add(alias, typeUri); + return alias; + } + } +} diff --git a/src/DotNetOpenAuth/OpenId/Extensions/AttributeExchange/AXAttributeFormats.cs b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/AttributeExchange/AXAttributeFormats.cs index decd296..decd296 100644 --- a/src/DotNetOpenAuth/OpenId/Extensions/AttributeExchange/AXAttributeFormats.cs +++ b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/AttributeExchange/AXAttributeFormats.cs diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Extensions/AttributeExchange/AXUtilities.cs b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/AttributeExchange/AXUtilities.cs new file mode 100644 index 0000000..96cc437 --- /dev/null +++ b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/AttributeExchange/AXUtilities.cs @@ -0,0 +1,144 @@ +//----------------------------------------------------------------------- +// <copyright file="AXUtilities.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.Extensions.AttributeExchange { + using System; + using System.Collections.Generic; + using System.Diagnostics.Contracts; + using System.Globalization; + using DotNetOpenAuth.Messaging; + + /// <summary> + /// Helper methods shared by multiple messages in the Attribute Exchange extension. + /// </summary> + public static class AXUtilities { + /// <summary> + /// Adds a request for an attribute considering it 'required'. + /// </summary> + /// <param name="collection">The attribute request collection.</param> + /// <param name="typeUri">The type URI of the required attribute.</param> + public static void AddRequired(this ICollection<AttributeRequest> collection, string typeUri) { + Requires.NotNull(collection, "collection"); + collection.Add(new AttributeRequest(typeUri, true)); + } + + /// <summary> + /// Adds a request for an attribute without considering it 'required'. + /// </summary> + /// <param name="collection">The attribute request collection.</param> + /// <param name="typeUri">The type URI of the requested attribute.</param> + public static void AddOptional(this ICollection<AttributeRequest> collection, string typeUri) { + Requires.NotNull(collection, "collection"); + collection.Add(new AttributeRequest(typeUri, false)); + } + + /// <summary> + /// Adds a given attribute with one or more values to the request for storage. + /// Applicable to Relying Parties only. + /// </summary> + /// <param name="collection">The collection of <see cref="AttributeValues"/> to add to.</param> + /// <param name="typeUri">The type URI of the attribute.</param> + /// <param name="values">The attribute values.</param> + public static void Add(this ICollection<AttributeValues> collection, string typeUri, params string[] values) { + Requires.NotNull(collection, "collection"); + collection.Add(new AttributeValues(typeUri, values)); + } + + /// <summary> + /// Serializes a set of attribute values to a dictionary of fields to send in the message. + /// </summary> + /// <param name="fields">The dictionary to fill with serialized attributes.</param> + /// <param name="attributes">The attributes.</param> + internal static void SerializeAttributes(IDictionary<string, string> fields, IEnumerable<AttributeValues> attributes) { + Requires.NotNull(fields, "fields"); + Requires.NotNull(attributes, "attributes"); + + AliasManager aliasManager = new AliasManager(); + foreach (var att in attributes) { + string alias = aliasManager.GetAlias(att.TypeUri); + fields.Add("type." + alias, att.TypeUri); + if (att.Values == null) { + continue; + } + if (att.Values.Count != 1) { + fields.Add("count." + alias, att.Values.Count.ToString(CultureInfo.InvariantCulture)); + for (int i = 0; i < att.Values.Count; i++) { + fields.Add(string.Format(CultureInfo.InvariantCulture, "value.{0}.{1}", alias, i + 1), att.Values[i]); + } + } else { + fields.Add("value." + alias, att.Values[0]); + } + } + } + + /// <summary> + /// Deserializes attribute values from an incoming set of message data. + /// </summary> + /// <param name="fields">The data coming in with the message.</param> + /// <returns>The attribute values found in the message.</returns> + internal static IEnumerable<AttributeValues> DeserializeAttributes(IDictionary<string, string> fields) { + AliasManager aliasManager = ParseAliases(fields); + foreach (string alias in aliasManager.Aliases) { + AttributeValues att = new AttributeValues(aliasManager.ResolveAlias(alias)); + int count = 1; + bool countSent = false; + string countString; + if (fields.TryGetValue("count." + alias, out countString)) { + if (!int.TryParse(countString, out count) || count < 0) { + Logger.OpenId.ErrorFormat("Failed to parse count.{0} value to a non-negative integer.", alias); + continue; + } + countSent = true; + } + if (countSent) { + for (int i = 1; i <= count; i++) { + string value; + if (fields.TryGetValue(string.Format(CultureInfo.InvariantCulture, "value.{0}.{1}", alias, i), out value)) { + att.Values.Add(value); + } else { + Logger.OpenId.ErrorFormat("Missing value for attribute '{0}'.", att.TypeUri); + continue; + } + } + } else { + string value; + if (fields.TryGetValue("value." + alias, out value)) { + att.Values.Add(value); + } else { + Logger.OpenId.ErrorFormat("Missing value for attribute '{0}'.", att.TypeUri); + continue; + } + } + yield return att; + } + } + + /// <summary> + /// Reads through the attributes included in the response to discover + /// the alias-TypeURI relationships. + /// </summary> + /// <param name="fields">The data included in the extension message.</param> + /// <returns>The alias manager that provides lookup between aliases and type URIs.</returns> + private static AliasManager ParseAliases(IDictionary<string, string> fields) { + Requires.NotNull(fields, "fields"); + + AliasManager aliasManager = new AliasManager(); + const string TypePrefix = "type."; + foreach (var pair in fields) { + if (!pair.Key.StartsWith(TypePrefix, StringComparison.Ordinal)) { + continue; + } + string alias = pair.Key.Substring(TypePrefix.Length); + if (alias.IndexOfAny(FetchRequest.IllegalAliasCharacters) >= 0) { + Logger.OpenId.ErrorFormat("Illegal characters in alias name '{0}'.", alias); + continue; + } + aliasManager.SetAlias(alias, pair.Value); + } + return aliasManager; + } + } +} diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Extensions/AttributeExchange/AttributeRequest.cs b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/AttributeExchange/AttributeRequest.cs new file mode 100644 index 0000000..cbcbff6 --- /dev/null +++ b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/AttributeExchange/AttributeRequest.cs @@ -0,0 +1,156 @@ +//----------------------------------------------------------------------- +// <copyright file="AttributeRequest.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.Extensions.AttributeExchange { + using System; + using System.Diagnostics; + using System.Diagnostics.Contracts; + using DotNetOpenAuth.Messaging; + + /// <summary> + /// An individual attribute to be requested of the OpenID Provider using + /// the Attribute Exchange extension. + /// </summary> + [Serializable] + [DebuggerDisplay("{TypeUri} (required: {IsRequired}) ({Count})")] + public class AttributeRequest { + /// <summary> + /// Backing field for the <see cref="Count"/> property. + /// </summary> + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private int count = 1; + + /// <summary> + /// Initializes a new instance of the <see cref="AttributeRequest"/> class + /// with <see cref="IsRequired"/> = false, <see cref="Count"/> = 1. + /// </summary> + public AttributeRequest() { + } + + /// <summary> + /// Initializes a new instance of the <see cref="AttributeRequest"/> class + /// with <see cref="IsRequired"/> = false, <see cref="Count"/> = 1. + /// </summary> + /// <param name="typeUri">The unique TypeURI for that describes the attribute being sought.</param> + public AttributeRequest(string typeUri) { + Requires.NotNullOrEmpty(typeUri, "typeUri"); + this.TypeUri = typeUri; + } + + /// <summary> + /// Initializes a new instance of the <see cref="AttributeRequest"/> class + /// with <see cref="Count"/> = 1. + /// </summary> + /// <param name="typeUri">The unique TypeURI for that describes the attribute being sought.</param> + /// <param name="isRequired">A value indicating whether the Relying Party considers this attribute to be required for registration.</param> + public AttributeRequest(string typeUri, bool isRequired) + : this(typeUri) { + this.IsRequired = isRequired; + } + + /// <summary> + /// Initializes a new instance of the <see cref="AttributeRequest"/> class. + /// </summary> + /// <param name="typeUri">The unique TypeURI for that describes the attribute being sought.</param> + /// <param name="isRequired">A value indicating whether the Relying Party considers this attribute to be required for registration.</param> + /// <param name="count">The maximum number of values for this attribute the Relying Party is prepared to receive.</param> + public AttributeRequest(string typeUri, bool isRequired, int count) + : this(typeUri, isRequired) { + this.Count = count; + } + + /// <summary> + /// Gets or sets the URI uniquely identifying the attribute being requested. + /// </summary> + public string TypeUri { get; set; } + + /// <summary> + /// Gets or sets a value indicating whether the relying party considers this a required field. + /// Note that even if set to true, the Provider may not provide the value. + /// </summary> + public bool IsRequired { get; set; } + + /// <summary> + /// Gets or sets the maximum number of values for this attribute the + /// Relying Party wishes to receive from the OpenID Provider. + /// A value of int.MaxValue is considered infinity. + /// </summary> + public int Count { + get { + return this.count; + } + + set { + Requires.InRange(value > 0, "value"); + this.count = value; + } + } + + /// <summary> + /// Used by a Provider to create a response to a request for an attribute's value(s) + /// using a given array of strings. + /// </summary> + /// <param name="values">The values for the requested attribute.</param> + /// <returns> + /// The newly created <see cref="AttributeValues"/> object that should be added to + /// the <see cref="FetchResponse"/> object. + /// </returns> + public AttributeValues Respond(params string[] values) { + Requires.NotNull(values, "values"); + Requires.True(values.Length <= this.Count, "values"); + return new AttributeValues(this.TypeUri, values); + } + + /// <summary> + /// Determines whether the specified <see cref="T:System.Object"/> is equal to the current <see cref="T:System.Object"/>. + /// </summary> + /// <param name="obj">The <see cref="T:System.Object"/> to compare with the current <see cref="T:System.Object"/>.</param> + /// <returns> + /// true if the specified <see cref="T:System.Object"/> is equal to the current <see cref="T:System.Object"/>; otherwise, false. + /// </returns> + /// <exception cref="T:System.NullReferenceException"> + /// The <paramref name="obj"/> parameter is null. + /// </exception> + public override bool Equals(object obj) { + AttributeRequest other = obj as AttributeRequest; + if (other == null) { + return false; + } + + if (this.TypeUri != other.TypeUri) { + return false; + } + + if (this.Count != other.Count) { + return false; + } + + if (this.IsRequired != other.IsRequired) { + return false; + } + + return true; + } + + /// <summary> + /// Serves as a hash function for a particular type. + /// </summary> + /// <returns> + /// A hash code for the current <see cref="T:System.Object"/>. + /// </returns> + public override int GetHashCode() { + int hashCode = this.IsRequired ? 1 : 0; + unchecked { + hashCode += this.Count; + if (this.TypeUri != null) { + hashCode += this.TypeUri.GetHashCode(); + } + } + + return hashCode; + } + } +} diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Extensions/AttributeExchange/AttributeValues.cs b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/AttributeExchange/AttributeValues.cs new file mode 100644 index 0000000..37ebe38 --- /dev/null +++ b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/AttributeExchange/AttributeValues.cs @@ -0,0 +1,114 @@ +//----------------------------------------------------------------------- +// <copyright file="AttributeValues.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.Extensions.AttributeExchange { + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Diagnostics.Contracts; + using DotNetOpenAuth.Messaging; + + /// <summary> + /// An individual attribute's value(s) as supplied by an OpenID Provider + /// in response to a prior request by an OpenID Relying Party as part of + /// a fetch request, or by a relying party as part of a store request. + /// </summary> + [Serializable] + [DebuggerDisplay("{TypeUri}")] + public class AttributeValues { + /// <summary> + /// Initializes a new instance of the <see cref="AttributeValues"/> class. + /// </summary> + /// <param name="typeUri">The TypeURI that uniquely identifies the attribute.</param> + /// <param name="values">The values for the attribute.</param> + public AttributeValues(string typeUri, params string[] values) { + Requires.NotNullOrEmpty(typeUri, "typeUri"); + + this.TypeUri = typeUri; + this.Values = (IList<string>)values ?? EmptyList<string>.Instance; + } + + /// <summary> + /// Initializes a new instance of the <see cref="AttributeValues"/> class. + /// </summary> + /// <remarks> + /// This is internal because web sites should be using the + /// <see cref="AttributeRequest.Respond"/> method to instantiate. + /// </remarks> + internal AttributeValues() { + this.Values = new List<string>(1); + } + + /// <summary> + /// Initializes a new instance of the <see cref="AttributeValues"/> class. + /// </summary> + /// <param name="typeUri">The TypeURI of the attribute whose values are being provided.</param> + internal AttributeValues(string typeUri) { + Requires.NotNullOrEmpty(typeUri, "typeUri"); + + this.TypeUri = typeUri; + this.Values = new List<string>(1); + } + + /// <summary> + /// Gets the URI uniquely identifying the attribute whose value is being supplied. + /// </summary> + public string TypeUri { get; internal set; } + + /// <summary> + /// Gets the values supplied by the Provider. + /// </summary> + public IList<string> Values { get; private set; } + + /// <summary> + /// Determines whether the specified <see cref="T:System.Object"/> is equal to the current <see cref="T:System.Object"/>. + /// </summary> + /// <param name="obj">The <see cref="T:System.Object"/> to compare with the current <see cref="T:System.Object"/>.</param> + /// <returns> + /// true if the specified <see cref="T:System.Object"/> is equal to the current <see cref="T:System.Object"/>; otherwise, false. + /// </returns> + /// <exception cref="T:System.NullReferenceException"> + /// The <paramref name="obj"/> parameter is null. + /// </exception> + public override bool Equals(object obj) { + AttributeValues other = obj as AttributeValues; + if (other == null) { + return false; + } + + if (this.TypeUri != other.TypeUri) { + return false; + } + + if (!MessagingUtilities.AreEquivalent<string>(this.Values, other.Values)) { + return false; + } + + return true; + } + + /// <summary> + /// Serves as a hash function for a particular type. + /// </summary> + /// <returns> + /// A hash code for the current <see cref="T:System.Object"/>. + /// </returns> + public override int GetHashCode() { + int hashCode = 0; + unchecked { + if (this.TypeUri != null) { + hashCode += this.TypeUri.GetHashCode(); + } + + foreach (string value in this.Values) { + hashCode += value.GetHashCode(); + } + } + + return hashCode; + } + } +} diff --git a/src/DotNetOpenAuth/OpenId/Extensions/AttributeExchange/Constants.cs b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/AttributeExchange/Constants.cs index 167d0d2..167d0d2 100644 --- a/src/DotNetOpenAuth/OpenId/Extensions/AttributeExchange/Constants.cs +++ b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/AttributeExchange/Constants.cs diff --git a/src/DotNetOpenAuth/OpenId/Extensions/AttributeExchange/FetchRequest.cs b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/AttributeExchange/FetchRequest.cs index 124a18c..124a18c 100644 --- a/src/DotNetOpenAuth/OpenId/Extensions/AttributeExchange/FetchRequest.cs +++ b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/AttributeExchange/FetchRequest.cs diff --git a/src/DotNetOpenAuth/OpenId/Extensions/AttributeExchange/FetchResponse.cs b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/AttributeExchange/FetchResponse.cs index 14b1caa..14b1caa 100644 --- a/src/DotNetOpenAuth/OpenId/Extensions/AttributeExchange/FetchResponse.cs +++ b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/AttributeExchange/FetchResponse.cs diff --git a/src/DotNetOpenAuth/OpenId/Extensions/AttributeExchange/StoreRequest.cs b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/AttributeExchange/StoreRequest.cs index 641b17a..641b17a 100644 --- a/src/DotNetOpenAuth/OpenId/Extensions/AttributeExchange/StoreRequest.cs +++ b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/AttributeExchange/StoreRequest.cs diff --git a/src/DotNetOpenAuth/OpenId/Extensions/AttributeExchange/StoreResponse.cs b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/AttributeExchange/StoreResponse.cs index ba7f091..ba7f091 100644 --- a/src/DotNetOpenAuth/OpenId/Extensions/AttributeExchange/StoreResponse.cs +++ b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/AttributeExchange/StoreResponse.cs diff --git a/src/DotNetOpenAuth/OpenId/Extensions/AttributeExchange/WellKnownAttributes.cs b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/AttributeExchange/WellKnownAttributes.cs index 5aa89c6..5aa89c6 100644 --- a/src/DotNetOpenAuth/OpenId/Extensions/AttributeExchange/WellKnownAttributes.cs +++ b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/AttributeExchange/WellKnownAttributes.cs diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Extensions/ExtensionArgumentsManager.cs b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/ExtensionArgumentsManager.cs new file mode 100644 index 0000000..328d81f --- /dev/null +++ b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/ExtensionArgumentsManager.cs @@ -0,0 +1,223 @@ +//----------------------------------------------------------------------- +// <copyright file="ExtensionArgumentsManager.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.Extensions { + using System; + using System.Collections.Generic; + using System.Diagnostics.Contracts; + using System.Globalization; + using System.Text; + using DotNetOpenAuth.Messaging; + + /// <summary> + /// Manages the processing and construction of OpenID extensions parts. + /// </summary> + internal class ExtensionArgumentsManager { + /// <summary> + /// This contains a set of aliases that we must be willing to implicitly + /// match to namespaces for backward compatibility with other OpenID libraries. + /// </summary> + private static readonly Dictionary<string, string> typeUriToAliasAffinity = new Dictionary<string, string> { + { Extensions.SimpleRegistration.Constants.sreg_ns, Extensions.SimpleRegistration.Constants.sreg_compatibility_alias }, + { Extensions.ProviderAuthenticationPolicy.Constants.TypeUri, Extensions.ProviderAuthenticationPolicy.Constants.CompatibilityAlias }, + }; + + /// <summary> + /// The version of OpenID that the message is using. + /// </summary> + private Protocol protocol; + + /// <summary> + /// Whether extensions are being read or written. + /// </summary> + private bool isReadMode; + + /// <summary> + /// The alias manager that will track Type URI to alias mappings. + /// </summary> + private AliasManager aliasManager = new AliasManager(); + + /// <summary> + /// A complex dictionary where the key is the Type URI of the extension, + /// and the value is another dictionary of the name/value args of the extension. + /// </summary> + private Dictionary<string, IDictionary<string, string>> extensions = new Dictionary<string, IDictionary<string, string>>(); + + /// <summary> + /// Prevents a default instance of the <see cref="ExtensionArgumentsManager"/> class from being created. + /// </summary> + private ExtensionArgumentsManager() { } + + /// <summary> + /// Gets a value indicating whether the extensions are being read (as opposed to written). + /// </summary> + internal bool ReadMode { + get { return this.isReadMode; } + } + + /// <summary> + /// Creates a <see cref="ExtensionArgumentsManager"/> instance to process incoming extensions. + /// </summary> + /// <param name="query">The parameters in the OpenID message.</param> + /// <returns>The newly created instance of <see cref="ExtensionArgumentsManager"/>.</returns> + public static ExtensionArgumentsManager CreateIncomingExtensions(IDictionary<string, string> query) { + Requires.NotNull(query, "query"); + var mgr = new ExtensionArgumentsManager(); + mgr.protocol = Protocol.Detect(query); + mgr.isReadMode = true; + string aliasPrefix = mgr.protocol.openid.ns + "."; + + // First pass looks for namespace aliases + foreach (var pair in query) { + if (pair.Key.StartsWith(aliasPrefix, StringComparison.Ordinal)) { + mgr.aliasManager.SetAlias(pair.Key.Substring(aliasPrefix.Length), pair.Value); + } + } + + // For backwards compatibility, add certain aliases if they aren't defined. + if (mgr.protocol.Version.Major < 2) { + foreach (var pair in typeUriToAliasAffinity) { + if (!mgr.aliasManager.IsAliasAssignedTo(pair.Key) && + !mgr.aliasManager.IsAliasUsed(pair.Value)) { + mgr.aliasManager.SetAlias(pair.Value, pair.Key); + } + } + } + + // Second pass looks for extensions using those aliases + foreach (var pair in query) { + if (!pair.Key.StartsWith(mgr.protocol.openid.Prefix, StringComparison.Ordinal)) { + continue; + } + string possibleAlias = pair.Key.Substring(mgr.protocol.openid.Prefix.Length); + int periodIndex = possibleAlias.IndexOf(".", StringComparison.Ordinal); + if (periodIndex >= 0) { + possibleAlias = possibleAlias.Substring(0, periodIndex); + } + string typeUri; + if ((typeUri = mgr.aliasManager.TryResolveAlias(possibleAlias)) != null) { + if (!mgr.extensions.ContainsKey(typeUri)) { + mgr.extensions[typeUri] = new Dictionary<string, string>(); + } + string key = periodIndex >= 0 ? pair.Key.Substring(mgr.protocol.openid.Prefix.Length + possibleAlias.Length + 1) : string.Empty; + mgr.extensions[typeUri].Add(key, pair.Value); + } + } + return mgr; + } + + /// <summary> + /// Creates a <see cref="ExtensionArgumentsManager"/> instance to prepare outgoing extensions. + /// </summary> + /// <param name="protocol">The protocol version used for the outgoing message.</param> + /// <returns> + /// The newly created instance of <see cref="ExtensionArgumentsManager"/>. + /// </returns> + public static ExtensionArgumentsManager CreateOutgoingExtensions(Protocol protocol) { + var mgr = new ExtensionArgumentsManager(); + mgr.protocol = protocol; + + // Affinity for certain alias for backwards compatibility + foreach (var pair in typeUriToAliasAffinity) { + mgr.aliasManager.SetAlias(pair.Value, pair.Key); + } + return mgr; + } + + /// <summary> + /// Adds query parameters for OpenID extensions to the request directed + /// at the OpenID provider. + /// </summary> + /// <param name="extensionTypeUri">The extension type URI.</param> + /// <param name="arguments">The arguments for this extension to add to the message.</param> + public void AddExtensionArguments(string extensionTypeUri, IDictionary<string, string> arguments) { + Requires.ValidState(!this.ReadMode); + Requires.NotNullOrEmpty(extensionTypeUri, "extensionTypeUri"); + Requires.NotNull(arguments, "arguments"); + if (arguments.Count == 0) { + return; + } + + IDictionary<string, string> extensionArgs; + if (!this.extensions.TryGetValue(extensionTypeUri, out extensionArgs)) { + this.extensions.Add(extensionTypeUri, extensionArgs = new Dictionary<string, string>(arguments.Count)); + } + + ErrorUtilities.VerifyProtocol(extensionArgs.Count == 0, OpenIdStrings.ExtensionAlreadyAddedWithSameTypeURI, extensionTypeUri); + foreach (var pair in arguments) { + extensionArgs.Add(pair.Key, pair.Value); + } + } + + /// <summary> + /// Gets the actual arguments to add to a querystring or other response, + /// where type URI, alias, and actual key/values are all defined. + /// </summary> + /// <param name="includeOpenIdPrefix"> + /// <c>true</c> if the generated parameter names should include the 'openid.' prefix. + /// This should be <c>true</c> for all but direct response messages. + /// </param> + /// <returns>A dictionary of key=value pairs to add to the message to carry the extension.</returns> + internal IDictionary<string, string> GetArgumentsToSend(bool includeOpenIdPrefix) { + Requires.ValidState(!this.ReadMode); + Dictionary<string, string> args = new Dictionary<string, string>(); + foreach (var typeUriAndExtension in this.extensions) { + string typeUri = typeUriAndExtension.Key; + var extensionArgs = typeUriAndExtension.Value; + if (extensionArgs.Count == 0) { + continue; + } + string alias = this.aliasManager.GetAlias(typeUri); + + // send out the alias declaration + string openidPrefix = includeOpenIdPrefix ? this.protocol.openid.Prefix : string.Empty; + args.Add(openidPrefix + this.protocol.openidnp.ns + "." + alias, typeUri); + string prefix = openidPrefix + alias; + foreach (var pair in extensionArgs) { + string key = prefix; + if (pair.Key.Length > 0) { + key += "." + pair.Key; + } + args.Add(key, pair.Value); + } + } + return args; + } + + /// <summary> + /// Gets the fields carried by a given OpenId extension. + /// </summary> + /// <param name="extensionTypeUri">The type URI of the extension whose fields are being queried for.</param> + /// <returns> + /// The fields included in the given extension, or null if the extension is not present. + /// </returns> + internal IDictionary<string, string> GetExtensionArguments(string extensionTypeUri) { + Requires.NotNullOrEmpty(extensionTypeUri, "extensionTypeUri"); + Requires.ValidState(this.ReadMode); + + IDictionary<string, string> extensionArgs; + this.extensions.TryGetValue(extensionTypeUri, out extensionArgs); + return extensionArgs; + } + + /// <summary> + /// Gets whether any arguments for a given extension are present. + /// </summary> + /// <param name="extensionTypeUri">The extension Type URI in question.</param> + /// <returns><c>true</c> if this extension is present; <c>false</c> otherwise.</returns> + internal bool ContainsExtension(string extensionTypeUri) { + return this.extensions.ContainsKey(extensionTypeUri); + } + + /// <summary> + /// Gets the type URIs of all discovered extensions in the message. + /// </summary> + /// <returns>A sequence of the type URIs.</returns> + internal IEnumerable<string> GetExtensionTypeUris() { + return this.extensions.Keys; + } + } +} diff --git a/src/DotNetOpenAuth/OpenId/Extensions/ExtensionBase.cs b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/ExtensionBase.cs index 108ac52..108ac52 100644 --- a/src/DotNetOpenAuth/OpenId/Extensions/ExtensionBase.cs +++ b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/ExtensionBase.cs diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Extensions/IClientScriptExtensionResponse.cs b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/IClientScriptExtensionResponse.cs new file mode 100644 index 0000000..b44f797 --- /dev/null +++ b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/IClientScriptExtensionResponse.cs @@ -0,0 +1,32 @@ +//----------------------------------------------------------------------- +// <copyright file="IClientScriptExtensionResponse.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.Extensions { + using System.Collections.Generic; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OpenId.Messages; + + /// <summary> + /// An interface that OpenID extensions can implement to allow authentication response + /// messages with included extensions to be processed by Javascript on the user agent. + /// </summary> + public interface IClientScriptExtensionResponse : IExtensionMessage { + /// <summary> + /// Reads the extension information on an authentication response from the provider. + /// </summary> + /// <param name="response">The incoming OpenID response carrying the extension.</param> + /// <returns> + /// A Javascript snippet that when executed on the user agent returns an object with + /// the information deserialized from the extension response. + /// </returns> + /// <remarks> + /// This method is called <b>before</b> the signature on the assertion response has been + /// verified. Therefore all information in these fields should be assumed unreliable + /// and potentially falsified. + /// </remarks> + string InitializeJavaScriptData(IProtocolMessageWithExtensions response); + } +} diff --git a/src/DotNetOpenAuth/OpenId/Extensions/OAuth/AuthorizationApprovedResponse.cs b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/OAuth/AuthorizationApprovedResponse.cs index 5e7bc49..5e7bc49 100644 --- a/src/DotNetOpenAuth/OpenId/Extensions/OAuth/AuthorizationApprovedResponse.cs +++ b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/OAuth/AuthorizationApprovedResponse.cs diff --git a/src/DotNetOpenAuth/OpenId/Extensions/OAuth/AuthorizationDeclinedResponse.cs b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/OAuth/AuthorizationDeclinedResponse.cs index 7c3a5ad..7c3a5ad 100644 --- a/src/DotNetOpenAuth/OpenId/Extensions/OAuth/AuthorizationDeclinedResponse.cs +++ b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/OAuth/AuthorizationDeclinedResponse.cs diff --git a/src/DotNetOpenAuth/OpenId/Extensions/OAuth/AuthorizationRequest.cs b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/OAuth/AuthorizationRequest.cs index 99f0880..99f0880 100644 --- a/src/DotNetOpenAuth/OpenId/Extensions/OAuth/AuthorizationRequest.cs +++ b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/OAuth/AuthorizationRequest.cs diff --git a/src/DotNetOpenAuth/OpenId/Extensions/OAuth/Constants.cs b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/OAuth/Constants.cs index 32efee9..32efee9 100644 --- a/src/DotNetOpenAuth/OpenId/Extensions/OAuth/Constants.cs +++ b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/OAuth/Constants.cs diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Extensions/OpenIdExtensionFactoryAggregator.cs b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/OpenIdExtensionFactoryAggregator.cs new file mode 100644 index 0000000..95dd2c9 --- /dev/null +++ b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/OpenIdExtensionFactoryAggregator.cs @@ -0,0 +1,81 @@ +//----------------------------------------------------------------------- +// <copyright file="OpenIdExtensionFactoryAggregator.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.Extensions { + using System.Collections.Generic; + using System.Diagnostics.Contracts; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OpenId.ChannelElements; + using DotNetOpenAuth.OpenId.Messages; + + /// <summary> + /// An OpenID extension factory that only delegates extension + /// instantiation requests to other factories. + /// </summary> + internal class OpenIdExtensionFactoryAggregator : IOpenIdExtensionFactory { + /// <summary> + /// The list of factories this factory delegates to. + /// </summary> + private List<IOpenIdExtensionFactory> factories = new List<IOpenIdExtensionFactory>(2); + + /// <summary> + /// Initializes a new instance of the <see cref="OpenIdExtensionFactoryAggregator"/> class. + /// </summary> + internal OpenIdExtensionFactoryAggregator() { + } + + /// <summary> + /// Gets the extension factories that this aggregating factory delegates to. + /// </summary> + /// <value>A list of factories. May be empty, but never null.</value> + internal IList<IOpenIdExtensionFactory> Factories { + get { return this.factories; } + } + + #region IOpenIdExtensionFactory Members + + /// <summary> + /// Creates a new instance of some extension based on the received extension parameters. + /// </summary> + /// <param name="typeUri">The type URI of the extension.</param> + /// <param name="data">The parameters associated specifically with this extension.</param> + /// <param name="baseMessage">The OpenID message carrying this extension.</param> + /// <param name="isProviderRole">A value indicating whether this extension is being received at the OpenID Provider.</param> + /// <returns> + /// An instance of <see cref="IOpenIdMessageExtension"/> if the factory recognizes + /// the extension described in the input parameters; <c>null</c> otherwise. + /// </returns> + /// <remarks> + /// This factory method need only initialize properties in the instantiated extension object + /// that are not bound using <see cref="MessagePartAttribute"/>. + /// </remarks> + public IOpenIdMessageExtension Create(string typeUri, IDictionary<string, string> data, IProtocolMessageWithExtensions baseMessage, bool isProviderRole) { + foreach (var factory in this.factories) { + IOpenIdMessageExtension result = factory.Create(typeUri, data, baseMessage, isProviderRole); + if (result != null) { + return result; + } + } + + return null; + } + + #endregion + + /// <summary> + /// Loads the default factory and additional ones given by the configuration. + /// </summary> + /// <returns>A new instance of <see cref="OpenIdExtensionFactoryAggregator"/>.</returns> + internal static OpenIdExtensionFactoryAggregator LoadFromConfiguration() { + Contract.Ensures(Contract.Result<OpenIdExtensionFactoryAggregator>() != null); + var factoriesElement = DotNetOpenAuth.Configuration.OpenIdElement.Configuration.ExtensionFactories; + var aggregator = new OpenIdExtensionFactoryAggregator(); + aggregator.Factories.Add(new StandardOpenIdExtensionFactory()); + aggregator.factories.AddRange(factoriesElement.CreateInstances(false)); + return aggregator; + } + } +} diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Extensions/OpenIdExtensionsInteropHelper.cs b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/OpenIdExtensionsInteropHelper.cs new file mode 100644 index 0000000..fb6202e --- /dev/null +++ b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/OpenIdExtensionsInteropHelper.cs @@ -0,0 +1,124 @@ +//----------------------------------------------------------------------- +// <copyright file="OpenIdExtensionsInteropHelper.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.Extensions { + using System; + using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; + using System.Diagnostics.Contracts; + using System.Linq; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OpenId.Extensions.AttributeExchange; + using DotNetOpenAuth.OpenId.Extensions.SimpleRegistration; + using DotNetOpenAuth.OpenId.Messages; + + /// <summary> + /// A set of methods designed to assist in improving interop across different + /// OpenID implementations and their extensions. + /// </summary> + internal static class OpenIdExtensionsInteropHelper { + /// <summary> + /// The gender decoder to translate AX genders to Sreg. + /// </summary> + private static GenderEncoder genderEncoder = new GenderEncoder(); + + /// <summary> + /// Gets the gender decoder to translate AX genders to Sreg. + /// </summary> + internal static GenderEncoder GenderEncoder { + get { return genderEncoder; } + } + + /// <summary> + /// Splits the AX attribute format flags into individual values for processing. + /// </summary> + /// <param name="formats">The formats to split up into individual flags.</param> + /// <returns>A sequence of individual flags.</returns> + internal static IEnumerable<AXAttributeFormats> ForEachFormat(AXAttributeFormats formats) { + if ((formats & AXAttributeFormats.AXSchemaOrg) != 0) { + yield return AXAttributeFormats.AXSchemaOrg; + } + + if ((formats & AXAttributeFormats.OpenIdNetSchema) != 0) { + yield return AXAttributeFormats.OpenIdNetSchema; + } + + if ((formats & AXAttributeFormats.SchemaOpenIdNet) != 0) { + yield return AXAttributeFormats.SchemaOpenIdNet; + } + } + + /// <summary> + /// Transforms an AX attribute type URI from the axschema.org format into a given format. + /// </summary> + /// <param name="axSchemaOrgFormatTypeUri">The ax schema org format type URI.</param> + /// <param name="targetFormat">The target format. Only one flag should be set.</param> + /// <returns>The AX attribute type URI in the target format.</returns> + internal static string TransformAXFormat(string axSchemaOrgFormatTypeUri, AXAttributeFormats targetFormat) { + Requires.NotNullOrEmpty(axSchemaOrgFormatTypeUri, "axSchemaOrgFormatTypeUri"); + + switch (targetFormat) { + case AXAttributeFormats.AXSchemaOrg: + return axSchemaOrgFormatTypeUri; + case AXAttributeFormats.SchemaOpenIdNet: + return axSchemaOrgFormatTypeUri.Replace("axschema.org", "schema.openid.net"); + case AXAttributeFormats.OpenIdNetSchema: + return axSchemaOrgFormatTypeUri.Replace("axschema.org", "openid.net/schema"); + default: + throw new ArgumentOutOfRangeException("targetFormat"); + } + } + + /// <summary> + /// Detects the AX attribute type URI format from a given sample. + /// </summary> + /// <param name="typeURIs">The type URIs to scan for recognized formats.</param> + /// <returns>The first AX type URI format recognized in the list.</returns> + internal static AXAttributeFormats DetectAXFormat(IEnumerable<string> typeURIs) { + Requires.NotNull(typeURIs, "typeURIs"); + + if (typeURIs.Any(uri => uri.StartsWith("http://axschema.org/", StringComparison.Ordinal))) { + return AXAttributeFormats.AXSchemaOrg; + } + + if (typeURIs.Any(uri => uri.StartsWith("http://schema.openid.net/", StringComparison.Ordinal))) { + return AXAttributeFormats.SchemaOpenIdNet; + } + + if (typeURIs.Any(uri => uri.StartsWith("http://openid.net/schema/", StringComparison.Ordinal))) { + return AXAttributeFormats.OpenIdNetSchema; + } + + return AXAttributeFormats.None; + } + + /// <summary> + /// Adds an attribute fetch request if it is not already present in the AX request. + /// </summary> + /// <param name="ax">The AX request to add the attribute request to.</param> + /// <param name="format">The format of the attribute's Type URI to use.</param> + /// <param name="axSchemaOrgFormatAttribute">The attribute in axschema.org format.</param> + /// <param name="demandLevel">The demand level.</param> + internal static void FetchAttribute(FetchRequest ax, AXAttributeFormats format, string axSchemaOrgFormatAttribute, DemandLevel demandLevel) { + Requires.NotNull(ax, "ax"); + Requires.NotNullOrEmpty(axSchemaOrgFormatAttribute, "axSchemaOrgFormatAttribute"); + + string typeUri = TransformAXFormat(axSchemaOrgFormatAttribute, format); + if (!ax.Attributes.Contains(typeUri)) { + switch (demandLevel) { + case DemandLevel.Request: + ax.Attributes.AddOptional(typeUri); + break; + case DemandLevel.Require: + ax.Attributes.AddRequired(typeUri); + break; + default: + break; + } + } + } + } +} diff --git a/src/DotNetOpenAuth/OpenId/Extensions/ProviderAuthenticationPolicy/AuthenticationPolicies.cs b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/ProviderAuthenticationPolicy/AuthenticationPolicies.cs index 99c7a2e..99c7a2e 100644 --- a/src/DotNetOpenAuth/OpenId/Extensions/ProviderAuthenticationPolicy/AuthenticationPolicies.cs +++ b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/ProviderAuthenticationPolicy/AuthenticationPolicies.cs diff --git a/src/DotNetOpenAuth/OpenId/Extensions/ProviderAuthenticationPolicy/Constants.cs b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/ProviderAuthenticationPolicy/Constants.cs index 93e76d5..93e76d5 100644 --- a/src/DotNetOpenAuth/OpenId/Extensions/ProviderAuthenticationPolicy/Constants.cs +++ b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/ProviderAuthenticationPolicy/Constants.cs diff --git a/src/DotNetOpenAuth/OpenId/Extensions/ProviderAuthenticationPolicy/DateTimeEncoder.cs b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/ProviderAuthenticationPolicy/DateTimeEncoder.cs index 9dc0574..9dc0574 100644 --- a/src/DotNetOpenAuth/OpenId/Extensions/ProviderAuthenticationPolicy/DateTimeEncoder.cs +++ b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/ProviderAuthenticationPolicy/DateTimeEncoder.cs diff --git a/src/DotNetOpenAuth/OpenId/Extensions/ProviderAuthenticationPolicy/NistAssuranceLevel.cs b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/ProviderAuthenticationPolicy/NistAssuranceLevel.cs index 3031aad..3031aad 100644 --- a/src/DotNetOpenAuth/OpenId/Extensions/ProviderAuthenticationPolicy/NistAssuranceLevel.cs +++ b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/ProviderAuthenticationPolicy/NistAssuranceLevel.cs diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Extensions/ProviderAuthenticationPolicy/PapeUtilities.cs b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/ProviderAuthenticationPolicy/PapeUtilities.cs new file mode 100644 index 0000000..d8ffb63 --- /dev/null +++ b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/ProviderAuthenticationPolicy/PapeUtilities.cs @@ -0,0 +1,65 @@ +//----------------------------------------------------------------------- +// <copyright file="PapeUtilities.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.Extensions.ProviderAuthenticationPolicy { + using System; + using System.Collections.Generic; + using System.Diagnostics.Contracts; + using System.Globalization; + using System.Linq; + using System.Text; + using DotNetOpenAuth.Messaging; + + /// <summary> + /// Utility methods for use by the PAPE extension. + /// </summary> + internal static class PapeUtilities { + /// <summary> + /// Looks at the incoming fields and figures out what the aliases and name spaces for auth level types are. + /// </summary> + /// <param name="fields">The incoming message data in which to discover TypeURIs and aliases.</param> + /// <returns>The <see cref="AliasManager"/> initialized with the given data.</returns> + internal static AliasManager FindIncomingAliases(IDictionary<string, string> fields) { + AliasManager aliasManager = new AliasManager(); + + foreach (var pair in fields) { + if (!pair.Key.StartsWith(Constants.AuthLevelNamespaceDeclarationPrefix, StringComparison.Ordinal)) { + continue; + } + + string alias = pair.Key.Substring(Constants.AuthLevelNamespaceDeclarationPrefix.Length); + aliasManager.SetAlias(alias, pair.Value); + } + + aliasManager.SetPreferredAliasesWhereNotSet(Constants.AssuranceLevels.PreferredTypeUriToAliasMap); + + return aliasManager; + } + + /// <summary> + /// Concatenates a sequence of strings using a space as a separator. + /// </summary> + /// <param name="values">The elements to concatenate together..</param> + /// <returns>The concatenated string of elements.</returns> + /// <exception cref="FormatException">Thrown if any element in the sequence includes a space.</exception> + internal static string ConcatenateListOfElements(IEnumerable<string> values) { + Requires.NotNull(values, "values"); + + StringBuilder valuesList = new StringBuilder(); + foreach (string value in values.Distinct()) { + if (value.Contains(" ")) { + throw new FormatException(string.Format(CultureInfo.CurrentCulture, OpenIdStrings.InvalidUri, value)); + } + valuesList.Append(value); + valuesList.Append(" "); + } + if (valuesList.Length > 0) { + valuesList.Length -= 1; // remove trailing space + } + return valuesList.ToString(); + } + } +} diff --git a/src/DotNetOpenAuth/OpenId/Extensions/ProviderAuthenticationPolicy/PolicyRequest.cs b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/ProviderAuthenticationPolicy/PolicyRequest.cs index 84589dc..84589dc 100644 --- a/src/DotNetOpenAuth/OpenId/Extensions/ProviderAuthenticationPolicy/PolicyRequest.cs +++ b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/ProviderAuthenticationPolicy/PolicyRequest.cs diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Extensions/ProviderAuthenticationPolicy/PolicyResponse.cs b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/ProviderAuthenticationPolicy/PolicyResponse.cs new file mode 100644 index 0000000..1fddc22 --- /dev/null +++ b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/ProviderAuthenticationPolicy/PolicyResponse.cs @@ -0,0 +1,282 @@ +//----------------------------------------------------------------------- +// <copyright file="PolicyResponse.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.Extensions.ProviderAuthenticationPolicy { + using System; + using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; + using System.Diagnostics.Contracts; + using System.Globalization; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OpenId.Messages; + + /// <summary> + /// The PAPE response part of an OpenID Authentication response message. + /// </summary> + [Serializable] + public sealed class PolicyResponse : ExtensionBase, IMessageWithEvents { + /// <summary> + /// The factory method that may be used in deserialization of this message. + /// </summary> + internal static readonly StandardOpenIdExtensionFactory.CreateDelegate Factory = (typeUri, data, baseMessage, isProviderRole) => { + if (typeUri == Constants.TypeUri && !isProviderRole) { + return new PolicyResponse(); + } + + return null; + }; + + /// <summary> + /// The first part of a parameter name that gives the custom string value for + /// the assurance level. The second part of the parameter name is the alias for + /// that assurance level. + /// </summary> + private const string AuthLevelAliasPrefix = "auth_level."; + + /// <summary> + /// One or more authentication policy URIs that the OP conformed to when authenticating the End User. + /// </summary> + /// <value>Space separated list of authentication policy URIs.</value> + /// <remarks> + /// If no policies were met though the OP wishes to convey other information in the response, this parameter MUST be included with the value of "none". + /// </remarks> + [MessagePart("auth_policies", IsRequired = true)] + private string actualPoliciesString; + + /// <summary> + /// Backing field for the <see cref="AuthenticationTimeUtc"/> property. + /// </summary> + private DateTime? authenticationTimeUtc; + + /// <summary> + /// Initializes a new instance of the <see cref="PolicyResponse"/> class. + /// </summary> + public PolicyResponse() + : base(new Version(1, 0), Constants.TypeUri, null) { + this.ActualPolicies = new List<string>(1); + this.AssuranceLevels = new Dictionary<string, string>(1); + } + + /// <summary> + /// Gets a list of authentication policy URIs that the + /// OP conformed to when authenticating the End User. + /// </summary> + public IList<string> ActualPolicies { get; private set; } + + /// <summary> + /// Gets or sets the most recent timestamp when the End User has + /// actively authenticated to the OP in a manner fitting the asserted policies. + /// </summary> + /// <remarks> + /// If the RP's request included the "openid.max_auth_age" parameter + /// then the OP MUST include "openid.auth_time" in its response. + /// If "openid.max_auth_age" was not requested, the OP MAY choose to include + /// "openid.auth_time" in its response. + /// </remarks> + [MessagePart("auth_time", Encoder = typeof(DateTimeEncoder))] + public DateTime? AuthenticationTimeUtc { + get { + return this.authenticationTimeUtc; + } + + set { + Requires.True(!value.HasValue || value.Value.Kind != DateTimeKind.Unspecified, "value", OpenIdStrings.UnspecifiedDateTimeKindNotAllowed); + + // Make sure that whatever is set here, it becomes UTC time. + if (value.HasValue) { + // Convert to UTC and cut to the second, since the protocol only allows for + // that level of precision. + this.authenticationTimeUtc = OpenIdUtilities.CutToSecond(value.Value.ToUniversalTimeSafe()); + } else { + this.authenticationTimeUtc = null; + } + } + } + + /// <summary> + /// Gets or sets the Assurance Level as defined by the National + /// Institute of Standards and Technology (NIST) in Special Publication + /// 800-63 (Burr, W., Dodson, D., and W. Polk, Ed., “Electronic + /// Authentication Guideline,” April 2006.) [NIST_SP800‑63] corresponding + /// to the authentication method and policies employed by the OP when + /// authenticating the End User. + /// </summary> + /// <remarks> + /// See PAPE spec Appendix A.1.2 (NIST Assurance Levels) for high-level + /// example classifications of authentication methods within the defined + /// levels. + /// </remarks> + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Nist", Justification = "Acronym")] + public NistAssuranceLevel? NistAssuranceLevel { + get { + string levelString; + if (this.AssuranceLevels.TryGetValue(Constants.AssuranceLevels.NistTypeUri, out levelString)) { + return (NistAssuranceLevel)Enum.Parse(typeof(NistAssuranceLevel), levelString); + } else { + return null; + } + } + + set { + if (value != null) { + this.AssuranceLevels[Constants.AssuranceLevels.NistTypeUri] = ((int)value).ToString(CultureInfo.InvariantCulture); + } else { + this.AssuranceLevels.Remove(Constants.AssuranceLevels.NistTypeUri); + } + } + } + + /// <summary> + /// Gets a dictionary where keys are the authentication level type URIs and + /// the values are the per authentication level defined custom value. + /// </summary> + /// <remarks> + /// A very common key is <see cref="Constants.AssuranceLevels.NistTypeUri"/> + /// and values for this key are available in <see cref="NistAssuranceLevel"/>. + /// </remarks> + public IDictionary<string, string> AssuranceLevels { get; private set; } + + /// <summary> + /// Gets a value indicating whether this extension is signed by the Provider. + /// </summary> + /// <value> + /// <c>true</c> if this instance is signed by the Provider; otherwise, <c>false</c>. + /// </value> + public bool IsSignedByProvider { + get { return this.IsSignedByRemoteParty; } + } + + #region IMessageWithEvents Members + + /// <summary> + /// Called when the message is about to be transmitted, + /// before it passes through the channel binding elements. + /// </summary> + void IMessageWithEvents.OnSending() { + var extraData = ((IMessage)this).ExtraData; + extraData.Clear(); + + this.actualPoliciesString = SerializePolicies(this.ActualPolicies); + + if (this.AssuranceLevels.Count > 0) { + AliasManager aliases = new AliasManager(); + aliases.AssignAliases(this.AssuranceLevels.Keys, Constants.AssuranceLevels.PreferredTypeUriToAliasMap); + + // Add a definition for each Auth Level Type alias. + foreach (string alias in aliases.Aliases) { + extraData.Add(Constants.AuthLevelNamespaceDeclarationPrefix + alias, aliases.ResolveAlias(alias)); + } + + // Now use the aliases for those type URIs to list the individual values. + foreach (var pair in this.AssuranceLevels) { + extraData.Add(AuthLevelAliasPrefix + aliases.GetAlias(pair.Key), pair.Value); + } + } + } + + /// <summary> + /// Called when the message has been received, + /// after it passes through the channel binding elements. + /// </summary> + void IMessageWithEvents.OnReceiving() { + var extraData = ((IMessage)this).ExtraData; + + this.ActualPolicies.Clear(); + string[] actualPolicies = this.actualPoliciesString.Split(' '); + foreach (string policy in actualPolicies) { + if (policy.Length > 0 && policy != AuthenticationPolicies.None) { + this.ActualPolicies.Add(policy); + } + } + + this.AssuranceLevels.Clear(); + AliasManager authLevelAliases = PapeUtilities.FindIncomingAliases(extraData); + foreach (string authLevelAlias in authLevelAliases.Aliases) { + string authValue; + if (extraData.TryGetValue(AuthLevelAliasPrefix + authLevelAlias, out authValue)) { + string authLevelType = authLevelAliases.ResolveAlias(authLevelAlias); + this.AssuranceLevels[authLevelType] = authValue; + } + } + } + + #endregion + + /// <summary> + /// Determines whether the specified <see cref="T:System.Object"/> is equal to the current <see cref="T:System.Object"/>. + /// </summary> + /// <param name="obj">The <see cref="T:System.Object"/> to compare with the current <see cref="T:System.Object"/>.</param> + /// <returns> + /// true if the specified <see cref="T:System.Object"/> is equal to the current <see cref="T:System.Object"/>; otherwise, false. + /// </returns> + /// <exception cref="T:System.NullReferenceException"> + /// The <paramref name="obj"/> parameter is null. + /// </exception> + public override bool Equals(object obj) { + PolicyResponse other = obj as PolicyResponse; + if (other == null) { + return false; + } + + if (this.AuthenticationTimeUtc != other.AuthenticationTimeUtc) { + return false; + } + + if (this.AssuranceLevels.Count != other.AssuranceLevels.Count) { + return false; + } + + foreach (var pair in this.AssuranceLevels) { + if (!other.AssuranceLevels.Contains(pair)) { + return false; + } + } + + if (this.ActualPolicies.Count != other.ActualPolicies.Count) { + return false; + } + + foreach (string policy in this.ActualPolicies) { + if (!other.ActualPolicies.Contains(policy)) { + return false; + } + } + + return true; + } + + /// <summary> + /// Serves as a hash function for a particular type. + /// </summary> + /// <returns> + /// A hash code for the current <see cref="T:System.Object"/>. + /// </returns> + public override int GetHashCode() { + // This is a poor hash function, but an site that cares will likely have a bunch + // of look-alike instances anyway, so a good hash function would still bunch + // all the instances into the same hash code. + if (this.AuthenticationTimeUtc.HasValue) { + return this.AuthenticationTimeUtc.Value.GetHashCode(); + } else { + return 1; + } + } + + /// <summary> + /// Serializes the applied policies for transmission from the Provider + /// to the Relying Party. + /// </summary> + /// <param name="policies">The applied policies.</param> + /// <returns>A space-delimited list of applied policies.</returns> + private static string SerializePolicies(IList<string> policies) { + if (policies.Count == 0) { + return AuthenticationPolicies.None; + } else { + return PapeUtilities.ConcatenateListOfElements(policies); + } + } + } +} diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Extensions/SimpleRegistration/ClaimsRequest.cs b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/SimpleRegistration/ClaimsRequest.cs new file mode 100644 index 0000000..18f63d4 --- /dev/null +++ b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/SimpleRegistration/ClaimsRequest.cs @@ -0,0 +1,316 @@ +//----------------------------------------------------------------------- +// <copyright file="ClaimsRequest.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.Extensions.SimpleRegistration { + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; + using System.Diagnostics.Contracts; + using System.Globalization; + using System.Text; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OpenId.Messages; + + /// <summary> + /// Carries the request/require/none demand state of the simple registration fields. + /// </summary> + [Serializable] + public sealed class ClaimsRequest : ExtensionBase { + /// <summary> + /// The factory method that may be used in deserialization of this message. + /// </summary> + internal static readonly StandardOpenIdExtensionFactory.CreateDelegate Factory = (typeUri, data, baseMessage, isProviderRole) => { + if (typeUri == Constants.sreg_ns && isProviderRole) { + return new ClaimsRequest(typeUri); + } + + return null; + }; + + /// <summary> + /// The type URI that this particular (deserialized) extension was read in using, + /// allowing a response to alter be crafted using the same type URI. + /// </summary> + private string typeUriDeserializedFrom; + + /// <summary> + /// Initializes a new instance of the <see cref="ClaimsRequest"/> class. + /// </summary> + public ClaimsRequest() + : base(new Version(1, 0), Constants.sreg_ns, Constants.AdditionalTypeUris) { + } + + /// <summary> + /// Initializes a new instance of the <see cref="ClaimsRequest"/> class + /// by deserializing from a message. + /// </summary> + /// <param name="typeUri">The type URI this extension was recognized by in the OpenID message.</param> + internal ClaimsRequest(string typeUri) + : this() { + Requires.NotNullOrEmpty(typeUri, "typeUri"); + + this.typeUriDeserializedFrom = typeUri; + } + + /// <summary> + /// Gets or sets the URL the consumer site provides for the authenticating user to review + /// for how his claims will be used by the consumer web site. + /// </summary> + [MessagePart(Constants.policy_url, IsRequired = false)] + public Uri PolicyUrl { get; set; } + + /// <summary> + /// Gets or sets the level of interest a relying party has in the nickname of the user. + /// </summary> + public DemandLevel Nickname { get; set; } + + /// <summary> + /// Gets or sets the level of interest a relying party has in the email of the user. + /// </summary> + public DemandLevel Email { get; set; } + + /// <summary> + /// Gets or sets the level of interest a relying party has in the full name of the user. + /// </summary> + public DemandLevel FullName { get; set; } + + /// <summary> + /// Gets or sets the level of interest a relying party has in the birthdate of the user. + /// </summary> + public DemandLevel BirthDate { get; set; } + + /// <summary> + /// Gets or sets the level of interest a relying party has in the gender of the user. + /// </summary> + public DemandLevel Gender { get; set; } + + /// <summary> + /// Gets or sets the level of interest a relying party has in the postal code of the user. + /// </summary> + public DemandLevel PostalCode { get; set; } + + /// <summary> + /// Gets or sets the level of interest a relying party has in the Country of the user. + /// </summary> + public DemandLevel Country { get; set; } + + /// <summary> + /// Gets or sets the level of interest a relying party has in the language of the user. + /// </summary> + public DemandLevel Language { get; set; } + + /// <summary> + /// Gets or sets the level of interest a relying party has in the time zone of the user. + /// </summary> + public DemandLevel TimeZone { get; set; } + + /// <summary> + /// Gets or sets a value indicating whether this <see cref="ClaimsRequest"/> instance + /// is synthesized from an AX request at the Provider. + /// </summary> + internal bool Synthesized { get; set; } + + /// <summary> + /// Gets or sets the value of the sreg.required parameter. + /// </summary> + /// <value>A comma-delimited list of sreg fields.</value> + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called by messaging framework via reflection.")] + [MessagePart(Constants.required, AllowEmpty = true)] + private string RequiredList { + get { return string.Join(",", this.AssembleProfileFields(DemandLevel.Require)); } + set { this.SetProfileRequestFromList(value.Split(','), DemandLevel.Require); } + } + + /// <summary> + /// Gets or sets the value of the sreg.optional parameter. + /// </summary> + /// <value>A comma-delimited list of sreg fields.</value> + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called by messaging framework via reflection.")] + [MessagePart(Constants.optional, AllowEmpty = true)] + private string OptionalList { + get { return string.Join(",", this.AssembleProfileFields(DemandLevel.Request)); } + set { this.SetProfileRequestFromList(value.Split(','), DemandLevel.Request); } + } + + /// <summary> + /// Tests equality between two <see cref="ClaimsRequest"/> structs. + /// </summary> + /// <param name="one">One instance to compare.</param> + /// <param name="other">Another instance to compare.</param> + /// <returns>The result of the operator.</returns> + public static bool operator ==(ClaimsRequest one, ClaimsRequest other) { + return one.EqualsNullSafe(other); + } + + /// <summary> + /// Tests inequality between two <see cref="ClaimsRequest"/> structs. + /// </summary> + /// <param name="one">One instance to compare.</param> + /// <param name="other">Another instance to compare.</param> + /// <returns>The result of the operator.</returns> + public static bool operator !=(ClaimsRequest one, ClaimsRequest other) { + return !(one == other); + } + + /// <summary> + /// Tests equality between two <see cref="ClaimsRequest"/> structs. + /// </summary> + /// <param name="obj">The <see cref="T:System.Object"/> to compare with the current <see cref="T:System.Object"/>.</param> + /// <returns> + /// true if the specified <see cref="T:System.Object"/> is equal to the current <see cref="T:System.Object"/>; otherwise, false. + /// </returns> + /// <exception cref="T:System.NullReferenceException"> + /// The <paramref name="obj"/> parameter is null. + /// </exception> + public override bool Equals(object obj) { + ClaimsRequest other = obj as ClaimsRequest; + if (other == null) { + return false; + } + + return + this.BirthDate.Equals(other.BirthDate) && + this.Country.Equals(other.Country) && + this.Language.Equals(other.Language) && + this.Email.Equals(other.Email) && + this.FullName.Equals(other.FullName) && + this.Gender.Equals(other.Gender) && + this.Nickname.Equals(other.Nickname) && + this.PostalCode.Equals(other.PostalCode) && + this.TimeZone.Equals(other.TimeZone) && + this.PolicyUrl.EqualsNullSafe(other.PolicyUrl); + } + + /// <summary> + /// Serves as a hash function for a particular type. + /// </summary> + /// <returns> + /// A hash code for the current <see cref="T:System.Object"/>. + /// </returns> + public override int GetHashCode() { + // It's important that if Equals returns true that the hash code also equals, + // so returning base.GetHashCode() is a BAD option. + // Return 1 is simple and poor for dictionary storage, but considering that every + // ClaimsRequest formulated at a single RP will likely have all the same fields, + // even a good hash code function will likely generate the same hash code. So + // we just cut to the chase and return a simple one. + return 1; + } + + /// <summary> + /// Renders the requested information as a string. + /// </summary> + /// <returns> + /// A <see cref="T:System.String"/> that represents the current <see cref="T:System.Object"/>. + /// </returns> + public override string ToString() { + string format = @"Nickname = '{0}' +Email = '{1}' +FullName = '{2}' +Birthdate = '{3}' +Gender = '{4}' +PostalCode = '{5}' +Country = '{6}' +Language = '{7}' +TimeZone = '{8}'"; + return string.Format(CultureInfo.CurrentCulture, format, this.Nickname, this.Email, this.FullName, this.BirthDate, this.Gender, this.PostalCode, this.Country, this.Language, this.TimeZone); + } + + /// <summary> + /// Prepares a Simple Registration response extension that is compatible with the + /// version of Simple Registration used in the request message. + /// </summary> + /// <returns>The newly created <see cref="ClaimsResponse"/> instance.</returns> + public ClaimsResponse CreateResponse() { + if (this.typeUriDeserializedFrom == null) { + throw new InvalidOperationException(OpenIdStrings.CallDeserializeBeforeCreateResponse); + } + + return new ClaimsResponse(this.typeUriDeserializedFrom); + } + + /// <summary> + /// Sets the profile request properties according to a list of + /// field names that might have been passed in the OpenId query dictionary. + /// </summary> + /// <param name="fieldNames"> + /// The list of field names that should receive a given + /// <paramref name="requestLevel"/>. These field names should match + /// the OpenId specification for field names, omitting the 'openid.sreg' prefix. + /// </param> + /// <param name="requestLevel">The none/request/require state of the listed fields.</param> + internal void SetProfileRequestFromList(IEnumerable<string> fieldNames, DemandLevel requestLevel) { + foreach (string field in fieldNames) { + switch (field) { + case "": // this occurs for empty lists + break; + case Constants.nickname: + this.Nickname = requestLevel; + break; + case Constants.email: + this.Email = requestLevel; + break; + case Constants.fullname: + this.FullName = requestLevel; + break; + case Constants.dob: + this.BirthDate = requestLevel; + break; + case Constants.gender: + this.Gender = requestLevel; + break; + case Constants.postcode: + this.PostalCode = requestLevel; + break; + case Constants.country: + this.Country = requestLevel; + break; + case Constants.language: + this.Language = requestLevel; + break; + case Constants.timezone: + this.TimeZone = requestLevel; + break; + default: + Logger.OpenId.WarnFormat("ClaimsRequest.SetProfileRequestFromList: Unrecognized field name '{0}'.", field); + break; + } + } + } + + /// <summary> + /// Assembles the profile parameter names that have a given <see cref="DemandLevel"/>. + /// </summary> + /// <param name="level">The demand level (request, require, none).</param> + /// <returns>An array of the profile parameter names that meet the criteria.</returns> + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called by messaging framework via reflection.")] + private string[] AssembleProfileFields(DemandLevel level) { + List<string> fields = new List<string>(10); + if (this.Nickname == level) { + fields.Add(Constants.nickname); + } if (this.Email == level) { + fields.Add(Constants.email); + } if (this.FullName == level) { + fields.Add(Constants.fullname); + } if (this.BirthDate == level) { + fields.Add(Constants.dob); + } if (this.Gender == level) { + fields.Add(Constants.gender); + } if (this.PostalCode == level) { + fields.Add(Constants.postcode); + } if (this.Country == level) { + fields.Add(Constants.country); + } if (this.Language == level) { + fields.Add(Constants.language); + } if (this.TimeZone == level) { + fields.Add(Constants.timezone); + } + + return fields.ToArray(); + } + } +} diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Extensions/SimpleRegistration/ClaimsResponse.cs b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/SimpleRegistration/ClaimsResponse.cs new file mode 100644 index 0000000..b50833b --- /dev/null +++ b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/SimpleRegistration/ClaimsResponse.cs @@ -0,0 +1,357 @@ +//----------------------------------------------------------------------- +// <copyright file="ClaimsResponse.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.Extensions.SimpleRegistration { + using System; + using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; + using System.Diagnostics.Contracts; + using System.Globalization; + using System.Net.Mail; + using System.Text; + using System.Text.RegularExpressions; + using System.Xml.Serialization; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OpenId.Messages; + + /// <summary> + /// A struct storing Simple Registration field values describing an + /// authenticating user. + /// </summary> + [Serializable] + public sealed class ClaimsResponse : ExtensionBase, IClientScriptExtensionResponse, IMessageWithEvents { + /// <summary> + /// The factory method that may be used in deserialization of this message. + /// </summary> + internal static readonly StandardOpenIdExtensionFactory.CreateDelegate Factory = (typeUri, data, baseMessage, isProviderRole) => { + if ((typeUri == Constants.sreg_ns || Array.IndexOf(Constants.AdditionalTypeUris, typeUri) >= 0) && !isProviderRole) { + return new ClaimsResponse(typeUri); + } + + return null; + }; + + /// <summary> + /// The allowed format for birthdates. + /// </summary> + private static readonly Regex birthDateValidator = new Regex(@"^\d\d\d\d-\d\d-\d\d$"); + + /// <summary> + /// Storage for the raw string birthdate value. + /// </summary> + private string birthDateRaw; + + /// <summary> + /// Backing field for the <see cref="BirthDate"/> property. + /// </summary> + private DateTime? birthDate; + + /// <summary> + /// Backing field for the <see cref="Culture"/> property. + /// </summary> + private CultureInfo culture; + + /// <summary> + /// Initializes a new instance of the <see cref="ClaimsResponse"/> class. + /// </summary> + internal ClaimsResponse() + : this(Constants.sreg_ns) { + } + + /// <summary> + /// Initializes a new instance of the <see cref="ClaimsResponse"/> class. + /// </summary> + /// <param name="typeUriToUse"> + /// The type URI that must be used to identify this extension in the response message. + /// This value should be the same one the relying party used to send the extension request. + /// </param> + internal ClaimsResponse(string typeUriToUse) + : base(new Version(1, 0), typeUriToUse, Constants.AdditionalTypeUris) { + Requires.NotNullOrEmpty(typeUriToUse, "typeUriToUse"); + } + + /// <summary> + /// Gets or sets the nickname the user goes by. + /// </summary> + [MessagePart(Constants.nickname)] + public string Nickname { get; set; } + + /// <summary> + /// Gets or sets the user's email address. + /// </summary> + [MessagePart(Constants.email)] + public string Email { get; set; } + + /// <summary> + /// Gets or sets the full name of a user as a single string. + /// </summary> + [MessagePart(Constants.fullname)] + public string FullName { get; set; } + + /// <summary> + /// Gets or sets the user's birthdate. + /// </summary> + public DateTime? BirthDate { + get { + return this.birthDate; + } + + set { + this.birthDate = value; + + // Don't use property accessor for peer property to avoid infinite loop between the two proeprty accessors. + if (value.HasValue) { + this.birthDateRaw = value.Value.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture); + } else { + this.birthDateRaw = null; + } + } + } + + /// <summary> + /// Gets or sets the raw birth date string given by the extension. + /// </summary> + /// <value>A string in the format yyyy-MM-dd.</value> + [MessagePart(Constants.dob)] + public string BirthDateRaw { + get { + return this.birthDateRaw; + } + + set { + ErrorUtilities.VerifyArgument(value == null || birthDateValidator.IsMatch(value), OpenIdStrings.SregInvalidBirthdate); + if (value != null) { + // Update the BirthDate property, if possible. + // Don't use property accessor for peer property to avoid infinite loop between the two proeprty accessors. + // Some valid sreg dob values like "2000-00-00" will not work as a DateTime struct, + // in which case we null it out, but don't show any error. + DateTime newBirthDate; + if (DateTime.TryParse(value, out newBirthDate)) { + this.birthDate = newBirthDate; + } else { + Logger.OpenId.WarnFormat("Simple Registration birthdate '{0}' could not be parsed into a DateTime and may not include month and/or day information. Setting BirthDate property to null.", value); + this.birthDate = null; + } + } else { + this.birthDate = null; + } + + this.birthDateRaw = value; + } + } + + /// <summary> + /// Gets or sets the gender of the user. + /// </summary> + [MessagePart(Constants.gender, Encoder = typeof(GenderEncoder))] + public Gender? Gender { get; set; } + + /// <summary> + /// Gets or sets the zip code / postal code of the user. + /// </summary> + [MessagePart(Constants.postcode)] + public string PostalCode { get; set; } + + /// <summary> + /// Gets or sets the country of the user. + /// </summary> + [MessagePart(Constants.country)] + public string Country { get; set; } + + /// <summary> + /// Gets or sets the primary/preferred language of the user. + /// </summary> + [MessagePart(Constants.language)] + public string Language { get; set; } + + /// <summary> + /// Gets or sets the user's timezone. + /// </summary> + [MessagePart(Constants.timezone)] + public string TimeZone { get; set; } + + /// <summary> + /// Gets a combination of the user's full name and email address. + /// </summary> + public MailAddress MailAddress { + get { + if (string.IsNullOrEmpty(this.Email)) { + return null; + } else if (string.IsNullOrEmpty(this.FullName)) { + return new MailAddress(this.Email); + } else { + return new MailAddress(this.Email, this.FullName); + } + } + } + + /// <summary> + /// Gets or sets a combination o the language and country of the user. + /// </summary> + [XmlIgnore] + public CultureInfo Culture { + get { + if (this.culture == null && !string.IsNullOrEmpty(this.Language)) { + string cultureString = string.Empty; + cultureString = this.Language; + if (!string.IsNullOrEmpty(this.Country)) { + cultureString += "-" + this.Country; + } + this.culture = CultureInfo.GetCultureInfo(cultureString); + } + + return this.culture; + } + + set { + this.culture = value; + this.Language = (value != null) ? value.TwoLetterISOLanguageName : null; + int indexOfHyphen = (value != null) ? value.Name.IndexOf('-') : -1; + this.Country = indexOfHyphen > 0 ? value.Name.Substring(indexOfHyphen + 1) : null; + } + } + + /// <summary> + /// Gets a value indicating whether this extension is signed by the Provider. + /// </summary> + /// <value> + /// <c>true</c> if this instance is signed by the Provider; otherwise, <c>false</c>. + /// </value> + public bool IsSignedByProvider { + get { return this.IsSignedByRemoteParty; } + } + + /// <summary> + /// Tests equality of two <see cref="ClaimsResponse"/> objects. + /// </summary> + /// <param name="one">One instance to compare.</param> + /// <param name="other">Another instance to compare.</param> + /// <returns>The result of the operator.</returns> + public static bool operator ==(ClaimsResponse one, ClaimsResponse other) { + return one.EqualsNullSafe(other); + } + + /// <summary> + /// Tests inequality of two <see cref="ClaimsResponse"/> objects. + /// </summary> + /// <param name="one">One instance to compare.</param> + /// <param name="other">Another instance to compare.</param> + /// <returns>The result of the operator.</returns> + public static bool operator !=(ClaimsResponse one, ClaimsResponse other) { + return !(one == other); + } + + /// <summary> + /// Tests equality of two <see cref="ClaimsResponse"/> objects. + /// </summary> + /// <param name="obj">The <see cref="T:System.Object"/> to compare with the current <see cref="T:System.Object"/>.</param> + /// <returns> + /// true if the specified <see cref="T:System.Object"/> is equal to the current <see cref="T:System.Object"/>; otherwise, false. + /// </returns> + /// <exception cref="T:System.NullReferenceException"> + /// The <paramref name="obj"/> parameter is null. + /// </exception> + public override bool Equals(object obj) { + ClaimsResponse other = obj as ClaimsResponse; + if (other == null) { + return false; + } + + return + this.BirthDateRaw.EqualsNullSafe(other.BirthDateRaw) && + this.Country.EqualsNullSafe(other.Country) && + this.Language.EqualsNullSafe(other.Language) && + this.Email.EqualsNullSafe(other.Email) && + this.FullName.EqualsNullSafe(other.FullName) && + this.Gender.Equals(other.Gender) && + this.Nickname.EqualsNullSafe(other.Nickname) && + this.PostalCode.EqualsNullSafe(other.PostalCode) && + this.TimeZone.EqualsNullSafe(other.TimeZone); + } + + /// <summary> + /// Serves as a hash function for a particular type. + /// </summary> + /// <returns> + /// A hash code for the current <see cref="T:System.Object"/>. + /// </returns> + public override int GetHashCode() { + return (this.Nickname != null) ? this.Nickname.GetHashCode() : base.GetHashCode(); + } + + #region IClientScriptExtension Members + + /// <summary> + /// Reads the extension information on an authentication response from the provider. + /// </summary> + /// <param name="response">The incoming OpenID response carrying the extension.</param> + /// <returns> + /// A Javascript snippet that when executed on the user agent returns an object with + /// the information deserialized from the extension response. + /// </returns> + /// <remarks> + /// This method is called <b>before</b> the signature on the assertion response has been + /// verified. Therefore all information in these fields should be assumed unreliable + /// and potentially falsified. + /// </remarks> + string IClientScriptExtensionResponse.InitializeJavaScriptData(IProtocolMessageWithExtensions response) { + var sreg = new Dictionary<string, string>(15); + + // Although we could probably whip up a trip with MessageDictionary + // to avoid explicitly setting each field, doing so would likely + // open ourselves up to security exploits from the OP as it would + // make possible sending arbitrary javascript in arbitrary field names. + sreg[Constants.nickname] = this.Nickname; + sreg[Constants.email] = this.Email; + sreg[Constants.fullname] = this.FullName; + sreg[Constants.dob] = this.BirthDateRaw; + sreg[Constants.gender] = this.Gender.HasValue ? this.Gender.Value.ToString() : null; + sreg[Constants.postcode] = this.PostalCode; + sreg[Constants.country] = this.Country; + sreg[Constants.language] = this.Language; + sreg[Constants.timezone] = this.TimeZone; + + return MessagingUtilities.CreateJsonObject(sreg, false); + } + + #endregion + + #region IMessageWithEvents Members + + /// <summary> + /// Called when the message is about to be transmitted, + /// before it passes through the channel binding elements. + /// </summary> + void IMessageWithEvents.OnSending() { + // Null out empty values so we don't send out a lot of empty parameters. + this.Country = EmptyToNull(this.Country); + this.Email = EmptyToNull(this.Email); + this.FullName = EmptyToNull(this.FullName); + this.Language = EmptyToNull(this.Language); + this.Nickname = EmptyToNull(this.Nickname); + this.PostalCode = EmptyToNull(this.PostalCode); + this.TimeZone = EmptyToNull(this.TimeZone); + } + + /// <summary> + /// Called when the message has been received, + /// after it passes through the channel binding elements. + /// </summary> + void IMessageWithEvents.OnReceiving() { + } + + #endregion + + /// <summary> + /// Translates an empty string value to null, or passes through non-empty values. + /// </summary> + /// <param name="value">The value to consider changing to null.</param> + /// <returns>Either null or a non-empty string.</returns> + private static string EmptyToNull(string value) { + return string.IsNullOrEmpty(value) ? null : value; + } + } +}
\ No newline at end of file diff --git a/src/DotNetOpenAuth/OpenId/Extensions/SimpleRegistration/Constants.cs b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/SimpleRegistration/Constants.cs index 9e00137..9e00137 100644 --- a/src/DotNetOpenAuth/OpenId/Extensions/SimpleRegistration/Constants.cs +++ b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/SimpleRegistration/Constants.cs diff --git a/src/DotNetOpenAuth/OpenId/Extensions/SimpleRegistration/DemandLevel.cs b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/SimpleRegistration/DemandLevel.cs index 7129270..7129270 100644 --- a/src/DotNetOpenAuth/OpenId/Extensions/SimpleRegistration/DemandLevel.cs +++ b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/SimpleRegistration/DemandLevel.cs diff --git a/src/DotNetOpenAuth/OpenId/Extensions/SimpleRegistration/Gender.cs b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/SimpleRegistration/Gender.cs index 979c481..979c481 100644 --- a/src/DotNetOpenAuth/OpenId/Extensions/SimpleRegistration/Gender.cs +++ b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/SimpleRegistration/Gender.cs diff --git a/src/DotNetOpenAuth/OpenId/Extensions/StandardOpenIdExtensionFactory.cs b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/StandardOpenIdExtensionFactory.cs index 1dcda27..1dcda27 100644 --- a/src/DotNetOpenAuth/OpenId/Extensions/StandardOpenIdExtensionFactory.cs +++ b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/StandardOpenIdExtensionFactory.cs diff --git a/src/DotNetOpenAuth/OpenId/Extensions/UI/UIConstants.cs b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/UI/UIConstants.cs index 1cc920a..1cc920a 100644 --- a/src/DotNetOpenAuth/OpenId/Extensions/UI/UIConstants.cs +++ b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/UI/UIConstants.cs diff --git a/src/DotNetOpenAuth/OpenId/Extensions/UI/UIModes.cs b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/UI/UIModes.cs index 8e3e20f..8e3e20f 100644 --- a/src/DotNetOpenAuth/OpenId/Extensions/UI/UIModes.cs +++ b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/UI/UIModes.cs diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Extensions/UI/UIRequest.cs b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/UI/UIRequest.cs new file mode 100644 index 0000000..9d506ca --- /dev/null +++ b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/UI/UIRequest.cs @@ -0,0 +1,197 @@ +//----------------------------------------------------------------------- +// <copyright file="UIRequest.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.Extensions.UI { + using System; + using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; + using System.Diagnostics.Contracts; + using System.Globalization; + using System.Linq; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OpenId.Messages; + using DotNetOpenAuth.Xrds; + + /// <summary> + /// OpenID User Interface extension 1.0 request message. + /// </summary> + /// <remarks> + /// <para>Implements the extension described by: http://wiki.openid.net/f/openid_ui_extension_draft01.html </para> + /// <para>This extension only applies to checkid_setup requests, since checkid_immediate requests display + /// no UI to the user. </para> + /// <para>For rules about how the popup window should be displayed, please see the documentation of + /// <see cref="UIModes.Popup"/>. </para> + /// <para>An RP may determine whether an arbitrary OP supports this extension (and thereby determine + /// whether to use a standard full window redirect or a popup) via the + /// <see cref="IdentifierDiscoveryResult.IsExtensionSupported<T>()"/> method.</para> + /// </remarks> + [Serializable] + public class UIRequest : IOpenIdMessageExtension, IMessageWithEvents { + /// <summary> + /// The factory method that may be used in deserialization of this message. + /// </summary> + internal static readonly StandardOpenIdExtensionFactory.CreateDelegate Factory = (typeUri, data, baseMessage, isProviderRole) => { + if (typeUri == UIConstants.UITypeUri && isProviderRole) { + return new UIRequest(); + } + + return null; + }; + + /// <summary> + /// Additional type URIs that this extension is sometimes known by remote parties. + /// </summary> + private static readonly string[] additionalTypeUris = new string[] { + UIConstants.LangPrefSupported, + UIConstants.PopupSupported, + UIConstants.IconSupported, + }; + + /// <summary> + /// Backing store for <see cref="ExtraData"/>. + /// </summary> + private Dictionary<string, string> extraData = new Dictionary<string, string>(); + + /// <summary> + /// Initializes a new instance of the <see cref="UIRequest"/> class. + /// </summary> + public UIRequest() { + this.LanguagePreference = new[] { CultureInfo.CurrentUICulture }; + this.Mode = UIModes.Popup; + } + + /// <summary> + /// Gets or sets the list of user's preferred languages, sorted in decreasing preferred order. + /// </summary> + /// <value>The default is the <see cref="CultureInfo.CurrentUICulture"/> of the thread that created this instance.</value> + /// <remarks> + /// The user's preferred languages as a [BCP 47] language priority list, represented as a comma-separated list of BCP 47 basic language ranges in descending priority order. For instance, the value "fr-CA,fr-FR,en-CA" represents the preference for French spoken in Canada, French spoken in France, followed by English spoken in Canada. + /// </remarks> + [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification = "By design.")] + [MessagePart("lang", AllowEmpty = false)] + public CultureInfo[] LanguagePreference { get; set; } + + /// <summary> + /// Gets or sets the style of UI that the RP is hosting the OP's authentication page in. + /// </summary> + /// <value>Some value from the <see cref="UIModes"/> class. Defaults to <see cref="UIModes.Popup"/>.</value> + [MessagePart("mode", AllowEmpty = false, IsRequired = true)] + public string Mode { get; set; } + + /// <summary> + /// Gets or sets a value indicating whether the Relying Party has an icon + /// it would like the Provider to display to the user while asking them + /// whether they would like to log in. + /// </summary> + /// <value><c>true</c> if the Provider should display an icon; otherwise, <c>false</c>.</value> + /// <remarks> + /// By default, the Provider displays the relying party's favicon.ico. + /// </remarks> + [MessagePart("icon", AllowEmpty = false, IsRequired = false)] + public bool? Icon { get; set; } + + #region IOpenIdMessageExtension Members + + /// <summary> + /// Gets the TypeURI the extension uses in the OpenID protocol and in XRDS advertisements. + /// </summary> + /// <value></value> + public string TypeUri { get { return UIConstants.UITypeUri; } } + + /// <summary> + /// Gets the additional TypeURIs that are supported by this extension, in preferred order. + /// May be empty if none other than <see cref="TypeUri"/> is supported, but + /// should not be null. + /// </summary> + /// <remarks> + /// Useful for reading in messages with an older version of an extension. + /// The value in the <see cref="TypeUri"/> property is always checked before + /// trying this list. + /// If you do support multiple versions of an extension using this method, + /// consider adding a CreateResponse method to your request extension class + /// so that the response can have the context it needs to remain compatible + /// given the version of the extension in the request message. + /// The <see cref="Extensions.SimpleRegistration.ClaimsRequest.CreateResponse"/> for an example. + /// </remarks> + public IEnumerable<string> AdditionalSupportedTypeUris { get { return additionalTypeUris; } } + + /// <summary> + /// Gets or sets a value indicating whether this extension was + /// signed by the sender. + /// </summary> + /// <value> + /// <c>true</c> if this instance is signed by the sender; otherwise, <c>false</c>. + /// </value> + public bool IsSignedByRemoteParty { get; set; } + + #endregion + + #region IMessage Properties + + /// <summary> + /// Gets the version of the protocol or extension this message is prepared to implement. + /// </summary> + /// <value>The value 1.0.</value> + /// <remarks> + /// Implementations of this interface should ensure that this property never returns null. + /// </remarks> + public Version Version { + get { return new Version(1, 0); } + } + + /// <summary> + /// Gets the extra, non-standard Protocol parameters included in the message. + /// </summary> + /// <remarks> + /// Implementations of this interface should ensure that this property never returns null. + /// </remarks> + public IDictionary<string, string> ExtraData { + get { return this.extraData; } + } + + #endregion + + #region IMessage methods + + /// <summary> + /// Checks the message state for conformity to the protocol specification + /// and throws an exception if the message is invalid. + /// </summary> + /// <remarks> + /// <para>Some messages have required fields, or combinations of fields that must relate to each other + /// in specialized ways. After deserializing a message, this method checks the state of the + /// message to see if it conforms to the protocol.</para> + /// <para>Note that this property should <i>not</i> check signatures or perform any state checks + /// outside this scope of this particular message.</para> + /// </remarks> + /// <exception cref="ProtocolException">Thrown if the message is invalid.</exception> + public void EnsureValidMessage() { + } + + #endregion + + #region IMessageWithEvents Members + + /// <summary> + /// Called when the message is about to be transmitted, + /// before it passes through the channel binding elements. + /// </summary> + public void OnSending() { + } + + /// <summary> + /// Called when the message has been received, + /// after it passes through the channel binding elements. + /// </summary> + public void OnReceiving() { + if (this.LanguagePreference != null) { + // TODO: see if we can change the CultureInfo.CurrentUICulture somehow + } + } + + #endregion + } +} diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Extensions/UI/UIUtilities.cs b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/UI/UIUtilities.cs new file mode 100644 index 0000000..478666b --- /dev/null +++ b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/UI/UIUtilities.cs @@ -0,0 +1,28 @@ +//----------------------------------------------------------------------- +// <copyright file="UIUtilities.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.Extensions.UI { + using System; + using System.Diagnostics.Contracts; + using System.Globalization; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OpenId.RelyingParty; + + /// <summary> + /// Constants used in implementing support for the UI extension. + /// </summary> + public static class UIUtilities { + /// <summary> + /// The required width of the popup window the relying party creates for the provider. + /// </summary> + public const int PopupWidth = 500; // UI extension calls for 450px, but Yahoo needs 500px + + /// <summary> + /// The required height of the popup window the relying party creates for the provider. + /// </summary> + public const int PopupHeight = 500; + } +} diff --git a/src/DotNetOpenAuth.OpenId/OpenId/HmacShaAssociation.cs b/src/DotNetOpenAuth.OpenId/OpenId/HmacShaAssociation.cs new file mode 100644 index 0000000..278cb2c --- /dev/null +++ b/src/DotNetOpenAuth.OpenId/OpenId/HmacShaAssociation.cs @@ -0,0 +1,262 @@ +//----------------------------------------------------------------------- +// <copyright file="HmacShaAssociation.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId { + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Diagnostics.Contracts; + using System.Globalization; + using System.Linq; + using System.Security.Cryptography; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OpenId; + using DotNetOpenAuth.OpenId.Messages; + + /// <summary> + /// An association that uses the HMAC-SHA family of algorithms for message signing. + /// </summary> + [ContractVerification(true)] + internal class HmacShaAssociation : Association { + /// <summary> + /// A list of HMAC-SHA algorithms in order of decreasing bit lengths. + /// </summary> + private static HmacSha[] hmacShaAssociationTypes = new List<HmacSha> { + new HmacSha { + CreateHasher = secretKey => new HMACSHA512(secretKey), + GetAssociationType = protocol => protocol.Args.SignatureAlgorithm.HMAC_SHA512, + BaseHashAlgorithm = SHA512.Create(), + }, + new HmacSha { + CreateHasher = secretKey => new HMACSHA384(secretKey), + GetAssociationType = protocol => protocol.Args.SignatureAlgorithm.HMAC_SHA384, + BaseHashAlgorithm = SHA384.Create(), + }, + new HmacSha { + CreateHasher = secretKey => new HMACSHA256(secretKey), + GetAssociationType = protocol => protocol.Args.SignatureAlgorithm.HMAC_SHA256, + BaseHashAlgorithm = SHA256.Create(), + }, + new HmacSha { + CreateHasher = secretKey => new HMACSHA1(secretKey), + GetAssociationType = protocol => protocol.Args.SignatureAlgorithm.HMAC_SHA1, + BaseHashAlgorithm = SHA1.Create(), + }, + } .ToArray(); + + /// <summary> + /// The specific variety of HMAC-SHA this association is based on (whether it be HMAC-SHA1, HMAC-SHA256, etc.) + /// </summary> + private HmacSha typeIdentity; + + /// <summary> + /// Initializes a new instance of the <see cref="HmacShaAssociation"/> class. + /// </summary> + /// <param name="typeIdentity">The specific variety of HMAC-SHA this association is based on (whether it be HMAC-SHA1, HMAC-SHA256, etc.)</param> + /// <param name="handle">The association handle.</param> + /// <param name="secret">The association secret.</param> + /// <param name="totalLifeLength">The time duration the association will be good for.</param> + private HmacShaAssociation(HmacSha typeIdentity, string handle, byte[] secret, TimeSpan totalLifeLength) + : base(handle, secret, totalLifeLength, DateTime.UtcNow) { + Requires.NotNull(typeIdentity, "typeIdentity"); + Requires.NotNullOrEmpty(handle, "handle"); + Requires.NotNull(secret, "secret"); + Requires.InRange(totalLifeLength > TimeSpan.Zero, "totalLifeLength"); + Contract.Ensures(this.TotalLifeLength == totalLifeLength); + ErrorUtilities.VerifyProtocol(secret.Length == typeIdentity.SecretLength, OpenIdStrings.AssociationSecretAndTypeLengthMismatch, secret.Length, typeIdentity.GetAssociationType(Protocol.Default)); + + this.typeIdentity = typeIdentity; + } + + /// <summary> + /// Gets the length (in bits) of the hash this association creates when signing. + /// </summary> + public override int HashBitLength { + get { + Protocol protocol = Protocol.Default; + return HmacShaAssociation.GetSecretLength(protocol, this.GetAssociationType(protocol)) * 8; + } + } + + /// <summary> + /// Creates an HMAC-SHA association. + /// </summary> + /// <param name="protocol">The OpenID protocol version that the request for an association came in on.</param> + /// <param name="associationType">The value of the openid.assoc_type parameter.</param> + /// <param name="handle">The association handle.</param> + /// <param name="secret">The association secret.</param> + /// <param name="totalLifeLength">How long the association will be good for.</param> + /// <returns>The newly created association.</returns> + public static HmacShaAssociation Create(Protocol protocol, string associationType, string handle, byte[] secret, TimeSpan totalLifeLength) { + Requires.NotNull(protocol, "protocol"); + Requires.NotNullOrEmpty(associationType, "associationType"); + Requires.NotNull(secret, "secret"); + Contract.Ensures(Contract.Result<HmacShaAssociation>() != null); + HmacSha match = hmacShaAssociationTypes.FirstOrDefault(sha => String.Equals(sha.GetAssociationType(protocol), associationType, StringComparison.Ordinal)); + ErrorUtilities.VerifyProtocol(match != null, OpenIdStrings.NoAssociationTypeFoundByName, associationType); + return new HmacShaAssociation(match, handle, secret, totalLifeLength); + } + + /// <summary> + /// Creates an association with the specified handle, secret, and lifetime. + /// </summary> + /// <param name="handle">The handle.</param> + /// <param name="secret">The secret.</param> + /// <param name="totalLifeLength">Total lifetime.</param> + /// <returns>The newly created association.</returns> + public static HmacShaAssociation Create(string handle, byte[] secret, TimeSpan totalLifeLength) { + Requires.NotNullOrEmpty(handle, "handle"); + Requires.NotNull(secret, "secret"); + Contract.Ensures(Contract.Result<HmacShaAssociation>() != null); + + HmacSha shaType = hmacShaAssociationTypes.FirstOrDefault(sha => sha.SecretLength == secret.Length); + ErrorUtilities.VerifyProtocol(shaType != null, OpenIdStrings.NoAssociationTypeFoundByLength, secret.Length); + return new HmacShaAssociation(shaType, handle, secret, totalLifeLength); + } + + /// <summary> + /// Returns the length of the shared secret (in bytes). + /// </summary> + /// <param name="protocol">The protocol version being used that will be used to lookup the text in <paramref name="associationType"/></param> + /// <param name="associationType">The value of the protocol argument specifying the type of association. For example: "HMAC-SHA1".</param> + /// <returns>The length (in bytes) of the association secret.</returns> + /// <exception cref="ProtocolException">Thrown if no association can be found by the given name.</exception> + public static int GetSecretLength(Protocol protocol, string associationType) { + HmacSha match = hmacShaAssociationTypes.FirstOrDefault(shaType => String.Equals(shaType.GetAssociationType(protocol), associationType, StringComparison.Ordinal)); + ErrorUtilities.VerifyProtocol(match != null, OpenIdStrings.NoAssociationTypeFoundByName, associationType); + return match.SecretLength; + } + + /// <summary> + /// Looks for the first association type in a preferred-order list that is + /// likely to be supported given a specific OpenID version and the security settings, + /// and perhaps a matching Diffie-Hellman session type. + /// </summary> + /// <param name="protocol">The OpenID version that dictates which associations are available.</param> + /// <param name="highSecurityIsBetter">A value indicating whether to consider higher strength security to be better. Use <c>true</c> for initial association requests from the Relying Party; use <c>false</c> from Providers when the Relying Party asks for an unrecognized association in order to pick a suggested alternative that is likely to be supported on both sides.</param> + /// <param name="securityRequirements">The set of requirements the selected association type must comply to.</param> + /// <param name="requireMatchingDHSessionType">Use <c>true</c> for HTTP associations, <c>false</c> for HTTPS associations.</param> + /// <param name="associationType">The resulting association type's well known protocol name. (i.e. HMAC-SHA256)</param> + /// <param name="sessionType">The resulting session type's well known protocol name, if a matching one is available. (i.e. DH-SHA256)</param> + /// <returns> + /// True if a qualifying association could be found; false otherwise. + /// </returns> + internal static bool TryFindBestAssociation(Protocol protocol, bool highSecurityIsBetter, SecuritySettings securityRequirements, bool requireMatchingDHSessionType, out string associationType, out string sessionType) { + Requires.NotNull(protocol, "protocol"); + Requires.NotNull(securityRequirements, "securityRequirements"); + + associationType = null; + sessionType = null; + + // We use AsEnumerable() to avoid VerificationException (http://stackoverflow.com/questions/478422/why-does-simple-array-and-linq-generate-verificationexception-operation-could-de) + IEnumerable<HmacSha> preferredOrder = highSecurityIsBetter ? + hmacShaAssociationTypes.AsEnumerable() : hmacShaAssociationTypes.Reverse(); + + foreach (HmacSha sha in preferredOrder) { + int hashSizeInBits = sha.SecretLength * 8; + if (hashSizeInBits > securityRequirements.MaximumHashBitLength || + hashSizeInBits < securityRequirements.MinimumHashBitLength) { + continue; + } +#if !ExcludeDiffieHellman + sessionType = DiffieHellmanUtilities.GetNameForSize(protocol, hashSizeInBits); +#else + sessionType = requireMatchingDHSessionType ? null : protocol.Args.SessionType.NoEncryption; +#endif + if (requireMatchingDHSessionType && sessionType == null) { + continue; + } + associationType = sha.GetAssociationType(protocol); + if (associationType == null) { + continue; + } + + return true; + } + + return false; + } + + /// <summary> + /// Determines whether a named Diffie-Hellman session type and association type can be used together. + /// </summary> + /// <param name="protocol">The protocol carrying the names of the session and association types.</param> + /// <param name="associationType">The value of the openid.assoc_type parameter.</param> + /// <param name="sessionType">The value of the openid.session_type parameter.</param> + /// <returns> + /// <c>true</c> if the named association and session types are compatible; otherwise, <c>false</c>. + /// </returns> + internal static bool IsDHSessionCompatible(Protocol protocol, string associationType, string sessionType) { + Requires.NotNull(protocol, "protocol"); + Requires.NotNullOrEmpty(associationType, "associationType"); + Requires.NotNull(sessionType, "sessionType"); + + // All association types can work when no DH session is used at all. + if (string.Equals(sessionType, protocol.Args.SessionType.NoEncryption, StringComparison.Ordinal)) { + return true; + } + +#if !ExcludeDiffieHellman + // When there _is_ a DH session, it must match in hash length with the association type. + int associationSecretLengthInBytes = GetSecretLength(protocol, associationType); + int sessionHashLengthInBytes = DiffieHellmanUtilities.Lookup(protocol, sessionType).HashSize / 8; + return associationSecretLengthInBytes == sessionHashLengthInBytes; +#else + return false; +#endif + } + + /// <summary> + /// Gets the string to pass as the assoc_type value in the OpenID protocol. + /// </summary> + /// <param name="protocol">The protocol version of the message that the assoc_type value will be included in.</param> + /// <returns> + /// The value that should be used for the openid.assoc_type parameter. + /// </returns> + [Pure] + internal override string GetAssociationType(Protocol protocol) { + return this.typeIdentity.GetAssociationType(protocol); + } + + /// <summary> + /// Returns the specific hash algorithm used for message signing. + /// </summary> + /// <returns> + /// The hash algorithm used for message signing. + /// </returns> + [Pure] + protected override HashAlgorithm CreateHasher() { + var result = this.typeIdentity.CreateHasher(SecretKey); + Contract.Assume(result != null); + return result; + } + + /// <summary> + /// Provides information about some HMAC-SHA hashing algorithm that OpenID supports. + /// </summary> + private class HmacSha { + /// <summary> + /// Gets or sets the function that takes a particular OpenID version and returns the value of the openid.assoc_type parameter in that protocol. + /// </summary> + internal Func<Protocol, string> GetAssociationType { get; set; } + + /// <summary> + /// Gets or sets a function that will create the <see cref="HashAlgorithm"/> using a given shared secret for the mac. + /// </summary> + internal Func<byte[], HashAlgorithm> CreateHasher { get; set; } + + /// <summary> + /// Gets or sets the base hash algorithm. + /// </summary> + internal HashAlgorithm BaseHashAlgorithm { get; set; } + + /// <summary> + /// Gets the size of the hash (in bytes). + /// </summary> + internal int SecretLength { get { return this.BaseHashAlgorithm.HashSize / 8; } } + } + } +}
\ No newline at end of file diff --git a/src/DotNetOpenAuth.OpenId/OpenId/IIdentifierDiscoveryService.cs b/src/DotNetOpenAuth.OpenId/OpenId/IIdentifierDiscoveryService.cs new file mode 100644 index 0000000..f5cefe2 --- /dev/null +++ b/src/DotNetOpenAuth.OpenId/OpenId/IIdentifierDiscoveryService.cs @@ -0,0 +1,67 @@ +//----------------------------------------------------------------------- +// <copyright file="IIdentifierDiscoveryService.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId { + using System; + using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; + using System.Diagnostics.Contracts; + using System.Linq; + using System.Text; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OpenId.RelyingParty; + + /// <summary> + /// A module that provides discovery services for OpenID identifiers. + /// </summary> + [ContractClass(typeof(IIdentifierDiscoveryServiceContract))] + public interface IIdentifierDiscoveryService { + /// <summary> + /// Performs discovery on the specified identifier. + /// </summary> + /// <param name="identifier">The identifier to perform discovery on.</param> + /// <param name="requestHandler">The means to place outgoing HTTP requests.</param> + /// <param name="abortDiscoveryChain">if set to <c>true</c>, no further discovery services will be called for this identifier.</param> + /// <returns> + /// A sequence of service endpoints yielded by discovery. Must not be null, but may be empty. + /// </returns> + [SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "2#", Justification = "By design")] + [Pure] + IEnumerable<IdentifierDiscoveryResult> Discover(Identifier identifier, IDirectWebRequestHandler requestHandler, out bool abortDiscoveryChain); + } + + /// <summary> + /// Code contract for the <see cref="IIdentifierDiscoveryService"/> interface. + /// </summary> + [ContractClassFor(typeof(IIdentifierDiscoveryService))] + internal abstract class IIdentifierDiscoveryServiceContract : IIdentifierDiscoveryService { + /// <summary> + /// Prevents a default instance of the <see cref="IIdentifierDiscoveryServiceContract"/> class from being created. + /// </summary> + private IIdentifierDiscoveryServiceContract() { + } + + #region IDiscoveryService Members + + /// <summary> + /// Performs discovery on the specified identifier. + /// </summary> + /// <param name="identifier">The identifier to perform discovery on.</param> + /// <param name="requestHandler">The means to place outgoing HTTP requests.</param> + /// <param name="abortDiscoveryChain">if set to <c>true</c>, no further discovery services will be called for this identifier.</param> + /// <returns> + /// A sequence of service endpoints yielded by discovery. Must not be null, but may be empty. + /// </returns> + IEnumerable<IdentifierDiscoveryResult> IIdentifierDiscoveryService.Discover(Identifier identifier, IDirectWebRequestHandler requestHandler, out bool abortDiscoveryChain) { + Requires.NotNull(identifier, "identifier"); + Requires.NotNull(requestHandler, "requestHandler"); + Contract.Ensures(Contract.Result<IEnumerable<IdentifierDiscoveryResult>>() != null); + throw new NotImplementedException(); + } + + #endregion + } +} diff --git a/src/DotNetOpenAuth/OpenId/IOpenIdApplicationStore.cs b/src/DotNetOpenAuth.OpenId/OpenId/IOpenIdApplicationStore.cs index 6c04a81..6c04a81 100644 --- a/src/DotNetOpenAuth/OpenId/IOpenIdApplicationStore.cs +++ b/src/DotNetOpenAuth.OpenId/OpenId/IOpenIdApplicationStore.cs diff --git a/src/DotNetOpenAuth.OpenId/OpenId/IProviderEndpoint.cs b/src/DotNetOpenAuth.OpenId/OpenId/IProviderEndpoint.cs new file mode 100644 index 0000000..e2a8929 --- /dev/null +++ b/src/DotNetOpenAuth.OpenId/OpenId/IProviderEndpoint.cs @@ -0,0 +1,143 @@ +//----------------------------------------------------------------------- +// <copyright file="IProviderEndpoint.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.RelyingParty { + using System; + using System.Collections.ObjectModel; + using System.Diagnostics.CodeAnalysis; + using System.Diagnostics.Contracts; + using System.Globalization; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OpenId.Messages; + + /// <summary> + /// Information published about an OpenId Provider by the + /// OpenId discovery documents found at a user's Claimed Identifier. + /// </summary> + /// <remarks> + /// Because information provided by this interface is suppplied by a + /// user's individually published documents, it may be incomplete or inaccurate. + /// </remarks> + [ContractClass(typeof(IProviderEndpointContract))] + public interface IProviderEndpoint { + /// <summary> + /// Gets the detected version of OpenID implemented by the Provider. + /// </summary> + Version Version { get; } + + /// <summary> + /// Gets the URL that the OpenID Provider receives authentication requests at. + /// </summary> + /// <value> + /// This value MUST be an absolute HTTP or HTTPS URL. + /// </value> + Uri Uri { get; } + + /// <summary> + /// Checks whether the OpenId Identifier claims support for a given extension. + /// </summary> + /// <typeparam name="T">The extension whose support is being queried.</typeparam> + /// <returns>True if support for the extension is advertised. False otherwise.</returns> + /// <remarks> + /// Note that a true or false return value is no guarantee of a Provider's + /// support for or lack of support for an extension. The return value is + /// determined by how the authenticating user filled out his/her XRDS document only. + /// The only way to be sure of support for a given extension is to include + /// the extension in the request and see if a response comes back for that extension. + /// </remarks> + [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "No parameter at all.")] + [Obsolete("Use IAuthenticationRequest.DiscoveryResult.IsExtensionSupported instead.")] + bool IsExtensionSupported<T>() where T : IOpenIdMessageExtension, new(); + + /// <summary> + /// Checks whether the OpenId Identifier claims support for a given extension. + /// </summary> + /// <param name="extensionType">The extension whose support is being queried.</param> + /// <returns>True if support for the extension is advertised. False otherwise.</returns> + /// <remarks> + /// Note that a true or false return value is no guarantee of a Provider's + /// support for or lack of support for an extension. The return value is + /// determined by how the authenticating user filled out his/her XRDS document only. + /// The only way to be sure of support for a given extension is to include + /// the extension in the request and see if a response comes back for that extension. + /// </remarks> + [Obsolete("Use IAuthenticationRequest.DiscoveryResult.IsExtensionSupported instead.")] + bool IsExtensionSupported(Type extensionType); + } + + /// <summary> + /// Code contract for the <see cref="IProviderEndpoint"/> type. + /// </summary> + [ContractClassFor(typeof(IProviderEndpoint))] + internal abstract class IProviderEndpointContract : IProviderEndpoint { + /// <summary> + /// Prevents a default instance of the <see cref="IProviderEndpointContract"/> class from being created. + /// </summary> + private IProviderEndpointContract() { + } + + #region IProviderEndpoint Members + + /// <summary> + /// Gets the detected version of OpenID implemented by the Provider. + /// </summary> + Version IProviderEndpoint.Version { + get { + Contract.Ensures(Contract.Result<Version>() != null); + throw new System.NotImplementedException(); + } + } + + /// <summary> + /// Gets the URL that the OpenID Provider receives authentication requests at. + /// </summary> + Uri IProviderEndpoint.Uri { + get { + Contract.Ensures(Contract.Result<Uri>() != null); + throw new System.NotImplementedException(); + } + } + + /// <summary> + /// Checks whether the OpenId Identifier claims support for a given extension. + /// </summary> + /// <typeparam name="T">The extension whose support is being queried.</typeparam> + /// <returns> + /// True if support for the extension is advertised. False otherwise. + /// </returns> + /// <remarks> + /// Note that a true or false return value is no guarantee of a Provider's + /// support for or lack of support for an extension. The return value is + /// determined by how the authenticating user filled out his/her XRDS document only. + /// The only way to be sure of support for a given extension is to include + /// the extension in the request and see if a response comes back for that extension. + /// </remarks> + bool IProviderEndpoint.IsExtensionSupported<T>() { + throw new NotImplementedException(); + } + + /// <summary> + /// Checks whether the OpenId Identifier claims support for a given extension. + /// </summary> + /// <param name="extensionType">The extension whose support is being queried.</param> + /// <returns> + /// True if support for the extension is advertised. False otherwise. + /// </returns> + /// <remarks> + /// Note that a true or false return value is no guarantee of a Provider's + /// support for or lack of support for an extension. The return value is + /// determined by how the authenticating user filled out his/her XRDS document only. + /// The only way to be sure of support for a given extension is to include + /// the extension in the request and see if a response comes back for that extension. + /// </remarks> + bool IProviderEndpoint.IsExtensionSupported(Type extensionType) { + Requires.NotNullSubtype<IOpenIdMessageExtension>(extensionType, "extensionType"); + throw new NotImplementedException(); + } + + #endregion + } +} diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Identifier.cs b/src/DotNetOpenAuth.OpenId/OpenId/Identifier.cs new file mode 100644 index 0000000..aeb0e6b --- /dev/null +++ b/src/DotNetOpenAuth.OpenId/OpenId/Identifier.cs @@ -0,0 +1,310 @@ +//----------------------------------------------------------------------- +// <copyright file="Identifier.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId { + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; + using System.Diagnostics.Contracts; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.Messaging.Reflection; + + /// <summary> + /// An Identifier is either a "http" or "https" URI, or an XRI. + /// </summary> + [Serializable] + [ContractVerification(true)] + [Pure] + [ContractClass(typeof(IdentifierContract))] + public abstract class Identifier { + /// <summary> + /// Initializes static members of the <see cref="Identifier"/> class. + /// </summary> + static Identifier() { + Func<string, Identifier> safeIdentifier = str => { + Contract.Assume(str != null); + ErrorUtilities.VerifyFormat(str.Length > 0, MessagingStrings.NonEmptyStringExpected); + return Identifier.Parse(str, true); + }; + MessagePart.Map<Identifier>(id => id.SerializedString, id => id.OriginalString, safeIdentifier); + } + + /// <summary> + /// Initializes a new instance of the <see cref="Identifier"/> class. + /// </summary> + /// <param name="originalString">The original string before any normalization.</param> + /// <param name="isDiscoverySecureEndToEnd">Whether the derived class is prepared to guarantee end-to-end discovery + /// and initial redirect for authentication is performed using SSL.</param> + [SuppressMessage("Microsoft.Naming", "CA1720:IdentifiersShouldNotContainTypeNames", MessageId = "string", Justification = "Emphasis on string instead of the strong-typed Identifier.")] + protected Identifier(string originalString, bool isDiscoverySecureEndToEnd) { + this.OriginalString = originalString; + this.IsDiscoverySecureEndToEnd = isDiscoverySecureEndToEnd; + } + + /// <summary> + /// Gets the original string that was normalized to create this Identifier. + /// </summary> + internal string OriginalString { get; private set; } + + /// <summary> + /// Gets the Identifier in the form in which it should be serialized. + /// </summary> + /// <value> + /// For Identifiers that were originally deserialized, this is the exact same + /// string that was deserialized. For Identifiers instantiated in some other way, this is + /// the normalized form of the string used to instantiate the identifier. + /// </value> + internal virtual string SerializedString { + get { return this.IsDeserializedInstance ? this.OriginalString : this.ToString(); } + } + + /// <summary> + /// Gets or sets a value indicating whether <see cref="Identifier"/> instances are considered equal + /// based solely on their string reprsentations. + /// </summary> + /// <remarks> + /// This property serves as a test hook, so that MockIdentifier instances can be considered "equal" + /// to UriIdentifier instances. + /// </remarks> + protected internal static bool EqualityOnStrings { get; set; } + + /// <summary> + /// Gets a value indicating whether this Identifier will ensure SSL is + /// used throughout the discovery phase and initial redirect of authentication. + /// </summary> + /// <remarks> + /// If this is <c>false</c>, a value of <c>true</c> may be obtained by calling + /// <see cref="TryRequireSsl"/>. + /// </remarks> + protected internal bool IsDiscoverySecureEndToEnd { get; private set; } + + /// <summary> + /// Gets a value indicating whether this instance was initialized from + /// deserializing a message. + /// </summary> + /// <remarks> + /// This is interesting because when an Identifier comes from the network, + /// we can't normalize it and then expect signatures to still verify. + /// But if the Identifier is initialized locally, we can and should normalize it + /// before serializing it. + /// </remarks> + protected bool IsDeserializedInstance { get; private set; } + + /// <summary> + /// Converts the string representation of an Identifier to its strong type. + /// </summary> + /// <param name="identifier">The identifier.</param> + /// <returns>The particular Identifier instance to represent the value given.</returns> + [SuppressMessage("Microsoft.Design", "CA1057:StringUriOverloadsCallSystemUriOverloads", Justification = "Not all identifiers are URIs.")] + [SuppressMessage("Microsoft.Usage", "CA2225:OperatorOverloadsHaveNamedAlternates", Justification = "Our named alternate is Parse.")] + [DebuggerStepThrough] + public static implicit operator Identifier(string identifier) { + Requires.True(identifier == null || identifier.Length > 0, "identifier"); + Contract.Ensures((identifier == null) == (Contract.Result<Identifier>() == null)); + + if (identifier == null) { + return null; + } + return Parse(identifier); + } + + /// <summary> + /// Converts a given Uri to a strongly-typed Identifier. + /// </summary> + /// <param name="identifier">The identifier to convert.</param> + /// <returns>The result of the conversion.</returns> + [SuppressMessage("Microsoft.Usage", "CA2225:OperatorOverloadsHaveNamedAlternates", Justification = "We have a Parse function.")] + [DebuggerStepThrough] + public static implicit operator Identifier(Uri identifier) { + Contract.Ensures((identifier == null) == (Contract.Result<Identifier>() == null)); + if (identifier == null) { + return null; + } + + return new UriIdentifier(identifier); + } + + /// <summary> + /// Converts an Identifier to its string representation. + /// </summary> + /// <param name="identifier">The identifier to convert to a string.</param> + /// <returns>The result of the conversion.</returns> + [SuppressMessage("Microsoft.Usage", "CA2225:OperatorOverloadsHaveNamedAlternates", Justification = "We have a Parse function.")] + [DebuggerStepThrough] + public static implicit operator string(Identifier identifier) { + Contract.Ensures((identifier == null) == (Contract.Result<string>() == null)); + if (identifier == null) { + return null; + } + return identifier.ToString(); + } + + /// <summary> + /// Parses an identifier string and automatically determines + /// whether it is an XRI or URI. + /// </summary> + /// <param name="identifier">Either a URI or XRI identifier.</param> + /// <returns>An <see cref="Identifier"/> instance for the given value.</returns> + [SuppressMessage("Microsoft.Usage", "CA2234:PassSystemUriObjectsInsteadOfStrings", Justification = "Some of these identifiers are not properly formatted to be Uris at this stage.")] + public static Identifier Parse(string identifier) { + Requires.NotNullOrEmpty(identifier, "identifier"); + Contract.Ensures(Contract.Result<Identifier>() != null); + + return Parse(identifier, false); + } + + /// <summary> + /// Parses an identifier string and automatically determines + /// whether it is an XRI or URI. + /// </summary> + /// <param name="identifier">Either a URI or XRI identifier.</param> + /// <param name="serializeExactValue">if set to <c>true</c> this Identifier will serialize exactly as given rather than in its normalized form.</param> + /// <returns> + /// An <see cref="Identifier"/> instance for the given value. + /// </returns> + [SuppressMessage("Microsoft.Usage", "CA2234:PassSystemUriObjectsInsteadOfStrings", Justification = "Some of these identifiers are not properly formatted to be Uris at this stage.")] + public static Identifier Parse(string identifier, bool serializeExactValue) { + Requires.NotNullOrEmpty(identifier, "identifier"); + Contract.Ensures(Contract.Result<Identifier>() != null); + + Identifier id; + if (XriIdentifier.IsValidXri(identifier)) { + id = new XriIdentifier(identifier); + } else { + id = new UriIdentifier(identifier); + } + + id.IsDeserializedInstance = serializeExactValue; + return id; + } + + /// <summary> + /// Attempts to parse a string for an OpenId Identifier. + /// </summary> + /// <param name="value">The string to be parsed.</param> + /// <param name="result">The parsed Identifier form.</param> + /// <returns> + /// True if the operation was successful. False if the string was not a valid OpenId Identifier. + /// </returns> + public static bool TryParse(string value, out Identifier result) { + if (string.IsNullOrEmpty(value)) { + result = null; + return false; + } + + if (IsValid(value)) { + result = Parse(value); + return true; + } else { + result = null; + return false; + } + } + + /// <summary> + /// Checks the validity of a given string representation of some Identifier. + /// </summary> + /// <param name="identifier">The identifier.</param> + /// <returns> + /// <c>true</c> if the specified identifier is valid; otherwise, <c>false</c>. + /// </returns> + [SuppressMessage("Microsoft.Usage", "CA2234:PassSystemUriObjectsInsteadOfStrings", Justification = "Some of these identifiers are not properly formatted to be Uris at this stage.")] + public static bool IsValid(string identifier) { + Requires.NotNullOrEmpty(identifier, "identifier"); + return XriIdentifier.IsValidXri(identifier) || UriIdentifier.IsValidUri(identifier); + } + + /// <summary> + /// Tests equality between two <see cref="Identifier"/>s. + /// </summary> + /// <param name="id1">The first Identifier.</param> + /// <param name="id2">The second Identifier.</param> + /// <returns> + /// <c>true</c> if the two instances should be considered equal; <c>false</c> otherwise. + /// </returns> + public static bool operator ==(Identifier id1, Identifier id2) { + return id1.EqualsNullSafe(id2); + } + + /// <summary> + /// Tests inequality between two <see cref="Identifier"/>s. + /// </summary> + /// <param name="id1">The first Identifier.</param> + /// <param name="id2">The second Identifier.</param> + /// <returns> + /// <c>true</c> if the two instances should be considered unequal; <c>false</c> if they are equal. + /// </returns> + public static bool operator !=(Identifier id1, Identifier id2) { + return !id1.EqualsNullSafe(id2); + } + + /// <summary> + /// Tests equality between two <see cref="Identifier"/>s. + /// </summary> + /// <param name="obj">The <see cref="T:System.Object"/> to compare with the current <see cref="T:System.Object"/>.</param> + /// <returns> + /// true if the specified <see cref="T:System.Object"/> is equal to the current <see cref="T:System.Object"/>; otherwise, false. + /// </returns> + /// <exception cref="T:System.NullReferenceException"> + /// The <paramref name="obj"/> parameter is null. + /// </exception> + public override bool Equals(object obj) { + Debug.Fail("This should be overridden in every derived class."); + return base.Equals(obj); + } + + /// <summary> + /// Gets the hash code for an <see cref="Identifier"/> for storage in a hashtable. + /// </summary> + /// <returns> + /// A hash code for the current <see cref="T:System.Object"/>. + /// </returns> + public override int GetHashCode() { + Debug.Fail("This should be overridden in every derived class."); + return base.GetHashCode(); + } + + /// <summary> + /// Reparses the specified identifier in order to be assured that the concrete type that + /// implements the identifier is one of the well-known ones. + /// </summary> + /// <param name="identifier">The identifier.</param> + /// <returns>Either <see cref="XriIdentifier"/> or <see cref="UriIdentifier"/>.</returns> + internal static Identifier Reparse(Identifier identifier) { + Requires.NotNull(identifier, "identifier"); + Contract.Ensures(Contract.Result<Identifier>() != null); + + return Parse(identifier, identifier.IsDeserializedInstance); + } + + /// <summary> + /// Returns an <see cref="Identifier"/> that has no URI fragment. + /// Quietly returns the original <see cref="Identifier"/> if it is not + /// a <see cref="UriIdentifier"/> or no fragment exists. + /// </summary> + /// <returns>A new <see cref="Identifier"/> instance if there was a + /// fragment to remove, otherwise this same instance..</returns> + [Pure] + internal abstract Identifier TrimFragment(); + + /// <summary> + /// Converts a given identifier to its secure equivalent. + /// UriIdentifiers originally created with an implied HTTP scheme change to HTTPS. + /// Discovery is made to require SSL for the entire resolution process. + /// </summary> + /// <param name="secureIdentifier"> + /// The newly created secure identifier. + /// If the conversion fails, <paramref name="secureIdentifier"/> retains + /// <i>this</i> identifiers identity, but will never discover any endpoints. + /// </param> + /// <returns> + /// True if the secure conversion was successful. + /// False if the Identifier was originally created with an explicit HTTP scheme. + /// </returns> + internal abstract bool TryRequireSsl(out Identifier secureIdentifier); + } +} diff --git a/src/DotNetOpenAuth.OpenId/OpenId/IdentifierContract.cs b/src/DotNetOpenAuth.OpenId/OpenId/IdentifierContract.cs new file mode 100644 index 0000000..7355491 --- /dev/null +++ b/src/DotNetOpenAuth.OpenId/OpenId/IdentifierContract.cs @@ -0,0 +1,56 @@ +//----------------------------------------------------------------------- +// <copyright file="IdentifierContract.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId { + using System; + using System.Collections.Generic; + using System.Diagnostics.Contracts; + using DotNetOpenAuth.Messaging; + + /// <summary> + /// Code Contract for the <see cref="Identifier"/> class. + /// </summary> + [ContractClassFor(typeof(Identifier))] + internal abstract class IdentifierContract : Identifier { + /// <summary> + /// Prevents a default instance of the IdentifierContract class from being created. + /// </summary> + private IdentifierContract() + : base(null, false) { + } + + /// <summary> + /// Returns an <see cref="Identifier"/> that has no URI fragment. + /// Quietly returns the original <see cref="Identifier"/> if it is not + /// a <see cref="UriIdentifier"/> or no fragment exists. + /// </summary> + /// <returns> + /// A new <see cref="Identifier"/> instance if there was a + /// fragment to remove, otherwise this same instance.. + /// </returns> + internal override Identifier TrimFragment() { + Contract.Ensures(Contract.Result<Identifier>() != null); + throw new NotImplementedException(); + } + + /// <summary> + /// Converts a given identifier to its secure equivalent. + /// UriIdentifiers originally created with an implied HTTP scheme change to HTTPS. + /// Discovery is made to require SSL for the entire resolution process. + /// </summary> + /// <param name="secureIdentifier">The newly created secure identifier. + /// If the conversion fails, <paramref name="secureIdentifier"/> retains + /// <i>this</i> identifiers identity, but will never discover any endpoints.</param> + /// <returns> + /// True if the secure conversion was successful. + /// False if the Identifier was originally created with an explicit HTTP scheme. + /// </returns> + internal override bool TryRequireSsl(out Identifier secureIdentifier) { + Contract.Ensures(Contract.ValueAtReturn(out secureIdentifier) != null); + throw new NotImplementedException(); + } + } +} diff --git a/src/DotNetOpenAuth.OpenId/OpenId/IdentifierDiscoveryResult.cs b/src/DotNetOpenAuth.OpenId/OpenId/IdentifierDiscoveryResult.cs new file mode 100644 index 0000000..07abf1e --- /dev/null +++ b/src/DotNetOpenAuth.OpenId/OpenId/IdentifierDiscoveryResult.cs @@ -0,0 +1,497 @@ +//----------------------------------------------------------------------- +// <copyright file="IdentifierDiscoveryResult.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId { + using System; + using System.Collections.Generic; + using System.Collections.ObjectModel; + using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; + using System.Diagnostics.Contracts; + using System.Globalization; + using System.IO; + using System.Linq; + using System.Text; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OpenId.Messages; + using DotNetOpenAuth.OpenId.RelyingParty; + + /// <summary> + /// Represents a single OP endpoint from discovery on some OpenID Identifier. + /// </summary> + [DebuggerDisplay("ClaimedIdentifier: {ClaimedIdentifier}, ProviderEndpoint: {ProviderEndpoint}, OpenId: {Protocol.Version}")] + public sealed class IdentifierDiscoveryResult : IProviderEndpoint { + /// <summary> + /// Backing field for the <see cref="Protocol"/> property. + /// </summary> + private Protocol protocol; + + /// <summary> + /// Backing field for the <see cref="ClaimedIdentifier"/> property. + /// </summary> + private Identifier claimedIdentifier; + + /// <summary> + /// Backing field for the <see cref="FriendlyIdentifierForDisplay"/> property. + /// </summary> + private string friendlyIdentifierForDisplay; + + /// <summary> + /// Initializes a new instance of the <see cref="IdentifierDiscoveryResult"/> class. + /// </summary> + /// <param name="providerEndpoint">The provider endpoint.</param> + /// <param name="claimedIdentifier">The Claimed Identifier.</param> + /// <param name="userSuppliedIdentifier">The User-supplied Identifier.</param> + /// <param name="providerLocalIdentifier">The Provider Local Identifier.</param> + /// <param name="servicePriority">The service priority.</param> + /// <param name="uriPriority">The URI priority.</param> + private IdentifierDiscoveryResult(ProviderEndpointDescription providerEndpoint, Identifier claimedIdentifier, Identifier userSuppliedIdentifier, Identifier providerLocalIdentifier, int? servicePriority, int? uriPriority) { + Requires.NotNull(providerEndpoint, "providerEndpoint"); + Requires.NotNull(claimedIdentifier, "claimedIdentifier"); + this.ProviderEndpoint = providerEndpoint.Uri; + this.Capabilities = new ReadOnlyCollection<string>(providerEndpoint.Capabilities); + this.Version = providerEndpoint.Version; + this.ClaimedIdentifier = claimedIdentifier; + this.ProviderLocalIdentifier = providerLocalIdentifier ?? claimedIdentifier; + this.UserSuppliedIdentifier = userSuppliedIdentifier; + this.ServicePriority = servicePriority; + this.ProviderEndpointPriority = uriPriority; + } + + /// <summary> + /// Gets the detected version of OpenID implemented by the Provider. + /// </summary> + public Version Version { get; private set; } + + /// <summary> + /// Gets the Identifier that was presented by the end user to the Relying Party, + /// or selected by the user at the OpenID Provider. + /// During the initiation phase of the protocol, an end user may enter + /// either their own Identifier or an OP Identifier. If an OP Identifier + /// is used, the OP may then assist the end user in selecting an Identifier + /// to share with the Relying Party. + /// </summary> + public Identifier UserSuppliedIdentifier { get; private set; } + + /// <summary> + /// Gets the Identifier that the end user claims to control. + /// </summary> + public Identifier ClaimedIdentifier { + get { + return this.claimedIdentifier; + } + + internal set { + // Take care to reparse the incoming identifier to make sure it's + // not a derived type that will override expected behavior. + // Elsewhere in this class, we count on the fact that this property + // is either UriIdentifier or XriIdentifier. MockIdentifier messes it up. + this.claimedIdentifier = value != null ? Identifier.Reparse(value) : null; + } + } + + /// <summary> + /// Gets an alternate Identifier for an end user that is local to a + /// particular OP and thus not necessarily under the end user's + /// control. + /// </summary> + public Identifier ProviderLocalIdentifier { get; private set; } + + /// <summary> + /// Gets a more user-friendly (but NON-secure!) string to display to the user as his identifier. + /// </summary> + /// <returns>A human-readable, abbreviated (but not secure) identifier the user MAY recognize as his own.</returns> + public string FriendlyIdentifierForDisplay { + get { + if (this.friendlyIdentifierForDisplay == null) { + XriIdentifier xri = this.ClaimedIdentifier as XriIdentifier; + UriIdentifier uri = this.ClaimedIdentifier as UriIdentifier; + if (xri != null) { + if (this.UserSuppliedIdentifier == null || String.Equals(this.UserSuppliedIdentifier, this.ClaimedIdentifier, StringComparison.OrdinalIgnoreCase)) { + this.friendlyIdentifierForDisplay = this.ClaimedIdentifier; + } else { + this.friendlyIdentifierForDisplay = this.UserSuppliedIdentifier; + } + } else if (uri != null) { + if (uri != this.Protocol.ClaimedIdentifierForOPIdentifier) { + string displayUri = uri.Uri.Host; + + // We typically want to display the path, because that will often have the username in it. + // As Google Apps for Domains and the like become more popular, a standard /openid path + // will often appear, which is not helpful to identifying the user so we'll avoid including + // that path if it's present. + if (!string.Equals(uri.Uri.AbsolutePath, "/openid", StringComparison.OrdinalIgnoreCase)) { + displayUri += uri.Uri.AbsolutePath.TrimEnd('/'); + } + + // Multi-byte unicode characters get encoded by the Uri class for transit. + // Since this is for display purposes, we want to reverse this and display a readable + // representation of these foreign characters. + this.friendlyIdentifierForDisplay = Uri.UnescapeDataString(displayUri); + } + } else { + ErrorUtilities.ThrowInternal("ServiceEndpoint.ClaimedIdentifier neither XRI nor URI."); + this.friendlyIdentifierForDisplay = this.ClaimedIdentifier; + } + } + + return this.friendlyIdentifierForDisplay; + } + } + + /// <summary> + /// Gets the provider endpoint. + /// </summary> + public Uri ProviderEndpoint { get; private set; } + + /// <summary> + /// Gets the @priority given in the XRDS document for this specific OP endpoint. + /// </summary> + public int? ProviderEndpointPriority { get; private set; } + + /// <summary> + /// Gets the @priority given in the XRDS document for this service + /// (which may consist of several endpoints). + /// </summary> + public int? ServicePriority { get; private set; } + + /// <summary> + /// Gets the collection of service type URIs found in the XRDS document describing this Provider. + /// </summary> + /// <value>Should never be null, but may be empty.</value> + public ReadOnlyCollection<string> Capabilities { get; private set; } + + #region IProviderEndpoint Members + + /// <summary> + /// Gets the URL that the OpenID Provider receives authentication requests at. + /// </summary> + /// <value>This value MUST be an absolute HTTP or HTTPS URL.</value> + Uri IProviderEndpoint.Uri { + get { return this.ProviderEndpoint; } + } + + #endregion + + /// <summary> + /// Gets an XRDS sorting routine that uses the XRDS Service/@Priority + /// attribute to determine order. + /// </summary> + /// <remarks> + /// Endpoints lacking any priority value are sorted to the end of the list. + /// </remarks> + internal static Comparison<IdentifierDiscoveryResult> EndpointOrder { + get { + // Sort first by service type (OpenID 2.0, 1.1, 1.0), + // then by Service/@priority, then by Service/Uri/@priority + return (se1, se2) => { + int result = GetEndpointPrecedenceOrderByServiceType(se1).CompareTo(GetEndpointPrecedenceOrderByServiceType(se2)); + if (result != 0) { + return result; + } + if (se1.ServicePriority.HasValue && se2.ServicePriority.HasValue) { + result = se1.ServicePriority.Value.CompareTo(se2.ServicePriority.Value); + if (result != 0) { + return result; + } + if (se1.ProviderEndpointPriority.HasValue && se2.ProviderEndpointPriority.HasValue) { + return se1.ProviderEndpointPriority.Value.CompareTo(se2.ProviderEndpointPriority.Value); + } else if (se1.ProviderEndpointPriority.HasValue) { + return -1; + } else if (se2.ProviderEndpointPriority.HasValue) { + return 1; + } else { + return 0; + } + } else { + if (se1.ServicePriority.HasValue) { + return -1; + } else if (se2.ServicePriority.HasValue) { + return 1; + } else { + // neither service defines a priority, so base ordering by uri priority. + if (se1.ProviderEndpointPriority.HasValue && se2.ProviderEndpointPriority.HasValue) { + return se1.ProviderEndpointPriority.Value.CompareTo(se2.ProviderEndpointPriority.Value); + } else if (se1.ProviderEndpointPriority.HasValue) { + return -1; + } else if (se2.ProviderEndpointPriority.HasValue) { + return 1; + } else { + return 0; + } + } + } + }; + } + } + + /// <summary> + /// Gets the protocol used by the OpenID Provider. + /// </summary> + internal Protocol Protocol { + get { + if (this.protocol == null) { + this.protocol = Protocol.Lookup(this.Version); + } + + return this.protocol; + } + } + + /// <summary> + /// Implements the operator ==. + /// </summary> + /// <param name="se1">The first service endpoint.</param> + /// <param name="se2">The second service endpoint.</param> + /// <returns>The result of the operator.</returns> + public static bool operator ==(IdentifierDiscoveryResult se1, IdentifierDiscoveryResult se2) { + return se1.EqualsNullSafe(se2); + } + + /// <summary> + /// Implements the operator !=. + /// </summary> + /// <param name="se1">The first service endpoint.</param> + /// <param name="se2">The second service endpoint.</param> + /// <returns>The result of the operator.</returns> + public static bool operator !=(IdentifierDiscoveryResult se1, IdentifierDiscoveryResult se2) { + return !(se1 == se2); + } + + /// <summary> + /// Determines whether the specified <see cref="T:System.Object"/> is equal to the current <see cref="T:System.Object"/>. + /// </summary> + /// <param name="obj">The <see cref="T:System.Object"/> to compare with the current <see cref="T:System.Object"/>.</param> + /// <returns> + /// true if the specified <see cref="T:System.Object"/> is equal to the current <see cref="T:System.Object"/>; otherwise, false. + /// </returns> + /// <exception cref="T:System.NullReferenceException"> + /// The <paramref name="obj"/> parameter is null. + /// </exception> + public override bool Equals(object obj) { + var other = obj as IdentifierDiscoveryResult; + if (other == null) { + return false; + } + + // We specifically do not check our ProviderSupportedServiceTypeUris array + // or the priority field + // as that is not persisted in our tokens, and it is not part of the + // important assertion validation that is part of the spec. + return + this.ClaimedIdentifier == other.ClaimedIdentifier && + this.ProviderEndpoint == other.ProviderEndpoint && + this.ProviderLocalIdentifier == other.ProviderLocalIdentifier && + this.Protocol.EqualsPractically(other.Protocol); + } + + /// <summary> + /// Serves as a hash function for a particular type. + /// </summary> + /// <returns> + /// A hash code for the current <see cref="T:System.Object"/>. + /// </returns> + public override int GetHashCode() { + return this.ClaimedIdentifier.GetHashCode(); + } + + /// <summary> + /// Returns a <see cref="T:System.String"/> that represents the current <see cref="T:System.Object"/>. + /// </summary> + /// <returns> + /// A <see cref="T:System.String"/> that represents the current <see cref="T:System.Object"/>. + /// </returns> + public override string ToString() { + StringBuilder builder = new StringBuilder(); + builder.AppendLine("ClaimedIdentifier: " + this.ClaimedIdentifier); + builder.AppendLine("ProviderLocalIdentifier: " + this.ProviderLocalIdentifier); + builder.AppendLine("ProviderEndpoint: " + this.ProviderEndpoint); + builder.AppendLine("OpenID version: " + this.Version); + builder.AppendLine("Service Type URIs:"); + foreach (string serviceTypeUri in this.Capabilities) { + builder.Append("\t"); + builder.AppendLine(serviceTypeUri); + } + builder.Length -= Environment.NewLine.Length; // trim last newline + return builder.ToString(); + } + + /// <summary> + /// Checks whether the OpenId Identifier claims support for a given extension. + /// </summary> + /// <typeparam name="T">The extension whose support is being queried.</typeparam> + /// <returns> + /// True if support for the extension is advertised. False otherwise. + /// </returns> + /// <remarks> + /// Note that a true or false return value is no guarantee of a Provider's + /// support for or lack of support for an extension. The return value is + /// determined by how the authenticating user filled out his/her XRDS document only. + /// The only way to be sure of support for a given extension is to include + /// the extension in the request and see if a response comes back for that extension. + /// </remarks> + [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "No parameter at all.")] + public bool IsExtensionSupported<T>() where T : IOpenIdMessageExtension, new() { + T extension = new T(); + return this.IsExtensionSupported(extension); + } + + /// <summary> + /// Checks whether the OpenId Identifier claims support for a given extension. + /// </summary> + /// <param name="extensionType">The extension whose support is being queried.</param> + /// <returns> + /// True if support for the extension is advertised. False otherwise. + /// </returns> + /// <remarks> + /// Note that a true or false return value is no guarantee of a Provider's + /// support for or lack of support for an extension. The return value is + /// determined by how the authenticating user filled out his/her XRDS document only. + /// The only way to be sure of support for a given extension is to include + /// the extension in the request and see if a response comes back for that extension. + /// </remarks> + public bool IsExtensionSupported(Type extensionType) { + var extension = (IOpenIdMessageExtension)Activator.CreateInstance(extensionType); + return this.IsExtensionSupported(extension); + } + + /// <summary> + /// Determines whether a given extension is supported by this endpoint. + /// </summary> + /// <param name="extension">An instance of the extension to check support for.</param> + /// <returns> + /// <c>true</c> if the extension is supported by this endpoint; otherwise, <c>false</c>. + /// </returns> + public bool IsExtensionSupported(IOpenIdMessageExtension extension) { + Requires.NotNull(extension, "extension"); + + // Consider the primary case. + if (this.IsTypeUriPresent(extension.TypeUri)) { + return true; + } + + // Consider the secondary cases. + if (extension.AdditionalSupportedTypeUris != null) { + if (extension.AdditionalSupportedTypeUris.Any(typeUri => this.IsTypeUriPresent(typeUri))) { + return true; + } + } + + return false; + } + + /// <summary> + /// Creates a <see cref="IdentifierDiscoveryResult"/> instance to represent some OP Identifier. + /// </summary> + /// <param name="providerIdentifier">The provider identifier (actually the user-supplied identifier).</param> + /// <param name="providerEndpoint">The provider endpoint.</param> + /// <param name="servicePriority">The service priority.</param> + /// <param name="uriPriority">The URI priority.</param> + /// <returns>The created <see cref="IdentifierDiscoveryResult"/> instance</returns> + internal static IdentifierDiscoveryResult CreateForProviderIdentifier(Identifier providerIdentifier, ProviderEndpointDescription providerEndpoint, int? servicePriority, int? uriPriority) { + Requires.NotNull(providerEndpoint, "providerEndpoint"); + + Protocol protocol = Protocol.Lookup(providerEndpoint.Version); + + return new IdentifierDiscoveryResult( + providerEndpoint, + protocol.ClaimedIdentifierForOPIdentifier, + providerIdentifier, + protocol.ClaimedIdentifierForOPIdentifier, + servicePriority, + uriPriority); + } + + /// <summary> + /// Creates a <see cref="IdentifierDiscoveryResult"/> instance to represent some Claimed Identifier. + /// </summary> + /// <param name="claimedIdentifier">The claimed identifier.</param> + /// <param name="providerLocalIdentifier">The provider local identifier.</param> + /// <param name="providerEndpoint">The provider endpoint.</param> + /// <param name="servicePriority">The service priority.</param> + /// <param name="uriPriority">The URI priority.</param> + /// <returns>The created <see cref="IdentifierDiscoveryResult"/> instance</returns> + internal static IdentifierDiscoveryResult CreateForClaimedIdentifier(Identifier claimedIdentifier, Identifier providerLocalIdentifier, ProviderEndpointDescription providerEndpoint, int? servicePriority, int? uriPriority) { + return CreateForClaimedIdentifier(claimedIdentifier, null, providerLocalIdentifier, providerEndpoint, servicePriority, uriPriority); + } + + /// <summary> + /// Creates a <see cref="IdentifierDiscoveryResult"/> instance to represent some Claimed Identifier. + /// </summary> + /// <param name="claimedIdentifier">The claimed identifier.</param> + /// <param name="userSuppliedIdentifier">The user supplied identifier.</param> + /// <param name="providerLocalIdentifier">The provider local identifier.</param> + /// <param name="providerEndpoint">The provider endpoint.</param> + /// <param name="servicePriority">The service priority.</param> + /// <param name="uriPriority">The URI priority.</param> + /// <returns>The created <see cref="IdentifierDiscoveryResult"/> instance</returns> + internal static IdentifierDiscoveryResult CreateForClaimedIdentifier(Identifier claimedIdentifier, Identifier userSuppliedIdentifier, Identifier providerLocalIdentifier, ProviderEndpointDescription providerEndpoint, int? servicePriority, int? uriPriority) { + return new IdentifierDiscoveryResult(providerEndpoint, claimedIdentifier, userSuppliedIdentifier, providerLocalIdentifier, servicePriority, uriPriority); + } + + /// <summary> + /// Determines whether a given type URI is present on the specified provider endpoint. + /// </summary> + /// <param name="typeUri">The type URI.</param> + /// <returns> + /// <c>true</c> if the type URI is present on the specified provider endpoint; otherwise, <c>false</c>. + /// </returns> + internal bool IsTypeUriPresent(string typeUri) { + Requires.NotNullOrEmpty(typeUri, "typeUri"); + return this.Capabilities.Contains(typeUri); + } + + /// <summary> + /// Sets the Capabilities property (this method is a test hook.) + /// </summary> + /// <param name="value">The value.</param> + /// <remarks>The publicize.exe tool should work for the unit tests, but for some reason it fails on the build server.</remarks> + internal void SetCapabilitiesForTestHook(ReadOnlyCollection<string> value) { + this.Capabilities = value; + } + + /// <summary> + /// Gets the priority rating for a given type of endpoint, allowing a + /// priority sorting of endpoints. + /// </summary> + /// <param name="endpoint">The endpoint to prioritize.</param> + /// <returns>An arbitary integer, which may be used for sorting against other returned values from this method.</returns> + private static double GetEndpointPrecedenceOrderByServiceType(IdentifierDiscoveryResult endpoint) { + // The numbers returned from this method only need to compare against other numbers + // from this method, which makes them arbitrary but relational to only others here. + if (endpoint.Capabilities.Contains(Protocol.V20.OPIdentifierServiceTypeURI)) { + return 0; + } + if (endpoint.Capabilities.Contains(Protocol.V20.ClaimedIdentifierServiceTypeURI)) { + return 1; + } + if (endpoint.Capabilities.Contains(Protocol.V11.ClaimedIdentifierServiceTypeURI)) { + return 2; + } + if (endpoint.Capabilities.Contains(Protocol.V10.ClaimedIdentifierServiceTypeURI)) { + return 3; + } + return 10; + } + +#if CONTRACTS_FULL + /// <summary> + /// Verifies conditions that should be true for any valid state of this object. + /// </summary> + [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Called by code contracts.")] + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called by code contracts.")] + [ContractInvariantMethod] + private void ObjectInvariant() { + Contract.Invariant(this.ProviderEndpoint != null); + Contract.Invariant(this.ClaimedIdentifier != null); + Contract.Invariant(this.ProviderLocalIdentifier != null); + Contract.Invariant(this.Capabilities != null); + Contract.Invariant(this.Version != null); + Contract.Invariant(this.Protocol != null); + } +#endif + } +} diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Messages/AssociateDiffieHellmanRequest.cs b/src/DotNetOpenAuth.OpenId/OpenId/Messages/AssociateDiffieHellmanRequest.cs new file mode 100644 index 0000000..b4f809f --- /dev/null +++ b/src/DotNetOpenAuth.OpenId/OpenId/Messages/AssociateDiffieHellmanRequest.cs @@ -0,0 +1,108 @@ +//----------------------------------------------------------------------- +// <copyright file="AssociateDiffieHellmanRequest.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.Messages { + using System; + using System.Collections.Generic; + using System.Globalization; + using System.Linq; + using System.Text; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.Messaging.Reflection; +#if !ExcludeDiffieHellman + using Org.Mentalis.Security.Cryptography; +#endif + + /// <summary> + /// An OpenID direct request from Relying Party to Provider to initiate an association that uses Diffie-Hellman encryption. + /// </summary> + internal class AssociateDiffieHellmanRequest : AssociateRequest { + /// <summary> + /// The (only) value we use for the X variable in the Diffie-Hellman algorithm. + /// </summary> + internal static readonly int DefaultX = 1024; + + /// <summary> + /// The default gen value for the Diffie-Hellman algorithm. + /// </summary> + internal static readonly byte[] DefaultGen = { 2 }; + + /// <summary> + /// The default modulus value for the Diffie-Hellman algorithm. + /// </summary> + internal static readonly byte[] DefaultMod = { + 0, 220, 249, 58, 11, 136, 57, 114, 236, 14, 25, 152, 154, 197, 162, + 206, 49, 14, 29, 55, 113, 126, 141, 149, 113, 187, 118, 35, 115, 24, + 102, 230, 30, 247, 90, 46, 39, 137, 139, 5, 127, 152, 145, 194, 226, + 122, 99, 156, 63, 41, 182, 8, 20, 88, 28, 211, 178, 202, 57, 134, 210, + 104, 55, 5, 87, 125, 69, 194, 231, 229, 45, 200, 28, 122, 23, 24, 118, + 229, 206, 167, 75, 20, 72, 191, 223, 175, 24, 130, 142, 253, 37, 25, + 241, 78, 69, 227, 130, 102, 52, 175, 25, 73, 229, 181, 53, 204, 130, + 154, 72, 59, 138, 118, 34, 62, 93, 73, 10, 37, 127, 5, 189, 255, 22, + 242, 251, 34, 197, 131, 171 }; + + /// <summary> + /// Initializes a new instance of the <see cref="AssociateDiffieHellmanRequest"/> class. + /// </summary> + /// <param name="version">The OpenID version this message must comply with.</param> + /// <param name="providerEndpoint">The OpenID Provider endpoint.</param> + internal AssociateDiffieHellmanRequest(Version version, Uri providerEndpoint) + : base(version, providerEndpoint) { + this.DiffieHellmanModulus = DefaultMod; + this.DiffieHellmanGen = DefaultGen; + } + + /// <summary> + /// Gets or sets the openid.dh_modulus value. + /// </summary> + /// <value>May be null if the default value given in the OpenID spec is to be used.</value> + [MessagePart("openid.dh_modulus", IsRequired = false, AllowEmpty = false)] + internal byte[] DiffieHellmanModulus { get; set; } + + /// <summary> + /// Gets or sets the openid.dh_gen value. + /// </summary> + /// <value>May be null if the default value given in the OpenID spec is to be used.</value> + [MessagePart("openid.dh_gen", IsRequired = false, AllowEmpty = false)] + internal byte[] DiffieHellmanGen { get; set; } + + /// <summary> + /// Gets or sets the openid.dh_consumer_public value. + /// </summary> + /// <remarks> + /// This property is initialized with a call to <see cref="InitializeRequest"/>. + /// </remarks> + [MessagePart("openid.dh_consumer_public", IsRequired = true, AllowEmpty = false)] + internal byte[] DiffieHellmanConsumerPublic { get; set; } + +#if !ExcludeDiffieHellman + /// <summary> + /// Gets the Diffie-Hellman algorithm. + /// </summary> + /// <remarks> + /// This property is initialized with a call to <see cref="InitializeRequest"/>. + /// </remarks> + internal DiffieHellman Algorithm { get; private set; } +#endif + + /// <summary> + /// Called by the Relying Party to initialize the Diffie-Hellman algorithm and consumer public key properties. + /// </summary> + internal void InitializeRequest() { +#if !ExcludeDiffieHellman + if (this.DiffieHellmanModulus == null || this.DiffieHellmanGen == null) { + throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, OpenIdStrings.DiffieHellmanRequiredPropertiesNotSet, string.Join(", ", new string[] { "DiffieHellmanModulus", "DiffieHellmanGen" }))); + } + + this.Algorithm = new DiffieHellmanManaged(this.DiffieHellmanModulus ?? DefaultMod, this.DiffieHellmanGen ?? DefaultGen, DefaultX); + byte[] consumerPublicKeyExchange = this.Algorithm.CreateKeyExchange(); + this.DiffieHellmanConsumerPublic = DiffieHellmanUtilities.EnsurePositive(consumerPublicKeyExchange); +#else + throw new NotSupportedException(); +#endif + } + } +} diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Messages/AssociateDiffieHellmanResponse.cs b/src/DotNetOpenAuth.OpenId/OpenId/Messages/AssociateDiffieHellmanResponse.cs new file mode 100644 index 0000000..d1836ec --- /dev/null +++ b/src/DotNetOpenAuth.OpenId/OpenId/Messages/AssociateDiffieHellmanResponse.cs @@ -0,0 +1,45 @@ +//----------------------------------------------------------------------- +// <copyright file="AssociateDiffieHellmanResponse.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.Messages { + using System; + using System.Diagnostics.Contracts; + using System.Security.Cryptography; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.Messaging.Reflection; + using Org.Mentalis.Security.Cryptography; + + /// <summary> + /// The successful Diffie-Hellman association response message. + /// </summary> + /// <remarks> + /// Association response messages are described in OpenID 2.0 section 8.2. This type covers section 8.2.3. + /// </remarks> + internal abstract class AssociateDiffieHellmanResponse : AssociateSuccessfulResponse { + /// <summary> + /// Initializes a new instance of the <see cref="AssociateDiffieHellmanResponse"/> class. + /// </summary> + /// <param name="responseVersion">The OpenID version of the response message.</param> + /// <param name="originatingRequest">The originating request.</param> + internal AssociateDiffieHellmanResponse(Version responseVersion, AssociateDiffieHellmanRequest originatingRequest) + : base(responseVersion, originatingRequest) { + } + + /// <summary> + /// Gets or sets the Provider's Diffie-Hellman public key. + /// </summary> + /// <value>btwoc(g ^ xb mod p)</value> + [MessagePart("dh_server_public", IsRequired = true, AllowEmpty = false)] + internal byte[] DiffieHellmanServerPublic { get; set; } + + /// <summary> + /// Gets or sets the MAC key (shared secret), encrypted with the secret Diffie-Hellman value. + /// </summary> + /// <value>H(btwoc(g ^ (xa * xb) mod p)) XOR MAC key. H is either "SHA1" or "SHA256" depending on the session type. </value> + [MessagePart("enc_mac_key", IsRequired = true, AllowEmpty = false)] + internal byte[] EncodedMacKey { get; set; } + } +} diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Messages/AssociateRequest.cs b/src/DotNetOpenAuth.OpenId/OpenId/Messages/AssociateRequest.cs new file mode 100644 index 0000000..db0c39e --- /dev/null +++ b/src/DotNetOpenAuth.OpenId/OpenId/Messages/AssociateRequest.cs @@ -0,0 +1,67 @@ +//----------------------------------------------------------------------- +// <copyright file="AssociateRequest.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.Messages { + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Diagnostics.Contracts; + using System.Linq; + using System.Text; + using DotNetOpenAuth.Messaging; + + /// <summary> + /// An OpenID direct request from Relying Party to Provider to initiate an association. + /// </summary> + [DebuggerDisplay("OpenID {Version} {Mode} {AssociationType} {SessionType}")] + internal abstract class AssociateRequest : RequestBase { + /// <summary> + /// Initializes a new instance of the <see cref="AssociateRequest"/> class. + /// </summary> + /// <param name="version">The OpenID version this message must comply with.</param> + /// <param name="providerEndpoint">The OpenID Provider endpoint.</param> + protected AssociateRequest(Version version, Uri providerEndpoint) + : base(version, providerEndpoint, GetProtocolConstant(version, p => p.Args.Mode.associate), MessageTransport.Direct) { + } + + /// <summary> + /// Gets or sets the preferred association type. The association type defines the algorithm to be used to sign subsequent messages. + /// </summary> + /// <value>Value: A valid association type from Section 8.3.</value> + [MessagePart("openid.assoc_type", IsRequired = true, AllowEmpty = false)] + internal string AssociationType { get; set; } + + /// <summary> + /// Gets or sets the preferred association session type. This defines the method used to encrypt the association's MAC key in transit. + /// </summary> + /// <value>Value: A valid association session type from Section 8.4 (Association Session Types). </value> + /// <remarks>Note: Unless using transport layer encryption, "no-encryption" MUST NOT be used. </remarks> + [MessagePart("openid.session_type", IsRequired = false, AllowEmpty = true)] + [MessagePart("openid.session_type", IsRequired = true, AllowEmpty = false, MinVersion = "2.0")] + internal string SessionType { get; set; } + + /// <summary> + /// Checks the message state for conformity to the protocol specification + /// and throws an exception if the message is invalid. + /// </summary> + /// <remarks> + /// <para>Some messages have required fields, or combinations of fields that must relate to each other + /// in specialized ways. After deserializing a message, this method checks the state of the + /// message to see if it conforms to the protocol.</para> + /// <para>Note that this property should <i>not</i> check signatures or perform any state checks + /// outside this scope of this particular message.</para> + /// </remarks> + /// <exception cref="ProtocolException">Thrown if the message is invalid.</exception> + public override void EnsureValidMessage() { + base.EnsureValidMessage(); + + ErrorUtilities.VerifyProtocol( + !string.Equals(this.SessionType, Protocol.Args.SessionType.NoEncryption, StringComparison.Ordinal) || this.Recipient.IsTransportSecure(), + OpenIdStrings.NoEncryptionSessionRequiresHttps, + this); + } + } +} diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Messages/AssociateSuccessfulResponse.cs b/src/DotNetOpenAuth.OpenId/OpenId/Messages/AssociateSuccessfulResponse.cs new file mode 100644 index 0000000..c3d8938 --- /dev/null +++ b/src/DotNetOpenAuth.OpenId/OpenId/Messages/AssociateSuccessfulResponse.cs @@ -0,0 +1,92 @@ +//----------------------------------------------------------------------- +// <copyright file="AssociateSuccessfulResponse.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.Messages { + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Diagnostics.Contracts; + using System.Linq; + using System.Text; + using DotNetOpenAuth.Messaging; + + /// <summary> + /// The base class that all successful association response messages derive from. + /// </summary> + /// <remarks> + /// Association response messages are described in OpenID 2.0 section 8.2. This type covers section 8.2.1. + /// </remarks> + [DebuggerDisplay("OpenID {Version} associate response {AssociationHandle} {AssociationType} {SessionType}")] + [ContractClass(typeof(AssociateSuccessfulResponseContract))] + internal abstract class AssociateSuccessfulResponse : DirectResponseBase { + /// <summary> + /// Initializes a new instance of the <see cref="AssociateSuccessfulResponse"/> class. + /// </summary> + /// <param name="responseVersion">The OpenID version of the response message.</param> + /// <param name="originatingRequest">The originating request.</param> + internal AssociateSuccessfulResponse(Version responseVersion, AssociateRequest originatingRequest) + : base(responseVersion, originatingRequest) { + } + + /// <summary> + /// Gets or sets the association handle is used as a key to refer to this association in subsequent messages. + /// </summary> + /// <value>A string 255 characters or less in length. It MUST consist only of ASCII characters in the range 33-126 inclusive (printable non-whitespace characters). </value> + [MessagePart("assoc_handle", IsRequired = true, AllowEmpty = false)] + internal string AssociationHandle { get; set; } + + /// <summary> + /// Gets or sets the preferred association type. The association type defines the algorithm to be used to sign subsequent messages. + /// </summary> + /// <value>Value: A valid association type from Section 8.3.</value> + [MessagePart("assoc_type", IsRequired = true, AllowEmpty = false)] + internal string AssociationType { get; set; } + + /// <summary> + /// Gets or sets the value of the "openid.session_type" parameter from the request. + /// If the OP is unwilling or unable to support this association type, it MUST return an + /// unsuccessful response (Unsuccessful Response Parameters). + /// </summary> + /// <value>Value: A valid association session type from Section 8.4 (Association Session Types). </value> + /// <remarks>Note: Unless using transport layer encryption, "no-encryption" MUST NOT be used. </remarks> + [MessagePart("session_type", IsRequired = false, AllowEmpty = true)] + [MessagePart("session_type", IsRequired = true, AllowEmpty = false, MinVersion = "2.0")] + internal string SessionType { get; set; } + + /// <summary> + /// Gets or sets the lifetime, in seconds, of this association. The Relying Party MUST NOT use the association after this time has passed. + /// </summary> + /// <value>An integer, represented in base 10 ASCII. </value> + [MessagePart("expires_in", IsRequired = true)] + internal long ExpiresIn { get; set; } + + /// <summary> + /// Checks the message state for conformity to the protocol specification + /// and throws an exception if the message is invalid. + /// </summary> + /// <remarks> + /// <para>Some messages have required fields, or combinations of fields that must relate to each other + /// in specialized ways. After deserializing a message, this method checks the state of the + /// message to see if it conforms to the protocol.</para> + /// <para>Note that this property should <i>not</i> check signatures or perform any state checks + /// outside this scope of this particular message.</para> + /// </remarks> + /// <exception cref="ProtocolException">Thrown if the message is invalid.</exception> + public override void EnsureValidMessage() { + base.EnsureValidMessage(); + + if (this.Version.Major < 2) { + ErrorUtilities.VerifyProtocol( + string.IsNullOrEmpty(this.SessionType) || string.Equals(this.SessionType, this.Protocol.Args.SessionType.DH_SHA1, StringComparison.Ordinal), + MessagingStrings.UnexpectedMessagePartValueForConstant, + GetType().Name, + Protocol.openid.session_type, + this.Protocol.Args.SessionType.DH_SHA1, + this.SessionType); + } + } + } +} diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Messages/AssociateSuccessfulResponseContract.cs b/src/DotNetOpenAuth.OpenId/OpenId/Messages/AssociateSuccessfulResponseContract.cs new file mode 100644 index 0000000..39a79a4 --- /dev/null +++ b/src/DotNetOpenAuth.OpenId/OpenId/Messages/AssociateSuccessfulResponseContract.cs @@ -0,0 +1,17 @@ +// <auto-generated /> + +namespace DotNetOpenAuth.OpenId.Messages { + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Diagnostics.Contracts; + using System.Linq; + using System.Text; + using DotNetOpenAuth.Messaging; + + [ContractClassFor(typeof(AssociateSuccessfulResponse))] + internal abstract class AssociateSuccessfulResponseContract : AssociateSuccessfulResponse { + protected AssociateSuccessfulResponseContract() : base(null, null) { + } + } +} diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Messages/AssociateUnencryptedRequest.cs b/src/DotNetOpenAuth.OpenId/OpenId/Messages/AssociateUnencryptedRequest.cs new file mode 100644 index 0000000..eda8cf3 --- /dev/null +++ b/src/DotNetOpenAuth.OpenId/OpenId/Messages/AssociateUnencryptedRequest.cs @@ -0,0 +1,50 @@ +//----------------------------------------------------------------------- +// <copyright file="AssociateUnencryptedRequest.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.Messages { + using System; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.Messaging.Reflection; + + /// <summary> + /// Represents an association request that is sent using HTTPS and otherwise communicates the shared secret in plain text. + /// </summary> + internal class AssociateUnencryptedRequest : AssociateRequest { + /// <summary> + /// Initializes a new instance of the <see cref="AssociateUnencryptedRequest"/> class. + /// </summary> + /// <param name="version">The OpenID version this message must comply with.</param> + /// <param name="providerEndpoint">The OpenID Provider endpoint.</param> + internal AssociateUnencryptedRequest(Version version, Uri providerEndpoint) + : base(version, providerEndpoint) { + SessionType = Protocol.Args.SessionType.NoEncryption; + } + + /// <summary> + /// Checks the message state for conformity to the protocol specification + /// and throws an exception if the message is invalid. + /// </summary> + /// <remarks> + /// <para>Some messages have required fields, or combinations of fields that must relate to each other + /// in specialized ways. After deserializing a message, this method checks the state of the + /// message to see if it conforms to the protocol.</para> + /// <para>Note that this property should <i>not</i> check signatures or perform any state checks + /// outside this scope of this particular message.</para> + /// </remarks> + /// <exception cref="ProtocolException">Thrown if the message is invalid.</exception> + public override void EnsureValidMessage() { + base.EnsureValidMessage(); + + ErrorUtilities.VerifyProtocol( + string.Equals(this.SessionType, Protocol.Args.SessionType.NoEncryption, StringComparison.Ordinal), + MessagingStrings.UnexpectedMessagePartValueForConstant, + GetType().Name, + Protocol.openid.session_type, + Protocol.Args.SessionType.NoEncryption, + SessionType); + } + } +} diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Messages/AssociateUnencryptedResponse.cs b/src/DotNetOpenAuth.OpenId/OpenId/Messages/AssociateUnencryptedResponse.cs new file mode 100644 index 0000000..b7a0139 --- /dev/null +++ b/src/DotNetOpenAuth.OpenId/OpenId/Messages/AssociateUnencryptedResponse.cs @@ -0,0 +1,35 @@ +//----------------------------------------------------------------------- +// <copyright file="AssociateUnencryptedResponse.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.Messages { + using System; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.Messaging.Reflection; + + /// <summary> + /// The successful unencrypted association response message. + /// </summary> + /// <remarks> + /// Association response messages are described in OpenID 2.0 section 8.2. This type covers section 8.2.2. + /// </remarks> + internal class AssociateUnencryptedResponse : AssociateSuccessfulResponse { + /// <summary> + /// Initializes a new instance of the <see cref="AssociateUnencryptedResponse"/> class. + /// </summary> + /// <param name="responseVersion">The OpenID version of the response message.</param> + /// <param name="originatingRequest">The originating request.</param> + internal AssociateUnencryptedResponse(Version responseVersion, AssociateUnencryptedRequest originatingRequest) + : base(responseVersion, originatingRequest) { + SessionType = Protocol.Args.SessionType.NoEncryption; + } + + /// <summary> + /// Gets or sets the MAC key (shared secret) for this association, Base 64 (Josefsson, S., “The Base16, Base32, and Base64 Data Encodings,” .) [RFC3548] encoded. + /// </summary> + [MessagePart("mac_key", IsRequired = true, AllowEmpty = false)] + internal byte[] MacKey { get; set; } + } +} diff --git a/src/DotNetOpenAuth/OpenId/Messages/AssociateUnsuccessfulResponse.cs b/src/DotNetOpenAuth.OpenId/OpenId/Messages/AssociateUnsuccessfulResponse.cs index 71ecc05..71ecc05 100644 --- a/src/DotNetOpenAuth/OpenId/Messages/AssociateUnsuccessfulResponse.cs +++ b/src/DotNetOpenAuth.OpenId/OpenId/Messages/AssociateUnsuccessfulResponse.cs diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Messages/CheckAuthenticationRequest.cs b/src/DotNetOpenAuth.OpenId/OpenId/Messages/CheckAuthenticationRequest.cs new file mode 100644 index 0000000..273c444 --- /dev/null +++ b/src/DotNetOpenAuth.OpenId/OpenId/Messages/CheckAuthenticationRequest.cs @@ -0,0 +1,79 @@ +//----------------------------------------------------------------------- +// <copyright file="CheckAuthenticationRequest.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.Messages { + using System; + using System.Collections.Generic; + using System.Diagnostics.Contracts; + using System.Linq; + using System.Text; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.Messaging.Reflection; + using DotNetOpenAuth.OpenId.ChannelElements; + + /// <summary> + /// A message a Relying Party sends to a Provider to confirm the validity + /// of a positive assertion that was signed by a Provider-only secret. + /// </summary> + /// <remarks> + /// The significant payload of this message depends entirely upon the + /// assertion message, and therefore is all in the + /// <see cref="DotNetOpenAuth.Messaging.IMessage.ExtraData"/> property bag. + /// </remarks> + internal class CheckAuthenticationRequest : RequestBase { + /// <summary> + /// Initializes a new instance of the <see cref="CheckAuthenticationRequest"/> class. + /// </summary> + /// <param name="version">The OpenID version this message must comply with.</param> + /// <param name="providerEndpoint">The OpenID Provider endpoint.</param> + internal CheckAuthenticationRequest(Version version, Uri providerEndpoint) + : base(version, providerEndpoint, GetProtocolConstant(version, p => p.Args.Mode.check_authentication), MessageTransport.Direct) { + } + + /// <summary> + /// Initializes a new instance of the <see cref="CheckAuthenticationRequest"/> class + /// based on the contents of some signed message whose signature must be verified. + /// </summary> + /// <param name="message">The message whose signature should be verified.</param> + /// <param name="channel">The channel. This is used only within the constructor and is not stored in a field.</param> + internal CheckAuthenticationRequest(IndirectSignedResponse message, Channel channel) + : base(message.Version, message.ProviderEndpoint, GetProtocolConstant(message.Version, p => p.Args.Mode.check_authentication), MessageTransport.Direct) { + Requires.NotNull(channel, "channel"); + + // Copy all message parts from the id_res message into this one, + // except for the openid.mode parameter. + MessageDictionary checkPayload = channel.MessageDescriptions.GetAccessor(message, true); + MessageDictionary thisPayload = channel.MessageDescriptions.GetAccessor(this); + foreach (var pair in checkPayload) { + if (!string.Equals(pair.Key, this.Protocol.openid.mode)) { + thisPayload[pair.Key] = pair.Value; + } + } + } + + /// <summary> + /// Gets or sets a value indicating whether the signature being verified by this request + /// is in fact valid. + /// </summary> + /// <value><c>true</c> if the signature is valid; otherwise, <c>false</c>.</value> + /// <remarks> + /// This property is automatically set as the message is received by the channel's + /// signing binding element. + /// </remarks> + internal bool IsValid { get; set; } + + /// <summary> + /// Gets or sets the ReturnTo that existed in the original signed message. + /// </summary> + /// <remarks> + /// This exists strictly for convenience in recreating the <see cref="IndirectSignedResponse"/> + /// message. + /// </remarks> + [MessagePart("openid.return_to", IsRequired = true, AllowEmpty = false, Encoder = typeof(OriginalStringUriEncoder))] + [MessagePart("openid.return_to", IsRequired = false, AllowEmpty = false, MinVersion = "2.0", Encoder = typeof(OriginalStringUriEncoder))] + internal Uri ReturnTo { get; set; } + } +} diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Messages/CheckAuthenticationResponse.cs b/src/DotNetOpenAuth.OpenId/OpenId/Messages/CheckAuthenticationResponse.cs new file mode 100644 index 0000000..f52ccf8 --- /dev/null +++ b/src/DotNetOpenAuth.OpenId/OpenId/Messages/CheckAuthenticationResponse.cs @@ -0,0 +1,55 @@ +//----------------------------------------------------------------------- +// <copyright file="CheckAuthenticationResponse.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.Messages { + using System; + using System.Collections.Generic; + using System.Diagnostics.Contracts; + using System.Linq; + using System.Text; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OpenId.ChannelElements; + + /// <summary> + /// The message sent from the Provider to the Relying Party to confirm/deny + /// the validity of an assertion that was signed by a private Provider secret. + /// </summary> + internal class CheckAuthenticationResponse : DirectResponseBase { + /// <summary> + /// Initializes a new instance of the <see cref="CheckAuthenticationResponse"/> class + /// for use by the Relying Party. + /// </summary> + /// <param name="responseVersion">The OpenID version of the response message.</param> + /// <param name="request">The request that this message is responding to.</param> + internal CheckAuthenticationResponse(Version responseVersion, CheckAuthenticationRequest request) + : base(responseVersion, request) { + } + + /// <summary> + /// Gets or sets a value indicating whether the signature of the verification request is valid. + /// </summary> + [MessagePart("is_valid", IsRequired = true)] + internal bool IsValid { get; set; } + + /// <summary> + /// Gets or sets the handle the relying party should invalidate if <see cref="IsValid"/> is true. + /// </summary> + /// <value>The "invalidate_handle" value sent in the verification request, if the OP confirms it is invalid.</value> + /// <remarks> + /// <para>If present in a verification response with "is_valid" set to "true", + /// the Relying Party SHOULD remove the corresponding association from + /// its store and SHOULD NOT send further authentication requests with + /// this handle.</para> + /// <para>This two-step process for invalidating associations is necessary + /// to prevent an attacker from invalidating an association at will by + /// adding "invalidate_handle" parameters to an authentication response.</para> + /// <para>For OpenID 1.1, we allow this to be present but empty to put up with poor implementations such as Blogger.</para> + /// </remarks> + [MessagePart("invalidate_handle", IsRequired = false, AllowEmpty = true, MaxVersion = "1.1")] + [MessagePart("invalidate_handle", IsRequired = false, AllowEmpty = false, MinVersion = "2.0")] + internal string InvalidateHandle { get; set; } + } +} diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Messages/CheckIdRequest.cs b/src/DotNetOpenAuth.OpenId/OpenId/Messages/CheckIdRequest.cs new file mode 100644 index 0000000..0667dcb --- /dev/null +++ b/src/DotNetOpenAuth.OpenId/OpenId/Messages/CheckIdRequest.cs @@ -0,0 +1,93 @@ +//----------------------------------------------------------------------- +// <copyright file="CheckIdRequest.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.Messages { + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Linq; + using System.Text; + using DotNetOpenAuth.Messaging; + + /// <summary> + /// An authentication request from a Relying Party to a Provider. + /// </summary> + /// <remarks> + /// This message type satisfies OpenID 2.0 section 9.1. + /// </remarks> + [DebuggerDisplay("OpenID {Version} {Mode} {ClaimedIdentifier}")] + [Serializable] + internal class CheckIdRequest : SignedResponseRequest { + /// <summary> + /// Initializes a new instance of the <see cref="CheckIdRequest"/> class. + /// </summary> + /// <param name="version">The OpenID version to use.</param> + /// <param name="providerEndpoint">The Provider endpoint that receives this message.</param> + /// <param name="mode"> + /// <see cref="AuthenticationRequestMode.Immediate"/> for asynchronous javascript clients; + /// <see cref="AuthenticationRequestMode.Setup"/> to allow the Provider to interact with the user in order to complete authentication. + /// </param> + internal CheckIdRequest(Version version, Uri providerEndpoint, AuthenticationRequestMode mode) : + base(version, providerEndpoint, mode) { + } + + /// <summary> + /// Gets or sets the Claimed Identifier. + /// </summary> + /// <remarks> + /// <para>"openid.claimed_id" and "openid.identity" SHALL be either both present or both absent. + /// If neither value is present, the assertion is not about an identifier, + /// and will contain other information in its payload, using extensions (Extensions). </para> + /// <para>It is RECOMMENDED that OPs accept XRI identifiers with or without the "xri://" prefix, as specified in the Normalization (Normalization) section. </para> + /// </remarks> + [MessagePart("openid.claimed_id", IsRequired = true, AllowEmpty = false, MinVersion = "2.0")] + internal Identifier ClaimedIdentifier { get; set; } + + /// <summary> + /// Gets or sets the OP Local Identifier. + /// </summary> + /// <value>The OP-Local Identifier. </value> + /// <remarks> + /// <para>If a different OP-Local Identifier is not specified, the claimed + /// identifier MUST be used as the value for openid.identity.</para> + /// <para>Note: If this is set to the special value + /// "http://specs.openid.net/auth/2.0/identifier_select" then the OP SHOULD + /// choose an Identifier that belongs to the end user. This parameter MAY + /// be omitted if the request is not about an identifier (for instance if + /// an extension is in use that makes the request meaningful without it; + /// see openid.claimed_id above). </para> + /// </remarks> + [MessagePart("openid.identity", IsRequired = true, AllowEmpty = false)] + internal Identifier LocalIdentifier { get; set; } + + /// <summary> + /// Checks the message state for conformity to the protocol specification + /// and throws an exception if the message is invalid. + /// </summary> + /// <remarks> + /// <para>Some messages have required fields, or combinations of fields that must relate to each other + /// in specialized ways. After deserializing a message, this method checks the state of the + /// message to see if it conforms to the protocol.</para> + /// <para>Note that this property should <i>not</i> check signatures or perform any state checks + /// outside this scope of this particular message.</para> + /// </remarks> + /// <exception cref="ProtocolException">Thrown if the message is invalid.</exception> + public override void EnsureValidMessage() { + base.EnsureValidMessage(); + + if (this.Protocol.ClaimedIdentifierForOPIdentifier != null) { + // Ensure that the claimed_id and identity parameters are either both the + // special identifier_select value or both NOT that value. + ErrorUtilities.VerifyProtocol( + (this.LocalIdentifier == this.Protocol.ClaimedIdentifierForOPIdentifier) == (this.ClaimedIdentifier == this.Protocol.ClaimedIdentifierForOPIdentifier), + OpenIdStrings.MatchingArgumentsExpected, + Protocol.openid.claimed_id, + Protocol.openid.identity, + Protocol.ClaimedIdentifierForOPIdentifier); + } + } + } +} diff --git a/src/DotNetOpenAuth/OpenId/Messages/DirectErrorResponse.cs b/src/DotNetOpenAuth.OpenId/OpenId/Messages/DirectErrorResponse.cs index 607a139..607a139 100644 --- a/src/DotNetOpenAuth/OpenId/Messages/DirectErrorResponse.cs +++ b/src/DotNetOpenAuth.OpenId/OpenId/Messages/DirectErrorResponse.cs diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Messages/DirectResponseBase.cs b/src/DotNetOpenAuth.OpenId/OpenId/Messages/DirectResponseBase.cs new file mode 100644 index 0000000..da6a1f6 --- /dev/null +++ b/src/DotNetOpenAuth.OpenId/OpenId/Messages/DirectResponseBase.cs @@ -0,0 +1,157 @@ +//----------------------------------------------------------------------- +// <copyright file="DirectResponseBase.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.Messages { + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; + using System.Diagnostics.Contracts; + using DotNetOpenAuth.Messaging; + + /// <summary> + /// A common base class for OpenID direct message responses. + /// </summary> + [DebuggerDisplay("OpenID {Version} response")] + internal class DirectResponseBase : IDirectResponseProtocolMessage { + /// <summary> + /// The openid.ns parameter in the message. + /// </summary> + /// <value>"http://specs.openid.net/auth/2.0" </value> + /// <remarks> + /// OpenID 2.0 Section 5.1.2: + /// This particular value MUST be present for the response to be a valid OpenID 2.0 response. + /// Future versions of the specification may define different values in order to allow message + /// recipients to properly interpret the request. + /// </remarks> + [SuppressMessage("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields", Justification = "Read by reflection.")] + [MessagePart("ns", IsRequired = true, AllowEmpty = false, MinVersion = "2.0")] +#pragma warning disable 0414 // read by reflection + private readonly string OpenIdNamespace = Protocol.OpenId2Namespace; +#pragma warning restore 0414 + + /// <summary> + /// Backing store for the <see cref="OriginatingRequest"/> properties. + /// </summary> + private IDirectedProtocolMessage originatingRequest; + + /// <summary> + /// Backing store for the <see cref="Incoming"/> properties. + /// </summary> + private bool incoming; + + /// <summary> + /// The dictionary of parameters that are not part of the OpenID specification. + /// </summary> + private Dictionary<string, string> extraData = new Dictionary<string, string>(); + + /// <summary> + /// Initializes a new instance of the <see cref="DirectResponseBase"/> class. + /// </summary> + /// <param name="responseVersion">The OpenID version of the response message.</param> + /// <param name="originatingRequest">The originating request. May be null in case the request is unrecognizable and this is an error response.</param> + protected DirectResponseBase(Version responseVersion, IDirectedProtocolMessage originatingRequest) { + Requires.NotNull(responseVersion, "responseVersion"); + + this.Version = responseVersion; + this.originatingRequest = originatingRequest; + } + + #region IProtocolMessage Properties + + /// <summary> + /// Gets the version of the protocol this message is prepared to implement. + /// </summary> + /// <value>Version 2.0</value> + public Version Version { get; private set; } + + /// <summary> + /// Gets the level of protection this message requires. + /// </summary> + /// <value><see cref="MessageProtections.None"/></value> + public MessageProtections RequiredProtection { + get { return MessageProtections.None; } + } + + /// <summary> + /// Gets a value indicating whether this is a direct or indirect message. + /// </summary> + /// <value><see cref="MessageTransport.Direct"/></value> + public MessageTransport Transport { + get { return MessageTransport.Direct; } + } + + /// <summary> + /// Gets the extra, non-OAuth parameters included in the message. + /// </summary> + public IDictionary<string, string> ExtraData { + get { return this.extraData; } + } + + #endregion + + #region IDirectResponseProtocolMessage Members + + /// <summary> + /// Gets the originating request message that caused this response to be formed. + /// </summary> + /// <remarks> + /// This property may be null if the request message was undecipherable. + /// </remarks> + IDirectedProtocolMessage IDirectResponseProtocolMessage.OriginatingRequest { + get { return this.originatingRequest; } + } + + #endregion + + /// <summary> + /// Gets a value indicating whether this message was deserialized as an incoming message. + /// </summary> + protected internal bool Incoming { + get { return this.incoming; } + } + + /// <summary> + /// Gets the protocol used by this message. + /// </summary> + protected Protocol Protocol { + get { return Protocol.Lookup(this.Version); } + } + + /// <summary> + /// Gets the originating request message that caused this response to be formed. + /// </summary> + protected IDirectedProtocolMessage OriginatingRequest { + get { return this.originatingRequest; } + } + + #region IProtocolMessage methods + + /// <summary> + /// Checks the message state for conformity to the protocol specification + /// and throws an exception if the message is invalid. + /// </summary> + /// <remarks> + /// <para>Some messages have required fields, or combinations of fields that must relate to each other + /// in specialized ways. After deserializing a message, this method checks the state of the + /// message to see if it conforms to the protocol.</para> + /// <para>Note that this property should <i>not</i> check signatures or perform any state checks + /// outside this scope of this particular message.</para> + /// </remarks> + /// <exception cref="ProtocolException">Thrown if the message is invalid.</exception> + public virtual void EnsureValidMessage() { + } + + #endregion + + /// <summary> + /// Sets a flag indicating that this message is received (as opposed to sent). + /// </summary> + internal void SetAsIncoming() { + this.incoming = true; + } + } +} diff --git a/src/DotNetOpenAuth/OpenId/Messages/IErrorMessage.cs b/src/DotNetOpenAuth.OpenId/OpenId/Messages/IErrorMessage.cs index 549b327..549b327 100644 --- a/src/DotNetOpenAuth/OpenId/Messages/IErrorMessage.cs +++ b/src/DotNetOpenAuth.OpenId/OpenId/Messages/IErrorMessage.cs diff --git a/src/DotNetOpenAuth/OpenId/Messages/IOpenIdMessageExtension.cs b/src/DotNetOpenAuth.OpenId/OpenId/Messages/IOpenIdMessageExtension.cs index 08e02ba..08e02ba 100644 --- a/src/DotNetOpenAuth/OpenId/Messages/IOpenIdMessageExtension.cs +++ b/src/DotNetOpenAuth.OpenId/OpenId/Messages/IOpenIdMessageExtension.cs diff --git a/src/DotNetOpenAuth/OpenId/Messages/IndirectErrorResponse.cs b/src/DotNetOpenAuth.OpenId/OpenId/Messages/IndirectErrorResponse.cs index eb006db..eb006db 100644 --- a/src/DotNetOpenAuth/OpenId/Messages/IndirectErrorResponse.cs +++ b/src/DotNetOpenAuth.OpenId/OpenId/Messages/IndirectErrorResponse.cs diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Messages/IndirectResponseBase.cs b/src/DotNetOpenAuth.OpenId/OpenId/Messages/IndirectResponseBase.cs new file mode 100644 index 0000000..f23d912 --- /dev/null +++ b/src/DotNetOpenAuth.OpenId/OpenId/Messages/IndirectResponseBase.cs @@ -0,0 +1,113 @@ +//----------------------------------------------------------------------- +// <copyright file="IndirectResponseBase.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.Messages { + using System; + using System.Collections.Generic; + using System.Diagnostics.Contracts; + using System.Linq; + using System.Text; + using DotNetOpenAuth.Messaging; + + /// <summary> + /// A common base class from which indirect response messages should derive. + /// </summary> + [Serializable] + internal class IndirectResponseBase : RequestBase, IProtocolMessageWithExtensions { + /// <summary> + /// Backing store for the <see cref="Extensions"/> property. + /// </summary> + private IList<IExtensionMessage> extensions = new List<IExtensionMessage>(); + + /// <summary> + /// Initializes a new instance of the <see cref="IndirectResponseBase"/> class. + /// </summary> + /// <param name="request">The request that caused this response message to be constructed.</param> + /// <param name="mode">The value of the openid.mode parameter.</param> + protected IndirectResponseBase(SignedResponseRequest request, string mode) + : base(GetVersion(request), GetReturnTo(request), mode, MessageTransport.Indirect) { + Requires.NotNull(request, "request"); + + this.OriginatingRequest = request; + } + + /// <summary> + /// Initializes a new instance of the <see cref="IndirectResponseBase"/> class + /// for unsolicited assertion scenarios. + /// </summary> + /// <param name="version">The OpenID version supported at the Relying Party.</param> + /// <param name="relyingPartyReturnTo"> + /// The URI at which the Relying Party receives OpenID indirect messages. + /// </param> + /// <param name="mode">The value to use for the openid.mode parameter.</param> + protected IndirectResponseBase(Version version, Uri relyingPartyReturnTo, string mode) + : base(version, relyingPartyReturnTo, mode, MessageTransport.Indirect) { + } + + #region IProtocolMessageWithExtensions Members + + /// <summary> + /// Gets the list of extensions that are included with this message. + /// </summary> + /// <value></value> + /// <remarks> + /// Implementations of this interface should ensure that this property never returns null. + /// </remarks> + public IList<IExtensionMessage> Extensions { + get { return this.extensions; } + } + + #endregion + + /// <summary> + /// Gets the signed extensions on this message. + /// </summary> + internal IEnumerable<IOpenIdMessageExtension> SignedExtensions { + get { return this.extensions.OfType<IOpenIdMessageExtension>().Where(ext => ext.IsSignedByRemoteParty); } + } + + /// <summary> + /// Gets the unsigned extensions on this message. + /// </summary> + internal IEnumerable<IOpenIdMessageExtension> UnsignedExtensions { + get { return this.extensions.OfType<IOpenIdMessageExtension>().Where(ext => !ext.IsSignedByRemoteParty); } + } + + /// <summary> + /// Gets the originating request message, if applicable. + /// </summary> + protected SignedResponseRequest OriginatingRequest { get; private set; } + + /// <summary> + /// Gets the <see cref="IMessage.Version"/> property of a message. + /// </summary> + /// <param name="message">The message to fetch the protocol version from.</param> + /// <returns>The value of the <see cref="IMessage.Version"/> property.</returns> + /// <remarks> + /// This method can be used by a constructor to throw an <see cref="ArgumentNullException"/> + /// instead of a <see cref="NullReferenceException"/>. + /// </remarks> + internal static Version GetVersion(IProtocolMessage message) { + Requires.NotNull(message, "message"); + return message.Version; + } + + /// <summary> + /// Gets the <see cref="SignedResponseRequest.ReturnTo"/> property of a message. + /// </summary> + /// <param name="message">The message to fetch the ReturnTo from.</param> + /// <returns>The value of the <see cref="SignedResponseRequest.ReturnTo"/> property.</returns> + /// <remarks> + /// This method can be used by a constructor to throw an <see cref="ArgumentNullException"/> + /// instead of a <see cref="NullReferenceException"/>. + /// </remarks> + private static Uri GetReturnTo(SignedResponseRequest message) { + Requires.NotNull(message, "message"); + ErrorUtilities.VerifyProtocol(message.ReturnTo != null, OpenIdStrings.ReturnToRequiredForResponse); + return message.ReturnTo; + } + } +} diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Messages/IndirectSignedResponse.cs b/src/DotNetOpenAuth.OpenId/OpenId/Messages/IndirectSignedResponse.cs new file mode 100644 index 0000000..9a60757 --- /dev/null +++ b/src/DotNetOpenAuth.OpenId/OpenId/Messages/IndirectSignedResponse.cs @@ -0,0 +1,409 @@ +//----------------------------------------------------------------------- +// <copyright file="IndirectSignedResponse.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.Messages { + using System; + using System.Collections.Generic; + using System.Collections.Specialized; + using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; + using System.Diagnostics.Contracts; + using System.Globalization; + using System.Linq; + using System.Net.Security; + using System.Web; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.Messaging.Bindings; + using DotNetOpenAuth.Messaging.Reflection; + using DotNetOpenAuth.OpenId.ChannelElements; + + /// <summary> + /// An indirect message from a Provider to a Relying Party where at least part of the + /// payload is signed so the Relying Party can verify it has not been tampered with. + /// </summary> + [DebuggerDisplay("OpenID {Version} {Mode} (no id assertion)")] + [Serializable] + internal class IndirectSignedResponse : IndirectResponseBase, ITamperResistantOpenIdMessage { + /// <summary> + /// The allowed date/time formats for the response_nonce parameter. + /// </summary> + /// <remarks> + /// This array of formats is not yet a complete list. + /// </remarks> + private static readonly string[] PermissibleDateTimeFormats = { "yyyy-MM-ddTHH:mm:ssZ" }; + + /// <summary> + /// Backing field for the <see cref="IExpiringProtocolMessage.UtcCreationDate"/> property. + /// </summary> + /// <remarks> + /// The field initializer being DateTime.UtcNow allows for OpenID 1.x messages + /// to pass through the StandardExpirationBindingElement. + /// </remarks> + private DateTime creationDateUtc = DateTime.UtcNow; + + /// <summary> + /// Backing store for the <see cref="ReturnToParameters"/> property. + /// </summary> + private IDictionary<string, string> returnToParameters; + + /// <summary> + /// Initializes a new instance of the <see cref="IndirectSignedResponse"/> class. + /// </summary> + /// <param name="request"> + /// The authentication request that caused this assertion to be generated. + /// </param> + internal IndirectSignedResponse(SignedResponseRequest request) + : base(request, Protocol.Lookup(GetVersion(request)).Args.Mode.id_res) { + Requires.NotNull(request, "request"); + + this.ReturnTo = request.ReturnTo; + this.ProviderEndpoint = request.Recipient.StripQueryArgumentsWithPrefix(Protocol.openid.Prefix); + ((ITamperResistantOpenIdMessage)this).AssociationHandle = request.AssociationHandle; + } + + /// <summary> + /// Initializes a new instance of the <see cref="IndirectSignedResponse"/> class + /// in order to perform signature verification at the Provider. + /// </summary> + /// <param name="previouslySignedMessage">The previously signed message.</param> + /// <param name="channel">The channel. This is used only within the constructor and is not stored in a field.</param> + internal IndirectSignedResponse(CheckAuthenticationRequest previouslySignedMessage, Channel channel) + : base(GetVersion(previouslySignedMessage), previouslySignedMessage.ReturnTo, Protocol.Lookup(GetVersion(previouslySignedMessage)).Args.Mode.id_res) { + Requires.NotNull(channel, "channel"); + + // Copy all message parts from the check_authentication message into this one, + // except for the openid.mode parameter. + MessageDictionary checkPayload = channel.MessageDescriptions.GetAccessor(previouslySignedMessage); + MessageDictionary thisPayload = channel.MessageDescriptions.GetAccessor(this); + foreach (var pair in checkPayload) { + if (!string.Equals(pair.Key, this.Protocol.openid.mode)) { + thisPayload[pair.Key] = pair.Value; + } + } + } + + /// <summary> + /// Initializes a new instance of the <see cref="IndirectSignedResponse"/> class + /// for unsolicited assertions. + /// </summary> + /// <param name="version">The OpenID version to use.</param> + /// <param name="relyingPartyReturnTo">The return_to URL of the Relying Party. + /// This value will commonly be from <see cref="SignedResponseRequest.ReturnTo"/>, + /// but for unsolicited assertions may come from the Provider performing RP discovery + /// to find the appropriate return_to URL to use.</param> + internal IndirectSignedResponse(Version version, Uri relyingPartyReturnTo) + : base(version, relyingPartyReturnTo, Protocol.Lookup(version).Args.Mode.id_res) { + this.ReturnTo = relyingPartyReturnTo; + } + + /// <summary> + /// Gets the level of protection this message requires. + /// </summary> + /// <value> + /// <see cref="MessageProtections.All"/> for OpenID 2.0 messages. + /// <see cref="MessageProtections.TamperProtection"/> for OpenID 1.x messages. + /// </value> + /// <remarks> + /// Although the required protection is reduced for OpenID 1.x, + /// this library will provide Relying Party hosts with all protections + /// by adding its own specially-crafted nonce to the authentication request + /// messages except for stateless RPs in OpenID 1.x messages. + /// </remarks> + public override MessageProtections RequiredProtection { + // We actually manage to provide All protections regardless of OpenID version + // on both the Provider and Relying Party side, except for stateless RPs for OpenID 1.x. + get { return this.Version.Major < 2 ? MessageProtections.TamperProtection : MessageProtections.All; } + } + + /// <summary> + /// Gets or sets the message signature. + /// </summary> + /// <value>Base 64 encoded signature calculated as specified in Section 6 (Generating Signatures).</value> + [MessagePart("openid.sig", IsRequired = true, AllowEmpty = false)] + string ITamperResistantProtocolMessage.Signature { get; set; } + + /// <summary> + /// Gets or sets the signed parameter order. + /// </summary> + /// <value>Comma-separated list of signed fields.</value> + /// <example>"op_endpoint,identity,claimed_id,return_to,assoc_handle,response_nonce"</example> + /// <remarks> + /// This entry consists of the fields without the "openid." prefix that the signature covers. + /// This list MUST contain at least "op_endpoint", "return_to" "response_nonce" and "assoc_handle", + /// and if present in the response, "claimed_id" and "identity". + /// Additional keys MAY be signed as part of the message. See Generating Signatures. + /// </remarks> + [MessagePart("openid.signed", IsRequired = true, AllowEmpty = false)] + string ITamperResistantOpenIdMessage.SignedParameterOrder { get; set; } + + /// <summary> + /// Gets or sets the association handle used to sign the message. + /// </summary> + /// <value>The handle for the association that was used to sign this assertion. </value> + [MessagePart("openid.assoc_handle", IsRequired = true, AllowEmpty = false, RequiredProtection = ProtectionLevel.Sign, MinVersion = "2.0")] + [MessagePart("openid.assoc_handle", IsRequired = true, AllowEmpty = false, RequiredProtection = ProtectionLevel.None, MaxVersion = "1.1")] + string ITamperResistantOpenIdMessage.AssociationHandle { get; set; } + + /// <summary> + /// Gets or sets the nonce that will protect the message from replay attacks. + /// </summary> + string IReplayProtectedProtocolMessage.Nonce { get; set; } + + /// <summary> + /// Gets the context within which the nonce must be unique. + /// </summary> + string IReplayProtectedProtocolMessage.NonceContext { + get { + if (this.ProviderEndpoint != null) { + return this.ProviderEndpoint.AbsoluteUri; + } else { + // This is the Provider, on an OpenID 1.x check_authentication message. + // We don't need any special nonce context because the Provider + // generated and consumed the nonce. + return string.Empty; + } + } + } + + /// <summary> + /// Gets or sets the UTC date/time the message was originally sent onto the network. + /// </summary> + /// <remarks> + /// The property setter should ensure a UTC date/time, + /// and throw an exception if this is not possible. + /// </remarks> + /// <exception cref="ArgumentException"> + /// Thrown when a DateTime that cannot be converted to UTC is set. + /// </exception> + DateTime IExpiringProtocolMessage.UtcCreationDate { + get { return this.creationDateUtc; } + set { this.creationDateUtc = value.ToUniversalTimeSafe(); } + } + + /// <summary> + /// Gets or sets the association handle that the Provider wants the Relying Party to not use any more. + /// </summary> + /// <value>If the Relying Party sent an invalid association handle with the request, it SHOULD be included here.</value> + /// <remarks> + /// For OpenID 1.1, we allow this to be present but empty to put up with poor implementations such as Blogger. + /// </remarks> + [MessagePart("openid.invalidate_handle", IsRequired = false, AllowEmpty = true, MaxVersion = "1.1")] + [MessagePart("openid.invalidate_handle", IsRequired = false, AllowEmpty = false, MinVersion = "2.0")] + string ITamperResistantOpenIdMessage.InvalidateHandle { get; set; } + + /// <summary> + /// Gets or sets the Provider Endpoint URI. + /// </summary> + [MessagePart("openid.op_endpoint", IsRequired = true, AllowEmpty = false, RequiredProtection = ProtectionLevel.Sign, MinVersion = "2.0")] + internal Uri ProviderEndpoint { get; set; } + + /// <summary> + /// Gets or sets the return_to parameter as the relying party provided + /// it in <see cref="SignedResponseRequest.ReturnTo"/>. + /// </summary> + /// <value>Verbatim copy of the return_to URL parameter sent in the + /// request, before the Provider modified it. </value> + [MessagePart("openid.return_to", IsRequired = true, AllowEmpty = false, RequiredProtection = ProtectionLevel.Sign, Encoder = typeof(OriginalStringUriEncoder))] + internal Uri ReturnTo { get; set; } + + /// <summary> + /// Gets or sets a value indicating whether the <see cref="ReturnTo"/> + /// URI's query string is unaltered between when the Relying Party + /// sent the original request and when the response was received. + /// </summary> + /// <remarks> + /// This property is not persisted in the transmitted message, and + /// has no effect on the Provider-side of the communication. + /// </remarks> + internal bool ReturnToParametersSignatureValidated { get; set; } + + /// <summary> + /// Gets or sets the nonce that will protect the message from replay attacks. + /// </summary> + /// <value> + /// <para>A string 255 characters or less in length, that MUST be unique to + /// this particular successful authentication response. The nonce MUST start + /// with the current time on the server, and MAY contain additional ASCII + /// characters in the range 33-126 inclusive (printable non-whitespace characters), + /// as necessary to make each response unique. The date and time MUST be + /// formatted as specified in section 5.6 of [RFC3339] + /// (Klyne, G. and C. Newman, “Date and Time on the Internet: Timestamps,” .), + /// with the following restrictions:</para> + /// <list type="bullet"> + /// <item>All times must be in the UTC timezone, indicated with a "Z".</item> + /// <item>No fractional seconds are allowed</item> + /// </list> + /// </value> + /// <example>2005-05-15T17:11:51ZUNIQUE</example> + internal string ResponseNonceTestHook { + get { return this.ResponseNonce; } + set { this.ResponseNonce = value; } + } + + /// <summary> + /// Gets or sets the nonce that will protect the message from replay attacks. + /// </summary> + /// <value> + /// <para>A string 255 characters or less in length, that MUST be unique to + /// this particular successful authentication response. The nonce MUST start + /// with the current time on the server, and MAY contain additional ASCII + /// characters in the range 33-126 inclusive (printable non-whitespace characters), + /// as necessary to make each response unique. The date and time MUST be + /// formatted as specified in section 5.6 of [RFC3339] + /// (Klyne, G. and C. Newman, “Date and Time on the Internet: Timestamps,” .), + /// with the following restrictions:</para> + /// <list type="bullet"> + /// <item>All times must be in the UTC timezone, indicated with a "Z".</item> + /// <item>No fractional seconds are allowed</item> + /// </list> + /// </value> + /// <example>2005-05-15T17:11:51ZUNIQUE</example> + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called by messaging framework via reflection.")] + [MessagePart("openid.response_nonce", IsRequired = true, AllowEmpty = false, RequiredProtection = ProtectionLevel.Sign, MinVersion = "2.0")] + [MessagePart("openid.response_nonce", IsRequired = false, AllowEmpty = false, RequiredProtection = ProtectionLevel.None, MaxVersion = "1.1")] + private string ResponseNonce { + get { + string uniqueFragment = ((IReplayProtectedProtocolMessage)this).Nonce; + return this.creationDateUtc.ToString(PermissibleDateTimeFormats[0], CultureInfo.InvariantCulture) + uniqueFragment; + } + + set { + if (value == null) { + ((IReplayProtectedProtocolMessage)this).Nonce = null; + } else { + int indexOfZ = value.IndexOf("Z", StringComparison.Ordinal); + ErrorUtilities.VerifyProtocol(indexOfZ >= 0, MessagingStrings.UnexpectedMessagePartValue, Protocol.openid.response_nonce, value); + this.creationDateUtc = DateTime.Parse(value.Substring(0, indexOfZ + 1), CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal); + ((IReplayProtectedProtocolMessage)this).Nonce = value.Substring(indexOfZ + 1); + } + } + } + + /// <summary> + /// Gets the querystring key=value pairs in the return_to URL. + /// </summary> + private IDictionary<string, string> ReturnToParameters { + get { + if (this.returnToParameters == null) { + this.returnToParameters = HttpUtility.ParseQueryString(this.ReturnTo.Query).ToDictionary(); + } + + return this.returnToParameters; + } + } + + /// <summary> + /// Checks the message state for conformity to the protocol specification + /// and throws an exception if the message is invalid. + /// </summary> + /// <remarks> + /// <para>Some messages have required fields, or combinations of fields that must relate to each other + /// in specialized ways. After deserializing a message, this method checks the state of the + /// message to see if it conforms to the protocol.</para> + /// <para>Note that this property should <i>not</i> check signatures or perform any state checks + /// outside this scope of this particular message.</para> + /// </remarks> + /// <exception cref="ProtocolException">Thrown if the message is invalid.</exception> + public override void EnsureValidMessage() { + base.EnsureValidMessage(); + + this.VerifyReturnToMatchesRecipient(); + } + + /// <summary> + /// Gets the value of a named parameter in the return_to URL without signature protection. + /// </summary> + /// <param name="key">The full name of the parameter whose value is being sought.</param> + /// <returns>The value of the parameter if it is present and unaltered from when + /// the Relying Party signed it; <c>null</c> otherwise.</returns> + /// <remarks> + /// This method will always return null on the Provider-side, since Providers + /// cannot verify the private signature made by the relying party. + /// </remarks> + internal string GetReturnToArgument(string key) { + Requires.NotNullOrEmpty(key, "key"); + ErrorUtilities.VerifyInternal(this.ReturnTo != null, "ReturnTo was expected to be required but is null."); + + string value; + this.ReturnToParameters.TryGetValue(key, out value); + return value; + } + + /// <summary> + /// Gets the names of the callback parameters added to the original authentication request + /// without signature protection. + /// </summary> + /// <returns>A sequence of the callback parameter names.</returns> + internal IEnumerable<string> GetReturnToParameterNames() { + return this.ReturnToParameters.Keys; + } + + /// <summary> + /// Gets a dictionary of all the message part names and values + /// that are included in the message signature. + /// </summary> + /// <param name="channel">The channel.</param> + /// <returns> + /// A dictionary of the signed message parts. + /// </returns> + internal IDictionary<string, string> GetSignedMessageParts(Channel channel) { + Requires.NotNull(channel, "channel"); + + ITamperResistantOpenIdMessage signedSelf = this; + if (signedSelf.SignedParameterOrder == null) { + return EmptyDictionary<string, string>.Instance; + } + + MessageDictionary messageDictionary = channel.MessageDescriptions.GetAccessor(this); + string[] signedPartNamesWithoutPrefix = signedSelf.SignedParameterOrder.Split(','); + Dictionary<string, string> signedParts = new Dictionary<string, string>(signedPartNamesWithoutPrefix.Length); + + var signedPartNames = signedPartNamesWithoutPrefix.Select(part => Protocol.openid.Prefix + part); + foreach (string partName in signedPartNames) { + signedParts[partName] = messageDictionary[partName]; + } + + return signedParts; + } + + /// <summary> + /// Determines whether one querystring contains every key=value pair that + /// another querystring contains. + /// </summary> + /// <param name="superset">The querystring that should contain at least all the key=value pairs of the other.</param> + /// <param name="subset">The querystring containing the set of key=value pairs to test for in the other.</param> + /// <returns> + /// <c>true</c> if <paramref name="superset"/> contains all the query parameters that <paramref name="subset"/> does; <c>false</c> otherwise. + /// </returns> + private static bool IsQuerySubsetOf(string superset, string subset) { + NameValueCollection subsetArgs = HttpUtility.ParseQueryString(subset); + NameValueCollection supersetArgs = HttpUtility.ParseQueryString(superset); + return subsetArgs.Keys.Cast<string>().All(key => string.Equals(subsetArgs[key], supersetArgs[key], StringComparison.Ordinal)); + } + + /// <summary> + /// Verifies that the openid.return_to field matches the URL of the actual HTTP request. + /// </summary> + /// <remarks> + /// From OpenId Authentication 2.0 section 11.1: + /// To verify that the "openid.return_to" URL matches the URL that is processing this assertion: + /// * The URL scheme, authority, and path MUST be the same between the two URLs. + /// * Any query parameters that are present in the "openid.return_to" URL MUST + /// also be present with the same values in the URL of the HTTP request the RP received. + /// </remarks> + private void VerifyReturnToMatchesRecipient() { + ErrorUtilities.VerifyProtocol( + string.Equals(this.Recipient.Scheme, this.ReturnTo.Scheme, StringComparison.OrdinalIgnoreCase) && + string.Equals(this.Recipient.Authority, this.ReturnTo.Authority, StringComparison.OrdinalIgnoreCase) && + string.Equals(this.Recipient.AbsolutePath, this.ReturnTo.AbsolutePath, StringComparison.Ordinal) && + IsQuerySubsetOf(this.Recipient.Query, this.ReturnTo.Query), + OpenIdStrings.ReturnToParamDoesNotMatchRequestUrl, + Protocol.openid.return_to, + this.ReturnTo, + this.Recipient); + } + } +} diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Messages/NegativeAssertionResponse.cs b/src/DotNetOpenAuth.OpenId/OpenId/Messages/NegativeAssertionResponse.cs new file mode 100644 index 0000000..a6303ab --- /dev/null +++ b/src/DotNetOpenAuth.OpenId/OpenId/Messages/NegativeAssertionResponse.cs @@ -0,0 +1,141 @@ +//----------------------------------------------------------------------- +// <copyright file="NegativeAssertionResponse.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.Messages { + using System; + using System.Collections.Generic; + using System.Diagnostics.Contracts; + using System.Linq; + using System.Text; + using DotNetOpenAuth.Messaging; + + /// <summary> + /// The message OpenID Providers send back to Relying Parties to refuse + /// to assert the identity of a user. + /// </summary> + [Serializable] + internal class NegativeAssertionResponse : IndirectResponseBase { + /// <summary> + /// Initializes a new instance of the <see cref="NegativeAssertionResponse"/> class. + /// </summary> + /// <param name="request">The request that the relying party sent.</param> + internal NegativeAssertionResponse(CheckIdRequest request) + : this(request, null) { + } + + /// <summary> + /// Initializes a new instance of the <see cref="NegativeAssertionResponse"/> class. + /// </summary> + /// <param name="request">The request that the relying party sent.</param> + /// <param name="channel">The channel to use to simulate construction of the user_setup_url, if applicable. May be null, but the user_setup_url will not be constructed.</param> + internal NegativeAssertionResponse(SignedResponseRequest request, Channel channel) + : base(request, GetMode(request)) { + // If appropriate, and when we're provided with a channel to do it, + // go ahead and construct the user_setup_url + if (this.Version.Major < 2 && request.Immediate && channel != null) { + // All requests are CheckIdRequests in OpenID 1.x, so this cast should be safe. + this.UserSetupUrl = ConstructUserSetupUrl((CheckIdRequest)request, channel); + } + } + + /// <summary> + /// Initializes a new instance of the <see cref="NegativeAssertionResponse"/> class. + /// </summary> + /// <param name="version">The version.</param> + /// <param name="relyingPartyReturnTo">The relying party return to.</param> + /// <param name="mode">The value of the openid.mode parameter.</param> + internal NegativeAssertionResponse(Version version, Uri relyingPartyReturnTo, string mode) + : base(version, relyingPartyReturnTo, mode) { + } + + /// <summary> + /// Gets or sets the URL the relying party can use to upgrade their authentication + /// request from an immediate to a setup message. + /// </summary> + /// <value>URL to redirect User-Agent to so the End User can do whatever's necessary to fulfill the assertion.</value> + /// <remarks> + /// This part is only included in OpenID 1.x responses. + /// </remarks> + [MessagePart("openid.user_setup_url", AllowEmpty = false, IsRequired = false, MaxVersion = "1.1")] + internal Uri UserSetupUrl { get; set; } + + /// <summary> + /// Gets a value indicating whether this <see cref="NegativeAssertionResponse"/> + /// is in response to an authentication request made in immediate mode. + /// </summary> + /// <value><c>true</c> if the request was in immediate mode; otherwise, <c>false</c>.</value> + internal bool Immediate { + get { + if (this.OriginatingRequest != null) { + return this.OriginatingRequest.Immediate; + } else { + if (String.Equals(this.Mode, Protocol.Args.Mode.setup_needed, StringComparison.Ordinal)) { + return true; + } else if (String.Equals(this.Mode, Protocol.Args.Mode.cancel, StringComparison.Ordinal)) { + return false; + } else { + throw ErrorUtilities.ThrowProtocol(MessagingStrings.UnexpectedMessagePartValue, Protocol.openid.mode, this.Mode); + } + } + } + } + + /// <summary> + /// Checks the message state for conformity to the protocol specification + /// and throws an exception if the message is invalid. + /// </summary> + /// <remarks> + /// <para>Some messages have required fields, or combinations of fields that must relate to each other + /// in specialized ways. After deserializing a message, this method checks the state of the + /// message to see if it conforms to the protocol.</para> + /// <para>Note that this property should <i>not</i> check signatures or perform any state checks + /// outside this scope of this particular message.</para> + /// </remarks> + /// <exception cref="ProtocolException">Thrown if the message is invalid.</exception> + public override void EnsureValidMessage() { + base.EnsureValidMessage(); + + // Since there are a couple of negative assertion modes, ensure that the mode given is one of the allowed ones. + ErrorUtilities.VerifyProtocol(String.Equals(this.Mode, Protocol.Args.Mode.setup_needed, StringComparison.Ordinal) || String.Equals(this.Mode, Protocol.Args.Mode.cancel, StringComparison.Ordinal), MessagingStrings.UnexpectedMessagePartValue, Protocol.openid.mode, this.Mode); + + if (this.Immediate && Protocol.Version.Major < 2) { + ErrorUtilities.VerifyProtocol(this.UserSetupUrl != null, OpenIdStrings.UserSetupUrlRequiredInImmediateNegativeResponse); + } + } + + /// <summary> + /// Constructs the value for the user_setup_url parameter to be sent back + /// in negative assertions in response to OpenID 1.x RP's checkid_immediate requests. + /// </summary> + /// <param name="immediateRequest">The immediate request.</param> + /// <param name="channel">The channel to use to simulate construction of the message.</param> + /// <returns>The value to use for the user_setup_url parameter.</returns> + private static Uri ConstructUserSetupUrl(CheckIdRequest immediateRequest, Channel channel) { + Requires.NotNull(immediateRequest, "immediateRequest"); + Requires.NotNull(channel, "channel"); + ErrorUtilities.VerifyInternal(immediateRequest.Immediate, "Only immediate requests should be sent here."); + + var setupRequest = new CheckIdRequest(immediateRequest.Version, immediateRequest.Recipient, AuthenticationRequestMode.Setup); + setupRequest.LocalIdentifier = immediateRequest.LocalIdentifier; + setupRequest.ReturnTo = immediateRequest.ReturnTo; + setupRequest.Realm = immediateRequest.Realm; + setupRequest.AssociationHandle = immediateRequest.AssociationHandle; + return channel.PrepareResponse(setupRequest).GetDirectUriRequest(channel); + } + + /// <summary> + /// Gets the value for the openid.mode that is appropriate for this response. + /// </summary> + /// <param name="request">The request that we're responding to.</param> + /// <returns>The value of the openid.mode parameter to use.</returns> + private static string GetMode(SignedResponseRequest request) { + Requires.NotNull(request, "request"); + + Protocol protocol = Protocol.Lookup(request.Version); + return request.Immediate ? protocol.Args.Mode.setup_needed : protocol.Args.Mode.cancel; + } + } +} diff --git a/src/DotNetOpenAuth/OpenId/Messages/PositiveAssertionResponse.cs b/src/DotNetOpenAuth.OpenId/OpenId/Messages/PositiveAssertionResponse.cs index 006ea93..006ea93 100644 --- a/src/DotNetOpenAuth/OpenId/Messages/PositiveAssertionResponse.cs +++ b/src/DotNetOpenAuth.OpenId/OpenId/Messages/PositiveAssertionResponse.cs diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Messages/RequestBase.cs b/src/DotNetOpenAuth.OpenId/OpenId/Messages/RequestBase.cs new file mode 100644 index 0000000..a24c7db --- /dev/null +++ b/src/DotNetOpenAuth.OpenId/OpenId/Messages/RequestBase.cs @@ -0,0 +1,185 @@ +//----------------------------------------------------------------------- +// <copyright file="RequestBase.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.Messages { + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; + using System.Diagnostics.Contracts; + using DotNetOpenAuth.Messaging; + + /// <summary> + /// A common base class for OpenID request messages and indirect responses (since they are ultimately requests). + /// </summary> + [DebuggerDisplay("OpenID {Version} {Mode}")] + [Serializable] + internal class RequestBase : IDirectedProtocolMessage { + /// <summary> + /// The openid.ns parameter in the message. + /// </summary> + /// <value>"http://specs.openid.net/auth/2.0" </value> + /// <remarks> + /// This particular value MUST be present for the request to be a valid OpenID Authentication 2.0 request. Future versions of the specification may define different values in order to allow message recipients to properly interpret the request. + /// </remarks> + [SuppressMessage("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields", Justification = "Read by reflection.")] + [MessagePart("openid.ns", IsRequired = true, AllowEmpty = false, MinVersion = "2.0")] +#pragma warning disable 0414 // read by reflection + private readonly string OpenIdNamespace = Protocol.OpenId2Namespace; +#pragma warning restore 0414 + + /// <summary> + /// Backing store for the <see cref="ExtraData"/> property. + /// </summary> + private readonly Dictionary<string, string> extraData = new Dictionary<string, string>(); + + /// <summary> + /// Backing store for the <see cref="Incoming"/> property. + /// </summary> + private bool incoming; + + /// <summary> + /// Initializes a new instance of the <see cref="RequestBase"/> class. + /// </summary> + /// <param name="version">The OpenID version this message must comply with.</param> + /// <param name="providerEndpoint">The OpenID Provider endpoint.</param> + /// <param name="mode">The value for the openid.mode parameter.</param> + /// <param name="transport">A value indicating whether the message will be transmitted directly or indirectly.</param> + protected RequestBase(Version version, Uri providerEndpoint, string mode, MessageTransport transport) { + Requires.NotNull(providerEndpoint, "providerEndpoint"); + Requires.NotNullOrEmpty(mode, "mode"); + + this.Recipient = providerEndpoint; + this.Mode = mode; + this.Transport = transport; + this.Version = version; + } + + /// <summary> + /// Gets the value of the openid.mode parameter. + /// </summary> + [MessagePart("openid.mode", IsRequired = true, AllowEmpty = false)] + public string Mode { get; private set; } + + #region IDirectedProtocolMessage Members + + /// <summary> + /// Gets the preferred method of transport for the message. + /// </summary> + /// <value> + /// For direct messages this is the OpenID mandated POST. + /// For indirect messages both GET and POST are allowed. + /// </value> + HttpDeliveryMethods IDirectedProtocolMessage.HttpMethods { + get { + // OpenID 2.0 section 5.1.1 + HttpDeliveryMethods methods = HttpDeliveryMethods.PostRequest; + if (this.Transport == MessageTransport.Indirect) { + methods |= HttpDeliveryMethods.GetRequest; + } + return methods; + } + } + + /// <summary> + /// Gets the recipient of the message. + /// </summary> + /// <value>The OP endpoint, or the RP return_to.</value> + public Uri Recipient { + get; + private set; + } + + #endregion + + #region IProtocolMessage Properties + + /// <summary> + /// Gets the version of the protocol this message is prepared to implement. + /// </summary> + /// <value>Version 2.0</value> + public Version Version { get; private set; } + + /// <summary> + /// Gets the level of protection this message requires. + /// </summary> + /// <value><see cref="MessageProtections.None"/></value> + public virtual MessageProtections RequiredProtection { + get { return MessageProtections.None; } + } + + /// <summary> + /// Gets a value indicating whether this is a direct or indirect message. + /// </summary> + /// <value><see cref="MessageTransport.Direct"/></value> + public MessageTransport Transport { get; private set; } + + /// <summary> + /// Gets the extra parameters included in the message. + /// </summary> + /// <value>An empty dictionary.</value> + public IDictionary<string, string> ExtraData { + get { return this.extraData; } + } + + #endregion + + /// <summary> + /// Gets a value indicating whether this message was deserialized as an incoming message. + /// </summary> + protected internal bool Incoming { + get { return this.incoming; } + } + + /// <summary> + /// Gets the protocol used by this message. + /// </summary> + protected Protocol Protocol { + get { return Protocol.Lookup(this.Version); } + } + + #region IProtocolMessage Methods + + /// <summary> + /// Checks the message state for conformity to the protocol specification + /// and throws an exception if the message is invalid. + /// </summary> + /// <remarks> + /// <para>Some messages have required fields, or combinations of fields that must relate to each other + /// in specialized ways. After deserializing a message, this method checks the state of the + /// message to see if it conforms to the protocol.</para> + /// <para>Note that this property should <i>not</i> check signatures or perform any state checks + /// outside this scope of this particular message.</para> + /// </remarks> + /// <exception cref="ProtocolException">Thrown if the message is invalid.</exception> + public virtual void EnsureValidMessage() { + } + + #endregion + + /// <summary> + /// Sets a flag indicating that this message is received (as opposed to sent). + /// </summary> + internal void SetAsIncoming() { + this.incoming = true; + } + + /// <summary> + /// Gets some string from a given version of the OpenID protocol. + /// </summary> + /// <param name="protocolVersion">The protocol version to use for lookup.</param> + /// <param name="mode">A function that can retrieve the desired protocol constant.</param> + /// <returns>The value of the constant.</returns> + /// <remarks> + /// This method can be used by a constructor to throw an <see cref="ArgumentNullException"/> + /// instead of a <see cref="NullReferenceException"/>. + /// </remarks> + protected static string GetProtocolConstant(Version protocolVersion, Func<Protocol, string> mode) { + Requires.NotNull(protocolVersion, "protocolVersion"); + return mode(Protocol.Lookup(protocolVersion)); + } + } +} diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Messages/SignedResponseRequest.cs b/src/DotNetOpenAuth.OpenId/OpenId/Messages/SignedResponseRequest.cs new file mode 100644 index 0000000..3719f96 --- /dev/null +++ b/src/DotNetOpenAuth.OpenId/OpenId/Messages/SignedResponseRequest.cs @@ -0,0 +1,184 @@ +//----------------------------------------------------------------------- +// <copyright file="SignedResponseRequest.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.Messages { + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Diagnostics.Contracts; + using System.Linq; + using System.Text; + using DotNetOpenAuth.Messaging; + + /// <summary> + /// An indirect request from a Relying Party to a Provider where the response + /// is expected to be signed. + /// </summary> + [Serializable] + internal class SignedResponseRequest : RequestBase, IProtocolMessageWithExtensions { + /// <summary> + /// Backing store for the <see cref="Extensions"/> property. + /// </summary> + private IList<IExtensionMessage> extensions = new List<IExtensionMessage>(); + + /// <summary> + /// Initializes a new instance of the <see cref="SignedResponseRequest"/> class. + /// </summary> + /// <param name="version">The OpenID version to use.</param> + /// <param name="providerEndpoint">The Provider endpoint that receives this message.</param> + /// <param name="mode"> + /// <see cref="AuthenticationRequestMode.Immediate"/> for asynchronous javascript clients; + /// <see cref="AuthenticationRequestMode.Setup"/> to allow the Provider to interact with the user in order to complete authentication. + /// </param> + internal SignedResponseRequest(Version version, Uri providerEndpoint, AuthenticationRequestMode mode) : + base(version, providerEndpoint, GetMode(version, mode), DotNetOpenAuth.Messaging.MessageTransport.Indirect) { + } + + #region IProtocolMessageWithExtensions Members + + /// <summary> + /// Gets the list of extensions that are included with this message. + /// </summary> + /// <value></value> + /// <remarks> + /// Implementations of this interface should ensure that this property never returns null. + /// </remarks> + public IList<IExtensionMessage> Extensions { + get { return this.extensions; } + } + + #endregion + + /// <summary> + /// Gets a value indicating whether the Provider is allowed to interact with the user + /// as part of authentication. + /// </summary> + /// <value><c>true</c> if using OpenID immediate mode; otherwise, <c>false</c>.</value> + internal bool Immediate { + get { return String.Equals(this.Mode, Protocol.Args.Mode.checkid_immediate, StringComparison.Ordinal); } + } + + /// <summary> + /// Gets or sets the handle of the association the RP would like the Provider + /// to use for signing a positive assertion in the response message. + /// </summary> + /// <value>A handle for an association between the Relying Party and the OP + /// that SHOULD be used to sign the response. </value> + /// <remarks> + /// If no association handle is sent, the transaction will take place in Stateless Mode + /// (Verifying Directly with the OpenID Provider). + /// </remarks> + [MessagePart("openid.assoc_handle", IsRequired = false, AllowEmpty = false)] + internal string AssociationHandle { get; set; } + + /// <summary> + /// Gets or sets the URL the Provider should redirect the user agent to following + /// the authentication attempt. + /// </summary> + /// <value>URL to which the OP SHOULD return the User-Agent with the response + /// indicating the status of the request.</value> + /// <remarks> + /// <para>If this value is not sent in the request it signifies that the Relying Party + /// does not wish for the end user to be returned. </para> + /// <para>The return_to URL MAY be used as a mechanism for the Relying Party to attach + /// context about the authentication request to the authentication response. + /// This document does not define a mechanism by which the RP can ensure that query + /// parameters are not modified by outside parties; such a mechanism can be defined + /// by the RP itself. </para> + /// </remarks> + [MessagePart("openid.return_to", IsRequired = true, AllowEmpty = false)] + [MessagePart("openid.return_to", IsRequired = false, AllowEmpty = false, MinVersion = "2.0")] + internal Uri ReturnTo { get; set; } + + /// <summary> + /// Gets or sets the Relying Party discovery URL the Provider may use to verify the + /// source of the authentication request. + /// </summary> + /// <value> + /// URL pattern the OP SHOULD ask the end user to trust. See Section 9.2 (Realms). + /// This value MUST be sent if openid.return_to is omitted. + /// Default: The <see cref="ReturnTo"/> URL. + /// </value> + [MessagePart("openid.trust_root", IsRequired = false, AllowEmpty = false)] + [MessagePart("openid.realm", IsRequired = false, AllowEmpty = false, MinVersion = "2.0")] + internal Realm Realm { get; set; } + + /// <summary> + /// Gets or sets a value indicating whether the return_to value should be signed. + /// </summary> + internal bool SignReturnTo { get; set; } + + /// <summary> + /// Checks the message state for conformity to the protocol specification + /// and throws an exception if the message is invalid. + /// </summary> + /// <remarks> + /// <para>Some messages have required fields, or combinations of fields that must relate to each other + /// in specialized ways. After deserializing a message, this method checks the state of the + /// message to see if it conforms to the protocol.</para> + /// <para>Note that this property should <i>not</i> check signatures or perform any state checks + /// outside this scope of this particular message.</para> + /// </remarks> + /// <exception cref="ProtocolException">Thrown if the message is invalid.</exception> + public override void EnsureValidMessage() { + base.EnsureValidMessage(); + + if (this.Realm == null) { + // Set the default Realm per the spec if it is not explicitly given. + this.Realm = this.ReturnTo; + } else if (this.ReturnTo != null) { + // Verify that the realm and return_to agree. + ErrorUtilities.VerifyProtocol(this.Realm.Contains(this.ReturnTo), OpenIdStrings.ReturnToNotUnderRealm, this.ReturnTo, this.Realm); + } + } + + /// <summary> + /// Adds parameters to the return_to querystring. + /// </summary> + /// <param name="keysValues">The keys=value pairs to add to the return_to query string.</param> + /// <remarks> + /// This method is useful if the Relying Party wants to recall some value + /// when and if a positive assertion comes back from the Provider. + /// </remarks> + internal void AddReturnToArguments(IEnumerable<KeyValuePair<string, string>> keysValues) { + Requires.NotNull(keysValues, "keysValues"); + ErrorUtilities.VerifyOperation(this.ReturnTo != null, OpenIdStrings.ReturnToRequiredForOperation); + UriBuilder returnToBuilder = new UriBuilder(this.ReturnTo); + returnToBuilder.AppendAndReplaceQueryArgs(keysValues); + this.ReturnTo = returnToBuilder.Uri; + } + + /// <summary> + /// Adds a parameter to the return_to querystring. + /// </summary> + /// <param name="key">The name of the parameter.</param> + /// <param name="value">The value of the argument.</param> + /// <remarks> + /// This method is useful if the Relying Party wants to recall some value + /// when and if a positive assertion comes back from the Provider. + /// </remarks> + internal void AddReturnToArguments(string key, string value) { + var pair = new KeyValuePair<string, string>(key, value); + this.AddReturnToArguments(new[] { pair }); + } + + /// <summary> + /// Gets the value of the openid.mode parameter based on the protocol version and immediate flag. + /// </summary> + /// <param name="version">The OpenID version to use.</param> + /// <param name="mode"> + /// <see cref="AuthenticationRequestMode.Immediate"/> for asynchronous javascript clients; + /// <see cref="AuthenticationRequestMode.Setup"/> to allow the Provider to interact with the user in order to complete authentication. + /// </param> + /// <returns>checkid_immediate or checkid_setup</returns> + private static string GetMode(Version version, AuthenticationRequestMode mode) { + Requires.NotNull(version, "version"); + + Protocol protocol = Protocol.Lookup(version); + return mode == AuthenticationRequestMode.Immediate ? protocol.Args.Mode.checkid_immediate : protocol.Args.Mode.checkid_setup; + } + } +} diff --git a/src/DotNetOpenAuth.OpenId/OpenId/NoDiscoveryIdentifier.cs b/src/DotNetOpenAuth.OpenId/OpenId/NoDiscoveryIdentifier.cs new file mode 100644 index 0000000..d1704fc --- /dev/null +++ b/src/DotNetOpenAuth.OpenId/OpenId/NoDiscoveryIdentifier.cs @@ -0,0 +1,100 @@ +//----------------------------------------------------------------------- +// <copyright file="NoDiscoveryIdentifier.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId { + using System; + using System.Collections.Generic; + using System.Diagnostics.Contracts; + using System.Linq; + using DotNetOpenAuth.Messaging; + + /// <summary> + /// Wraps an existing Identifier and prevents it from performing discovery. + /// </summary> + [ContractVerification(true)] + [Pure] + internal class NoDiscoveryIdentifier : Identifier { + /// <summary> + /// The wrapped identifier. + /// </summary> + private readonly Identifier wrappedIdentifier; + + /// <summary> + /// Initializes a new instance of the <see cref="NoDiscoveryIdentifier"/> class. + /// </summary> + /// <param name="wrappedIdentifier">The ordinary Identifier whose discovery is being masked.</param> + /// <param name="claimSsl">Whether this Identifier should claim to be SSL-secure, although no discovery will never generate service endpoints anyway.</param> + internal NoDiscoveryIdentifier(Identifier wrappedIdentifier, bool claimSsl) + : base(wrappedIdentifier.OriginalString, claimSsl) { + Requires.NotNull(wrappedIdentifier, "wrappedIdentifier"); + + this.wrappedIdentifier = wrappedIdentifier; + } + + /// <summary> + /// Returns a <see cref="T:System.String"/> that represents the current <see cref="T:System.Object"/>. + /// </summary> + /// <returns> + /// A <see cref="T:System.String"/> that represents the current <see cref="T:System.Object"/>. + /// </returns> + public override string ToString() { + return this.wrappedIdentifier.ToString(); + } + + /// <summary> + /// Tests equality between two <see cref="Identifier"/>s. + /// </summary> + /// <param name="obj">The <see cref="T:System.Object"/> to compare with the current <see cref="T:System.Object"/>.</param> + /// <returns> + /// true if the specified <see cref="T:System.Object"/> is equal to the current <see cref="T:System.Object"/>; otherwise, false. + /// </returns> + /// <exception cref="T:System.NullReferenceException"> + /// The <paramref name="obj"/> parameter is null. + /// </exception> + public override bool Equals(object obj) { + return this.wrappedIdentifier.Equals(obj); + } + + /// <summary> + /// Gets the hash code for an <see cref="Identifier"/> for storage in a hashtable. + /// </summary> + /// <returns> + /// A hash code for the current <see cref="T:System.Object"/>. + /// </returns> + public override int GetHashCode() { + return this.wrappedIdentifier.GetHashCode(); + } + + /// <summary> + /// Returns an <see cref="Identifier"/> that has no URI fragment. + /// Quietly returns the original <see cref="Identifier"/> if it is not + /// a <see cref="UriIdentifier"/> or no fragment exists. + /// </summary> + /// <returns> + /// A new <see cref="Identifier"/> instance if there was a + /// fragment to remove, otherwise this same instance.. + /// </returns> + internal override Identifier TrimFragment() { + return new NoDiscoveryIdentifier(this.wrappedIdentifier.TrimFragment(), IsDiscoverySecureEndToEnd); + } + + /// <summary> + /// Converts a given identifier to its secure equivalent. + /// UriIdentifiers originally created with an implied HTTP scheme change to HTTPS. + /// Discovery is made to require SSL for the entire resolution process. + /// </summary> + /// <param name="secureIdentifier">The newly created secure identifier. + /// If the conversion fails, <paramref name="secureIdentifier"/> retains + /// <i>this</i> identifiers identity, but will never discover any endpoints.</param> + /// <returns> + /// True if the secure conversion was successful. + /// False if the Identifier was originally created with an explicit HTTP scheme. + /// </returns> + internal override bool TryRequireSsl(out Identifier secureIdentifier) { + return this.wrappedIdentifier.TryRequireSsl(out secureIdentifier); + } + } +} diff --git a/src/DotNetOpenAuth/OpenId/OpenIdStrings.Designer.cs b/src/DotNetOpenAuth.OpenId/OpenId/OpenIdStrings.Designer.cs index f45af93..f45af93 100644 --- a/src/DotNetOpenAuth/OpenId/OpenIdStrings.Designer.cs +++ b/src/DotNetOpenAuth.OpenId/OpenId/OpenIdStrings.Designer.cs diff --git a/src/DotNetOpenAuth/OpenId/OpenIdStrings.resx b/src/DotNetOpenAuth.OpenId/OpenId/OpenIdStrings.resx index b700d76..b700d76 100644 --- a/src/DotNetOpenAuth/OpenId/OpenIdStrings.resx +++ b/src/DotNetOpenAuth.OpenId/OpenId/OpenIdStrings.resx diff --git a/src/DotNetOpenAuth/OpenId/OpenIdStrings.sr.resx b/src/DotNetOpenAuth.OpenId/OpenId/OpenIdStrings.sr.resx index 0df62c0..0df62c0 100644 --- a/src/DotNetOpenAuth/OpenId/OpenIdStrings.sr.resx +++ b/src/DotNetOpenAuth.OpenId/OpenId/OpenIdStrings.sr.resx diff --git a/src/DotNetOpenAuth.OpenId/OpenId/OpenIdUtilities.cs b/src/DotNetOpenAuth.OpenId/OpenId/OpenIdUtilities.cs new file mode 100644 index 0000000..b7f857e --- /dev/null +++ b/src/DotNetOpenAuth.OpenId/OpenId/OpenIdUtilities.cs @@ -0,0 +1,172 @@ +//----------------------------------------------------------------------- +// <copyright file="OpenIdUtilities.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId { + using System; + using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; + using System.Diagnostics.Contracts; + using System.Globalization; + using System.Linq; + using System.Text; + using System.Text.RegularExpressions; + using System.Web.UI; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OpenId.ChannelElements; + using DotNetOpenAuth.OpenId.Extensions; + using DotNetOpenAuth.OpenId.Messages; + + /// <summary> + /// A set of utilities especially useful to OpenID. + /// </summary> + public static class OpenIdUtilities { + /// <summary> + /// The prefix to designate this library's proprietary parameters added to the protocol. + /// </summary> + internal const string CustomParameterPrefix = "dnoa."; + + /// <summary> + /// Creates a random association handle. + /// </summary> + /// <returns>The association handle.</returns> + public static string GenerateRandomAssociationHandle() { + Contract.Ensures(!String.IsNullOrEmpty(Contract.Result<string>())); + + // Generate the handle. It must be unique, and preferably unpredictable, + // so we use a time element and a random data element to generate it. + string uniq = MessagingUtilities.GetCryptoRandomDataAsBase64(4); + return string.Format(CultureInfo.InvariantCulture, "{{{0}}}{{{1}}}", DateTime.UtcNow.Ticks, uniq); + } + + /// <summary> + /// Gets the OpenID protocol instance for the version in a message. + /// </summary> + /// <param name="message">The message.</param> + /// <returns>The OpenID protocol instance.</returns> + internal static Protocol GetProtocol(this IProtocolMessage message) { + Requires.NotNull(message, "message"); + return Protocol.Lookup(message.Version); + } + + /// <summary> + /// Changes the position of some element in a list. + /// </summary> + /// <typeparam name="T">The type of elements stored in the list.</typeparam> + /// <param name="list">The list to be modified.</param> + /// <param name="position">The new position for the given element.</param> + /// <param name="value">The element to move within the list.</param> + /// <exception cref="InternalErrorException">Thrown if the element does not already exist in the list.</exception> + internal static void MoveTo<T>(this IList<T> list, int position, T value) { + ErrorUtilities.VerifyInternal(list.Remove(value), "Unable to find element in list."); + list.Insert(position, value); + } + + /// <summary> + /// Corrects any URI decoding the Provider may have inappropriately done + /// to our return_to URL, resulting in an otherwise corrupted base64 encoded value. + /// </summary> + /// <param name="value">The base64 encoded value. May be null.</param> + /// <returns> + /// The value; corrected if corruption had occurred. + /// </returns> + /// <remarks> + /// AOL may have incorrectly URI-decoded the token for us in the return_to, + /// resulting in a token URI-decoded twice by the time we see it, and no + /// longer being a valid base64 string. + /// It turns out that the only symbols from base64 that is also encoded + /// in URI encoding rules are the + and / characters. + /// AOL decodes the %2b sequence to the + character + /// and the %2f sequence to the / character (it shouldn't decode at all). + /// When we do our own URI decoding, the + character becomes a space (corrupting base64) + /// but the / character remains a /, so no further corruption happens to this character. + /// So to correct this we just need to change any spaces we find in the token + /// back to + characters. + /// </remarks> + internal static string FixDoublyUriDecodedBase64String(string value) { + if (value == null) { + return null; + } + + if (value.Contains(" ")) { + Logger.OpenId.Error("Deserializing a corrupted token. The OpenID Provider may have inappropriately decoded the return_to URL before sending it back to us."); + value = value.Replace(' ', '+'); // Undo any extra decoding the Provider did + } + + return value; + } + + /// <summary> + /// Rounds the given <see cref="DateTime"/> downward to the whole second. + /// </summary> + /// <param name="dateTime">The DateTime object to adjust.</param> + /// <returns>The new <see cref="DateTime"/> value.</returns> + internal static DateTime CutToSecond(DateTime dateTime) { + return new DateTime(dateTime.Ticks - (dateTime.Ticks % TimeSpan.TicksPerSecond), dateTime.Kind); + } + + /// <summary> + /// Gets the fully qualified Realm URL, given a Realm that may be relative to a particular page. + /// </summary> + /// <param name="page">The hosting page that has the realm value to resolve.</param> + /// <param name="realm">The realm, which may begin with "*." or "~/".</param> + /// <param name="requestContext">The request context.</param> + /// <returns>The fully-qualified realm.</returns> + [SuppressMessage("Microsoft.Usage", "CA1806:DoNotIgnoreMethodResults", MessageId = "DotNetOpenAuth.OpenId.Realm", Justification = "Using ctor for validation.")] + internal static UriBuilder GetResolvedRealm(Page page, string realm, HttpRequestInfo requestContext) { + Requires.NotNull(page, "page"); + Requires.NotNull(requestContext, "requestContext"); + + // Allow for *. realm notation, as well as ASP.NET ~/ shortcuts. + + // We have to temporarily remove the *. notation if it's there so that + // the rest of our URL manipulation will succeed. + bool foundWildcard = false; + + // Note: we don't just use string.Replace because poorly written URLs + // could potentially have multiple :// sequences in them. + MatchEvaluator matchDelegate = delegate(Match m) { + foundWildcard = true; + return m.Groups[1].Value; + }; + string realmNoWildcard = Regex.Replace(realm, @"^(\w+://)\*\.", matchDelegate); + + UriBuilder fullyQualifiedRealm = new UriBuilder( + new Uri(requestContext.UrlBeforeRewriting, page.ResolveUrl(realmNoWildcard))); + + if (foundWildcard) { + fullyQualifiedRealm.Host = "*." + fullyQualifiedRealm.Host; + } + + // Is it valid? + new Realm(fullyQualifiedRealm); // throws if not valid + + return fullyQualifiedRealm; + } + + /// <summary> + /// Gets the extension factories from the extension aggregator on an OpenID channel. + /// </summary> + /// <param name="channel">The channel.</param> + /// <returns>The list of factories that will be used to generate extension instances.</returns> + /// <remarks> + /// This is an extension method on <see cref="Channel"/> rather than an instance + /// method on <see cref="OpenIdChannel"/> because the OpenIdRelyingParty + /// and OpenIdProvider classes don't strong-type to <see cref="OpenIdChannel"/> + /// to allow flexibility in the specific type of channel the user (or tests) + /// can plug in. + /// </remarks> + internal static IList<IOpenIdExtensionFactory> GetExtensionFactories(this Channel channel) { + Requires.NotNull(channel, "channel"); + + var extensionsBindingElement = channel.BindingElements.OfType<ExtensionsBindingElement>().SingleOrDefault(); + ErrorUtilities.VerifyOperation(extensionsBindingElement != null, OpenIdStrings.UnsupportedChannelConfiguration); + IOpenIdExtensionFactory factory = extensionsBindingElement.ExtensionFactory; + var aggregator = factory as OpenIdExtensionFactoryAggregator; + ErrorUtilities.VerifyOperation(aggregator != null, OpenIdStrings.UnsupportedChannelConfiguration); + return aggregator.Factories; + } + } +} diff --git a/src/DotNetOpenAuth.OpenId/OpenId/OpenIdXrdsHelper.cs b/src/DotNetOpenAuth.OpenId/OpenId/OpenIdXrdsHelper.cs new file mode 100644 index 0000000..9f996a5 --- /dev/null +++ b/src/DotNetOpenAuth.OpenId/OpenId/OpenIdXrdsHelper.cs @@ -0,0 +1,68 @@ +//----------------------------------------------------------------------- +// <copyright file="OpenIdXrdsHelper.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId { + using System; + using System.Collections.Generic; + using System.Diagnostics.Contracts; + using System.Linq; + using System.Text; + using DotNetOpenAuth.Xrds; + + /// <summary> + /// Utility methods for working with XRDS documents. + /// </summary> + internal static class OpenIdXrdsHelper { + /// <summary> + /// Finds the Relying Party return_to receiving endpoints. + /// </summary> + /// <param name="xrds">The XrdsDocument instance to use in this process.</param> + /// <returns>A sequence of Relying Party descriptors for the return_to endpoints.</returns> + /// <remarks> + /// This is useful for Providers to send unsolicited assertions to Relying Parties, + /// or for Provider's to perform RP discovery/verification as part of authentication. + /// </remarks> + internal static IEnumerable<RelyingPartyEndpointDescription> FindRelyingPartyReceivingEndpoints(this XrdsDocument xrds) { + Requires.NotNull(xrds, "xrds"); + Contract.Ensures(Contract.Result<IEnumerable<RelyingPartyEndpointDescription>>() != null); + + return from service in xrds.FindReturnToServices() + from uri in service.UriElements + select new RelyingPartyEndpointDescription(uri.Uri, service.TypeElementUris); + } + + /// <summary> + /// Finds the icons the relying party wants an OP to display as part of authentication, + /// per the UI extension spec. + /// </summary> + /// <param name="xrds">The XrdsDocument to search.</param> + /// <returns>A sequence of the icon URLs in preferred order.</returns> + internal static IEnumerable<Uri> FindRelyingPartyIcons(this XrdsDocument xrds) { + Requires.NotNull(xrds, "xrds"); + Contract.Ensures(Contract.Result<IEnumerable<Uri>>() != null); + + return from xrd in xrds.XrdElements + from service in xrd.OpenIdRelyingPartyIcons + from uri in service.UriElements + select uri.Uri; + } + + /// <summary> + /// Enumerates the XRDS service elements that describe OpenID Relying Party return_to URLs + /// that can receive authentication assertions. + /// </summary> + /// <param name="xrds">The XrdsDocument instance to use in this process.</param> + /// <returns>A sequence of service elements.</returns> + private static IEnumerable<ServiceElement> FindReturnToServices(this XrdsDocument xrds) { + Requires.NotNull(xrds, "xrds"); + Contract.Ensures(Contract.Result<IEnumerable<ServiceElement>>() != null); + + return from xrd in xrds.XrdElements + from service in xrd.OpenIdRelyingPartyReturnToServices + select service; + } + } +} diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Protocol.cs b/src/DotNetOpenAuth.OpenId/OpenId/Protocol.cs new file mode 100644 index 0000000..a651f3c --- /dev/null +++ b/src/DotNetOpenAuth.OpenId/OpenId/Protocol.cs @@ -0,0 +1,472 @@ +// <auto-generated/> // disable StyleCop on this file +//----------------------------------------------------------------------- +// <copyright file="Protocol.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId { + using System; + using System.Collections.Generic; + using DotNetOpenAuth.Messaging; + using System.Globalization; + using System.Diagnostics.CodeAnalysis; + using System.Diagnostics.Contracts; + using System.Diagnostics; + + /// <summary> + /// An enumeration of the OpenID protocol versions supported by this library. + /// </summary> + public enum ProtocolVersion { + /// <summary> + /// OpenID Authentication 1.0 + /// </summary> + V10, + /// <summary> + /// OpenID Authentication 1.1 + /// </summary> + V11, + /// <summary> + /// OpenID Authentication 2.0 + /// </summary> + V20, + } + + /// <summary> + /// Tracks the several versions of OpenID this library supports and the unique + /// constants to each version used in the protocol. + /// </summary> + [DebuggerDisplay("OpenID {Version}")] + internal sealed class Protocol { + /// <summary> + /// The value of the openid.ns parameter in the OpenID 2.0 specification. + /// </summary> + internal const string OpenId2Namespace = "http://specs.openid.net/auth/2.0"; + + /// <summary> + /// Scans a list for matches with some element of the OpenID protocol, + /// searching from newest to oldest protocol for the first and best match. + /// </summary> + /// <typeparam name="T">The type of element retrieved from the <see cref="Protocol"/> instance.</typeparam> + /// <param name="elementOf">Takes a <see cref="Protocol"/> instance and returns an element of it.</param> + /// <param name="list">The list to scan for matches.</param> + /// <returns>The protocol with the element that matches some item in the list.</returns> + internal static Protocol FindBestVersion<T>(Func<Protocol, T> elementOf, IEnumerable<T> list) { + foreach (var protocol in Protocol.AllVersions) { + foreach (var item in list) { + if (item != null && item.Equals(elementOf(protocol))) + return protocol; + } + } + return null; + } + + Protocol(QueryParameters queryBits) { + openidnp = queryBits; + openid = new QueryParameters(queryBits); + } + + // Well-known, supported versions of the OpenID spec. + public static readonly Protocol V10 = new Protocol(new QueryParameters()) { + Version = new Version(1, 0), + XmlNamespace = "http://openid.net/xmlns/1.0", + QueryDeclaredNamespaceVersion = null, + ClaimedIdentifierServiceTypeURI = "http://openid.net/signon/1.0", + OPIdentifierServiceTypeURI = null, // not supported + ClaimedIdentifierForOPIdentifier = null, // not supported + RPReturnToTypeURI = null, // not supported + HtmlDiscoveryProviderKey = "openid.server", + HtmlDiscoveryLocalIdKey = "openid.delegate", + }; + public static readonly Protocol V11 = new Protocol(new QueryParameters()) { + Version = new Version(1, 1), + XmlNamespace = "http://openid.net/xmlns/1.0", + QueryDeclaredNamespaceVersion = null, + ClaimedIdentifierServiceTypeURI = "http://openid.net/signon/1.1", + OPIdentifierServiceTypeURI = null, // not supported + ClaimedIdentifierForOPIdentifier = null, // not supported + RPReturnToTypeURI = null, // not supported + HtmlDiscoveryProviderKey = "openid.server", + HtmlDiscoveryLocalIdKey = "openid.delegate", + }; + public static readonly Protocol V20 = new Protocol(new QueryParameters() { + Realm = "realm", + op_endpoint = "op_endpoint", + response_nonce = "response_nonce", + error_code = "error_code", + user_setup_url = null, + }) { + Version = new Version(2, 0), + XmlNamespace = null, // no longer applicable + QueryDeclaredNamespaceVersion = Protocol.OpenId2Namespace, + ClaimedIdentifierServiceTypeURI = "http://specs.openid.net/auth/2.0/signon", + OPIdentifierServiceTypeURI = "http://specs.openid.net/auth/2.0/server", + ClaimedIdentifierForOPIdentifier = "http://specs.openid.net/auth/2.0/identifier_select", + RPReturnToTypeURI = "http://specs.openid.net/auth/2.0/return_to", + HtmlDiscoveryProviderKey = "openid2.provider", + HtmlDiscoveryLocalIdKey = "openid2.local_id", + Args = new QueryArguments() { + SessionType = new QueryArguments.SessionTypes() { + NoEncryption = "no-encryption", + DH_SHA256 = "DH-SHA256", + DH_SHA384 = "DH-SHA384", + DH_SHA512 = "DH-SHA512", + }, + SignatureAlgorithm = new QueryArguments.SignatureAlgorithms() { + HMAC_SHA256 = "HMAC-SHA256", + HMAC_SHA384 = "HMAC-SHA384", + HMAC_SHA512 = "HMAC-SHA512", + }, + Mode = new QueryArguments.Modes() { + setup_needed = "setup_needed", + }, + }, + }; + + /// <summary> + /// A list of all supported OpenID versions, in order starting from newest version. + /// </summary> + public readonly static List<Protocol> AllVersions = new List<Protocol>() { V20, V11, V10 }; + + /// <summary> + /// A list of all supported OpenID versions, in order starting from newest version. + /// V1.1 and V1.0 are considered the same and only V1.1 is in the list. + /// </summary> + public readonly static List<Protocol> AllPracticalVersions = new List<Protocol>() { V20, V11 }; + + /// <summary> + /// The default (or most recent) supported version of the OpenID protocol. + /// </summary> + public readonly static Protocol Default = AllVersions[0]; + public static Protocol Lookup(Version version) { + foreach (Protocol protocol in AllVersions) { + if (protocol.Version == version) return protocol; + } + throw new ArgumentOutOfRangeException("version"); + } + public static Protocol Lookup(ProtocolVersion version) { + switch (version) { + case ProtocolVersion.V10: return Protocol.V10; + case ProtocolVersion.V11: return Protocol.V11; + case ProtocolVersion.V20: return Protocol.V20; + default: throw new ArgumentOutOfRangeException("version"); + } + } + /// <summary> + /// Attempts to detect the right OpenID protocol version based on the contents + /// of an incoming OpenID <i>indirect</i> message or <i>direct request</i>. + /// </summary> + internal static Protocol Detect(IDictionary<string, string> query) { + Requires.NotNull(query, "query"); + return query.ContainsKey(V20.openid.ns) ? V20 : V11; + } + /// <summary> + /// Attempts to detect the right OpenID protocol version based on the contents + /// of an incoming OpenID <i>direct</i> response message. + /// </summary> + internal static Protocol DetectFromDirectResponse(IDictionary<string, string> query) { + Requires.NotNull(query, "query"); + return query.ContainsKey(V20.openidnp.ns) ? V20 : V11; + } + /// <summary> + /// Attemps to detect the highest OpenID protocol version supported given a set + /// of XRDS Service Type URIs included for some service. + /// </summary> + internal static Protocol Detect(IEnumerable<string> serviceTypeURIs) { + Requires.NotNull(serviceTypeURIs, "serviceTypeURIs"); + return FindBestVersion(p => p.OPIdentifierServiceTypeURI, serviceTypeURIs) ?? + FindBestVersion(p => p.ClaimedIdentifierServiceTypeURI, serviceTypeURIs) ?? + FindBestVersion(p => p.RPReturnToTypeURI, serviceTypeURIs); + } + + /// <summary> + /// The OpenID version that this <see cref="Protocol"/> instance describes. + /// </summary> + public Version Version; + /// <summary> + /// Returns the <see cref="ProtocolVersion"/> enum value for the <see cref="Protocol"/> instance. + /// </summary> + public ProtocolVersion ProtocolVersion { + get { + switch (Version.Major) { + case 1: return ProtocolVersion.V11; + case 2: return ProtocolVersion.V20; + default: throw new ArgumentException(null); // this should never happen + } + } + } + /// <summary> + /// The namespace of OpenId 1.x elements in XRDS documents. + /// </summary> + public string XmlNamespace; + /// <summary> + /// The value of the openid.ns parameter that appears on the query string + /// whenever data is passed between relying party and provider for OpenID 2.0 + /// and later. + /// </summary> + public string QueryDeclaredNamespaceVersion; + /// <summary> + /// The XRD/Service/Type value discovered in an XRDS document when + /// "discovering" on a Claimed Identifier (http://andrewarnott.yahoo.com) + /// </summary> + public string ClaimedIdentifierServiceTypeURI; + /// <summary> + /// The XRD/Service/Type value discovered in an XRDS document when + /// "discovering" on an OP Identifier rather than a Claimed Identifier. + /// (http://yahoo.com) + /// </summary> + public string OPIdentifierServiceTypeURI; + /// <summary> + /// The XRD/Service/Type value discovered in an XRDS document when + /// "discovering" on a Realm URL and looking for the endpoint URL + /// that can receive authentication assertions. + /// </summary> + public string RPReturnToTypeURI; + /// <summary> + /// Used as the Claimed Identifier and the OP Local Identifier when + /// the User Supplied Identifier is an OP Identifier. + /// </summary> + public string ClaimedIdentifierForOPIdentifier; + /// <summary> + /// The value of the 'rel' attribute in an HTML document's LINK tag + /// when the same LINK tag's HREF attribute value contains the URL to an + /// OP Endpoint URL. + /// </summary> + public string HtmlDiscoveryProviderKey; + /// <summary> + /// The value of the 'rel' attribute in an HTML document's LINK tag + /// when the same LINK tag's HREF attribute value contains the URL to use + /// as the OP Local Identifier. + /// </summary> + public string HtmlDiscoveryLocalIdKey; + /// <summary> + /// Parts of the protocol that define parameter names that appear in the + /// query string. Each parameter name is prefixed with 'openid.'. + /// </summary> + public readonly QueryParameters openid; + /// <summary> + /// Parts of the protocol that define parameter names that appear in the + /// query string. Each parameter name is NOT prefixed with 'openid.'. + /// </summary> + public readonly QueryParameters openidnp; + /// <summary> + /// The various 'constants' that appear as parameter arguments (values). + /// </summary> + public QueryArguments Args = new QueryArguments(); + + internal sealed class QueryParameters { + /// <summary> + /// The value "openid." + /// </summary> + public readonly string Prefix = "openid."; + [SuppressMessage("Microsoft.Performance", "CA1805:DoNotInitializeUnnecessarily")] + public QueryParameters() { } + [SuppressMessage("Microsoft.Performance", "CA1805:DoNotInitializeUnnecessarily")] + public QueryParameters(QueryParameters addPrefixTo) { + ns = addPrefix(addPrefixTo.ns); + return_to = addPrefix(addPrefixTo.return_to); + Realm = addPrefix(addPrefixTo.Realm); + mode = addPrefix(addPrefixTo.mode); + error = addPrefix(addPrefixTo.error); + error_code = addPrefix(addPrefixTo.error_code); + identity = addPrefix(addPrefixTo.identity); + op_endpoint = addPrefix(addPrefixTo.op_endpoint); + response_nonce = addPrefix(addPrefixTo.response_nonce); + claimed_id = addPrefix(addPrefixTo.claimed_id); + expires_in = addPrefix(addPrefixTo.expires_in); + assoc_type = addPrefix(addPrefixTo.assoc_type); + assoc_handle = addPrefix(addPrefixTo.assoc_handle); + session_type = addPrefix(addPrefixTo.session_type); + is_valid = addPrefix(addPrefixTo.is_valid); + sig = addPrefix(addPrefixTo.sig); + signed = addPrefix(addPrefixTo.signed); + user_setup_url = addPrefix(addPrefixTo.user_setup_url); + invalidate_handle = addPrefix(addPrefixTo.invalidate_handle); + dh_modulus = addPrefix(addPrefixTo.dh_modulus); + dh_gen = addPrefix(addPrefixTo.dh_gen); + dh_consumer_public = addPrefix(addPrefixTo.dh_consumer_public); + dh_server_public = addPrefix(addPrefixTo.dh_server_public); + enc_mac_key = addPrefix(addPrefixTo.enc_mac_key); + mac_key = addPrefix(addPrefixTo.mac_key); + } + string addPrefix(string original) { + return (original != null) ? Prefix + original : null; + } + // These fields default to 1.x specifications, and are overridden + // as necessary by later versions in the Protocol class initializers. + // Null values in any version suggests that that feature is absent from that version. + public string ns = "ns"; + public string return_to = "return_to"; + public string Realm = "trust_root"; + public string mode = "mode"; + public string error = "error"; + public string error_code = null; + public string identity = "identity"; + public string op_endpoint = null; + public string response_nonce = null; + public string claimed_id = "claimed_id"; + public string expires_in = "expires_in"; + public string assoc_type = "assoc_type"; + public string assoc_handle = "assoc_handle"; + public string session_type = "session_type"; + public string is_valid = "is_valid"; + public string sig = "sig"; + public string signed = "signed"; + public string user_setup_url = "user_setup_url"; + public string invalidate_handle = "invalidate_handle"; + public string dh_modulus = "dh_modulus"; + public string dh_gen = "dh_gen"; + public string dh_consumer_public = "dh_consumer_public"; + public string dh_server_public = "dh_server_public"; + public string enc_mac_key = "enc_mac_key"; + public string mac_key = "mac_key"; + +#if CONTRACTS_FULL + /// <summary> + /// Verifies conditions that should be true for any valid state of this object. + /// </summary> + [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Called by code contracts.")] + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called by code contracts.")] + [ContractInvariantMethod] + private void ObjectInvariant() { + Contract.Invariant(!string.IsNullOrEmpty(this.Prefix)); + } +#endif + } + + internal sealed class QueryArguments { + public ErrorCodes ErrorCode = new ErrorCodes(); + public SessionTypes SessionType = new SessionTypes(); + public SignatureAlgorithms SignatureAlgorithm = new SignatureAlgorithms(); + public Modes Mode = new Modes(); + public IsValidValues IsValid = new IsValidValues(); + + internal sealed class ErrorCodes { + public string UnsupportedType = "unsupported-type"; + } + internal sealed class SessionTypes { + /// <summary> + /// A preference order list of all supported session types. + /// </summary> + public string[] All { get { return new[] { DH_SHA512, DH_SHA384, DH_SHA256, DH_SHA1, NoEncryption }; } } + public string[] AllDiffieHellman { get { return new[] { DH_SHA512, DH_SHA384, DH_SHA256, DH_SHA1 }; } } + public string DH_SHA1 = "DH-SHA1"; + public string DH_SHA256; + public string DH_SHA384; + public string DH_SHA512; + public string NoEncryption = string.Empty; + public string Best { + get { + foreach (string algorithmName in All) { + if (algorithmName != null) { + return algorithmName; + } + } + throw new ProtocolException(); // really bad... we have no signing algorithms at all + } + } + } + internal sealed class SignatureAlgorithms { + /// <summary> + /// A preference order list of signature algorithms we support. + /// </summary> + public string[] All { get { return new[] { HMAC_SHA512, HMAC_SHA384, HMAC_SHA256, HMAC_SHA1 }; } } + public string HMAC_SHA1 = "HMAC-SHA1"; + public string HMAC_SHA256; + public string HMAC_SHA384; + public string HMAC_SHA512; + public string Best { + get { + foreach (string algorithmName in All) { + if (algorithmName != null) { + return algorithmName; + } + } + throw new ProtocolException(); // really bad... we have no signing algorithms at all + } + } + } + internal sealed class Modes { + public string cancel = "cancel"; + public string error = "error"; + public string id_res = "id_res"; + public string checkid_immediate = "checkid_immediate"; + public string checkid_setup = "checkid_setup"; + public string check_authentication = "check_authentication"; + public string associate = "associate"; + public string setup_needed = "id_res"; // V2 overrides this + } + internal sealed class IsValidValues { + public string True = "true"; + public string False = "false"; + } + } + + /// <summary> + /// The maximum time a user can be allowed to take to complete authentication. + /// </summary> + /// <remarks> + /// This is used to calculate the length of time that nonces are stored. + /// This is internal until we can decide whether to leave this static, or make + /// it an instance member, or put it inside the IConsumerApplicationStore interface. + /// </remarks> + internal static TimeSpan MaximumUserAgentAuthenticationTime = TimeSpan.FromMinutes(5); + /// <summary> + /// The maximum permissible difference in clocks between relying party and + /// provider web servers, discounting time zone differences. + /// </summary> + /// <remarks> + /// This is used when storing/validating nonces from the provider. + /// If it is conceivable that a server's clock could be up to five minutes + /// off from true UTC time, then the maximum time skew should be set to + /// ten minutes to allow one server to be five minutes ahead and the remote + /// server to be five minutes behind and still be able to communicate. + /// </remarks> + internal static TimeSpan MaximumAllowableTimeSkew = TimeSpan.FromMinutes(10); + + /// <summary> + /// Checks whether a given Protocol version practically equals this one + /// for purposes of verifying a match for assertion verification. + /// </summary> + /// <param name="other">The other version to check against this one.</param> + /// <returns><c>true</c> if this and the given Protocol versions are essentially the same.</returns> + /// <remarks> + /// OpenID v1.0 never had a spec, and 1.0 and 1.1 are indistinguishable because of that. + /// Therefore for assertion verification, 1.0 and 1.1 are considered equivalent. + /// </remarks> + public bool EqualsPractically(Protocol other) { + if (other == null) { + return false; + } + + // Exact version match is definitely equality. + if (this.Version == other.Version) { + return true; + } + + // If both protocol versions are 1.x, it doesn't matter if one + // is 1.0 and the other is 1.1 for assertion verification purposes. + if (this.Version.Major == 1 && other.Version.Major == 1) { + return true; + } + + // Different version. + return false; + } + + public override bool Equals(object obj) { + Protocol other = obj as Protocol; + if (other == null) { + return false; + } + + return this.Version == other.Version; + } + public override int GetHashCode() { + return Version.GetHashCode(); + } + public override string ToString() { + return string.Format(CultureInfo.CurrentCulture, "OpenID Authentication {0}.{1}", Version.Major, Version.Minor); + } + } +} diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Provider/IAuthenticationRequest.cs b/src/DotNetOpenAuth.OpenId/OpenId/Provider/IAuthenticationRequest.cs new file mode 100644 index 0000000..1045282 --- /dev/null +++ b/src/DotNetOpenAuth.OpenId/OpenId/Provider/IAuthenticationRequest.cs @@ -0,0 +1,367 @@ +//----------------------------------------------------------------------- +// <copyright file="IAuthenticationRequest.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.Provider { + using System; + using System.Collections.Generic; + using System.Diagnostics.Contracts; + using System.Text; + using DotNetOpenAuth.Messaging; + + /// <summary> + /// Instances of this interface represent incoming authentication requests. + /// This interface provides the details of the request and allows setting + /// the response. + /// </summary> + [ContractClass(typeof(IAuthenticationRequestContract))] + public interface IAuthenticationRequest : IHostProcessedRequest { + /// <summary> + /// Gets a value indicating whether the Provider should help the user + /// select a Claimed Identifier to send back to the relying party. + /// </summary> + bool IsDirectedIdentity { get; } + + /// <summary> + /// Gets a value indicating whether the requesting Relying Party is using a delegated URL. + /// </summary> + /// <remarks> + /// When delegated identifiers are used, the <see cref="ClaimedIdentifier"/> should not + /// be changed at the Provider during authentication. + /// Delegation is only detectable on requests originating from OpenID 2.0 relying parties. + /// A relying party implementing only OpenID 1.x may use delegation and this property will + /// return false anyway. + /// </remarks> + bool IsDelegatedIdentifier { get; } + + /// <summary> + /// Gets or sets the Local Identifier to this OpenID Provider of the user attempting + /// to authenticate. Check <see cref="IsDirectedIdentity"/> to see if + /// this value is valid. + /// </summary> + /// <remarks> + /// This may or may not be the same as the Claimed Identifier that the user agent + /// originally supplied to the relying party. The Claimed Identifier + /// endpoint may be delegating authentication to this provider using + /// this provider's local id, which is what this property contains. + /// Use this identifier when looking up this user in the provider's user account + /// list. + /// </remarks> + Identifier LocalIdentifier { get; set; } + + /// <summary> + /// Gets or sets the identifier that the user agent is claiming at the relying party site. + /// Check <see cref="IsDirectedIdentity"/> to see if this value is valid. + /// </summary> + /// <remarks> + /// <para>This property can only be set if <see cref="IsDelegatedIdentifier"/> is + /// false, to prevent breaking URL delegation.</para> + /// <para>This will not be the same as this provider's local identifier for the user + /// if the user has set up his/her own identity page that points to this + /// provider for authentication.</para> + /// <para>The provider may use this identifier for displaying to the user when + /// asking for the user's permission to authenticate to the relying party.</para> + /// </remarks> + /// <exception cref="InvalidOperationException">Thrown from the setter + /// if <see cref="IsDelegatedIdentifier"/> is true.</exception> + Identifier ClaimedIdentifier { get; set; } + + /// <summary> + /// Gets or sets a value indicating whether the provider has determined that the + /// <see cref="ClaimedIdentifier"/> belongs to the currently logged in user + /// and wishes to share this information with the consumer. + /// </summary> + bool? IsAuthenticated { get; set; } + + /// <summary> + /// Adds an optional fragment (#fragment) portion to the ClaimedIdentifier. + /// Useful for identifier recycling. + /// </summary> + /// <param name="fragment"> + /// Should not include the # prefix character as that will be added internally. + /// May be null or the empty string to clear a previously set fragment. + /// </param> + /// <remarks> + /// <para>Unlike the <see cref="ClaimedIdentifier"/> property, which can only be set if + /// using directed identity, this method can be called on any URI claimed identifier.</para> + /// <para>Because XRI claimed identifiers (the canonical IDs) are never recycled, + /// this method should<i>not</i> be called for XRIs.</para> + /// </remarks> + /// <exception cref="InvalidOperationException"> + /// Thrown when this method is called on an XRI, or on a directed identity + /// request before the <see cref="ClaimedIdentifier"/> property is set. + /// </exception> + void SetClaimedIdentifierFragment(string fragment); + } + + /// <summary> + /// Code contract class for the <see cref="IAuthenticationRequest"/> type. + /// </summary> + [ContractClassFor(typeof(IAuthenticationRequest))] + internal abstract class IAuthenticationRequestContract : IAuthenticationRequest { + /// <summary> + /// Initializes a new instance of the <see cref="IAuthenticationRequestContract"/> class. + /// </summary> + protected IAuthenticationRequestContract() { + } + + #region IAuthenticationRequest Properties + + /// <summary> + /// Gets a value indicating whether the Provider should help the user + /// select a Claimed Identifier to send back to the relying party. + /// </summary> + bool IAuthenticationRequest.IsDirectedIdentity { + get { throw new NotImplementedException(); } + } + + /// <summary> + /// Gets a value indicating whether the requesting Relying Party is using a delegated URL. + /// </summary> + /// <remarks> + /// When delegated identifiers are used, the <see cref="IAuthenticationRequest.ClaimedIdentifier"/> should not + /// be changed at the Provider during authentication. + /// Delegation is only detectable on requests originating from OpenID 2.0 relying parties. + /// A relying party implementing only OpenID 1.x may use delegation and this property will + /// return false anyway. + /// </remarks> + bool IAuthenticationRequest.IsDelegatedIdentifier { + get { throw new NotImplementedException(); } + } + + /// <summary> + /// Gets or sets the Local Identifier to this OpenID Provider of the user attempting + /// to authenticate. Check <see cref="IAuthenticationRequest.IsDirectedIdentity"/> to see if + /// this value is valid. + /// </summary> + /// <remarks> + /// This may or may not be the same as the Claimed Identifier that the user agent + /// originally supplied to the relying party. The Claimed Identifier + /// endpoint may be delegating authentication to this provider using + /// this provider's local id, which is what this property contains. + /// Use this identifier when looking up this user in the provider's user account + /// list. + /// </remarks> + Identifier IAuthenticationRequest.LocalIdentifier { + get { + throw new NotImplementedException(); + } + + set { + throw new NotImplementedException(); + } + } + + /// <summary> + /// Gets or sets the identifier that the user agent is claiming at the relying party site. + /// Check <see cref="IAuthenticationRequest.IsDirectedIdentity"/> to see if this value is valid. + /// </summary> + /// <remarks> + /// <para>This property can only be set if <see cref="IAuthenticationRequest.IsDelegatedIdentifier"/> is + /// false, to prevent breaking URL delegation.</para> + /// <para>This will not be the same as this provider's local identifier for the user + /// if the user has set up his/her own identity page that points to this + /// provider for authentication.</para> + /// <para>The provider may use this identifier for displaying to the user when + /// asking for the user's permission to authenticate to the relying party.</para> + /// </remarks> + /// <exception cref="InvalidOperationException">Thrown from the setter + /// if <see cref="IAuthenticationRequest.IsDelegatedIdentifier"/> is true.</exception> + Identifier IAuthenticationRequest.ClaimedIdentifier { + get { + throw new NotImplementedException(); + } + + set { + IAuthenticationRequest req = this; + Requires.ValidState(!req.IsDelegatedIdentifier, OpenIdStrings.ClaimedIdentifierCannotBeSetOnDelegatedAuthentication); + Requires.ValidState(!req.IsDirectedIdentity || !(req.LocalIdentifier != null && req.LocalIdentifier != value), OpenIdStrings.IdentifierSelectRequiresMatchingIdentifiers); + } + } + + /// <summary> + /// Gets or sets a value indicating whether the provider has determined that the + /// <see cref="IAuthenticationRequest.ClaimedIdentifier"/> belongs to the currently logged in user + /// and wishes to share this information with the consumer. + /// </summary> + bool? IAuthenticationRequest.IsAuthenticated { + get { + throw new NotImplementedException(); + } + + set { + throw new NotImplementedException(); + } + } + + #endregion + + #region IHostProcessedRequest Properties + + /// <summary> + /// Gets the version of OpenID being used by the relying party that sent the request. + /// </summary> + ProtocolVersion IHostProcessedRequest.RelyingPartyVersion { + get { throw new NotImplementedException(); } + } + + /// <summary> + /// Gets the URL the consumer site claims to use as its 'base' address. + /// </summary> + Realm IHostProcessedRequest.Realm { + get { throw new NotImplementedException(); } + } + + /// <summary> + /// Gets a value indicating whether the consumer demands an immediate response. + /// If false, the consumer is willing to wait for the identity provider + /// to authenticate the user. + /// </summary> + bool IHostProcessedRequest.Immediate { + get { throw new NotImplementedException(); } + } + + /// <summary> + /// Gets or sets the provider endpoint claimed in the positive assertion. + /// </summary> + /// <value> + /// The default value is the URL that the request came in on from the relying party. + /// This value MUST match the value for the OP Endpoint in the discovery results for the + /// claimed identifier being asserted in a positive response. + /// </value> + Uri IHostProcessedRequest.ProviderEndpoint { + get { + throw new NotImplementedException(); + } + + set { + throw new NotImplementedException(); + } + } + + #endregion + + #region IRequest Properties + + /// <summary> + /// Gets a value indicating whether the response is ready to be sent to the user agent. + /// </summary> + /// <remarks> + /// This property returns false if there are properties that must be set on this + /// request instance before the response can be sent. + /// </remarks> + bool IRequest.IsResponseReady { + get { throw new NotImplementedException(); } + } + + /// <summary> + /// Gets or sets the security settings that apply to this request. + /// </summary> + /// <value> + /// Defaults to the OpenIdProvider.SecuritySettings on the OpenIdProvider. + /// </value> + ProviderSecuritySettings IRequest.SecuritySettings { + get { + throw new NotImplementedException(); + } + + set { + throw new NotImplementedException(); + } + } + + #endregion + + #region IAuthenticationRequest Methods + + /// <summary> + /// Adds an optional fragment (#fragment) portion to the ClaimedIdentifier. + /// Useful for identifier recycling. + /// </summary> + /// <param name="fragment">Should not include the # prefix character as that will be added internally. + /// May be null or the empty string to clear a previously set fragment.</param> + /// <remarks> + /// <para>Unlike the <see cref="IAuthenticationRequest.ClaimedIdentifier"/> property, which can only be set if + /// using directed identity, this method can be called on any URI claimed identifier.</para> + /// <para>Because XRI claimed identifiers (the canonical IDs) are never recycled, + /// this method should<i>not</i> be called for XRIs.</para> + /// </remarks> + /// <exception cref="InvalidOperationException"> + /// Thrown when this method is called on an XRI, or on a directed identity + /// request before the <see cref="IAuthenticationRequest.ClaimedIdentifier"/> property is set. + /// </exception> + void IAuthenticationRequest.SetClaimedIdentifierFragment(string fragment) { + Requires.ValidState(!(((IAuthenticationRequest)this).IsDirectedIdentity && ((IAuthenticationRequest)this).ClaimedIdentifier == null), OpenIdStrings.ClaimedIdentifierMustBeSetFirst); + Requires.ValidState(!(((IAuthenticationRequest)this).ClaimedIdentifier is XriIdentifier), OpenIdStrings.FragmentNotAllowedOnXRIs); + + throw new NotImplementedException(); + } + + #endregion + + #region IHostProcessedRequest Methods + + /// <summary> + /// Attempts to perform relying party discovery of the return URL claimed by the Relying Party. + /// </summary> + /// <param name="webRequestHandler">The web request handler to use for the RP discovery request.</param> + /// <returns> + /// The details of how successful the relying party discovery was. + /// </returns> + /// <remarks> + /// <para>Return URL verification is only attempted if this method is called.</para> + /// <para>See OpenID Authentication 2.0 spec section 9.2.1.</para> + /// </remarks> + RelyingPartyDiscoveryResult IHostProcessedRequest.IsReturnUrlDiscoverable(IDirectWebRequestHandler webRequestHandler) { + throw new NotImplementedException(); + } + + #endregion + + #region IRequest Methods + + /// <summary> + /// Adds an extension to the response to send to the relying party. + /// </summary> + /// <param name="extension">The extension to add to the response message.</param> + void IRequest.AddResponseExtension(DotNetOpenAuth.OpenId.Messages.IOpenIdMessageExtension extension) { + throw new NotImplementedException(); + } + + /// <summary> + /// Removes any response extensions previously added using <see cref="IRequest.AddResponseExtension"/>. + /// </summary> + /// <remarks> + /// This should be called before sending a negative response back to the relying party + /// if extensions were already added, since negative responses cannot carry extensions. + /// </remarks> + void IRequest.ClearResponseExtensions() { + } + + /// <summary> + /// Gets an extension sent from the relying party. + /// </summary> + /// <typeparam name="T">The type of the extension.</typeparam> + /// <returns> + /// An instance of the extension initialized with values passed in with the request. + /// </returns> + T IRequest.GetExtension<T>() { + throw new NotImplementedException(); + } + + /// <summary> + /// Gets an extension sent from the relying party. + /// </summary> + /// <param name="extensionType">The type of the extension.</param> + /// <returns> + /// An instance of the extension initialized with values passed in with the request. + /// </returns> + DotNetOpenAuth.OpenId.Messages.IOpenIdMessageExtension IRequest.GetExtension(Type extensionType) { + throw new NotImplementedException(); + } + + #endregion + } +} diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Provider/IHostProcessedRequest.cs b/src/DotNetOpenAuth.OpenId/OpenId/Provider/IHostProcessedRequest.cs new file mode 100644 index 0000000..809c0f3 --- /dev/null +++ b/src/DotNetOpenAuth.OpenId/OpenId/Provider/IHostProcessedRequest.cs @@ -0,0 +1,202 @@ +//----------------------------------------------------------------------- +// <copyright file="IHostProcessedRequest.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.Provider { + using System; + using System.Diagnostics.Contracts; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OpenId.Messages; + + /// <summary> + /// Interface exposing incoming messages to the OpenID Provider that + /// require interaction with the host site. + /// </summary> + [ContractClass(typeof(IHostProcessedRequestContract))] + public interface IHostProcessedRequest : IRequest { + /// <summary> + /// Gets the version of OpenID being used by the relying party that sent the request. + /// </summary> + ProtocolVersion RelyingPartyVersion { get; } + + /// <summary> + /// Gets the URL the consumer site claims to use as its 'base' address. + /// </summary> + Realm Realm { get; } + + /// <summary> + /// Gets a value indicating whether the consumer demands an immediate response. + /// If false, the consumer is willing to wait for the identity provider + /// to authenticate the user. + /// </summary> + bool Immediate { get; } + + /// <summary> + /// Gets or sets the provider endpoint claimed in the positive assertion. + /// </summary> + /// <value> + /// The default value is the URL that the request came in on from the relying party. + /// This value MUST match the value for the OP Endpoint in the discovery results for the + /// claimed identifier being asserted in a positive response. + /// </value> + Uri ProviderEndpoint { get; set; } + + /// <summary> + /// Attempts to perform relying party discovery of the return URL claimed by the Relying Party. + /// </summary> + /// <param name="webRequestHandler">The web request handler.</param> + /// <returns> + /// The details of how successful the relying party discovery was. + /// </returns> + /// <remarks> + /// <para>Return URL verification is only attempted if this method is called.</para> + /// <para>See OpenID Authentication 2.0 spec section 9.2.1.</para> + /// </remarks> + RelyingPartyDiscoveryResult IsReturnUrlDiscoverable(IDirectWebRequestHandler webRequestHandler); + } + + /// <summary> + /// Code contract for the <see cref="IHostProcessedRequest"/> type. + /// </summary> + [ContractClassFor(typeof(IHostProcessedRequest))] + internal abstract class IHostProcessedRequestContract : IHostProcessedRequest { + /// <summary> + /// Initializes a new instance of the <see cref="IHostProcessedRequestContract"/> class. + /// </summary> + protected IHostProcessedRequestContract() { + } + + #region IHostProcessedRequest Properties + + /// <summary> + /// Gets the version of OpenID being used by the relying party that sent the request. + /// </summary> + ProtocolVersion IHostProcessedRequest.RelyingPartyVersion { + get { throw new System.NotImplementedException(); } + } + + /// <summary> + /// Gets the URL the consumer site claims to use as its 'base' address. + /// </summary> + Realm IHostProcessedRequest.Realm { + get { throw new System.NotImplementedException(); } + } + + /// <summary> + /// Gets a value indicating whether the consumer demands an immediate response. + /// If false, the consumer is willing to wait for the identity provider + /// to authenticate the user. + /// </summary> + bool IHostProcessedRequest.Immediate { + get { throw new System.NotImplementedException(); } + } + + /// <summary> + /// Gets or sets the provider endpoint. + /// </summary> + /// <value> + /// The default value is the URL that the request came in on from the relying party. + /// </value> + Uri IHostProcessedRequest.ProviderEndpoint { + get { + Contract.Ensures(Contract.Result<Uri>() != null); + throw new NotImplementedException(); + } + + set { + Contract.Requires(value != null); + throw new NotImplementedException(); + } + } + + #endregion + + #region IRequest Members + + /// <summary> + /// Gets or sets the security settings that apply to this request. + /// </summary> + /// <value> + /// Defaults to the OpenIdProvider.SecuritySettings on the OpenIdProvider. + /// </value> + ProviderSecuritySettings IRequest.SecuritySettings { + get { throw new NotImplementedException(); } + set { throw new NotImplementedException(); } + } + + /// <summary> + /// Gets a value indicating whether the response is ready to be sent to the user agent. + /// </summary> + /// <remarks> + /// This property returns false if there are properties that must be set on this + /// request instance before the response can be sent. + /// </remarks> + bool IRequest.IsResponseReady { + get { throw new System.NotImplementedException(); } + } + + /// <summary> + /// Adds an extension to the response to send to the relying party. + /// </summary> + /// <param name="extension">The extension to add to the response message.</param> + void IRequest.AddResponseExtension(DotNetOpenAuth.OpenId.Messages.IOpenIdMessageExtension extension) { + throw new System.NotImplementedException(); + } + + /// <summary> + /// Removes any response extensions previously added using <see cref="IRequest.AddResponseExtension"/>. + /// </summary> + /// <remarks> + /// This should be called before sending a negative response back to the relying party + /// if extensions were already added, since negative responses cannot carry extensions. + /// </remarks> + void IRequest.ClearResponseExtensions() { + } + + /// <summary> + /// Gets an extension sent from the relying party. + /// </summary> + /// <typeparam name="T">The type of the extension.</typeparam> + /// <returns> + /// An instance of the extension initialized with values passed in with the request. + /// </returns> + T IRequest.GetExtension<T>() { + throw new System.NotImplementedException(); + } + + /// <summary> + /// Gets an extension sent from the relying party. + /// </summary> + /// <param name="extensionType">The type of the extension.</param> + /// <returns> + /// An instance of the extension initialized with values passed in with the request. + /// </returns> + DotNetOpenAuth.OpenId.Messages.IOpenIdMessageExtension IRequest.GetExtension(System.Type extensionType) { + throw new System.NotImplementedException(); + } + + #endregion + + #region IHostProcessedRequest Methods + + /// <summary> + /// Attempts to perform relying party discovery of the return URL claimed by the Relying Party. + /// </summary> + /// <param name="webRequestHandler">The web request handler.</param> + /// <returns> + /// The details of how successful the relying party discovery was. + /// </returns> + /// <remarks> + /// <para>Return URL verification is only attempted if this method is called.</para> + /// <para>See OpenID Authentication 2.0 spec section 9.2.1.</para> + /// </remarks> + RelyingPartyDiscoveryResult IHostProcessedRequest.IsReturnUrlDiscoverable(IDirectWebRequestHandler webRequestHandler) { + Requires.NotNull(webRequestHandler, "webRequestHandler"); + throw new System.NotImplementedException(); + } + + #endregion + } +} diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Provider/IProviderBehavior.cs b/src/DotNetOpenAuth.OpenId/OpenId/Provider/IProviderBehavior.cs new file mode 100644 index 0000000..9750625 --- /dev/null +++ b/src/DotNetOpenAuth.OpenId/OpenId/Provider/IProviderBehavior.cs @@ -0,0 +1,114 @@ +//----------------------------------------------------------------------- +// <copyright file="IProviderBehavior.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.Provider { + using System; + using System.Diagnostics.Contracts; + using DotNetOpenAuth.OpenId.ChannelElements; + + /// <summary> + /// Applies a custom security policy to certain OpenID security settings and behaviors. + /// </summary> + [ContractClass(typeof(IProviderBehaviorContract))] + public interface IProviderBehavior { + /// <summary> + /// Applies a well known set of security requirements to a default set of security settings. + /// </summary> + /// <param name="securitySettings">The security settings to enhance with the requirements of this profile.</param> + /// <remarks> + /// Care should be taken to never decrease security when applying a profile. + /// Profiles should only enhance security requirements to avoid being + /// incompatible with each other. + /// </remarks> + void ApplySecuritySettings(ProviderSecuritySettings securitySettings); + + /// <summary> + /// Called when a request is received by the Provider. + /// </summary> + /// <param name="request">The incoming request.</param> + /// <returns> + /// <c>true</c> if this behavior owns this request and wants to stop other behaviors + /// from handling it; <c>false</c> to allow other behaviors to process this request. + /// </returns> + /// <remarks> + /// Implementations may set a new value to <see cref="IRequest.SecuritySettings"/> but + /// should not change the properties on the instance of <see cref="ProviderSecuritySettings"/> + /// itself as that instance may be shared across many requests. + /// </remarks> + bool OnIncomingRequest(IRequest request); + + /// <summary> + /// Called when the Provider is preparing to send a response to an authentication request. + /// </summary> + /// <param name="request">The request that is configured to generate the outgoing response.</param> + /// <returns> + /// <c>true</c> if this behavior owns this request and wants to stop other behaviors + /// from handling it; <c>false</c> to allow other behaviors to process this request. + /// </returns> + bool OnOutgoingResponse(IAuthenticationRequest request); + } + + /// <summary> + /// Code contract for the <see cref="IProviderBehavior"/> type. + /// </summary> + [ContractClassFor(typeof(IProviderBehavior))] + internal abstract class IProviderBehaviorContract : IProviderBehavior { + /// <summary> + /// Initializes a new instance of the <see cref="IProviderBehaviorContract"/> class. + /// </summary> + protected IProviderBehaviorContract() { + } + + #region IProviderBehavior Members + + /// <summary> + /// Applies a well known set of security requirements to a default set of security settings. + /// </summary> + /// <param name="securitySettings">The security settings to enhance with the requirements of this profile.</param> + /// <remarks> + /// Care should be taken to never decrease security when applying a profile. + /// Profiles should only enhance security requirements to avoid being + /// incompatible with each other. + /// </remarks> + void IProviderBehavior.ApplySecuritySettings(ProviderSecuritySettings securitySettings) { + Requires.NotNull(securitySettings, "securitySettings"); + throw new System.NotImplementedException(); + } + + /// <summary> + /// Called when a request is received by the Provider. + /// </summary> + /// <param name="request">The incoming request.</param> + /// <returns> + /// <c>true</c> if this behavior owns this request and wants to stop other behaviors + /// from handling it; <c>false</c> to allow other behaviors to process this request. + /// </returns> + /// <remarks> + /// Implementations may set a new value to <see cref="IRequest.SecuritySettings"/> but + /// should not change the properties on the instance of <see cref="ProviderSecuritySettings"/> + /// itself as that instance may be shared across many requests. + /// </remarks> + bool IProviderBehavior.OnIncomingRequest(IRequest request) { + Requires.NotNull(request, "request"); + throw new System.NotImplementedException(); + } + + /// <summary> + /// Called when the Provider is preparing to send a response to an authentication request. + /// </summary> + /// <param name="request">The request that is configured to generate the outgoing response.</param> + /// <returns> + /// <c>true</c> if this behavior owns this request and wants to stop other behaviors + /// from handling it; <c>false</c> to allow other behaviors to process this request. + /// </returns> + bool IProviderBehavior.OnOutgoingResponse(IAuthenticationRequest request) { + Requires.NotNull(request, "request"); + throw new System.NotImplementedException(); + } + + #endregion + } +} diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Provider/IRequest.cs b/src/DotNetOpenAuth.OpenId/OpenId/Provider/IRequest.cs new file mode 100644 index 0000000..1870bfe --- /dev/null +++ b/src/DotNetOpenAuth.OpenId/OpenId/Provider/IRequest.cs @@ -0,0 +1,149 @@ +//----------------------------------------------------------------------- +// <copyright file="IRequest.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.Provider { + using System; + using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; + using System.Diagnostics.Contracts; + using System.Text; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OpenId.Messages; + + /// <summary> + /// Represents an incoming OpenId authentication request. + /// </summary> + /// <remarks> + /// Requests may be infrastructural to OpenID and allow auto-responses, or they may + /// be authentication requests where the Provider site has to make decisions based + /// on its own user database and policies. + /// </remarks> + [ContractClass(typeof(IRequestContract))] + public interface IRequest { + /// <summary> + /// Gets a value indicating whether the response is ready to be sent to the user agent. + /// </summary> + /// <remarks> + /// This property returns false if there are properties that must be set on this + /// request instance before the response can be sent. + /// </remarks> + bool IsResponseReady { get; } + + /// <summary> + /// Gets or sets the security settings that apply to this request. + /// </summary> + /// <value>Defaults to the OpenIdProvider.SecuritySettings on the OpenIdProvider.</value> + ProviderSecuritySettings SecuritySettings { get; set; } + + /// <summary> + /// Adds an extension to the response to send to the relying party. + /// </summary> + /// <param name="extension">The extension to add to the response message.</param> + void AddResponseExtension(IOpenIdMessageExtension extension); + + /// <summary> + /// Removes any response extensions previously added using <see cref="IRequest.AddResponseExtension"/>. + /// </summary> + /// <remarks> + /// This should be called before sending a negative response back to the relying party + /// if extensions were already added, since negative responses cannot carry extensions. + /// </remarks> + void ClearResponseExtensions(); + + /// <summary> + /// Gets an extension sent from the relying party. + /// </summary> + /// <typeparam name="T">The type of the extension.</typeparam> + /// <returns>An instance of the extension initialized with values passed in with the request.</returns> + [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "No parameter to make of type T.")] + T GetExtension<T>() where T : IOpenIdMessageExtension, new(); + + /// <summary> + /// Gets an extension sent from the relying party. + /// </summary> + /// <param name="extensionType">The type of the extension.</param> + /// <returns>An instance of the extension initialized with values passed in with the request.</returns> + IOpenIdMessageExtension GetExtension(Type extensionType); + } + + /// <summary> + /// Code contract for the <see cref="IRequest"/> interface. + /// </summary> + [ContractClassFor(typeof(IRequest))] + internal abstract class IRequestContract : IRequest { + /// <summary> + /// Prevents a default instance of the <see cref="IRequestContract"/> class from being created. + /// </summary> + private IRequestContract() { + } + + #region IRequest Members + + /// <summary> + /// Gets or sets the security settings that apply to this request. + /// </summary> + /// <value>Defaults to the OpenIdProvider.SecuritySettings on the OpenIdProvider.</value> + ProviderSecuritySettings IRequest.SecuritySettings { + get { throw new NotImplementedException(); } + set { throw new NotImplementedException(); } + } + + /// <summary> + /// Gets a value indicating whether the response is ready to be sent to the user agent. + /// </summary> + /// <remarks> + /// This property returns false if there are properties that must be set on this + /// request instance before the response can be sent. + /// </remarks> + bool IRequest.IsResponseReady { + get { throw new NotImplementedException(); } + } + + /// <summary> + /// Adds an extension to the response to send to the relying party. + /// </summary> + /// <param name="extension">The extension to add to the response message.</param> + void IRequest.AddResponseExtension(IOpenIdMessageExtension extension) { + Requires.NotNull(extension, "extension"); + throw new NotImplementedException(); + } + + /// <summary> + /// Removes any response extensions previously added using <see cref="IRequest.AddResponseExtension"/>. + /// </summary> + /// <remarks> + /// This should be called before sending a negative response back to the relying party + /// if extensions were already added, since negative responses cannot carry extensions. + /// </remarks> + void IRequest.ClearResponseExtensions() { + } + + /// <summary> + /// Gets an extension sent from the relying party. + /// </summary> + /// <typeparam name="T">The type of the extension.</typeparam> + /// <returns> + /// An instance of the extension initialized with values passed in with the request. + /// </returns> + T IRequest.GetExtension<T>() { + throw new NotImplementedException(); + } + + /// <summary> + /// Gets an extension sent from the relying party. + /// </summary> + /// <param name="extensionType">The type of the extension.</param> + /// <returns> + /// An instance of the extension initialized with values passed in with the request. + /// </returns> + IOpenIdMessageExtension IRequest.GetExtension(Type extensionType) { + Requires.NotNull(extensionType, "extensionType"); + throw new NotImplementedException(); + } + + #endregion + } +} diff --git a/src/DotNetOpenAuth/OpenId/Provider/ProviderSecuritySettings.cs b/src/DotNetOpenAuth.OpenId/OpenId/Provider/ProviderSecuritySettings.cs index 130e6dd..130e6dd 100644 --- a/src/DotNetOpenAuth/OpenId/Provider/ProviderSecuritySettings.cs +++ b/src/DotNetOpenAuth.OpenId/OpenId/Provider/ProviderSecuritySettings.cs diff --git a/src/DotNetOpenAuth/OpenId/Provider/RelyingPartyDiscoveryResult.cs b/src/DotNetOpenAuth.OpenId/OpenId/Provider/RelyingPartyDiscoveryResult.cs index 4eca6d6..4eca6d6 100644 --- a/src/DotNetOpenAuth/OpenId/Provider/RelyingPartyDiscoveryResult.cs +++ b/src/DotNetOpenAuth.OpenId/OpenId/Provider/RelyingPartyDiscoveryResult.cs diff --git a/src/DotNetOpenAuth.OpenId/OpenId/ProviderEndpointDescription.cs b/src/DotNetOpenAuth.OpenId/OpenId/ProviderEndpointDescription.cs new file mode 100644 index 0000000..092ec22 --- /dev/null +++ b/src/DotNetOpenAuth.OpenId/OpenId/ProviderEndpointDescription.cs @@ -0,0 +1,134 @@ +//----------------------------------------------------------------------- +// <copyright file="ProviderEndpointDescription.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId { + using System; + using System.Collections.Generic; + using System.Collections.ObjectModel; + using System.Diagnostics.CodeAnalysis; + using System.Diagnostics.Contracts; + using System.Linq; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OpenId.Messages; + using DotNetOpenAuth.OpenId.RelyingParty; + + /// <summary> + /// Describes some OpenID Provider endpoint and its capabilities. + /// </summary> + /// <remarks> + /// This is an immutable type. + /// </remarks> + [Serializable] + internal sealed class ProviderEndpointDescription : IProviderEndpoint { + /// <summary> + /// Initializes a new instance of the <see cref="ProviderEndpointDescription"/> class. + /// </summary> + /// <param name="providerEndpoint">The OpenID Provider endpoint URL.</param> + /// <param name="openIdVersion">The OpenID version supported by this particular endpoint.</param> + internal ProviderEndpointDescription(Uri providerEndpoint, Version openIdVersion) { + Requires.NotNull(providerEndpoint, "providerEndpoint"); + Requires.NotNull(openIdVersion, "openIdVersion"); + + this.Uri = providerEndpoint; + this.Version = openIdVersion; + this.Capabilities = new ReadOnlyCollection<string>(EmptyList<string>.Instance); + } + + /// <summary> + /// Initializes a new instance of the <see cref="ProviderEndpointDescription"/> class. + /// </summary> + /// <param name="providerEndpoint">The URI the provider listens on for OpenID requests.</param> + /// <param name="serviceTypeURIs">The set of services offered by this endpoint.</param> + internal ProviderEndpointDescription(Uri providerEndpoint, IEnumerable<string> serviceTypeURIs) { + Requires.NotNull(providerEndpoint, "providerEndpoint"); + Requires.NotNull(serviceTypeURIs, "serviceTypeURIs"); + + this.Uri = providerEndpoint; + this.Capabilities = new ReadOnlyCollection<string>(serviceTypeURIs.ToList()); + + Protocol opIdentifierProtocol = Protocol.FindBestVersion(p => p.OPIdentifierServiceTypeURI, serviceTypeURIs); + Protocol claimedIdentifierProviderVersion = Protocol.FindBestVersion(p => p.ClaimedIdentifierServiceTypeURI, serviceTypeURIs); + if (opIdentifierProtocol != null) { + this.Version = opIdentifierProtocol.Version; + } else if (claimedIdentifierProviderVersion != null) { + this.Version = claimedIdentifierProviderVersion.Version; + } else { + ErrorUtilities.ThrowProtocol(OpenIdStrings.ProviderVersionUnrecognized, this.Uri); + } + } + + /// <summary> + /// Gets the URL that the OpenID Provider listens for incoming OpenID messages on. + /// </summary> + public Uri Uri { get; private set; } + + /// <summary> + /// Gets the OpenID protocol version this endpoint supports. + /// </summary> + /// <remarks> + /// If an endpoint supports multiple versions, each version must be represented + /// by its own <see cref="ProviderEndpointDescription"/> object. + /// </remarks> + public Version Version { get; private set; } + + /// <summary> + /// Gets the collection of service type URIs found in the XRDS document describing this Provider. + /// </summary> + internal ReadOnlyCollection<string> Capabilities { get; private set; } + + #region IProviderEndpoint Members + + /// <summary> + /// Checks whether the OpenId Identifier claims support for a given extension. + /// </summary> + /// <typeparam name="T">The extension whose support is being queried.</typeparam> + /// <returns> + /// True if support for the extension is advertised. False otherwise. + /// </returns> + /// <remarks> + /// Note that a true or false return value is no guarantee of a Provider's + /// support for or lack of support for an extension. The return value is + /// determined by how the authenticating user filled out his/her XRDS document only. + /// The only way to be sure of support for a given extension is to include + /// the extension in the request and see if a response comes back for that extension. + /// </remarks> + bool IProviderEndpoint.IsExtensionSupported<T>() { + throw new NotImplementedException(); + } + + /// <summary> + /// Checks whether the OpenId Identifier claims support for a given extension. + /// </summary> + /// <param name="extensionType">The extension whose support is being queried.</param> + /// <returns> + /// True if support for the extension is advertised. False otherwise. + /// </returns> + /// <remarks> + /// Note that a true or false return value is no guarantee of a Provider's + /// support for or lack of support for an extension. The return value is + /// determined by how the authenticating user filled out his/her XRDS document only. + /// The only way to be sure of support for a given extension is to include + /// the extension in the request and see if a response comes back for that extension. + /// </remarks> + bool IProviderEndpoint.IsExtensionSupported(Type extensionType) { + throw new NotImplementedException(); + } + + #endregion + +#if CONTRACTS_FULL + /// <summary> + /// Verifies conditions that should be true for any valid state of this object. + /// </summary> + [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Called by code contracts.")] + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called by code contracts.")] + [ContractInvariantMethod] + private void ObjectInvariant() { + Contract.Invariant(this.Capabilities != null); + } +#endif + } +} diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Realm.cs b/src/DotNetOpenAuth.OpenId/OpenId/Realm.cs new file mode 100644 index 0000000..685b922 --- /dev/null +++ b/src/DotNetOpenAuth.OpenId/OpenId/Realm.cs @@ -0,0 +1,512 @@ +//----------------------------------------------------------------------- +// <copyright file="Realm.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId { + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; + using System.Diagnostics.Contracts; + using System.Globalization; + using System.Linq; + using System.Text.RegularExpressions; + using System.Web; + using System.Xml; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.Messaging.Reflection; + using DotNetOpenAuth.Xrds; + using DotNetOpenAuth.Yadis; + + /// <summary> + /// A trust root to validate requests and match return URLs against. + /// </summary> + /// <remarks> + /// This fills the OpenID Authentication 2.0 specification for realms. + /// See http://openid.net/specs/openid-authentication-2_0.html#realms + /// </remarks> + [Serializable] + [Pure] + public class Realm { + /// <summary> + /// A regex used to detect a wildcard that is being used in the realm. + /// </summary> + private const string WildcardDetectionPattern = @"^(\w+://)\*\."; + + /// <summary> + /// A (more or less) comprehensive list of top-level (i.e. ".com") domains, + /// for use by <see cref="IsSane"/> in order to disallow overly-broad realms + /// that allow all web sites ending with '.com', for example. + /// </summary> + private static readonly string[] topLevelDomains = { "com", "edu", "gov", "int", "mil", "net", "org", "biz", "info", "name", "museum", "coop", "aero", "ac", "ad", "ae", + "af", "ag", "ai", "al", "am", "an", "ao", "aq", "ar", "as", "at", "au", "aw", "az", "ba", "bb", "bd", "be", "bf", "bg", "bh", "bi", "bj", + "bm", "bn", "bo", "br", "bs", "bt", "bv", "bw", "by", "bz", "ca", "cc", "cd", "cf", "cg", "ch", "ci", "ck", "cl", "cm", "cn", "co", "cr", + "cu", "cv", "cx", "cy", "cz", "de", "dj", "dk", "dm", "do", "dz", "ec", "ee", "eg", "eh", "er", "es", "et", "fi", "fj", "fk", "fm", "fo", + "fr", "ga", "gd", "ge", "gf", "gg", "gh", "gi", "gl", "gm", "gn", "gp", "gq", "gr", "gs", "gt", "gu", "gw", "gy", "hk", "hm", "hn", "hr", + "ht", "hu", "id", "ie", "il", "im", "in", "io", "iq", "ir", "is", "it", "je", "jm", "jo", "jp", "ke", "kg", "kh", "ki", "km", "kn", "kp", + "kr", "kw", "ky", "kz", "la", "lb", "lc", "li", "lk", "lr", "ls", "lt", "lu", "lv", "ly", "ma", "mc", "md", "mg", "mh", "mk", "ml", "mm", + "mn", "mo", "mp", "mq", "mr", "ms", "mt", "mu", "mv", "mw", "mx", "my", "mz", "na", "nc", "ne", "nf", "ng", "ni", "nl", "no", "np", "nr", + "nu", "nz", "om", "pa", "pe", "pf", "pg", "ph", "pk", "pl", "pm", "pn", "pr", "ps", "pt", "pw", "py", "qa", "re", "ro", "ru", "rw", "sa", + "sb", "sc", "sd", "se", "sg", "sh", "si", "sj", "sk", "sl", "sm", "sn", "so", "sr", "st", "sv", "sy", "sz", "tc", "td", "tf", "tg", "th", + "tj", "tk", "tm", "tn", "to", "tp", "tr", "tt", "tv", "tw", "tz", "ua", "ug", "uk", "um", "us", "uy", "uz", "va", "vc", "ve", "vg", "vi", + "vn", "vu", "wf", "ws", "ye", "yt", "yu", "za", "zm", "zw" }; + + /// <summary> + /// The Uri of the realm, with the wildcard (if any) removed. + /// </summary> + private Uri uri; + + /// <summary> + /// Initializes static members of the <see cref="Realm"/> class. + /// </summary> + static Realm() { + Func<string, Realm> safeRealm = str => { + Contract.Assume(str != null); + return new Realm(str); + }; + MessagePart.Map<Realm>(realm => realm.ToString(), realm => realm.OriginalString, safeRealm); + } + + /// <summary> + /// Initializes a new instance of the <see cref="Realm"/> class. + /// </summary> + /// <param name="realmUrl">The realm URL to use in the new instance.</param> + [SuppressMessage("Microsoft.Design", "CA1057:StringUriOverloadsCallSystemUriOverloads", Justification = "Not all realms are valid URLs (because of wildcards).")] + public Realm(string realmUrl) { + Requires.NotNull(realmUrl, "realmUrl"); // not non-zero check so we throw UriFormatException later + this.DomainWildcard = Regex.IsMatch(realmUrl, WildcardDetectionPattern); + this.uri = new Uri(Regex.Replace(realmUrl, WildcardDetectionPattern, m => m.Groups[1].Value)); + if (!this.uri.Scheme.Equals("http", StringComparison.OrdinalIgnoreCase) && + !this.uri.Scheme.Equals("https", StringComparison.OrdinalIgnoreCase)) { + throw new UriFormatException( + string.Format(CultureInfo.CurrentCulture, OpenIdStrings.InvalidScheme, this.uri.Scheme)); + } + } + + /// <summary> + /// Initializes a new instance of the <see cref="Realm"/> class. + /// </summary> + /// <param name="realmUrl">The realm URL of the Relying Party.</param> + public Realm(Uri realmUrl) { + Requires.NotNull(realmUrl, "realmUrl"); + this.uri = realmUrl; + if (!this.uri.Scheme.Equals("http", StringComparison.OrdinalIgnoreCase) && + !this.uri.Scheme.Equals("https", StringComparison.OrdinalIgnoreCase)) { + throw new UriFormatException( + string.Format(CultureInfo.CurrentCulture, OpenIdStrings.InvalidScheme, this.uri.Scheme)); + } + } + + /// <summary> + /// Initializes a new instance of the <see cref="Realm"/> class. + /// </summary> + /// <param name="realmUriBuilder">The realm URI builder.</param> + /// <remarks> + /// This is useful because UriBuilder can construct a host with a wildcard + /// in the Host property, but once there it can't be converted to a Uri. + /// </remarks> + internal Realm(UriBuilder realmUriBuilder) + : this(SafeUriBuilderToString(realmUriBuilder)) { } + + /// <summary> + /// Gets the suggested realm to use for the calling web application. + /// </summary> + /// <value>A realm that matches this applications root URL.</value> + /// <remarks> + /// <para>For most circumstances the Realm generated by this property is sufficient. + /// However a wildcard Realm, such as "http://*.microsoft.com/" may at times be more + /// desirable than "http://www.microsoft.com/" in order to allow identifier + /// correlation across related web sites for directed identity Providers.</para> + /// <para>Requires an <see cref="HttpContext.Current">HttpContext.Current</see> context.</para> + /// </remarks> + public static Realm AutoDetect { + get { + Requires.ValidState(HttpContext.Current != null && HttpContext.Current.Request != null, MessagingStrings.HttpContextRequired); + Contract.Ensures(Contract.Result<Realm>() != null); + + HttpRequestInfo requestInfo = new HttpRequestInfo(HttpContext.Current.Request); + UriBuilder realmUrl = new UriBuilder(requestInfo.UrlBeforeRewriting); + realmUrl.Path = HttpContext.Current.Request.ApplicationPath; + realmUrl.Query = null; + realmUrl.Fragment = null; + + // For RP discovery, the realm url MUST NOT redirect. To prevent this for + // virtual directory hosted apps, we need to make sure that the realm path ends + // in a slash (since our calculation above guarantees it doesn't end in a specific + // page like default.aspx). + if (!realmUrl.Path.EndsWith("/", StringComparison.Ordinal)) { + realmUrl.Path += "/"; + } + + return realmUrl.Uri; + } + } + + /// <summary> + /// Gets a value indicating whether a '*.' prefix to the hostname is + /// used in the realm to allow subdomains or hosts to be added to the URL. + /// </summary> + public bool DomainWildcard { get; private set; } + + /// <summary> + /// Gets the host component of this instance. + /// </summary> + public string Host { + [DebuggerStepThrough] + get { return this.uri.Host; } + } + + /// <summary> + /// Gets the scheme name for this URI. + /// </summary> + public string Scheme { + [DebuggerStepThrough] + get { return this.uri.Scheme; } + } + + /// <summary> + /// Gets the port number of this URI. + /// </summary> + public int Port { + [DebuggerStepThrough] + get { return this.uri.Port; } + } + + /// <summary> + /// Gets the absolute path of the URI. + /// </summary> + public string AbsolutePath { + [DebuggerStepThrough] + get { return this.uri.AbsolutePath; } + } + + /// <summary> + /// Gets the System.Uri.AbsolutePath and System.Uri.Query properties separated + /// by a question mark (?). + /// </summary> + public string PathAndQuery { + [DebuggerStepThrough] + get { return this.uri.PathAndQuery; } + } + + /// <summary> + /// Gets the original string. + /// </summary> + /// <value>The original string.</value> + internal string OriginalString { + get { return this.uri.OriginalString; } + } + + /// <summary> + /// Gets the realm URL. If the realm includes a wildcard, it is not included here. + /// </summary> + internal Uri NoWildcardUri { + [DebuggerStepThrough] + get { return this.uri; } + } + + /// <summary> + /// Gets the Realm discovery URL, where the wildcard (if present) is replaced with "www.". + /// </summary> + /// <remarks> + /// See OpenID 2.0 spec section 9.2.1 for the explanation on the addition of + /// the "www" prefix. + /// </remarks> + internal Uri UriWithWildcardChangedToWww { + get { + if (this.DomainWildcard) { + UriBuilder builder = new UriBuilder(this.NoWildcardUri); + builder.Host = "www." + builder.Host; + return builder.Uri; + } else { + return this.NoWildcardUri; + } + } + } + + /// <summary> + /// Gets a value indicating whether this realm represents a reasonable (sane) set of URLs. + /// </summary> + /// <remarks> + /// 'http://*.com/', for example is not a reasonable pattern, as it cannot meaningfully + /// specify the site claiming it. This function attempts to find many related examples, + /// but it can only work via heuristics. Negative responses from this method should be + /// treated as advisory, used only to alert the user to examine the trust root carefully. + /// </remarks> + internal bool IsSane { + get { + if (this.Host.Equals("localhost", StringComparison.OrdinalIgnoreCase)) { + return true; + } + + string[] host_parts = this.Host.Split('.'); + + string tld = host_parts[host_parts.Length - 1]; + + if (Array.IndexOf(topLevelDomains, tld) < 0) { + return false; + } + + if (tld.Length == 2) { + if (host_parts.Length == 1) { + return false; + } + + if (host_parts[host_parts.Length - 2].Length <= 3) { + return host_parts.Length > 2; + } + } else { + return host_parts.Length > 1; + } + + return false; + } + } + + /// <summary> + /// Implicitly converts the string-form of a URI to a <see cref="Realm"/> object. + /// </summary> + /// <param name="uri">The URI that the new Realm instance will represent.</param> + /// <returns>The result of the conversion.</returns> + [SuppressMessage("Microsoft.Design", "CA1057:StringUriOverloadsCallSystemUriOverloads", Justification = "Not all realms are valid URLs (because of wildcards).")] + [SuppressMessage("Microsoft.Usage", "CA2234:PassSystemUriObjectsInsteadOfStrings", Justification = "Not all Realms are valid URLs.")] + [DebuggerStepThrough] + public static implicit operator Realm(string uri) { + Contract.Ensures((Contract.Result<Realm>() != null) == (uri != null)); + return uri != null ? new Realm(uri) : null; + } + + /// <summary> + /// Implicitly converts a <see cref="Uri"/> to a <see cref="Realm"/> object. + /// </summary> + /// <param name="uri">The URI to convert to a realm.</param> + /// <returns>The result of the conversion.</returns> + [DebuggerStepThrough] + public static implicit operator Realm(Uri uri) { + Contract.Ensures((Contract.Result<Realm>() != null) == (uri != null)); + return uri != null ? new Realm(uri) : null; + } + + /// <summary> + /// Implicitly converts a <see cref="Realm"/> object to its <see cref="String"/> form. + /// </summary> + /// <param name="realm">The realm to convert to a string value.</param> + /// <returns>The result of the conversion.</returns> + [DebuggerStepThrough] + public static implicit operator string(Realm realm) { + Contract.Ensures((Contract.Result<string>() != null) == (realm != null)); + return realm != null ? realm.ToString() : null; + } + + /// <summary> + /// Checks whether one <see cref="Realm"/> is equal to another. + /// </summary> + /// <param name="obj">The <see cref="T:System.Object"/> to compare with the current <see cref="T:System.Object"/>.</param> + /// <returns> + /// true if the specified <see cref="T:System.Object"/> is equal to the current <see cref="T:System.Object"/>; otherwise, false. + /// </returns> + /// <exception cref="T:System.NullReferenceException"> + /// The <paramref name="obj"/> parameter is null. + /// </exception> + public override bool Equals(object obj) { + Realm other = obj as Realm; + if (other == null) { + return false; + } + return this.uri.Equals(other.uri) && this.DomainWildcard == other.DomainWildcard; + } + + /// <summary> + /// Returns the hash code used for storing this object in a hash table. + /// </summary> + /// <returns> + /// A hash code for the current <see cref="T:System.Object"/>. + /// </returns> + public override int GetHashCode() { + return this.uri.GetHashCode() + (this.DomainWildcard ? 1 : 0); + } + + /// <summary> + /// Returns the string form of this <see cref="Realm"/>. + /// </summary> + /// <returns> + /// A <see cref="T:System.String"/> that represents the current <see cref="T:System.Object"/>. + /// </returns> + public override string ToString() { + if (this.DomainWildcard) { + UriBuilder builder = new UriBuilder(this.uri); + builder.Host = "*." + builder.Host; + return builder.ToStringWithImpliedPorts(); + } else { + return this.uri.AbsoluteUri; + } + } + + /// <summary> + /// Validates a URL against this trust root. + /// </summary> + /// <param name="url">A string specifying URL to check.</param> + /// <returns>Whether the given URL is within this trust root.</returns> + internal bool Contains(string url) { + return this.Contains(new Uri(url)); + } + + /// <summary> + /// Validates a URL against this trust root. + /// </summary> + /// <param name="url">The URL to check.</param> + /// <returns>Whether the given URL is within this trust root.</returns> + internal bool Contains(Uri url) { + if (url.Scheme != this.Scheme) { + return false; + } + + if (url.Port != this.Port) { + return false; + } + + if (!this.DomainWildcard) { + if (url.Host != this.Host) { + return false; + } + } else { + Debug.Assert(!string.IsNullOrEmpty(this.Host), "The host part of the Regex should evaluate to at least one char for successful parsed trust roots."); + string[] host_parts = this.Host.Split('.'); + string[] url_parts = url.Host.Split('.'); + + // If the domain containing the wildcard has more parts than the URL to match against, + // it naturally can't be valid. + // Unless *.example.com actually matches example.com too. + if (host_parts.Length > url_parts.Length) { + return false; + } + + // Compare last part first and move forward. + // Maybe could be done by using EndsWith, but piecewies helps ensure that + // *.my.com doesn't match ohmeohmy.com but can still match my.com. + for (int i = 0; i < host_parts.Length; i++) { + string hostPart = host_parts[host_parts.Length - 1 - i]; + string urlPart = url_parts[url_parts.Length - 1 - i]; + if (!string.Equals(hostPart, urlPart, StringComparison.OrdinalIgnoreCase)) { + return false; + } + } + } + + // If path matches or is specified to root ... + // (deliberately case sensitive to protect security on case sensitive systems) + if (this.PathAndQuery.Equals(url.PathAndQuery, StringComparison.Ordinal) + || this.PathAndQuery.Equals("/", StringComparison.Ordinal)) { + return true; + } + + // If trust root has a longer path, the return URL must be invalid. + if (this.PathAndQuery.Length > url.PathAndQuery.Length) { + return false; + } + + // The following code assures that http://example.com/directory isn't below http://example.com/dir, + // but makes sure http://example.com/dir/ectory is below http://example.com/dir + int path_len = this.PathAndQuery.Length; + string url_prefix = url.PathAndQuery.Substring(0, path_len); + + if (this.PathAndQuery != url_prefix) { + return false; + } + + // If trust root includes a query string ... + if (this.PathAndQuery.Contains("?")) { + // ... make sure return URL begins with a new argument + return url.PathAndQuery[path_len] == '&'; + } + + // Or make sure a query string is introduced or a path below trust root + return this.PathAndQuery.EndsWith("/", StringComparison.Ordinal) + || url.PathAndQuery[path_len] == '?' + || url.PathAndQuery[path_len] == '/'; + } + + /// <summary> + /// Searches for an XRDS document at the realm URL, and if found, searches + /// for a description of a relying party endpoints (OpenId login pages). + /// </summary> + /// <param name="requestHandler">The mechanism to use for sending HTTP requests.</param> + /// <param name="allowRedirects">Whether redirects may be followed when discovering the Realm. + /// This may be true when creating an unsolicited assertion, but must be + /// false when performing return URL verification per 2.0 spec section 9.2.1.</param> + /// <returns> + /// The details of the endpoints if found; or <c>null</c> if no service document was discovered. + /// </returns> + internal virtual IEnumerable<RelyingPartyEndpointDescription> DiscoverReturnToEndpoints(IDirectWebRequestHandler requestHandler, bool allowRedirects) { + XrdsDocument xrds = this.Discover(requestHandler, allowRedirects); + if (xrds != null) { + return xrds.FindRelyingPartyReceivingEndpoints(); + } + + return null; + } + + /// <summary> + /// Searches for an XRDS document at the realm URL. + /// </summary> + /// <param name="requestHandler">The mechanism to use for sending HTTP requests.</param> + /// <param name="allowRedirects">Whether redirects may be followed when discovering the Realm. + /// This may be true when creating an unsolicited assertion, but must be + /// false when performing return URL verification per 2.0 spec section 9.2.1.</param> + /// <returns> + /// The XRDS document if found; or <c>null</c> if no service document was discovered. + /// </returns> + internal virtual XrdsDocument Discover(IDirectWebRequestHandler requestHandler, bool allowRedirects) { + // Attempt YADIS discovery + DiscoveryResult yadisResult = Yadis.Discover(requestHandler, this.UriWithWildcardChangedToWww, false); + if (yadisResult != null) { + // Detect disallowed redirects, since realm discovery never allows them for security. + ErrorUtilities.VerifyProtocol(allowRedirects || yadisResult.NormalizedUri == yadisResult.RequestUri, OpenIdStrings.RealmCausedRedirectUponDiscovery, yadisResult.RequestUri); + if (yadisResult.IsXrds) { + try { + return new XrdsDocument(yadisResult.ResponseText); + } catch (XmlException ex) { + throw ErrorUtilities.Wrap(ex, XrdsStrings.InvalidXRDSDocument); + } + } + } + + return null; + } + + /// <summary> + /// Calls <see cref="UriBuilder.ToString"/> if the argument is non-null. + /// Otherwise throws <see cref="ArgumentNullException"/>. + /// </summary> + /// <param name="realmUriBuilder">The realm URI builder.</param> + /// <returns>The result of UriBuilder.ToString()</returns> + /// <remarks> + /// This simple method is worthwhile because it checks for null + /// before dereferencing the UriBuilder. Since this is called from + /// within a constructor's base(...) call, this avoids a <see cref="NullReferenceException"/> + /// when we should be throwing an <see cref="ArgumentNullException"/>. + /// </remarks> + private static string SafeUriBuilderToString(UriBuilder realmUriBuilder) { + Requires.NotNull(realmUriBuilder, "realmUriBuilder"); + + // Note: we MUST use ToString. Uri property throws if wildcard is present. + // Note that Uri.ToString() should generally be avoided, but UriBuilder.ToString() + // is safe: http://blog.nerdbank.net/2008/04/uriabsoluteuri-and-uritostring-are-not.html + return realmUriBuilder.ToString(); + } + +#if CONTRACTS_FULL + /// <summary> + /// Verifies conditions that should be true for any valid state of this object. + /// </summary> + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called by code contracts.")] + [ContractInvariantMethod] + private void ObjectInvariant() { + Contract.Invariant(this.uri != null); + Contract.Invariant(this.uri.AbsoluteUri != null); + } +#endif + } +} diff --git a/src/DotNetOpenAuth.OpenId/OpenId/RelyingParty/AuthenticationStatus.cs b/src/DotNetOpenAuth.OpenId/OpenId/RelyingParty/AuthenticationStatus.cs new file mode 100644 index 0000000..89eca78 --- /dev/null +++ b/src/DotNetOpenAuth.OpenId/OpenId/RelyingParty/AuthenticationStatus.cs @@ -0,0 +1,43 @@ +//----------------------------------------------------------------------- +// <copyright file="AuthenticationStatus.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.RelyingParty { + /// <summary> + /// An enumeration of the possible results of an authentication attempt. + /// </summary> + public enum AuthenticationStatus { + /// <summary> + /// The authentication was canceled by the user agent while at the provider. + /// </summary> + Canceled, + + /// <summary> + /// The authentication failed because an error was detected in the OpenId communication. + /// </summary> + Failed, + + /// <summary> + /// <para>The Provider responded to a request for immediate authentication approval + /// with a message stating that additional user agent interaction is required + /// before authentication can be completed.</para> + /// <para>Casting the <see cref="IAuthenticationResponse"/> to a + /// ISetupRequiredAuthenticationResponse in this case can help + /// you retry the authentication using setup (non-immediate) mode.</para> + /// </summary> + SetupRequired, + + /// <summary> + /// Authentication is completed successfully. + /// </summary> + Authenticated, + + /// <summary> + /// The Provider sent a message that did not contain an identity assertion, + /// but may carry OpenID extensions. + /// </summary> + ExtensionsOnly, + } +} diff --git a/src/DotNetOpenAuth.OpenId/OpenId/RelyingParty/IAuthenticationRequest.cs b/src/DotNetOpenAuth.OpenId/OpenId/RelyingParty/IAuthenticationRequest.cs new file mode 100644 index 0000000..fa58b7d --- /dev/null +++ b/src/DotNetOpenAuth.OpenId/OpenId/RelyingParty/IAuthenticationRequest.cs @@ -0,0 +1,186 @@ +//----------------------------------------------------------------------- +// <copyright file="IAuthenticationRequest.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.RelyingParty { + using System; + using System.Collections.Generic; + using System.Diagnostics.Contracts; + using System.Linq; + using System.Text; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OpenId.Messages; + + /// <summary> + /// Instances of this interface represent relying party authentication + /// requests that may be queried/modified in specific ways before being + /// routed to the OpenID Provider. + /// </summary> + [ContractClass(typeof(IAuthenticationRequestContract))] + public interface IAuthenticationRequest { + /// <summary> + /// Gets or sets the mode the Provider should use during authentication. + /// </summary> + AuthenticationRequestMode Mode { get; set; } + + /// <summary> + /// Gets the HTTP response the relying party should send to the user agent + /// to redirect it to the OpenID Provider to start the OpenID authentication process. + /// </summary> + OutgoingWebResponse RedirectingResponse { get; } + + /// <summary> + /// Gets the URL that the user agent will return to after authentication + /// completes or fails at the Provider. + /// </summary> + Uri ReturnToUrl { get; } + + /// <summary> + /// Gets the URL that identifies this consumer web application that + /// the Provider will display to the end user. + /// </summary> + Realm Realm { get; } + + /// <summary> + /// Gets the Claimed Identifier that the User Supplied Identifier + /// resolved to. Null if the user provided an OP Identifier + /// (directed identity). + /// </summary> + /// <remarks> + /// Null is returned if the user is using the directed identity feature + /// of OpenID 2.0 to make it nearly impossible for a relying party site + /// to improperly store the reserved OpenID URL used for directed identity + /// as a user's own Identifier. + /// However, to test for the Directed Identity feature, please test the + /// <see cref="IsDirectedIdentity"/> property rather than testing this + /// property for a null value. + /// </remarks> + Identifier ClaimedIdentifier { get; } + + /// <summary> + /// Gets a value indicating whether the authenticating user has chosen to let the Provider + /// determine and send the ClaimedIdentifier after authentication. + /// </summary> + bool IsDirectedIdentity { get; } + + /// <summary> + /// Gets or sets a value indicating whether this request only carries extensions + /// and is not a request to verify that the user controls some identifier. + /// </summary> + /// <value> + /// <c>true</c> if this request is merely a carrier of extensions and is not + /// about an OpenID identifier; otherwise, <c>false</c>. + /// </value> + /// <remarks> + /// <para>Although OpenID is first and primarily an authentication protocol, its extensions + /// can be interesting all by themselves. For instance, a relying party might want + /// to know that its user is over 21 years old, or perhaps a member of some organization. + /// OpenID extensions can provide this, without any need for asserting the identity of the user.</para> + /// <para>Constructing an OpenID request for only extensions can be done by calling + /// OpenIdRelyingParty.CreateRequest with any valid OpenID identifier + /// (claimed identifier or OP identifier). But once this property is set to <c>true</c>, + /// the claimed identifier value in the request is not included in the transmitted message.</para> + /// <para>It is anticipated that an RP would only issue these types of requests to OPs that + /// trusts to make assertions regarding the individual holding an account at that OP, so it + /// is not likely that the RP would allow the user to type in an arbitrary claimed identifier + /// without checking that it resolved to an OP endpoint the RP has on a trust whitelist.</para> + /// </remarks> + bool IsExtensionOnly { get; set; } + + /// <summary> + /// Gets information about the OpenId Provider, as advertised by the + /// OpenID discovery documents found at the <see cref="ClaimedIdentifier"/> + /// location. + /// </summary> + IProviderEndpoint Provider { get; } + + /// <summary> + /// Gets the discovery result leading to the formulation of this request. + /// </summary> + /// <value>The discovery result.</value> + IdentifierDiscoveryResult DiscoveryResult { get; } + + /// <summary> + /// Makes a dictionary of key/value pairs available when the authentication is completed. + /// </summary> + /// <param name="arguments">The arguments to add to the request's return_to URI. Values must not be null.</param> + /// <remarks> + /// <para>Note that these values are NOT protected against eavesdropping in transit. No + /// privacy-sensitive data should be stored using this method.</para> + /// <para>The values stored here can be retrieved using + /// <see cref="IAuthenticationResponse.GetCallbackArgument"/>, which will only return the value + /// if it can be verified as untampered with in transit.</para> + /// <para>Since the data set here is sent in the querystring of the request and some + /// servers place limits on the size of a request URL, this data should be kept relatively + /// small to ensure successful authentication. About 1.5KB is about all that should be stored.</para> + /// </remarks> + void AddCallbackArguments(IDictionary<string, string> arguments); + + /// <summary> + /// Makes a key/value pair available when the authentication is completed. + /// </summary> + /// <param name="key">The parameter name.</param> + /// <param name="value">The value of the argument. Must not be null.</param> + /// <remarks> + /// <para>Note that these values are NOT protected against eavesdropping in transit. No + /// privacy-sensitive data should be stored using this method.</para> + /// <para>The value stored here can be retrieved using + /// <see cref="IAuthenticationResponse.GetCallbackArgument"/>, which will only return the value + /// if it can be verified as untampered with in transit.</para> + /// <para>Since the data set here is sent in the querystring of the request and some + /// servers place limits on the size of a request URL, this data should be kept relatively + /// small to ensure successful authentication. About 1.5KB is about all that should be stored.</para> + /// </remarks> + void AddCallbackArguments(string key, string value); + + /// <summary> + /// Makes a key/value pair available when the authentication is completed. + /// </summary> + /// <param name="key">The parameter name.</param> + /// <param name="value">The value of the argument. Must not be null.</param> + /// <remarks> + /// <para>Note that these values are NOT protected against eavesdropping in transit. No + /// security-sensitive data should be stored using this method.</para> + /// <para>The value stored here can be retrieved using + /// <see cref="IAuthenticationResponse.GetCallbackArgument"/>.</para> + /// <para>Since the data set here is sent in the querystring of the request and some + /// servers place limits on the size of a request URL, this data should be kept relatively + /// small to ensure successful authentication. About 1.5KB is about all that should be stored.</para> + /// </remarks> + void SetCallbackArgument(string key, string value); + + /// <summary> + /// Makes a key/value pair available when the authentication is completed without + /// requiring a return_to signature to protect against tampering of the callback argument. + /// </summary> + /// <param name="key">The parameter name.</param> + /// <param name="value">The value of the argument. Must not be null.</param> + /// <remarks> + /// <para>Note that these values are NOT protected against eavesdropping or tampering in transit. No + /// security-sensitive data should be stored using this method. </para> + /// <para>The value stored here can be retrieved using + /// <see cref="IAuthenticationResponse.GetCallbackArgument"/>.</para> + /// <para>Since the data set here is sent in the querystring of the request and some + /// servers place limits on the size of a request URL, this data should be kept relatively + /// small to ensure successful authentication. About 1.5KB is about all that should be stored.</para> + /// </remarks> + void SetUntrustedCallbackArgument(string key, string value); + + /// <summary> + /// Adds an OpenID extension to the request directed at the OpenID provider. + /// </summary> + /// <param name="extension">The initialized extension to add to the request.</param> + void AddExtension(IOpenIdMessageExtension extension); + + /// <summary> + /// Redirects the user agent to the provider for authentication. + /// Execution of the current page terminates after this call. + /// </summary> + /// <remarks> + /// This method requires an ASP.NET HttpContext. + /// </remarks> + void RedirectToProvider(); + } +} diff --git a/src/DotNetOpenAuth.OpenId/OpenId/RelyingParty/IAuthenticationRequestContract.cs b/src/DotNetOpenAuth.OpenId/OpenId/RelyingParty/IAuthenticationRequestContract.cs new file mode 100644 index 0000000..fa16c41 --- /dev/null +++ b/src/DotNetOpenAuth.OpenId/OpenId/RelyingParty/IAuthenticationRequestContract.cs @@ -0,0 +1,111 @@ +// <auto-generated /> + +namespace DotNetOpenAuth.OpenId.RelyingParty { + using System; + using System.Collections.Generic; + using System.Diagnostics.Contracts; + using System.Linq; + using System.Text; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OpenId.Messages; + + [ContractClassFor(typeof(IAuthenticationRequest))] + internal abstract class IAuthenticationRequestContract : IAuthenticationRequest { + #region IAuthenticationRequest Members + + AuthenticationRequestMode IAuthenticationRequest.Mode { + get { + throw new NotImplementedException(); + } + + set { + throw new NotImplementedException(); + } + } + + OutgoingWebResponse IAuthenticationRequest.RedirectingResponse { + get { throw new NotImplementedException(); } + } + + Uri IAuthenticationRequest.ReturnToUrl { + get { throw new NotImplementedException(); } + } + + Realm IAuthenticationRequest.Realm { + get { + Contract.Ensures(Contract.Result<Realm>() != null); + throw new NotImplementedException(); + } + } + + Identifier IAuthenticationRequest.ClaimedIdentifier { + get { + throw new NotImplementedException(); + } + } + + bool IAuthenticationRequest.IsDirectedIdentity { + get { throw new NotImplementedException(); } + } + + bool IAuthenticationRequest.IsExtensionOnly { + get { + throw new NotImplementedException(); + } + + set { + throw new NotImplementedException(); + } + } + + IProviderEndpoint IAuthenticationRequest.Provider { + get { + Contract.Ensures(Contract.Result<IProviderEndpoint>() != null); + throw new NotImplementedException(); + } + } + + IdentifierDiscoveryResult IAuthenticationRequest.DiscoveryResult { + get { + Contract.Ensures(Contract.Result<IdentifierDiscoveryResult>() != null); + throw new NotImplementedException(); + } + } + + void IAuthenticationRequest.AddCallbackArguments(IDictionary<string, string> arguments) { + Requires.NotNull(arguments, "arguments"); + Requires.True(arguments.Keys.All(k => !String.IsNullOrEmpty(k)), "arguments"); + Requires.True(arguments.Values.All(v => v != null), "arguments"); + throw new NotImplementedException(); + } + + void IAuthenticationRequest.AddCallbackArguments(string key, string value) { + Requires.NotNullOrEmpty(key, "key"); + Requires.NotNull(value, "value"); + throw new NotImplementedException(); + } + + void IAuthenticationRequest.SetCallbackArgument(string key, string value) { + Requires.NotNullOrEmpty(key, "key"); + Requires.NotNull(value, "value"); + throw new NotImplementedException(); + } + + void IAuthenticationRequest.AddExtension(IOpenIdMessageExtension extension) { + Requires.NotNull(extension, "extension"); + throw new NotImplementedException(); + } + + void IAuthenticationRequest.RedirectToProvider() { + throw new NotImplementedException(); + } + + void IAuthenticationRequest.SetUntrustedCallbackArgument(string key, string value) { + Requires.NotNullOrEmpty(key, "key"); + Requires.NotNull(value, "value"); + throw new NotImplementedException(); + } + + #endregion + } +} diff --git a/src/DotNetOpenAuth.OpenId/OpenId/RelyingParty/IAuthenticationResponse.cs b/src/DotNetOpenAuth.OpenId/OpenId/RelyingParty/IAuthenticationResponse.cs new file mode 100644 index 0000000..7b55f65 --- /dev/null +++ b/src/DotNetOpenAuth.OpenId/OpenId/RelyingParty/IAuthenticationResponse.cs @@ -0,0 +1,530 @@ +//----------------------------------------------------------------------- +// <copyright file="IAuthenticationResponse.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.RelyingParty { + using System; + using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; + using System.Diagnostics.Contracts; + using System.Globalization; + using System.Text; + using System.Web; + using DotNetOpenAuth.OpenId.Extensions; + using DotNetOpenAuth.OpenId.Messages; + + /// <summary> + /// An instance of this interface represents an identity assertion + /// from an OpenID Provider. It may be in response to an authentication + /// request previously put to it by a Relying Party site or it may be an + /// unsolicited assertion. + /// </summary> + /// <remarks> + /// Relying party web sites should handle both solicited and unsolicited + /// assertions. This interface does not offer a way to discern between + /// solicited and unsolicited assertions as they should be treated equally. + /// </remarks> + [ContractClass(typeof(IAuthenticationResponseContract))] + public interface IAuthenticationResponse { + /// <summary> + /// Gets the Identifier that the end user claims to own. For use with user database storage and lookup. + /// May be null for some failed authentications (i.e. failed directed identity authentications). + /// </summary> + /// <remarks> + /// <para> + /// This is the secure identifier that should be used for database storage and lookup. + /// It is not always friendly (i.e. =Arnott becomes =!9B72.7DD1.50A9.5CCD), but it protects + /// user identities against spoofing and other attacks. + /// </para> + /// <para> + /// For user-friendly identifiers to display, use the + /// <see cref="FriendlyIdentifierForDisplay"/> property. + /// </para> + /// </remarks> + Identifier ClaimedIdentifier { get; } + + /// <summary> + /// Gets a user-friendly OpenID Identifier for display purposes ONLY. + /// </summary> + /// <remarks> + /// <para> + /// This <i>should</i> be put through <see cref="HttpUtility.HtmlEncode(string)"/> before + /// sending to a browser to secure against javascript injection attacks. + /// </para> + /// <para> + /// This property retains some aspects of the user-supplied identifier that get lost + /// in the <see cref="ClaimedIdentifier"/>. For example, XRIs used as user-supplied + /// identifiers (i.e. =Arnott) become unfriendly unique strings (i.e. =!9B72.7DD1.50A9.5CCD). + /// For display purposes, such as text on a web page that says "You're logged in as ...", + /// this property serves to provide the =Arnott string, or whatever else is the most friendly + /// string close to what the user originally typed in. + /// </para> + /// <para> + /// If the user-supplied identifier is a URI, this property will be the URI after all + /// redirects, and with the protocol and fragment trimmed off. + /// If the user-supplied identifier is an XRI, this property will be the original XRI. + /// If the user-supplied identifier is an OpenID Provider identifier (i.e. yahoo.com), + /// this property will be the Claimed Identifier, with the protocol stripped if it is a URI. + /// </para> + /// <para> + /// It is <b>very</b> important that this property <i>never</i> be used for database storage + /// or lookup to avoid identity spoofing and other security risks. For database storage + /// and lookup please use the <see cref="ClaimedIdentifier"/> property. + /// </para> + /// </remarks> + string FriendlyIdentifierForDisplay { get; } + + /// <summary> + /// Gets the detailed success or failure status of the authentication attempt. + /// </summary> + AuthenticationStatus Status { get; } + + /// <summary> + /// Gets information about the OpenId Provider, as advertised by the + /// OpenID discovery documents found at the <see cref="ClaimedIdentifier"/> + /// location, if available. + /// </summary> + /// <value> + /// The Provider endpoint that issued the positive assertion; + /// or <c>null</c> if information about the Provider is unavailable. + /// </value> + IProviderEndpoint Provider { get; } + + /// <summary> + /// Gets the details regarding a failed authentication attempt, if available. + /// This will be set if and only if <see cref="Status"/> is <see cref="AuthenticationStatus.Failed"/>. + /// </summary> + Exception Exception { get; } + + /// <summary> + /// Gets a callback argument's value that was previously added using + /// <see cref="IAuthenticationRequest.AddCallbackArguments(string, string)"/>. + /// </summary> + /// <param name="key">The name of the parameter whose value is sought.</param> + /// <returns> + /// The value of the argument, or null if the named parameter could not be found. + /// </returns> + /// <remarks> + /// Callback parameters are only available if they are complete and untampered with + /// since the original request message (as proven by a signature). + /// If the relying party is operating in stateless mode <c>null</c> is always + /// returned since the callback arguments could not be signed to protect against + /// tampering. + /// </remarks> + string GetCallbackArgument(string key); + + /// <summary> + /// Gets a callback argument's value that was previously added using + /// <see cref="IAuthenticationRequest.AddCallbackArguments(string, string)"/>. + /// </summary> + /// <param name="key">The name of the parameter whose value is sought.</param> + /// <returns> + /// The value of the argument, or null if the named parameter could not be found. + /// </returns> + /// <remarks> + /// Callback parameters are only available even if the RP is in stateless mode, + /// or the callback parameters are otherwise unverifiable as untampered with. + /// Therefore, use this method only when the callback argument is not to be + /// used to make a security-sensitive decision. + /// </remarks> + string GetUntrustedCallbackArgument(string key); + + /// <summary> + /// Gets all the callback arguments that were previously added using + /// <see cref="IAuthenticationRequest.AddCallbackArguments(string, string)"/> or as a natural part + /// of the return_to URL. + /// </summary> + /// <returns>A name-value dictionary. Never null.</returns> + /// <remarks> + /// Callback parameters are only available if they are complete and untampered with + /// since the original request message (as proven by a signature). + /// If the relying party is operating in stateless mode an empty dictionary is always + /// returned since the callback arguments could not be signed to protect against + /// tampering. + /// </remarks> + [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "Historically an expensive operation.")] + IDictionary<string, string> GetCallbackArguments(); + + /// <summary> + /// Gets all the callback arguments that were previously added using + /// <see cref="IAuthenticationRequest.AddCallbackArguments(string, string)"/> or as a natural part + /// of the return_to URL. + /// </summary> + /// <returns>A name-value dictionary. Never null.</returns> + /// <remarks> + /// Callback parameters are only available even if the RP is in stateless mode, + /// or the callback parameters are otherwise unverifiable as untampered with. + /// Therefore, use this method only when the callback argument is not to be + /// used to make a security-sensitive decision. + /// </remarks> + [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "Historically an expensive operation.")] + IDictionary<string, string> GetUntrustedCallbackArguments(); + + /// <summary> + /// Tries to get an OpenID extension that may be present in the response. + /// </summary> + /// <typeparam name="T">The type of extension to look for in the response message.</typeparam> + /// <returns> + /// The extension, if it is found. Null otherwise. + /// </returns> + /// <remarks> + /// <para>Extensions are returned only if the Provider signed them. + /// Relying parties that do not care if the values were modified in + /// transit should use the <see cref="GetUntrustedExtension<T>"/> method + /// in order to allow the Provider to not sign the extension. </para> + /// <para>Unsigned extensions are completely unreliable and should be + /// used only to prefill user forms since the user or any other third + /// party may have tampered with the data carried by the extension.</para> + /// <para>Signed extensions are only reliable if the relying party + /// trusts the OpenID Provider that signed them. Signing does not mean + /// the relying party can trust the values -- it only means that the values + /// have not been tampered with since the Provider sent the message.</para> + /// </remarks> + [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "No parameter at all is required. T is used for return type.")] + T GetExtension<T>() where T : IOpenIdMessageExtension; + + /// <summary> + /// Tries to get an OpenID extension that may be present in the response. + /// </summary> + /// <param name="extensionType">Type of the extension to look for in the response.</param> + /// <returns> + /// The extension, if it is found. Null otherwise. + /// </returns> + /// <remarks> + /// <para>Extensions are returned only if the Provider signed them. + /// Relying parties that do not care if the values were modified in + /// transit should use the <see cref="GetUntrustedExtension"/> method + /// in order to allow the Provider to not sign the extension. </para> + /// <para>Unsigned extensions are completely unreliable and should be + /// used only to prefill user forms since the user or any other third + /// party may have tampered with the data carried by the extension.</para> + /// <para>Signed extensions are only reliable if the relying party + /// trusts the OpenID Provider that signed them. Signing does not mean + /// the relying party can trust the values -- it only means that the values + /// have not been tampered with since the Provider sent the message.</para> + /// </remarks> + IOpenIdMessageExtension GetExtension(Type extensionType); + + /// <summary> + /// Tries to get an OpenID extension that may be present in the response, without + /// requiring it to be signed by the Provider. + /// </summary> + /// <typeparam name="T">The type of extension to look for in the response message.</typeparam> + /// <returns> + /// The extension, if it is found. Null otherwise. + /// </returns> + /// <remarks> + /// <para>Extensions are returned whether they are signed or not. + /// Use the <see cref="GetExtension<T>"/> method to retrieve + /// extension responses only if they are signed by the Provider to + /// protect against tampering. </para> + /// <para>Unsigned extensions are completely unreliable and should be + /// used only to prefill user forms since the user or any other third + /// party may have tampered with the data carried by the extension.</para> + /// <para>Signed extensions are only reliable if the relying party + /// trusts the OpenID Provider that signed them. Signing does not mean + /// the relying party can trust the values -- it only means that the values + /// have not been tampered with since the Provider sent the message.</para> + /// </remarks> + [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "No parameter at all is required. T is used for return type.")] + T GetUntrustedExtension<T>() where T : IOpenIdMessageExtension; + + /// <summary> + /// Tries to get an OpenID extension that may be present in the response, without + /// requiring it to be signed by the Provider. + /// </summary> + /// <param name="extensionType">Type of the extension to look for in the response.</param> + /// <returns> + /// The extension, if it is found. Null otherwise. + /// </returns> + /// <remarks> + /// <para>Extensions are returned whether they are signed or not. + /// Use the <see cref="GetExtension"/> method to retrieve + /// extension responses only if they are signed by the Provider to + /// protect against tampering. </para> + /// <para>Unsigned extensions are completely unreliable and should be + /// used only to prefill user forms since the user or any other third + /// party may have tampered with the data carried by the extension.</para> + /// <para>Signed extensions are only reliable if the relying party + /// trusts the OpenID Provider that signed them. Signing does not mean + /// the relying party can trust the values -- it only means that the values + /// have not been tampered with since the Provider sent the message.</para> + /// </remarks> + IOpenIdMessageExtension GetUntrustedExtension(Type extensionType); + } + + /// <summary> + /// Code contract for the <see cref="IAuthenticationResponse"/> type. + /// </summary> + [ContractClassFor(typeof(IAuthenticationResponse))] + internal abstract class IAuthenticationResponseContract : IAuthenticationResponse { + /// <summary> + /// Initializes a new instance of the <see cref="IAuthenticationResponseContract"/> class. + /// </summary> + protected IAuthenticationResponseContract() { + } + + #region IAuthenticationResponse Members + + /// <summary> + /// Gets the Identifier that the end user claims to own. For use with user database storage and lookup. + /// May be null for some failed authentications (i.e. failed directed identity authentications). + /// </summary> + /// <value></value> + /// <remarks> + /// <para> + /// This is the secure identifier that should be used for database storage and lookup. + /// It is not always friendly (i.e. =Arnott becomes =!9B72.7DD1.50A9.5CCD), but it protects + /// user identities against spoofing and other attacks. + /// </para> + /// <para> + /// For user-friendly identifiers to display, use the + /// <see cref="IAuthenticationResponse.FriendlyIdentifierForDisplay"/> property. + /// </para> + /// </remarks> + Identifier IAuthenticationResponse.ClaimedIdentifier { + get { throw new NotImplementedException(); } + } + + /// <summary> + /// Gets a user-friendly OpenID Identifier for display purposes ONLY. + /// </summary> + /// <value></value> + /// <remarks> + /// <para> + /// This <i>should</i> be put through <see cref="HttpUtility.HtmlEncode(string)"/> before + /// sending to a browser to secure against javascript injection attacks. + /// </para> + /// <para> + /// This property retains some aspects of the user-supplied identifier that get lost + /// in the <see cref="IAuthenticationResponse.ClaimedIdentifier"/>. For example, XRIs used as user-supplied + /// identifiers (i.e. =Arnott) become unfriendly unique strings (i.e. =!9B72.7DD1.50A9.5CCD). + /// For display purposes, such as text on a web page that says "You're logged in as ...", + /// this property serves to provide the =Arnott string, or whatever else is the most friendly + /// string close to what the user originally typed in. + /// </para> + /// <para> + /// If the user-supplied identifier is a URI, this property will be the URI after all + /// redirects, and with the protocol and fragment trimmed off. + /// If the user-supplied identifier is an XRI, this property will be the original XRI. + /// If the user-supplied identifier is an OpenID Provider identifier (i.e. yahoo.com), + /// this property will be the Claimed Identifier, with the protocol stripped if it is a URI. + /// </para> + /// <para> + /// It is <b>very</b> important that this property <i>never</i> be used for database storage + /// or lookup to avoid identity spoofing and other security risks. For database storage + /// and lookup please use the <see cref="IAuthenticationResponse.ClaimedIdentifier"/> property. + /// </para> + /// </remarks> + string IAuthenticationResponse.FriendlyIdentifierForDisplay { + get { throw new NotImplementedException(); } + } + + /// <summary> + /// Gets the detailed success or failure status of the authentication attempt. + /// </summary> + /// <value></value> + AuthenticationStatus IAuthenticationResponse.Status { + get { throw new NotImplementedException(); } + } + + /// <summary> + /// Gets information about the OpenId Provider, as advertised by the + /// OpenID discovery documents found at the <see cref="IAuthenticationResponse.ClaimedIdentifier"/> + /// location, if available. + /// </summary> + /// <value> + /// The Provider endpoint that issued the positive assertion; + /// or <c>null</c> if information about the Provider is unavailable. + /// </value> + IProviderEndpoint IAuthenticationResponse.Provider { + get { throw new NotImplementedException(); } + } + + /// <summary> + /// Gets the details regarding a failed authentication attempt, if available. + /// This will be set if and only if <see cref="IAuthenticationResponse.Status"/> is <see cref="AuthenticationStatus.Failed"/>. + /// </summary> + /// <value></value> + Exception IAuthenticationResponse.Exception { + get { throw new NotImplementedException(); } + } + + /// <summary> + /// Gets a callback argument's value that was previously added using + /// <see cref="IAuthenticationRequest.AddCallbackArguments(string, string)"/>. + /// </summary> + /// <param name="key">The name of the parameter whose value is sought.</param> + /// <returns> + /// The value of the argument, or null if the named parameter could not be found. + /// </returns> + /// <remarks> + /// <para>This may return any argument on the querystring that came with the authentication response, + /// which may include parameters not explicitly added using + /// <see cref="IAuthenticationRequest.AddCallbackArguments(string, string)"/>.</para> + /// <para>Note that these values are NOT protected against tampering in transit.</para> + /// </remarks> + string IAuthenticationResponse.GetCallbackArgument(string key) { + Requires.NotNullOrEmpty(key, "key"); + throw new NotImplementedException(); + } + + /// <summary> + /// Gets all the callback arguments that were previously added using + /// <see cref="IAuthenticationRequest.AddCallbackArguments(string, string)"/> or as a natural part + /// of the return_to URL. + /// </summary> + /// <returns>A name-value dictionary. Never null.</returns> + /// <remarks> + /// <para>This MAY return any argument on the querystring that came with the authentication response, + /// which may include parameters not explicitly added using + /// <see cref="IAuthenticationRequest.AddCallbackArguments(string, string)"/>.</para> + /// <para>Note that these values are NOT protected against tampering in transit.</para> + /// </remarks> + IDictionary<string, string> IAuthenticationResponse.GetCallbackArguments() { + throw new NotImplementedException(); + } + + /// <summary> + /// Tries to get an OpenID extension that may be present in the response. + /// </summary> + /// <typeparam name="T">The type of extension to look for in the response message.</typeparam> + /// <returns> + /// The extension, if it is found. Null otherwise. + /// </returns> + /// <remarks> + /// <para>Extensions are returned only if the Provider signed them. + /// Relying parties that do not care if the values were modified in + /// transit should use the <see cref="IAuthenticationResponse.GetUntrustedExtension<T>"/> method + /// in order to allow the Provider to not sign the extension. </para> + /// <para>Unsigned extensions are completely unreliable and should be + /// used only to prefill user forms since the user or any other third + /// party may have tampered with the data carried by the extension.</para> + /// <para>Signed extensions are only reliable if the relying party + /// trusts the OpenID Provider that signed them. Signing does not mean + /// the relying party can trust the values -- it only means that the values + /// have not been tampered with since the Provider sent the message.</para> + /// </remarks> + T IAuthenticationResponse.GetExtension<T>() { + throw new NotImplementedException(); + } + + /// <summary> + /// Tries to get an OpenID extension that may be present in the response. + /// </summary> + /// <param name="extensionType">Type of the extension to look for in the response.</param> + /// <returns> + /// The extension, if it is found. Null otherwise. + /// </returns> + /// <remarks> + /// <para>Extensions are returned only if the Provider signed them. + /// Relying parties that do not care if the values were modified in + /// transit should use the <see cref="IAuthenticationResponse.GetUntrustedExtension"/> method + /// in order to allow the Provider to not sign the extension. </para> + /// <para>Unsigned extensions are completely unreliable and should be + /// used only to prefill user forms since the user or any other third + /// party may have tampered with the data carried by the extension.</para> + /// <para>Signed extensions are only reliable if the relying party + /// trusts the OpenID Provider that signed them. Signing does not mean + /// the relying party can trust the values -- it only means that the values + /// have not been tampered with since the Provider sent the message.</para> + /// </remarks> + IOpenIdMessageExtension IAuthenticationResponse.GetExtension(Type extensionType) { + Requires.NotNullSubtype<IOpenIdMessageExtension>(extensionType, "extensionType"); + ////ErrorUtilities.VerifyArgument(typeof(IOpenIdMessageExtension).IsAssignableFrom(extensionType), string.Format(CultureInfo.CurrentCulture, OpenIdStrings.TypeMustImplementX, typeof(IOpenIdMessageExtension).FullName)); + throw new NotImplementedException(); + } + + /// <summary> + /// Tries to get an OpenID extension that may be present in the response, without + /// requiring it to be signed by the Provider. + /// </summary> + /// <typeparam name="T">The type of extension to look for in the response message.</typeparam> + /// <returns> + /// The extension, if it is found. Null otherwise. + /// </returns> + /// <remarks> + /// <para>Extensions are returned whether they are signed or not. + /// Use the <see cref="IAuthenticationResponse.GetExtension<T>"/> method to retrieve + /// extension responses only if they are signed by the Provider to + /// protect against tampering. </para> + /// <para>Unsigned extensions are completely unreliable and should be + /// used only to prefill user forms since the user or any other third + /// party may have tampered with the data carried by the extension.</para> + /// <para>Signed extensions are only reliable if the relying party + /// trusts the OpenID Provider that signed them. Signing does not mean + /// the relying party can trust the values -- it only means that the values + /// have not been tampered with since the Provider sent the message.</para> + /// </remarks> + T IAuthenticationResponse.GetUntrustedExtension<T>() { + throw new NotImplementedException(); + } + + /// <summary> + /// Tries to get an OpenID extension that may be present in the response, without + /// requiring it to be signed by the Provider. + /// </summary> + /// <param name="extensionType">Type of the extension to look for in the response.</param> + /// <returns> + /// The extension, if it is found. Null otherwise. + /// </returns> + /// <remarks> + /// <para>Extensions are returned whether they are signed or not. + /// Use the <see cref="IAuthenticationResponse.GetExtension"/> method to retrieve + /// extension responses only if they are signed by the Provider to + /// protect against tampering. </para> + /// <para>Unsigned extensions are completely unreliable and should be + /// used only to prefill user forms since the user or any other third + /// party may have tampered with the data carried by the extension.</para> + /// <para>Signed extensions are only reliable if the relying party + /// trusts the OpenID Provider that signed them. Signing does not mean + /// the relying party can trust the values -- it only means that the values + /// have not been tampered with since the Provider sent the message.</para> + /// </remarks> + IOpenIdMessageExtension IAuthenticationResponse.GetUntrustedExtension(Type extensionType) { + Requires.NotNullSubtype<IOpenIdMessageExtension>(extensionType, "extensionType"); + ////ErrorUtilities.VerifyArgument(typeof(IOpenIdMessageExtension).IsAssignableFrom(extensionType), string.Format(CultureInfo.CurrentCulture, OpenIdStrings.TypeMustImplementX, typeof(IOpenIdMessageExtension).FullName)); + throw new NotImplementedException(); + } + + /// <summary> + /// Gets a callback argument's value that was previously added using + /// <see cref="IAuthenticationRequest.AddCallbackArguments(string, string)"/>. + /// </summary> + /// <param name="key">The name of the parameter whose value is sought.</param> + /// <returns> + /// The value of the argument, or null if the named parameter could not be found. + /// </returns> + /// <remarks> + /// Callback parameters are only available even if the RP is in stateless mode, + /// or the callback parameters are otherwise unverifiable as untampered with. + /// Therefore, use this method only when the callback argument is not to be + /// used to make a security-sensitive decision. + /// </remarks> + string IAuthenticationResponse.GetUntrustedCallbackArgument(string key) { + Requires.NotNullOrEmpty(key, "key"); + throw new NotImplementedException(); + } + + /// <summary> + /// Gets all the callback arguments that were previously added using + /// <see cref="IAuthenticationRequest.AddCallbackArguments(string, string)"/> or as a natural part + /// of the return_to URL. + /// </summary> + /// <returns>A name-value dictionary. Never null.</returns> + /// <remarks> + /// Callback parameters are only available even if the RP is in stateless mode, + /// or the callback parameters are otherwise unverifiable as untampered with. + /// Therefore, use this method only when the callback argument is not to be + /// used to make a security-sensitive decision. + /// </remarks> + IDictionary<string, string> IAuthenticationResponse.GetUntrustedCallbackArguments() { + Contract.Ensures(Contract.Result<IDictionary<string, string>>() != null); + throw new NotImplementedException(); + } + + #endregion + } +} diff --git a/src/DotNetOpenAuth.OpenId/OpenId/RelyingParty/IRelyingPartyBehavior.cs b/src/DotNetOpenAuth.OpenId/OpenId/RelyingParty/IRelyingPartyBehavior.cs new file mode 100644 index 0000000..28cad6d --- /dev/null +++ b/src/DotNetOpenAuth.OpenId/OpenId/RelyingParty/IRelyingPartyBehavior.cs @@ -0,0 +1,92 @@ +//----------------------------------------------------------------------- +// <copyright file="IRelyingPartyBehavior.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.RelyingParty { + using System; + using System.Diagnostics.Contracts; + + /// <summary> + /// Applies a custom security policy to certain OpenID security settings and behaviors. + /// </summary> + [ContractClass(typeof(IRelyingPartyBehaviorContract))] + public interface IRelyingPartyBehavior { + /// <summary> + /// Applies a well known set of security requirements to a default set of security settings. + /// </summary> + /// <param name="securitySettings">The security settings to enhance with the requirements of this profile.</param> + /// <remarks> + /// Care should be taken to never decrease security when applying a profile. + /// Profiles should only enhance security requirements to avoid being + /// incompatible with each other. + /// </remarks> + void ApplySecuritySettings(RelyingPartySecuritySettings securitySettings); + + /// <summary> + /// Called when an authentication request is about to be sent. + /// </summary> + /// <param name="request">The request.</param> + /// <remarks> + /// Implementations should be prepared to be called multiple times on the same outgoing message + /// without malfunctioning. + /// </remarks> + void OnOutgoingAuthenticationRequest(IAuthenticationRequest request); + + /// <summary> + /// Called when an incoming positive assertion is received. + /// </summary> + /// <param name="assertion">The positive assertion.</param> + void OnIncomingPositiveAssertion(IAuthenticationResponse assertion); + } + + /// <summary> + /// Contract class for the <see cref="IRelyingPartyBehavior"/> interface. + /// </summary> + [ContractClassFor(typeof(IRelyingPartyBehavior))] + internal abstract class IRelyingPartyBehaviorContract : IRelyingPartyBehavior { + /// <summary> + /// Prevents a default instance of the <see cref="IRelyingPartyBehaviorContract"/> class from being created. + /// </summary> + private IRelyingPartyBehaviorContract() { + } + + #region IRelyingPartyBehavior Members + + /// <summary> + /// Applies a well known set of security requirements to a default set of security settings. + /// </summary> + /// <param name="securitySettings">The security settings to enhance with the requirements of this profile.</param> + /// <remarks> + /// Care should be taken to never decrease security when applying a profile. + /// Profiles should only enhance security requirements to avoid being + /// incompatible with each other. + /// </remarks> + void IRelyingPartyBehavior.ApplySecuritySettings(RelyingPartySecuritySettings securitySettings) { + Requires.NotNull(securitySettings, "securitySettings"); + } + + /// <summary> + /// Called when an authentication request is about to be sent. + /// </summary> + /// <param name="request">The request.</param> + /// <remarks> + /// Implementations should be prepared to be called multiple times on the same outgoing message + /// without malfunctioning. + /// </remarks> + void IRelyingPartyBehavior.OnOutgoingAuthenticationRequest(IAuthenticationRequest request) { + Requires.NotNull(request, "request"); + } + + /// <summary> + /// Called when an incoming positive assertion is received. + /// </summary> + /// <param name="assertion">The positive assertion.</param> + void IRelyingPartyBehavior.OnIncomingPositiveAssertion(IAuthenticationResponse assertion) { + Requires.NotNull(assertion, "assertion"); + } + + #endregion + } +} diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/RelyingPartySecuritySettings.cs b/src/DotNetOpenAuth.OpenId/OpenId/RelyingParty/RelyingPartySecuritySettings.cs index fc6d4c7..fc6d4c7 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/RelyingPartySecuritySettings.cs +++ b/src/DotNetOpenAuth.OpenId/OpenId/RelyingParty/RelyingPartySecuritySettings.cs diff --git a/src/DotNetOpenAuth.OpenId/OpenId/RelyingPartyDescription.cs b/src/DotNetOpenAuth.OpenId/OpenId/RelyingPartyDescription.cs new file mode 100644 index 0000000..ab0fd13 --- /dev/null +++ b/src/DotNetOpenAuth.OpenId/OpenId/RelyingPartyDescription.cs @@ -0,0 +1,63 @@ +//----------------------------------------------------------------------- +// <copyright file="RelyingPartyDescription.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId { + using System; + using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; + using System.Diagnostics.Contracts; + using System.Linq; + using System.Text; + using DotNetOpenAuth.Messaging; + + /// <summary> + /// A description of some OpenID Relying Party endpoint. + /// </summary> + /// <remarks> + /// This is an immutable type. + /// </remarks> + internal class RelyingPartyEndpointDescription { + /// <summary> + /// Initializes a new instance of the <see cref="RelyingPartyEndpointDescription"/> class. + /// </summary> + /// <param name="returnTo">The return to.</param> + /// <param name="supportedServiceTypeUris"> + /// The Type URIs of supported services advertised on a relying party's XRDS document. + /// </param> + internal RelyingPartyEndpointDescription(Uri returnTo, string[] supportedServiceTypeUris) { + Requires.NotNull(returnTo, "returnTo"); + Requires.NotNull(supportedServiceTypeUris, "supportedServiceTypeUris"); + + this.ReturnToEndpoint = returnTo; + this.Protocol = GetProtocolFromServices(supportedServiceTypeUris); + } + + /// <summary> + /// Gets the URL to the login page on the discovered relying party web site. + /// </summary> + public Uri ReturnToEndpoint { get; private set; } + + /// <summary> + /// Gets the OpenId protocol that the discovered relying party supports. + /// </summary> + public Protocol Protocol { get; private set; } + + /// <summary> + /// Derives the highest OpenID protocol that this library and the OpenID Provider have + /// in common. + /// </summary> + /// <param name="supportedServiceTypeUris">The supported service type URIs.</param> + /// <returns>The best OpenID protocol version to use when communicating with this Provider.</returns> + [SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "OpenID", Justification = "Spelling correct")] + private static Protocol GetProtocolFromServices(string[] supportedServiceTypeUris) { + Protocol protocol = Protocol.FindBestVersion(p => p.RPReturnToTypeURI, supportedServiceTypeUris); + if (protocol == null) { + throw new InvalidOperationException("Unable to determine the version of OpenID the Relying Party supports."); + } + return protocol; + } + } +} diff --git a/src/DotNetOpenAuth.OpenId/OpenId/SecuritySettings.cs b/src/DotNetOpenAuth.OpenId/OpenId/SecuritySettings.cs new file mode 100644 index 0000000..a083cc6 --- /dev/null +++ b/src/DotNetOpenAuth.OpenId/OpenId/SecuritySettings.cs @@ -0,0 +1,95 @@ +//----------------------------------------------------------------------- +// <copyright file="SecuritySettings.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId { + using System; + using System.Collections.Generic; + using System.Collections.Specialized; + using System.Diagnostics.Contracts; + using DotNetOpenAuth.Messaging; + + /// <summary> + /// Security settings that may be applicable to both relying parties and providers. + /// </summary> + [Serializable] + public abstract class SecuritySettings { + /// <summary> + /// Gets the default minimum hash bit length. + /// </summary> + internal const int MinimumHashBitLengthDefault = 160; + + /// <summary> + /// Gets the maximum hash bit length default for relying parties. + /// </summary> + internal const int MaximumHashBitLengthRPDefault = 256; + + /// <summary> + /// Gets the maximum hash bit length default for providers. + /// </summary> + internal const int MaximumHashBitLengthOPDefault = 512; + + /// <summary> + /// Initializes a new instance of the <see cref="SecuritySettings"/> class. + /// </summary> + /// <param name="isProvider">A value indicating whether this class is being instantiated for a Provider.</param> + protected SecuritySettings(bool isProvider) { + this.MaximumHashBitLength = isProvider ? MaximumHashBitLengthOPDefault : MaximumHashBitLengthRPDefault; + this.MinimumHashBitLength = MinimumHashBitLengthDefault; + } + + /// <summary> + /// Gets or sets the minimum hash length (in bits) allowed to be used in an <see cref="Association"/> + /// with the remote party. The default is 160. + /// </summary> + /// <remarks> + /// SHA-1 (160 bits) has been broken. The minimum secure hash length is now 256 bits. + /// The default is still a 160 bit minimum to allow interop with common remote parties, + /// such as Yahoo! that only supports 160 bits. + /// For sites that require high security such as to store bank account information and + /// health records, 256 is the recommended value. + /// </remarks> + public int MinimumHashBitLength { get; set; } + + /// <summary> + /// Gets or sets the maximum hash length (in bits) allowed to be used in an <see cref="Association"/> + /// with the remote party. The default is 256 for relying parties and 512 for providers. + /// </summary> + /// <remarks> + /// The longer the bit length, the more secure the identities of your visitors are. + /// Setting a value higher than 256 on a relying party site may reduce performance + /// as many association requests will be denied, causing secondary requests or even + /// authentication failures. + /// Setting a value higher than 256 on a provider increases security where possible + /// without these side-effects. + /// </remarks> + public int MaximumHashBitLength { get; set; } + + /// <summary> + /// Determines whether a named association fits the security requirements. + /// </summary> + /// <param name="protocol">The protocol carrying the association.</param> + /// <param name="associationType">The value of the openid.assoc_type parameter.</param> + /// <returns> + /// <c>true</c> if the association is permitted given the security requirements; otherwise, <c>false</c>. + /// </returns> + internal bool IsAssociationInPermittedRange(Protocol protocol, string associationType) { + int lengthInBits = HmacShaAssociation.GetSecretLength(protocol, associationType) * 8; + return lengthInBits >= this.MinimumHashBitLength && lengthInBits <= this.MaximumHashBitLength; + } + + /// <summary> + /// Determines whether a given association fits the security requirements. + /// </summary> + /// <param name="association">The association to check.</param> + /// <returns> + /// <c>true</c> if the association is permitted given the security requirements; otherwise, <c>false</c>. + /// </returns> + internal bool IsAssociationInPermittedRange(Association association) { + Requires.NotNull(association, "association"); + return association.HashBitLength >= this.MinimumHashBitLength && association.HashBitLength <= this.MaximumHashBitLength; + } + } +} diff --git a/src/DotNetOpenAuth/OpenId/UriDiscoveryService.cs b/src/DotNetOpenAuth.OpenId/OpenId/UriDiscoveryService.cs index 7d17fd9..7d17fd9 100644 --- a/src/DotNetOpenAuth/OpenId/UriDiscoveryService.cs +++ b/src/DotNetOpenAuth.OpenId/OpenId/UriDiscoveryService.cs diff --git a/src/DotNetOpenAuth.OpenId/OpenId/UriIdentifier.cs b/src/DotNetOpenAuth.OpenId/OpenId/UriIdentifier.cs new file mode 100644 index 0000000..aa72869 --- /dev/null +++ b/src/DotNetOpenAuth.OpenId/OpenId/UriIdentifier.cs @@ -0,0 +1,726 @@ +//----------------------------------------------------------------------- +// <copyright file="UriIdentifier.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId { + using System; + using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; + using System.Diagnostics.Contracts; + using System.Linq; + using System.Reflection; + using System.Security; + using System.Text; + using System.Text.RegularExpressions; + using System.Web.UI.HtmlControls; + using System.Xml; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.Xrds; + using DotNetOpenAuth.Yadis; + + /// <summary> + /// A URI style of OpenID Identifier. + /// </summary> + [Serializable] + [Pure] + public sealed class UriIdentifier : Identifier { + /// <summary> + /// The allowed protocol schemes in a URI Identifier. + /// </summary> + private static readonly string[] allowedSchemes = { "http", "https" }; + + /// <summary> + /// The special scheme to use for HTTP URLs that should not have their paths compressed. + /// </summary> + private static NonPathCompressingUriParser roundTrippingHttpParser = new NonPathCompressingUriParser(Uri.UriSchemeHttp); + + /// <summary> + /// The special scheme to use for HTTPS URLs that should not have their paths compressed. + /// </summary> + private static NonPathCompressingUriParser roundTrippingHttpsParser = new NonPathCompressingUriParser(Uri.UriSchemeHttps); + + /// <summary> + /// The special scheme to use for HTTP URLs that should not have their paths compressed. + /// </summary> + private static NonPathCompressingUriParser publishableHttpParser = new NonPathCompressingUriParser(Uri.UriSchemeHttp); + + /// <summary> + /// The special scheme to use for HTTPS URLs that should not have their paths compressed. + /// </summary> + private static NonPathCompressingUriParser publishableHttpsParser = new NonPathCompressingUriParser(Uri.UriSchemeHttps); + + /// <summary> + /// A value indicating whether scheme substitution is being used to workaround + /// .NET path compression that invalidates some OpenIDs that have trailing periods + /// in one of their path segments. + /// </summary> + private static bool schemeSubstitution; + + /// <summary> + /// Initializes static members of the <see cref="UriIdentifier"/> class. + /// </summary> + /// <remarks> + /// This method attempts to workaround the .NET Uri class parsing bug described here: + /// https://connect.microsoft.com/VisualStudio/feedback/details/386695/system-uri-incorrectly-strips-trailing-dots?wa=wsignin1.0#tabs + /// since some identifiers (like some of the pseudonymous identifiers from Yahoo) include path segments + /// that end with periods, which the Uri class will typically trim off. + /// </remarks> + [SuppressMessage("Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline", Justification = "Some things just can't be done in a field initializer.")] + static UriIdentifier() { + // Our first attempt to handle trailing periods in path segments is to leverage + // full trust if it's available to rewrite the rules. + // In fact this is the ONLY way in .NET 3.5 (and arguably in .NET 4.0) to send + // outbound HTTP requests with trailing periods, so it's the only way to perform + // discovery on such an identifier. + try { + UriParser.Register(roundTrippingHttpParser, "dnoarthttp", 80); + UriParser.Register(roundTrippingHttpsParser, "dnoarthttps", 443); + UriParser.Register(publishableHttpParser, "dnoahttp", 80); + UriParser.Register(publishableHttpsParser, "dnoahttps", 443); + roundTrippingHttpParser.Initialize(false); + roundTrippingHttpsParser.Initialize(false); + publishableHttpParser.Initialize(true); + publishableHttpsParser.Initialize(true); + schemeSubstitution = true; + Logger.OpenId.Debug(".NET Uri class path compression overridden."); + Reporting.RecordFeatureUse("FullTrust"); + } catch (SecurityException) { + // We must be running in partial trust. Nothing more we can do. + Logger.OpenId.Warn("Unable to coerce .NET to stop compressing URI paths due to partial trust limitations. Some URL identifiers may be unable to complete login."); + Reporting.RecordFeatureUse("PartialTrust"); + } + } + + /// <summary> + /// Initializes a new instance of the <see cref="UriIdentifier"/> class. + /// </summary> + /// <param name="uri">The value this identifier will represent.</param> + internal UriIdentifier(string uri) + : this(uri, false) { + Requires.NotNullOrEmpty(uri, "uri"); + } + + /// <summary> + /// Initializes a new instance of the <see cref="UriIdentifier"/> class. + /// </summary> + /// <param name="uri">The value this identifier will represent.</param> + /// <param name="requireSslDiscovery">if set to <c>true</c> [require SSL discovery].</param> + internal UriIdentifier(string uri, bool requireSslDiscovery) + : base(uri, requireSslDiscovery) { + Requires.NotNullOrEmpty(uri, "uri"); + Uri canonicalUri; + bool schemePrepended; + if (!TryCanonicalize(uri, out canonicalUri, requireSslDiscovery, out schemePrepended)) { + throw new UriFormatException(); + } + if (requireSslDiscovery && canonicalUri.Scheme != Uri.UriSchemeHttps) { + throw new ArgumentException(OpenIdStrings.ExplicitHttpUriSuppliedWithSslRequirement); + } + this.Uri = canonicalUri; + this.SchemeImplicitlyPrepended = schemePrepended; + } + + /// <summary> + /// Initializes a new instance of the <see cref="UriIdentifier"/> class. + /// </summary> + /// <param name="uri">The value this identifier will represent.</param> + internal UriIdentifier(Uri uri) + : this(uri, false) { + } + + /// <summary> + /// Initializes a new instance of the <see cref="UriIdentifier"/> class. + /// </summary> + /// <param name="uri">The value this identifier will represent.</param> + /// <param name="requireSslDiscovery">if set to <c>true</c> [require SSL discovery].</param> + internal UriIdentifier(Uri uri, bool requireSslDiscovery) + : base(uri != null ? uri.OriginalString : null, requireSslDiscovery) { + Requires.NotNull(uri, "uri"); + + string uriAsString = uri.OriginalString; + if (schemeSubstitution) { + uriAsString = NormalSchemeToSpecialRoundTrippingScheme(uriAsString); + } + + if (!TryCanonicalize(uriAsString, out uri)) { + throw new UriFormatException(); + } + if (requireSslDiscovery && uri.Scheme != Uri.UriSchemeHttps) { + throw new ArgumentException(OpenIdStrings.ExplicitHttpUriSuppliedWithSslRequirement); + } + this.Uri = uri; + this.SchemeImplicitlyPrepended = false; + } + + /// <summary> + /// Gets or sets a value indicating whether scheme substitution is being used to workaround + /// .NET path compression that invalidates some OpenIDs that have trailing periods + /// in one of their path segments. + /// </summary> + internal static bool SchemeSubstitutionTestHook { + get { return schemeSubstitution; } + set { schemeSubstitution = value; } + } + + /// <summary> + /// Gets the URI this instance represents. + /// </summary> + internal Uri Uri { get; private set; } + + /// <summary> + /// Gets a value indicating whether the scheme was missing when this + /// Identifier was created and added automatically as part of the + /// normalization process. + /// </summary> + internal bool SchemeImplicitlyPrepended { get; private set; } + + /// <summary> + /// Gets a value indicating whether this Identifier has characters or patterns that + /// the <see cref="Uri"/> class normalizes away and invalidating the Identifier. + /// </summary> + internal bool ProblematicNormalization { + get { + if (schemeSubstitution) { + // With full trust, we have no problematic URIs + return false; + } + + var simpleUri = new SimpleUri(this.OriginalString); + if (simpleUri.Path.EndsWith(".", StringComparison.Ordinal) || simpleUri.Path.Contains("./")) { + return true; + } + + return false; + } + } + + /// <summary> + /// Converts a <see cref="UriIdentifier"/> instance to a <see cref="Uri"/> instance. + /// </summary> + /// <param name="identifier">The identifier to convert to an ordinary <see cref="Uri"/> instance.</param> + /// <returns>The result of the conversion.</returns> + public static implicit operator Uri(UriIdentifier identifier) { + if (identifier == null) { + return null; + } + return identifier.Uri; + } + + /// <summary> + /// Converts a <see cref="Uri"/> instance to a <see cref="UriIdentifier"/> instance. + /// </summary> + /// <param name="identifier">The <see cref="Uri"/> instance to turn into a <see cref="UriIdentifier"/>.</param> + /// <returns>The result of the conversion.</returns> + public static implicit operator UriIdentifier(Uri identifier) { + if (identifier == null) { + return null; + } + return new UriIdentifier(identifier); + } + + /// <summary> + /// Tests equality between this URI and another URI. + /// </summary> + /// <param name="obj">The <see cref="T:System.Object"/> to compare with the current <see cref="T:System.Object"/>.</param> + /// <returns> + /// true if the specified <see cref="T:System.Object"/> is equal to the current <see cref="T:System.Object"/>; otherwise, false. + /// </returns> + /// <exception cref="T:System.NullReferenceException"> + /// The <paramref name="obj"/> parameter is null. + /// </exception> + public override bool Equals(object obj) { + UriIdentifier other = obj as UriIdentifier; + if (obj != null && other == null && Identifier.EqualityOnStrings) { // test hook to enable MockIdentifier comparison + other = Identifier.Parse(obj.ToString()) as UriIdentifier; + } + if (other == null) { + return false; + } + + if (this.ProblematicNormalization || other.ProblematicNormalization) { + return new SimpleUri(this.OriginalString).Equals(new SimpleUri(other.OriginalString)); + } else { + return this.Uri == other.Uri; + } + } + + /// <summary> + /// Returns the hash code of this XRI. + /// </summary> + /// <returns> + /// A hash code for the current <see cref="T:System.Object"/>. + /// </returns> + public override int GetHashCode() { + return Uri.GetHashCode(); + } + + /// <summary> + /// Returns the string form of the URI. + /// </summary> + /// <returns> + /// A <see cref="T:System.String"/> that represents the current <see cref="T:System.Object"/>. + /// </returns> + public override string ToString() { + if (this.ProblematicNormalization) { + return new SimpleUri(this.OriginalString).ToString(); + } else { + return this.Uri.AbsoluteUri; + } + } + + /// <summary> + /// Determines whether a URI is a valid OpenID Identifier (of any kind). + /// </summary> + /// <param name="uri">The URI to test for OpenID validity.</param> + /// <returns> + /// <c>true</c> if the identifier is valid; otherwise, <c>false</c>. + /// </returns> + /// <remarks> + /// A valid URI is absolute (not relative) and uses an http(s) scheme. + /// </remarks> + internal static bool IsValidUri(string uri) { + Uri normalized; + bool schemePrepended; + return TryCanonicalize(uri, out normalized, false, out schemePrepended); + } + + /// <summary> + /// Determines whether a URI is a valid OpenID Identifier (of any kind). + /// </summary> + /// <param name="uri">The URI to test for OpenID validity.</param> + /// <returns> + /// <c>true</c> if the identifier is valid; otherwise, <c>false</c>. + /// </returns> + /// <remarks> + /// A valid URI is absolute (not relative) and uses an http(s) scheme. + /// </remarks> + internal static bool IsValidUri(Uri uri) { + if (uri == null) { + return false; + } + if (!uri.IsAbsoluteUri) { + return false; + } + if (!IsAllowedScheme(uri)) { + return false; + } + return true; + } + + /// <summary> + /// Returns an <see cref="Identifier"/> that has no URI fragment. + /// Quietly returns the original <see cref="Identifier"/> if it is not + /// a <see cref="UriIdentifier"/> or no fragment exists. + /// </summary> + /// <returns> + /// A new <see cref="Identifier"/> instance if there was a + /// fragment to remove, otherwise this same instance.. + /// </returns> + internal override Identifier TrimFragment() { + // If there is no fragment, we have no need to rebuild the Identifier. + if (Uri.Fragment == null || Uri.Fragment.Length == 0) { + return this; + } + + // Strip the fragment. + return new UriIdentifier(this.OriginalString.Substring(0, this.OriginalString.IndexOf('#'))); + } + + /// <summary> + /// Converts a given identifier to its secure equivalent. + /// UriIdentifiers originally created with an implied HTTP scheme change to HTTPS. + /// Discovery is made to require SSL for the entire resolution process. + /// </summary> + /// <param name="secureIdentifier">The newly created secure identifier. + /// If the conversion fails, <paramref name="secureIdentifier"/> retains + /// <i>this</i> identifiers identity, but will never discover any endpoints.</param> + /// <returns> + /// True if the secure conversion was successful. + /// False if the Identifier was originally created with an explicit HTTP scheme. + /// </returns> + internal override bool TryRequireSsl(out Identifier secureIdentifier) { + // If this Identifier is already secure, reuse it. + if (IsDiscoverySecureEndToEnd) { + secureIdentifier = this; + return true; + } + + // If this identifier already uses SSL for initial discovery, return one + // that guarantees it will be used throughout the discovery process. + if (String.Equals(Uri.Scheme, Uri.UriSchemeHttps, StringComparison.OrdinalIgnoreCase)) { + secureIdentifier = new UriIdentifier(this.Uri, true); + return true; + } + + // Otherwise, try to make this Identifier secure by normalizing to HTTPS instead of HTTP. + if (this.SchemeImplicitlyPrepended) { + UriBuilder newIdentifierUri = new UriBuilder(this.Uri); + newIdentifierUri.Scheme = Uri.UriSchemeHttps; + if (newIdentifierUri.Port == 80) { + newIdentifierUri.Port = 443; + } + secureIdentifier = new UriIdentifier(newIdentifierUri.Uri, true); + return true; + } + + // This identifier is explicitly NOT https, so we cannot change it. + secureIdentifier = new NoDiscoveryIdentifier(this, true); + return false; + } + + /// <summary> + /// Determines whether the given URI is using a scheme in the list of allowed schemes. + /// </summary> + /// <param name="uri">The URI whose scheme is to be checked.</param> + /// <returns> + /// <c>true</c> if the scheme is allowed; otherwise, <c>false</c>. + /// <c>false</c> is also returned if <paramref name="uri"/> is null. + /// </returns> + private static bool IsAllowedScheme(string uri) { + if (string.IsNullOrEmpty(uri)) { + return false; + } + return Array.FindIndex( + allowedSchemes, + s => uri.StartsWith(s + Uri.SchemeDelimiter, StringComparison.OrdinalIgnoreCase)) >= 0; + } + + /// <summary> + /// Determines whether the given URI is using a scheme in the list of allowed schemes. + /// </summary> + /// <param name="uri">The URI whose scheme is to be checked.</param> + /// <returns> + /// <c>true</c> if the scheme is allowed; otherwise, <c>false</c>. + /// <c>false</c> is also returned if <paramref name="uri"/> is null. + /// </returns> + private static bool IsAllowedScheme(Uri uri) { + if (uri == null) { + return false; + } + return Array.FindIndex( + allowedSchemes, + s => uri.Scheme.Equals(s, StringComparison.OrdinalIgnoreCase)) >= 0; + } + + /// <summary> + /// Tries to canonicalize a user-supplied identifier. + /// This does NOT convert a user-supplied identifier to a Claimed Identifier! + /// </summary> + /// <param name="uri">The user-supplied identifier.</param> + /// <param name="canonicalUri">The resulting canonical URI.</param> + /// <param name="forceHttpsDefaultScheme">If set to <c>true</c> and the user-supplied identifier lacks a scheme, the "https://" scheme will be prepended instead of the standard "http://" one.</param> + /// <param name="schemePrepended">if set to <c>true</c> [scheme prepended].</param> + /// <returns> + /// <c>true</c> if the identifier was valid and could be canonicalized. + /// <c>false</c> if the identifier is outside the scope of allowed inputs and should be rejected. + /// </returns> + /// <remarks> + /// Canonicalization is done by adding a scheme in front of an + /// identifier if it isn't already present. Other trivial changes that do not + /// require network access are also done, such as lower-casing the hostname in the URI. + /// </remarks> + private static bool TryCanonicalize(string uri, out Uri canonicalUri, bool forceHttpsDefaultScheme, out bool schemePrepended) { + Requires.NotNullOrEmpty(uri, "uri"); + + canonicalUri = null; + try { + uri = DoSimpleCanonicalize(uri, forceHttpsDefaultScheme, out schemePrepended); + if (schemeSubstitution) { + uri = NormalSchemeToSpecialRoundTrippingScheme(uri); + } + + // Use a UriBuilder because it helps to normalize the URL as well. + return TryCanonicalize(uri, out canonicalUri); + } catch (UriFormatException) { + // We try not to land here with checks in the try block, but just in case. + schemePrepended = false; + return false; + } + } + + /// <summary> + /// Fixes up the scheme if appropriate. + /// </summary> + /// <param name="uri">The URI, already in legal form (with http(s):// prepended if necessary).</param> + /// <param name="canonicalUri">The resulting canonical URI.</param> + /// <returns><c>true</c> if the canonicalization was successful; <c>false</c> otherwise.</returns> + /// <remarks> + /// This does NOT standardize an OpenID URL for storage in a database, as + /// it does nothing to convert the URL to a Claimed Identifier, besides the fact + /// that it only deals with URLs whereas OpenID 2.0 supports XRIs. + /// For this, you should lookup the value stored in IAuthenticationResponse.ClaimedIdentifier. + /// </remarks> + [SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase", Justification = "The user will see the result of this operation and they want to see it in lower case.")] + private static bool TryCanonicalize(string uri, out Uri canonicalUri) { + Requires.NotNull(uri, "uri"); + + if (schemeSubstitution) { + UriBuilder uriBuilder = new UriBuilder(uri); + + // Swap out our round-trippable scheme for the publishable (hidden) scheme. + uriBuilder.Scheme = uriBuilder.Scheme == roundTrippingHttpParser.RegisteredScheme ? publishableHttpParser.RegisteredScheme : publishableHttpsParser.RegisteredScheme; + canonicalUri = uriBuilder.Uri; + } else { + canonicalUri = new Uri(uri); + } + + return true; + } + + /// <summary> + /// Gets the special non-compressing scheme or URL for a standard scheme or URL. + /// </summary> + /// <param name="normal">The ordinary URL or scheme name.</param> + /// <returns>The non-compressing equivalent scheme or URL for the given value.</returns> + private static string NormalSchemeToSpecialRoundTrippingScheme(string normal) { + Requires.NotNullOrEmpty(normal, "normal"); + Contract.Ensures(!string.IsNullOrEmpty(Contract.Result<string>())); + ErrorUtilities.VerifyInternal(schemeSubstitution, "Wrong schemeSubstitution value."); + + int delimiterIndex = normal.IndexOf(Uri.SchemeDelimiter, StringComparison.Ordinal); + string normalScheme = delimiterIndex < 0 ? normal : normal.Substring(0, delimiterIndex); + string nonCompressingScheme; + if (string.Equals(normalScheme, Uri.UriSchemeHttp, StringComparison.OrdinalIgnoreCase) || + string.Equals(normalScheme, publishableHttpParser.RegisteredScheme, StringComparison.OrdinalIgnoreCase)) { + nonCompressingScheme = roundTrippingHttpParser.RegisteredScheme; + } else if (string.Equals(normalScheme, Uri.UriSchemeHttps, StringComparison.OrdinalIgnoreCase) || + string.Equals(normalScheme, publishableHttpsParser.RegisteredScheme, StringComparison.OrdinalIgnoreCase)) { + nonCompressingScheme = roundTrippingHttpsParser.RegisteredScheme; + } else { + throw new NotSupportedException(); + } + + return delimiterIndex < 0 ? nonCompressingScheme : nonCompressingScheme + normal.Substring(delimiterIndex); + } + + /// <summary> + /// Performs the minimal URL normalization to allow a string to be passed to the <see cref="Uri"/> constructor. + /// </summary> + /// <param name="uri">The user-supplied identifier URI to normalize.</param> + /// <param name="forceHttpsDefaultScheme">if set to <c>true</c>, a missing scheme should result in HTTPS being prepended instead of HTTP.</param> + /// <param name="schemePrepended">if set to <c>true</c>, the scheme was prepended during normalization.</param> + /// <returns>The somewhat normalized URL.</returns> + private static string DoSimpleCanonicalize(string uri, bool forceHttpsDefaultScheme, out bool schemePrepended) { + Requires.NotNullOrEmpty(uri, "uri"); + + schemePrepended = false; + uri = uri.Trim(); + + // Assume http:// scheme if an allowed scheme isn't given, and strip + // fragments off. Consistent with spec section 7.2#3 + if (!IsAllowedScheme(uri)) { + uri = (forceHttpsDefaultScheme ? Uri.UriSchemeHttps : Uri.UriSchemeHttp) + + Uri.SchemeDelimiter + uri; + schemePrepended = true; + } + + return uri; + } + +#if CONTRACTS_FULL + /// <summary> + /// Verifies conditions that should be true for any valid state of this object. + /// </summary> + [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Called by code contracts.")] + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called by code contracts.")] + [ContractInvariantMethod] + private void ObjectInvariant() { + Contract.Invariant(this.Uri != null); + Contract.Invariant(this.Uri.AbsoluteUri != null); + } +#endif + + /// <summary> + /// A simple URI class that doesn't suffer from the parsing problems of the <see cref="Uri"/> class. + /// </summary> + internal class SimpleUri { + /// <summary> + /// URI characters that separate the URI Path from subsequent elements. + /// </summary> + private static readonly char[] PathEndingCharacters = new char[] { '?', '#' }; + + /// <summary> + /// Initializes a new instance of the <see cref="SimpleUri"/> class. + /// </summary> + /// <param name="value">The value.</param> + internal SimpleUri(string value) { + Requires.NotNullOrEmpty(value, "value"); + + bool schemePrepended; + value = DoSimpleCanonicalize(value, false, out schemePrepended); + + // Leverage the Uri class's parsing where we can. + Uri uri = new Uri(value); + this.Scheme = uri.Scheme; + this.Authority = uri.Authority; + this.Query = uri.Query; + this.Fragment = uri.Fragment; + + // Get the Path out ourselves, since the default Uri parser compresses it too much for OpenID. + int schemeLength = value.IndexOf(Uri.SchemeDelimiter, StringComparison.Ordinal); + Contract.Assume(schemeLength > 0); + int hostStart = schemeLength + Uri.SchemeDelimiter.Length; + int hostFinish = value.IndexOf('/', hostStart); + if (hostFinish < 0) { + this.Path = "/"; + } else { + int pathFinish = value.IndexOfAny(PathEndingCharacters, hostFinish); + Contract.Assume(pathFinish >= hostFinish || pathFinish < 0); + if (pathFinish < 0) { + this.Path = value.Substring(hostFinish); + } else { + this.Path = value.Substring(hostFinish, pathFinish - hostFinish); + } + } + + this.Path = NormalizePathEscaping(this.Path); + } + + /// <summary> + /// Gets the scheme. + /// </summary> + /// <value>The scheme.</value> + public string Scheme { get; private set; } + + /// <summary> + /// Gets the authority. + /// </summary> + /// <value>The authority.</value> + public string Authority { get; private set; } + + /// <summary> + /// Gets the path of the URI. + /// </summary> + /// <value>The path from the URI.</value> + public string Path { get; private set; } + + /// <summary> + /// Gets the query. + /// </summary> + /// <value>The query.</value> + public string Query { get; private set; } + + /// <summary> + /// Gets the fragment. + /// </summary> + /// <value>The fragment.</value> + public string Fragment { get; private set; } + + /// <summary> + /// Returns a <see cref="System.String"/> that represents this instance. + /// </summary> + /// <returns> + /// A <see cref="System.String"/> that represents this instance. + /// </returns> + public override string ToString() { + return this.Scheme + Uri.SchemeDelimiter + this.Authority + this.Path + this.Query + this.Fragment; + } + + /// <summary> + /// Determines whether the specified <see cref="System.Object"/> is equal to this instance. + /// </summary> + /// <param name="obj">The <see cref="System.Object"/> to compare with this instance.</param> + /// <returns> + /// <c>true</c> if the specified <see cref="System.Object"/> is equal to this instance; otherwise, <c>false</c>. + /// </returns> + /// <exception cref="T:System.NullReferenceException"> + /// The <paramref name="obj"/> parameter is null. + /// </exception> + public override bool Equals(object obj) { + SimpleUri other = obj as SimpleUri; + if (other == null) { + return false; + } + + // Note that this equality check is intentionally leaving off the Fragment part + // to match Uri behavior, and is intentionally being case sensitive and insensitive + // for different parts. + return string.Equals(this.Scheme, other.Scheme, StringComparison.OrdinalIgnoreCase) && + string.Equals(this.Authority, other.Authority, StringComparison.OrdinalIgnoreCase) && + string.Equals(this.Path, other.Path, StringComparison.Ordinal) && + string.Equals(this.Query, other.Query, StringComparison.Ordinal); + } + + /// <summary> + /// Returns a hash code for this instance. + /// </summary> + /// <returns> + /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. + /// </returns> + public override int GetHashCode() { + int hashCode = 0; + hashCode += StringComparer.OrdinalIgnoreCase.GetHashCode(this.Scheme); + hashCode += StringComparer.OrdinalIgnoreCase.GetHashCode(this.Authority); + hashCode += StringComparer.Ordinal.GetHashCode(this.Path); + hashCode += StringComparer.Ordinal.GetHashCode(this.Query); + return hashCode; + } + + /// <summary> + /// Normalizes the characters that are escaped in the given URI path. + /// </summary> + /// <param name="path">The path to normalize.</param> + /// <returns>The given path, with exactly those characters escaped which should be.</returns> + private static string NormalizePathEscaping(string path) { + Requires.NotNull(path, "path"); + + string[] segments = path.Split('/'); + for (int i = 0; i < segments.Length; i++) { + segments[i] = Uri.EscapeDataString(Uri.UnescapeDataString(segments[i])); + } + + return string.Join("/", segments); + } + } + + /// <summary> + /// A URI parser that does not compress paths, such as trimming trailing periods from path segments. + /// </summary> + private class NonPathCompressingUriParser : GenericUriParser { + /// <summary> + /// The field that stores the scheme that this parser is registered under. + /// </summary> + private static FieldInfo schemeField; + + /// <summary> + /// The standard "http" or "https" scheme that this parser is subverting. + /// </summary> + private string standardScheme; + + /// <summary> + /// Initializes a new instance of the <see cref="NonPathCompressingUriParser"/> class. + /// </summary> + /// <param name="standardScheme">The standard scheme that this parser will be subverting.</param> + public NonPathCompressingUriParser(string standardScheme) + : base(GenericUriParserOptions.DontCompressPath | GenericUriParserOptions.IriParsing | GenericUriParserOptions.Idn) { + Requires.NotNullOrEmpty(standardScheme, "standardScheme"); + this.standardScheme = standardScheme; + } + + /// <summary> + /// Gets the scheme this parser is registered under. + /// </summary> + /// <value>The registered scheme.</value> + internal string RegisteredScheme { get; private set; } + + /// <summary> + /// Initializes this parser with the actual scheme it should appear to be. + /// </summary> + /// <param name="hideNonStandardScheme">if set to <c>true</c> Uris using this scheme will look like they're using the original standard scheme.</param> + [SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase", Justification = "Schemes are traditionally displayed in lowercase.")] + internal void Initialize(bool hideNonStandardScheme) { + if (schemeField == null) { + schemeField = typeof(UriParser).GetField("m_Scheme", BindingFlags.NonPublic | BindingFlags.Instance); + } + + this.RegisteredScheme = (string)schemeField.GetValue(this); + + if (hideNonStandardScheme) { + schemeField.SetValue(this, this.standardScheme.ToLowerInvariant()); + } + } + } + } +} diff --git a/src/DotNetOpenAuth.OpenId/OpenId/XriDiscoveryProxyService.cs b/src/DotNetOpenAuth.OpenId/OpenId/XriDiscoveryProxyService.cs new file mode 100644 index 0000000..52bb92e --- /dev/null +++ b/src/DotNetOpenAuth.OpenId/OpenId/XriDiscoveryProxyService.cs @@ -0,0 +1,108 @@ +//----------------------------------------------------------------------- +// <copyright file="XriDiscoveryProxyService.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId { + using System; + using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; + using System.Diagnostics.Contracts; + using System.Globalization; + using System.Linq; + using System.Text; + using System.Xml; + using DotNetOpenAuth.Configuration; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OpenId.RelyingParty; + using DotNetOpenAuth.Xrds; + using DotNetOpenAuth.Yadis; + + /// <summary> + /// The discovery service for XRI identifiers that uses an XRI proxy resolver for discovery. + /// </summary> + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Xri", Justification = "Acronym")] + public class XriDiscoveryProxyService : IIdentifierDiscoveryService { + /// <summary> + /// The magic URL that will provide us an XRDS document for a given XRI identifier. + /// </summary> + /// <remarks> + /// We use application/xrd+xml instead of application/xrds+xml because it gets + /// xri.net to automatically give us exactly the right XRD element for community i-names + /// automatically, saving us having to choose which one to use out of the result. + /// The ssl=true parameter tells the proxy resolver to accept only SSL connections + /// when resolving community i-names. + /// </remarks> + private const string XriResolverProxyTemplate = "https://{1}/{0}?_xrd_r=application/xrd%2Bxml;sep=false"; + + /// <summary> + /// Initializes a new instance of the <see cref="XriDiscoveryProxyService"/> class. + /// </summary> + public XriDiscoveryProxyService() { + } + + #region IDiscoveryService Members + + /// <summary> + /// Performs discovery on the specified identifier. + /// </summary> + /// <param name="identifier">The identifier to perform discovery on.</param> + /// <param name="requestHandler">The means to place outgoing HTTP requests.</param> + /// <param name="abortDiscoveryChain">if set to <c>true</c>, no further discovery services will be called for this identifier.</param> + /// <returns> + /// A sequence of service endpoints yielded by discovery. Must not be null, but may be empty. + /// </returns> + public IEnumerable<IdentifierDiscoveryResult> Discover(Identifier identifier, IDirectWebRequestHandler requestHandler, out bool abortDiscoveryChain) { + abortDiscoveryChain = false; + var xriIdentifier = identifier as XriIdentifier; + if (xriIdentifier == null) { + return Enumerable.Empty<IdentifierDiscoveryResult>(); + } + + return DownloadXrds(xriIdentifier, requestHandler).XrdElements.CreateServiceEndpoints(xriIdentifier); + } + + #endregion + + /// <summary> + /// Downloads the XRDS document for this XRI. + /// </summary> + /// <param name="identifier">The identifier.</param> + /// <param name="requestHandler">The request handler.</param> + /// <returns>The XRDS document.</returns> + private static XrdsDocument DownloadXrds(XriIdentifier identifier, IDirectWebRequestHandler requestHandler) { + Requires.NotNull(identifier, "identifier"); + Requires.NotNull(requestHandler, "requestHandler"); + Contract.Ensures(Contract.Result<XrdsDocument>() != null); + XrdsDocument doc; + using (var xrdsResponse = Yadis.Request(requestHandler, GetXrdsUrl(identifier), identifier.IsDiscoverySecureEndToEnd)) { + doc = new XrdsDocument(XmlReader.Create(xrdsResponse.ResponseStream)); + } + ErrorUtilities.VerifyProtocol(doc.IsXrdResolutionSuccessful, OpenIdStrings.XriResolutionFailed); + return doc; + } + + /// <summary> + /// Gets the URL from which this XRI's XRDS document may be downloaded. + /// </summary> + /// <param name="identifier">The identifier.</param> + /// <returns>The URI to HTTP GET from to get the services.</returns> + private static Uri GetXrdsUrl(XriIdentifier identifier) { + ErrorUtilities.VerifyProtocol(OpenIdElement.Configuration.XriResolver.Enabled, OpenIdStrings.XriResolutionDisabled); + string xriResolverProxy = XriResolverProxyTemplate; + if (identifier.IsDiscoverySecureEndToEnd) { + // Indicate to xri.net that we require SSL to be used for delegated resolution + // of community i-names. + xriResolverProxy += ";https=true"; + } + + return new Uri( + string.Format( + CultureInfo.InvariantCulture, + xriResolverProxy, + identifier, + OpenIdElement.Configuration.XriResolver.Proxy.Name)); + } + } +} diff --git a/src/DotNetOpenAuth.OpenId/OpenId/XriIdentifier.cs b/src/DotNetOpenAuth.OpenId/OpenId/XriIdentifier.cs new file mode 100644 index 0000000..28460f1 --- /dev/null +++ b/src/DotNetOpenAuth.OpenId/OpenId/XriIdentifier.cs @@ -0,0 +1,207 @@ +//----------------------------------------------------------------------- +// <copyright file="XriIdentifier.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId { + using System; + using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; + using System.Diagnostics.Contracts; + using System.Globalization; + using System.Xml; + using DotNetOpenAuth.Configuration; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.Xrds; + using DotNetOpenAuth.Yadis; + + /// <summary> + /// An XRI style of OpenID Identifier. + /// </summary> + [Serializable] + [ContractVerification(true)] + [Pure] + public sealed class XriIdentifier : Identifier { + /// <summary> + /// An XRI always starts with one of these symbols. + /// </summary> + internal static readonly char[] GlobalContextSymbols = { '=', '@', '+', '$', '!' }; + + /// <summary> + /// The scheme and separator "xri://" + /// </summary> + private const string XriScheme = "xri://"; + + /// <summary> + /// Backing store for the <see cref="CanonicalXri"/> property. + /// </summary> + private readonly string canonicalXri; + + /// <summary> + /// Initializes a new instance of the <see cref="XriIdentifier"/> class. + /// </summary> + /// <param name="xri">The string value of the XRI.</param> + internal XriIdentifier(string xri) + : this(xri, false) { + Requires.NotNullOrEmpty(xri, "xri"); + Requires.Format(IsValidXri(xri), OpenIdStrings.InvalidXri); + } + + /// <summary> + /// Initializes a new instance of the <see cref="XriIdentifier"/> class. + /// </summary> + /// <param name="xri">The XRI that this Identifier will represent.</param> + /// <param name="requireSsl"> + /// If set to <c>true</c>, discovery and the initial authentication redirect will + /// only succeed if it can be done entirely using SSL. + /// </param> + internal XriIdentifier(string xri, bool requireSsl) + : base(xri, requireSsl) { + Requires.NotNullOrEmpty(xri, "xri"); + Requires.Format(IsValidXri(xri), OpenIdStrings.InvalidXri); + Contract.Assume(xri != null); // Proven by IsValidXri + this.OriginalXri = xri; + this.canonicalXri = CanonicalizeXri(xri); + } + + /// <summary> + /// Gets the original XRI supplied to the constructor. + /// </summary> + internal string OriginalXri { get; private set; } + + /// <summary> + /// Gets the canonical form of the XRI string. + /// </summary> + internal string CanonicalXri { + get { + Contract.Ensures(Contract.Result<string>() != null); + return this.canonicalXri; + } + } + + /// <summary> + /// Tests equality between this XRI and another XRI. + /// </summary> + /// <param name="obj">The <see cref="T:System.Object"/> to compare with the current <see cref="T:System.Object"/>.</param> + /// <returns> + /// true if the specified <see cref="T:System.Object"/> is equal to the current <see cref="T:System.Object"/>; otherwise, false. + /// </returns> + /// <exception cref="T:System.NullReferenceException"> + /// The <paramref name="obj"/> parameter is null. + /// </exception> + public override bool Equals(object obj) { + XriIdentifier other = obj as XriIdentifier; + if (obj != null && other == null && Identifier.EqualityOnStrings) { // test hook to enable MockIdentifier comparison + string objString = obj.ToString(); + ErrorUtilities.VerifyInternal(!string.IsNullOrEmpty(objString), "Identifier.ToString() returned a null or empty string."); + other = Identifier.Parse(objString) as XriIdentifier; + } + if (other == null) { + return false; + } + return this.CanonicalXri == other.CanonicalXri; + } + + /// <summary> + /// Returns the hash code of this XRI. + /// </summary> + /// <returns> + /// A hash code for the current <see cref="T:System.Object"/>. + /// </returns> + public override int GetHashCode() { + return this.CanonicalXri.GetHashCode(); + } + + /// <summary> + /// Returns the canonical string form of the XRI. + /// </summary> + /// <returns> + /// A <see cref="T:System.String"/> that represents the current <see cref="T:System.Object"/>. + /// </returns> + public override string ToString() { + return this.CanonicalXri; + } + + /// <summary> + /// Tests whether a given string represents a valid XRI format. + /// </summary> + /// <param name="xri">The value to test for XRI validity.</param> + /// <returns> + /// <c>true</c> if the given string constitutes a valid XRI; otherwise, <c>false</c>. + /// </returns> + internal static bool IsValidXri(string xri) { + Requires.NotNullOrEmpty(xri, "xri"); + xri = xri.Trim(); + + // TODO: better validation code here + return xri.IndexOfAny(GlobalContextSymbols) == 0 + || xri.StartsWith("(", StringComparison.Ordinal) + || xri.StartsWith(XriScheme, StringComparison.OrdinalIgnoreCase); + } + + /// <summary> + /// Returns an <see cref="Identifier"/> that has no URI fragment. + /// Quietly returns the original <see cref="Identifier"/> if it is not + /// a <see cref="UriIdentifier"/> or no fragment exists. + /// </summary> + /// <returns> + /// A new <see cref="Identifier"/> instance if there was a + /// fragment to remove, otherwise this same instance.. + /// </returns> + /// <remarks> + /// XRI Identifiers never have a fragment part, and thus this method + /// always returns this same instance. + /// </remarks> + internal override Identifier TrimFragment() { + return this; + } + + /// <summary> + /// Converts a given identifier to its secure equivalent. + /// UriIdentifiers originally created with an implied HTTP scheme change to HTTPS. + /// Discovery is made to require SSL for the entire resolution process. + /// </summary> + /// <param name="secureIdentifier">The newly created secure identifier. + /// If the conversion fails, <paramref name="secureIdentifier"/> retains + /// <i>this</i> identifiers identity, but will never discover any endpoints.</param> + /// <returns> + /// True if the secure conversion was successful. + /// False if the Identifier was originally created with an explicit HTTP scheme. + /// </returns> + [ContractVerification(false)] // bugs/limitations in CC static analysis + internal override bool TryRequireSsl(out Identifier secureIdentifier) { + secureIdentifier = IsDiscoverySecureEndToEnd ? this : new XriIdentifier(this, true); + return true; + } + + /// <summary> + /// Takes any valid form of XRI string and returns the canonical form of the same XRI. + /// </summary> + /// <param name="xri">The xri to canonicalize.</param> + /// <returns>The canonicalized form of the XRI.</returns> + /// <remarks>The canonical form, per the OpenID spec, is no scheme and no whitespace on either end.</remarks> + private static string CanonicalizeXri(string xri) { + Requires.NotNull(xri, "xri"); + Contract.Ensures(Contract.Result<string>() != null); + xri = xri.Trim(); + if (xri.StartsWith(XriScheme, StringComparison.OrdinalIgnoreCase)) { + Contract.Assume(XriScheme.Length <= xri.Length); // should be implied by StartsWith + xri = xri.Substring(XriScheme.Length); + } + return xri; + } + +#if CONTRACTS_FULL + /// <summary> + /// Verifies conditions that should be true for any valid state of this object. + /// </summary> + [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Called by code contracts.")] + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called by code contracts.")] + [ContractInvariantMethod] + private void ObjectInvariant() { + Contract.Invariant(this.canonicalXri != null); + } +#endif + } +} diff --git a/src/DotNetOpenAuth.OpenId/OpenIdXrdsHelper.cs b/src/DotNetOpenAuth.OpenId/OpenIdXrdsHelper.cs new file mode 100644 index 0000000..617c467 --- /dev/null +++ b/src/DotNetOpenAuth.OpenId/OpenIdXrdsHelper.cs @@ -0,0 +1,163 @@ +//----------------------------------------------------------------------- +// <copyright file="OpenIdXrdsHelper.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId { + using System; + using System.Collections.Generic; + using System.Diagnostics.Contracts; + using System.Linq; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OpenId.RelyingParty; + using DotNetOpenAuth.Xrds; + + /// <summary> + /// Adds OpenID-specific extension methods to the XrdsDocument class. + /// </summary> + internal static class OpenIdXrdsHelperRelyingParty { + /// <summary> + /// Creates the service endpoints described in this document, useful for requesting + /// authentication of one of the OpenID Providers that result from it. + /// </summary> + /// <param name="xrds">The XrdsDocument instance to use in this process.</param> + /// <param name="claimedIdentifier">The claimed identifier that was used to discover this XRDS document.</param> + /// <param name="userSuppliedIdentifier">The user supplied identifier.</param> + /// <returns> + /// A sequence of OpenID Providers that can assert ownership of the <paramref name="claimedIdentifier"/>. + /// </returns> + internal static IEnumerable<IdentifierDiscoveryResult> CreateServiceEndpoints(this IEnumerable<XrdElement> xrds, UriIdentifier claimedIdentifier, UriIdentifier userSuppliedIdentifier) { + Requires.NotNull(xrds, "xrds"); + Requires.NotNull(claimedIdentifier, "claimedIdentifier"); + Requires.NotNull(userSuppliedIdentifier, "userSuppliedIdentifier"); + Contract.Ensures(Contract.Result<IEnumerable<IdentifierDiscoveryResult>>() != null); + + var endpoints = new List<IdentifierDiscoveryResult>(); + endpoints.AddRange(xrds.GenerateOPIdentifierServiceEndpoints(userSuppliedIdentifier)); + endpoints.AddRange(xrds.GenerateClaimedIdentifierServiceEndpoints(claimedIdentifier, userSuppliedIdentifier)); + + Logger.Yadis.DebugFormat("Total services discovered in XRDS: {0}", endpoints.Count); + Logger.Yadis.Debug(endpoints.ToStringDeferred(true)); + return endpoints; + } + + /// <summary> + /// Creates the service endpoints described in this document, useful for requesting + /// authentication of one of the OpenID Providers that result from it. + /// </summary> + /// <param name="xrds">The XrdsDocument instance to use in this process.</param> + /// <param name="userSuppliedIdentifier">The user-supplied i-name that was used to discover this XRDS document.</param> + /// <returns>A sequence of OpenID Providers that can assert ownership of the canonical ID given in this document.</returns> + internal static IEnumerable<IdentifierDiscoveryResult> CreateServiceEndpoints(this IEnumerable<XrdElement> xrds, XriIdentifier userSuppliedIdentifier) { + Requires.NotNull(xrds, "xrds"); + Requires.NotNull(userSuppliedIdentifier, "userSuppliedIdentifier"); + Contract.Ensures(Contract.Result<IEnumerable<IdentifierDiscoveryResult>>() != null); + + var endpoints = new List<IdentifierDiscoveryResult>(); + endpoints.AddRange(xrds.GenerateOPIdentifierServiceEndpoints(userSuppliedIdentifier)); + endpoints.AddRange(xrds.GenerateClaimedIdentifierServiceEndpoints(userSuppliedIdentifier)); + Logger.Yadis.DebugFormat("Total services discovered in XRDS: {0}", endpoints.Count); + Logger.Yadis.Debug(endpoints.ToStringDeferred(true)); + return endpoints; + } + + /// <summary> + /// Generates OpenID Providers that can authenticate using directed identity. + /// </summary> + /// <param name="xrds">The XrdsDocument instance to use in this process.</param> + /// <param name="opIdentifier">The OP Identifier entered (and resolved) by the user. Essentially the user-supplied identifier.</param> + /// <returns>A sequence of the providers that can offer directed identity services.</returns> + private static IEnumerable<IdentifierDiscoveryResult> GenerateOPIdentifierServiceEndpoints(this IEnumerable<XrdElement> xrds, Identifier opIdentifier) { + Requires.NotNull(xrds, "xrds"); + Requires.NotNull(opIdentifier, "opIdentifier"); + Contract.Ensures(Contract.Result<IEnumerable<IdentifierDiscoveryResult>>() != null); + return from service in xrds.FindOPIdentifierServices() + from uri in service.UriElements + let protocol = Protocol.FindBestVersion(p => p.OPIdentifierServiceTypeURI, service.TypeElementUris) + let providerDescription = new ProviderEndpointDescription(uri.Uri, service.TypeElementUris) + select IdentifierDiscoveryResult.CreateForProviderIdentifier(opIdentifier, providerDescription, service.Priority, uri.Priority); + } + + /// <summary> + /// Generates the OpenID Providers that are capable of asserting ownership + /// of a particular URI claimed identifier. + /// </summary> + /// <param name="xrds">The XrdsDocument instance to use in this process.</param> + /// <param name="claimedIdentifier">The claimed identifier.</param> + /// <param name="userSuppliedIdentifier">The user supplied identifier.</param> + /// <returns> + /// A sequence of the providers that can assert ownership of the given identifier. + /// </returns> + private static IEnumerable<IdentifierDiscoveryResult> GenerateClaimedIdentifierServiceEndpoints(this IEnumerable<XrdElement> xrds, UriIdentifier claimedIdentifier, UriIdentifier userSuppliedIdentifier) { + Requires.NotNull(xrds, "xrds"); + Requires.NotNull(claimedIdentifier, "claimedIdentifier"); + Contract.Ensures(Contract.Result<IEnumerable<IdentifierDiscoveryResult>>() != null); + + return from service in xrds.FindClaimedIdentifierServices() + from uri in service.UriElements + where uri.Uri != null + let providerEndpoint = new ProviderEndpointDescription(uri.Uri, service.TypeElementUris) + select IdentifierDiscoveryResult.CreateForClaimedIdentifier(claimedIdentifier, userSuppliedIdentifier, service.ProviderLocalIdentifier, providerEndpoint, service.Priority, uri.Priority); + } + + /// <summary> + /// Generates the OpenID Providers that are capable of asserting ownership + /// of a particular XRI claimed identifier. + /// </summary> + /// <param name="xrds">The XrdsDocument instance to use in this process.</param> + /// <param name="userSuppliedIdentifier">The i-name supplied by the user.</param> + /// <returns>A sequence of the providers that can assert ownership of the given identifier.</returns> + private static IEnumerable<IdentifierDiscoveryResult> GenerateClaimedIdentifierServiceEndpoints(this IEnumerable<XrdElement> xrds, XriIdentifier userSuppliedIdentifier) { + // Cannot use code contracts because this method uses yield return. + ////Requires.NotNull(xrds, "xrds"); + ////Contract.Ensures(Contract.Result<IEnumerable<IdentifierDiscoveryResult>>() != null); + ErrorUtilities.VerifyArgumentNotNull(xrds, "xrds"); + + foreach (var service in xrds.FindClaimedIdentifierServices()) { + foreach (var uri in service.UriElements) { + // spec section 7.3.2.3 on Claimed Id -> CanonicalID substitution + if (service.Xrd.CanonicalID == null) { + Logger.Yadis.WarnFormat(XrdsStrings.MissingCanonicalIDElement, userSuppliedIdentifier); + break; // skip on to next service + } + ErrorUtilities.VerifyProtocol(service.Xrd.IsCanonicalIdVerified, XrdsStrings.CIDVerificationFailed, userSuppliedIdentifier); + + // In the case of XRI names, the ClaimedId is actually the CanonicalID. + var claimedIdentifier = new XriIdentifier(service.Xrd.CanonicalID); + var providerEndpoint = new ProviderEndpointDescription(uri.Uri, service.TypeElementUris); + yield return IdentifierDiscoveryResult.CreateForClaimedIdentifier(claimedIdentifier, userSuppliedIdentifier, service.ProviderLocalIdentifier, providerEndpoint, service.Priority, uri.Priority); + } + } + } + + /// <summary> + /// Enumerates the XRDS service elements that describe OpenID Providers offering directed identity assertions. + /// </summary> + /// <param name="xrds">The XrdsDocument instance to use in this process.</param> + /// <returns>A sequence of service elements.</returns> + private static IEnumerable<ServiceElement> FindOPIdentifierServices(this IEnumerable<XrdElement> xrds) { + Requires.NotNull(xrds, "xrds"); + Contract.Ensures(Contract.Result<IEnumerable<ServiceElement>>() != null); + + return from xrd in xrds + from service in xrd.OpenIdProviderIdentifierServices + select service; + } + + /// <summary> + /// Returns the OpenID-compatible services described by a given XRDS document, + /// in priority order. + /// </summary> + /// <param name="xrds">The XrdsDocument instance to use in this process.</param> + /// <returns>A sequence of the services offered.</returns> + private static IEnumerable<ServiceElement> FindClaimedIdentifierServices(this IEnumerable<XrdElement> xrds) { + Requires.NotNull(xrds, "xrds"); + Contract.Ensures(Contract.Result<IEnumerable<ServiceElement>>() != null); + + return from xrd in xrds + from service in xrd.OpenIdClaimedIdentifierServices + select service; + } + } +} diff --git a/src/DotNetOpenAuth.OpenId/Properties/AssemblyInfo.cs b/src/DotNetOpenAuth.OpenId/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..c5226ed --- /dev/null +++ b/src/DotNetOpenAuth.OpenId/Properties/AssemblyInfo.cs @@ -0,0 +1,66 @@ +//----------------------------------------------------------------------- +// <copyright file="AssemblyInfo.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +// We DON'T put an AssemblyVersionAttribute in here because it is generated in the build. + +using System; +using System.Diagnostics.Contracts; +using System.Net; +using System.Reflection; +using System.Resources; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Security; +using System.Security.Permissions; +using System.Web.UI; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("DotNetOpenAuth OpenID")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("DotNetOpenAuth")] +[assembly: AssemblyCopyright("Copyright © 2008")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: NeutralResourcesLanguage("en-US")] +[assembly: CLSCompliant(true)] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("7d73990c-47c0-4256-9f20-a893add9e289")] + +[assembly: ContractVerification(true)] + +#if StrongNameSigned +// See comment at top of this file. We need this so that strong-naming doesn't +// keep this assembly from being useful to shared host (medium trust) web sites. +[assembly: AllowPartiallyTrustedCallers] + +[assembly: InternalsVisibleTo("DotNetOpenAuth.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100AD093C3765257C89A7010E853F2C7C741FF92FA8ACE06D7B8254702CAD5CF99104447F63AB05F8BB6F51CE0D81C8C93D2FCE8C20AAFF7042E721CBA16EAAE98778611DED11C0ABC8900DC5667F99B50A9DADEC24DBD8F2C91E3E8AD300EF64F1B4B9536CEB16FB440AF939F57624A9B486F867807C649AE4830EAB88C6C03998")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.OAuth, PublicKey=0024000004800000940000000602000000240000525341310004000001000100AD093C3765257C89A7010E853F2C7C741FF92FA8ACE06D7B8254702CAD5CF99104447F63AB05F8BB6F51CE0D81C8C93D2FCE8C20AAFF7042E721CBA16EAAE98778611DED11C0ABC8900DC5667F99B50A9DADEC24DBD8F2C91E3E8AD300EF64F1B4B9536CEB16FB440AF939F57624A9B486F867807C649AE4830EAB88C6C03998")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.OpenId.UI, PublicKey=0024000004800000940000000602000000240000525341310004000001000100AD093C3765257C89A7010E853F2C7C741FF92FA8ACE06D7B8254702CAD5CF99104447F63AB05F8BB6F51CE0D81C8C93D2FCE8C20AAFF7042E721CBA16EAAE98778611DED11C0ABC8900DC5667F99B50A9DADEC24DBD8F2C91E3E8AD300EF64F1B4B9536CEB16FB440AF939F57624A9B486F867807C649AE4830EAB88C6C03998")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.OpenId.RelyingParty, PublicKey=0024000004800000940000000602000000240000525341310004000001000100AD093C3765257C89A7010E853F2C7C741FF92FA8ACE06D7B8254702CAD5CF99104447F63AB05F8BB6F51CE0D81C8C93D2FCE8C20AAFF7042E721CBA16EAAE98778611DED11C0ABC8900DC5667F99B50A9DADEC24DBD8F2C91E3E8AD300EF64F1B4B9536CEB16FB440AF939F57624A9B486F867807C649AE4830EAB88C6C03998")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.OpenId.RelyingParty.UI, PublicKey=0024000004800000940000000602000000240000525341310004000001000100AD093C3765257C89A7010E853F2C7C741FF92FA8ACE06D7B8254702CAD5CF99104447F63AB05F8BB6F51CE0D81C8C93D2FCE8C20AAFF7042E721CBA16EAAE98778611DED11C0ABC8900DC5667F99B50A9DADEC24DBD8F2C91E3E8AD300EF64F1B4B9536CEB16FB440AF939F57624A9B486F867807C649AE4830EAB88C6C03998")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.OpenId.Provider, PublicKey=0024000004800000940000000602000000240000525341310004000001000100AD093C3765257C89A7010E853F2C7C741FF92FA8ACE06D7B8254702CAD5CF99104447F63AB05F8BB6F51CE0D81C8C93D2FCE8C20AAFF7042E721CBA16EAAE98778611DED11C0ABC8900DC5667F99B50A9DADEC24DBD8F2C91E3E8AD300EF64F1B4B9536CEB16FB440AF939F57624A9B486F867807C649AE4830EAB88C6C03998")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.OpenId.Provider.UI, PublicKey=0024000004800000940000000602000000240000525341310004000001000100AD093C3765257C89A7010E853F2C7C741FF92FA8ACE06D7B8254702CAD5CF99104447F63AB05F8BB6F51CE0D81C8C93D2FCE8C20AAFF7042E721CBA16EAAE98778611DED11C0ABC8900DC5667F99B50A9DADEC24DBD8F2C91E3E8AD300EF64F1B4B9536CEB16FB440AF939F57624A9B486F867807C649AE4830EAB88C6C03998")] +[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")] +#else +[assembly: InternalsVisibleTo("DotNetOpenAuth.Test")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.OAuth")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.OpenId.UI")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.OpenId.RelyingParty")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.OpenId.RelyingParty.UI")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.OpenId.Provider")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.OpenId.Provider.UI")] +[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] +#endif diff --git a/src/DotNetOpenAuth/Xrds/ServiceElement.cs b/src/DotNetOpenAuth.OpenId/Xrds/ServiceElement.cs index 0acf2b5..0acf2b5 100644 --- a/src/DotNetOpenAuth/Xrds/ServiceElement.cs +++ b/src/DotNetOpenAuth.OpenId/Xrds/ServiceElement.cs diff --git a/src/DotNetOpenAuth.OpenId/Xrds/TypeElement.cs b/src/DotNetOpenAuth.OpenId/Xrds/TypeElement.cs new file mode 100644 index 0000000..e1ad188 --- /dev/null +++ b/src/DotNetOpenAuth.OpenId/Xrds/TypeElement.cs @@ -0,0 +1,34 @@ +//----------------------------------------------------------------------- +// <copyright file="TypeElement.cs" company="Andrew Arnott, Scott Hanselman"> +// Copyright (c) Andrew Arnott, Scott Hanselman. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Xrds { + using System; + using System.Diagnostics.Contracts; + using System.Xml.XPath; + + /// <summary> + /// The Type element in an XRDS document. + /// </summary> + internal class TypeElement : XrdsNode { + /// <summary> + /// Initializes a new instance of the <see cref="TypeElement"/> class. + /// </summary> + /// <param name="typeElement">The type element.</param> + /// <param name="parent">The parent.</param> + public TypeElement(XPathNavigator typeElement, ServiceElement parent) : + base(typeElement, parent) { + Requires.NotNull(typeElement, "typeElement"); + Requires.NotNull(parent, "parent"); + } + + /// <summary> + /// Gets the URI. + /// </summary> + public string Uri { + get { return Node.Value; } + } + } +} diff --git a/src/DotNetOpenAuth/Xrds/UriElement.cs b/src/DotNetOpenAuth.OpenId/Xrds/UriElement.cs index a67d259..a67d259 100644 --- a/src/DotNetOpenAuth/Xrds/UriElement.cs +++ b/src/DotNetOpenAuth.OpenId/Xrds/UriElement.cs diff --git a/src/DotNetOpenAuth/Xrds/XrdElement.cs b/src/DotNetOpenAuth.OpenId/Xrds/XrdElement.cs index 2cdc720..2cdc720 100644 --- a/src/DotNetOpenAuth/Xrds/XrdElement.cs +++ b/src/DotNetOpenAuth.OpenId/Xrds/XrdElement.cs diff --git a/src/DotNetOpenAuth/Xrds/XrdsDocument.cs b/src/DotNetOpenAuth.OpenId/Xrds/XrdsDocument.cs index 040c994..040c994 100644 --- a/src/DotNetOpenAuth/Xrds/XrdsDocument.cs +++ b/src/DotNetOpenAuth.OpenId/Xrds/XrdsDocument.cs diff --git a/src/DotNetOpenAuth.OpenId/Xrds/XrdsNode.cs b/src/DotNetOpenAuth.OpenId/Xrds/XrdsNode.cs new file mode 100644 index 0000000..7a27a9c --- /dev/null +++ b/src/DotNetOpenAuth.OpenId/Xrds/XrdsNode.cs @@ -0,0 +1,69 @@ +//----------------------------------------------------------------------- +// <copyright file="XrdsNode.cs" company="Andrew Arnott, Scott Hanselman"> +// Copyright (c) Andrew Arnott, Scott Hanselman. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Xrds { + using System; + using System.Diagnostics.Contracts; + using System.Xml; + using System.Xml.XPath; + using DotNetOpenAuth.Messaging; + + /// <summary> + /// A node in an XRDS document. + /// </summary> + internal class XrdsNode { + /// <summary> + /// The XRD namespace xri://$xrd*($v*2.0) + /// </summary> + internal const string XrdNamespace = "xri://$xrd*($v*2.0)"; + + /// <summary> + /// The XRDS namespace xri://$xrds + /// </summary> + internal const string XrdsNamespace = "xri://$xrds"; + + /// <summary> + /// Initializes a new instance of the <see cref="XrdsNode"/> class. + /// </summary> + /// <param name="node">The node represented by this instance.</param> + /// <param name="parentNode">The parent node.</param> + protected XrdsNode(XPathNavigator node, XrdsNode parentNode) { + Requires.NotNull(node, "node"); + Requires.NotNull(parentNode, "parentNode"); + + this.Node = node; + this.ParentNode = parentNode; + this.XmlNamespaceResolver = this.ParentNode.XmlNamespaceResolver; + } + + /// <summary> + /// Initializes a new instance of the <see cref="XrdsNode"/> class. + /// </summary> + /// <param name="document">The document's root node, which this instance represents.</param> + protected XrdsNode(XPathNavigator document) { + Requires.NotNull(document, "document"); + Requires.True(document.NameTable != null, null); + + this.Node = document; + this.XmlNamespaceResolver = new XmlNamespaceManager(document.NameTable); + } + + /// <summary> + /// Gets the node. + /// </summary> + internal XPathNavigator Node { get; private set; } + + /// <summary> + /// Gets the parent node, or null if this is the root node. + /// </summary> + protected internal XrdsNode ParentNode { get; private set; } + + /// <summary> + /// Gets the XML namespace resolver to use in XPath expressions. + /// </summary> + protected internal XmlNamespaceManager XmlNamespaceResolver { get; private set; } + } +} diff --git a/src/DotNetOpenAuth/Xrds/XrdsStrings.Designer.cs b/src/DotNetOpenAuth.OpenId/Xrds/XrdsStrings.Designer.cs index 2279b5f..2279b5f 100644 --- a/src/DotNetOpenAuth/Xrds/XrdsStrings.Designer.cs +++ b/src/DotNetOpenAuth.OpenId/Xrds/XrdsStrings.Designer.cs diff --git a/src/DotNetOpenAuth/Xrds/XrdsStrings.resx b/src/DotNetOpenAuth.OpenId/Xrds/XrdsStrings.resx index acb43f2..acb43f2 100644 --- a/src/DotNetOpenAuth/Xrds/XrdsStrings.resx +++ b/src/DotNetOpenAuth.OpenId/Xrds/XrdsStrings.resx diff --git a/src/DotNetOpenAuth/Xrds/XrdsStrings.sr.resx b/src/DotNetOpenAuth.OpenId/Xrds/XrdsStrings.sr.resx index 8e2d09a..8e2d09a 100644 --- a/src/DotNetOpenAuth/Xrds/XrdsStrings.sr.resx +++ b/src/DotNetOpenAuth.OpenId/Xrds/XrdsStrings.sr.resx diff --git a/src/DotNetOpenAuth/Yadis/ContentTypes.cs b/src/DotNetOpenAuth.OpenId/Yadis/ContentTypes.cs index 30745ee..30745ee 100644 --- a/src/DotNetOpenAuth/Yadis/ContentTypes.cs +++ b/src/DotNetOpenAuth.OpenId/Yadis/ContentTypes.cs diff --git a/src/DotNetOpenAuth/Yadis/DiscoveryResult.cs b/src/DotNetOpenAuth.OpenId/Yadis/DiscoveryResult.cs index 06c6fc7..06c6fc7 100644 --- a/src/DotNetOpenAuth/Yadis/DiscoveryResult.cs +++ b/src/DotNetOpenAuth.OpenId/Yadis/DiscoveryResult.cs diff --git a/src/DotNetOpenAuth.OpenId/Yadis/HtmlParser.cs b/src/DotNetOpenAuth.OpenId/Yadis/HtmlParser.cs new file mode 100644 index 0000000..9002acb --- /dev/null +++ b/src/DotNetOpenAuth.OpenId/Yadis/HtmlParser.cs @@ -0,0 +1,142 @@ +//----------------------------------------------------------------------- +// <copyright file="HtmlParser.cs" company="Andrew Arnott, Scott Hanselman, Jason Alexander"> +// Copyright (c) Andrew Arnott, Scott Hanselman, Jason Alexander. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Yadis { + using System; + using System.Collections.Generic; + using System.Diagnostics.Contracts; + using System.Globalization; + using System.Linq; + using System.Text; + using System.Text.RegularExpressions; + using System.Web; + using System.Web.UI.HtmlControls; + + /// <summary> + /// An HTML HEAD tag parser. + /// </summary> + internal static class HtmlParser { + /// <summary> + /// Common flags to use on regex tests. + /// </summary> + private const RegexOptions Flags = RegexOptions.IgnorePatternWhitespace | RegexOptions.Singleline | RegexOptions.Compiled | RegexOptions.IgnoreCase; + + /// <summary> + /// A regular expression designed to select tags (?) + /// </summary> + private const string TagExpr = "\n# Starts with the tag name at a word boundary, where the tag name is\n# not a namespace\n<{0}\\b(?!:)\n \n# All of the stuff up to a \">\", hopefully attributes.\n(?<attrs>[^>]*?)\n \n(?: # Match a short tag\n />\n \n| # Match a full tag\n >\n \n (?<contents>.*?)\n \n # Closed by\n (?: # One of the specified close tags\n </?{1}\\s*>\n \n # End of the string\n | \\Z\n \n )\n \n)\n "; + + /// <summary> + /// A regular expression designed to select start tags (?) + /// </summary> + private const string StartTagExpr = "\n# Starts with the tag name at a word boundary, where the tag name is\n# not a namespace\n<{0}\\b(?!:)\n \n# All of the stuff up to a \">\", hopefully attributes.\n(?<attrs>[^>]*?)\n \n(?: # Match a short tag\n />\n \n| # Match a full tag\n >\n )\n "; + + /// <summary> + /// A regular expression designed to select attributes within a tag. + /// </summary> + private static readonly Regex attrRe = new Regex("\n# Must start with a sequence of word-characters, followed by an equals sign\n(?<attrname>(\\w|-)+)=\n\n# Then either a quoted or unquoted attribute\n(?:\n\n # Match everything that's between matching quote marks\n (?<qopen>[\"\\'])(?<attrval>.*?)\\k<qopen>\n|\n\n # If the value is not quoted, match up to whitespace\n (?<attrval>(?:[^\\s<>/]|/(?!>))+)\n)\n\n|\n\n(?<endtag>[<>])\n ", Flags); + + /// <summary> + /// A regular expression designed to select the HEAD tag. + /// </summary> + private static readonly Regex headRe = TagMatcher("head", new[] { "body" }); + + /// <summary> + /// A regular expression designed to select the HTML tag. + /// </summary> + private static readonly Regex htmlRe = TagMatcher("html", new string[0]); + + /// <summary> + /// A regular expression designed to remove all comments and scripts from a string. + /// </summary> + private static readonly Regex removedRe = new Regex(@"<!--.*?-->|<!\[CDATA\[.*?\]\]>|<script\b[^>]*>.*?</script>", Flags); + + /// <summary> + /// Finds all the HTML HEAD tag child elements that match the tag name of a given type. + /// </summary> + /// <typeparam name="T">The HTML tag of interest.</typeparam> + /// <param name="html">The HTML to scan.</param> + /// <returns>A sequence of the matching elements.</returns> + public static IEnumerable<T> HeadTags<T>(string html) where T : HtmlControl, new() { + html = removedRe.Replace(html, string.Empty); + Match match = htmlRe.Match(html); + string tagName = (new T()).TagName; + if (match.Success) { + Match match2 = headRe.Match(html, match.Index, match.Length); + if (match2.Success) { + string text = null; + string text2 = null; + Regex regex = StartTagMatcher(tagName); + for (Match match3 = regex.Match(html, match2.Index, match2.Length); match3.Success; match3 = match3.NextMatch()) { + int beginning = (match3.Index + tagName.Length) + 1; + int length = (match3.Index + match3.Length) - beginning; + Match match4 = attrRe.Match(html, beginning, length); + var headTag = new T(); + while (match4.Success) { + if (match4.Groups["endtag"].Success) { + break; + } + text = match4.Groups["attrname"].Value; + text2 = HttpUtility.HtmlDecode(match4.Groups["attrval"].Value); + headTag.Attributes.Add(text, text2); + match4 = match4.NextMatch(); + } + yield return headTag; + } + } + } + } + + /// <summary> + /// Filters a list of controls based on presence of an attribute. + /// </summary> + /// <typeparam name="T">The type of HTML controls being filtered.</typeparam> + /// <param name="sequence">The sequence.</param> + /// <param name="attribute">The attribute.</param> + /// <returns>A filtered sequence of attributes.</returns> + internal static IEnumerable<T> WithAttribute<T>(this IEnumerable<T> sequence, string attribute) where T : HtmlControl { + Requires.NotNull(sequence, "sequence"); + Requires.NotNullOrEmpty(attribute, "attribute"); + return sequence.Where(tag => tag.Attributes[attribute] != null); + } + + /// <summary> + /// Generates a regular expression that will find a given HTML tag. + /// </summary> + /// <param name="tagName">Name of the tag.</param> + /// <param name="closeTags">The close tags (?).</param> + /// <returns>The created regular expression.</returns> + private static Regex TagMatcher(string tagName, params string[] closeTags) { + string text2; + if (closeTags.Length > 0) { + StringBuilder builder = new StringBuilder(); + builder.AppendFormat("(?:{0}", tagName); + int index = 0; + string[] textArray = closeTags; + int length = textArray.Length; + while (index < length) { + string text = textArray[index]; + index++; + builder.AppendFormat("|{0}", text); + } + builder.Append(")"); + text2 = builder.ToString(); + } else { + text2 = tagName; + } + return new Regex(string.Format(CultureInfo.InvariantCulture, TagExpr, tagName, text2), Flags); + } + + /// <summary> + /// Generates a regular expression designed to find a given tag. + /// </summary> + /// <param name="tagName">The tag to find.</param> + /// <returns>The created regular expression.</returns> + private static Regex StartTagMatcher(string tagName) { + return new Regex(string.Format(CultureInfo.InvariantCulture, StartTagExpr, tagName), Flags); + } + } +} diff --git a/src/DotNetOpenAuth.OpenId/Yadis/Yadis.cs b/src/DotNetOpenAuth.OpenId/Yadis/Yadis.cs new file mode 100644 index 0000000..955f2f4 --- /dev/null +++ b/src/DotNetOpenAuth.OpenId/Yadis/Yadis.cs @@ -0,0 +1,205 @@ +//----------------------------------------------------------------------- +// <copyright file="Yadis.cs" company="Andrew Arnott, Scott Hanselman"> +// Copyright (c) Andrew Arnott, Scott Hanselman. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Yadis { + using System; + using System.Diagnostics.Contracts; + using System.IO; + using System.Net; + using System.Net.Cache; + using System.Web.UI.HtmlControls; + using System.Xml; + using DotNetOpenAuth.Configuration; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OpenId; + using DotNetOpenAuth.Xrds; + + /// <summary> + /// YADIS discovery manager. + /// </summary> + internal class Yadis { + /// <summary> + /// The HTTP header to look for in responses to declare where the XRDS document should be found. + /// </summary> + internal const string HeaderName = "X-XRDS-Location"; + + /// <summary> + /// Gets or sets the cache that can be used for HTTP requests made during identifier discovery. + /// </summary> +#if DEBUG + internal static readonly RequestCachePolicy IdentifierDiscoveryCachePolicy = new HttpRequestCachePolicy(HttpRequestCacheLevel.BypassCache); +#else + internal static readonly RequestCachePolicy IdentifierDiscoveryCachePolicy = new HttpRequestCachePolicy(OpenIdElement.Configuration.CacheDiscovery ? HttpRequestCacheLevel.CacheIfAvailable : HttpRequestCacheLevel.BypassCache); +#endif + + /// <summary> + /// The maximum number of bytes to read from an HTTP response + /// in searching for a link to a YADIS document. + /// </summary> + internal const int MaximumResultToScan = 1024 * 1024; + + /// <summary> + /// Performs YADIS discovery on some identifier. + /// </summary> + /// <param name="requestHandler">The mechanism to use for sending HTTP requests.</param> + /// <param name="uri">The URI to perform discovery on.</param> + /// <param name="requireSsl">Whether discovery should fail if any step of it is not encrypted.</param> + /// <returns> + /// The result of discovery on the given URL. + /// Null may be returned if an error occurs, + /// or if <paramref name="requireSsl"/> is true but part of discovery + /// is not protected by SSL. + /// </returns> + public static DiscoveryResult Discover(IDirectWebRequestHandler requestHandler, UriIdentifier uri, bool requireSsl) { + CachedDirectWebResponse response; + try { + if (requireSsl && !string.Equals(uri.Uri.Scheme, Uri.UriSchemeHttps, StringComparison.OrdinalIgnoreCase)) { + Logger.Yadis.WarnFormat("Discovery on insecure identifier '{0}' aborted.", uri); + return null; + } + response = Request(requestHandler, uri, requireSsl, ContentTypes.Html, ContentTypes.XHtml, ContentTypes.Xrds).GetSnapshot(MaximumResultToScan); + if (response.Status != System.Net.HttpStatusCode.OK) { + Logger.Yadis.ErrorFormat("HTTP error {0} {1} while performing discovery on {2}.", (int)response.Status, response.Status, uri); + return null; + } + } catch (ArgumentException ex) { + // Unsafe URLs generate this + Logger.Yadis.WarnFormat("Unsafe OpenId URL detected ({0}). Request aborted. {1}", uri, ex); + return null; + } + CachedDirectWebResponse response2 = null; + if (IsXrdsDocument(response)) { + Logger.Yadis.Debug("An XRDS response was received from GET at user-supplied identifier."); + Reporting.RecordEventOccurrence("Yadis", "XRDS in initial response"); + response2 = response; + } else { + string uriString = response.Headers.Get(HeaderName); + Uri url = null; + if (uriString != null) { + if (Uri.TryCreate(uriString, UriKind.Absolute, out url)) { + Logger.Yadis.DebugFormat("{0} found in HTTP header. Preparing to pull XRDS from {1}", HeaderName, url); + Reporting.RecordEventOccurrence("Yadis", "XRDS referenced in HTTP header"); + } + } + if (url == null && response.ContentType != null && (response.ContentType.MediaType == ContentTypes.Html || response.ContentType.MediaType == ContentTypes.XHtml)) { + url = FindYadisDocumentLocationInHtmlMetaTags(response.GetResponseString()); + if (url != null) { + Logger.Yadis.DebugFormat("{0} found in HTML Http-Equiv tag. Preparing to pull XRDS from {1}", HeaderName, url); + Reporting.RecordEventOccurrence("Yadis", "XRDS referenced in HTML"); + } + } + if (url != null) { + if (!requireSsl || string.Equals(url.Scheme, Uri.UriSchemeHttps, StringComparison.OrdinalIgnoreCase)) { + response2 = Request(requestHandler, url, requireSsl, ContentTypes.Xrds).GetSnapshot(MaximumResultToScan); + if (response2.Status != HttpStatusCode.OK) { + Logger.Yadis.ErrorFormat("HTTP error {0} {1} while performing discovery on {2}.", (int)response2.Status, response2.Status, uri); + } + } else { + Logger.Yadis.WarnFormat("XRDS document at insecure location '{0}'. Aborting YADIS discovery.", url); + } + } + } + return new DiscoveryResult(uri, response, response2); + } + + /// <summary> + /// Searches an HTML document for a + /// <meta http-equiv="X-XRDS-Location" content="{YadisURL}"> + /// tag and returns the content of YadisURL. + /// </summary> + /// <param name="html">The HTML to search.</param> + /// <returns>The URI of the XRDS document if found; otherwise <c>null</c>.</returns> + public static Uri FindYadisDocumentLocationInHtmlMetaTags(string html) { + foreach (var metaTag in HtmlParser.HeadTags<HtmlMeta>(html)) { + if (HeaderName.Equals(metaTag.HttpEquiv, StringComparison.OrdinalIgnoreCase)) { + if (metaTag.Content != null) { + Uri uri; + if (Uri.TryCreate(metaTag.Content, UriKind.Absolute, out uri)) { + return uri; + } + } + } + } + return null; + } + + /// <summary> + /// Sends a YADIS HTTP request as part of identifier discovery. + /// </summary> + /// <param name="requestHandler">The request handler to use to actually submit the request.</param> + /// <param name="uri">The URI to GET.</param> + /// <param name="requireSsl">Whether only HTTPS URLs should ever be retrieved.</param> + /// <param name="acceptTypes">The value of the Accept HTTP header to include in the request.</param> + /// <returns>The HTTP response retrieved from the request.</returns> + internal static IncomingWebResponse Request(IDirectWebRequestHandler requestHandler, Uri uri, bool requireSsl, params string[] acceptTypes) { + Requires.NotNull(requestHandler, "requestHandler"); + Requires.NotNull(uri, "uri"); + Contract.Ensures(Contract.Result<IncomingWebResponse>() != null); + Contract.Ensures(Contract.Result<IncomingWebResponse>().ResponseStream != null); + + HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uri); + request.CachePolicy = IdentifierDiscoveryCachePolicy; + if (acceptTypes != null) { + request.Accept = string.Join(",", acceptTypes); + } + + DirectWebRequestOptions options = DirectWebRequestOptions.None; + if (requireSsl) { + options |= DirectWebRequestOptions.RequireSsl; + } + + try { + return requestHandler.GetResponse(request, options); + } catch (ProtocolException ex) { + var webException = ex.InnerException as WebException; + if (webException != null) { + var response = webException.Response as HttpWebResponse; + if (response != null && response.IsFromCache) { + // We don't want to report error responses from the cache, since the server may have fixed + // whatever was causing the problem. So try again with cache disabled. + Logger.Messaging.Error("An HTTP error response was obtained from the cache. Retrying with cache disabled.", ex); + var nonCachingRequest = request.Clone(); + nonCachingRequest.CachePolicy = new HttpRequestCachePolicy(HttpRequestCacheLevel.Reload); + return requestHandler.GetResponse(nonCachingRequest, options); + } + } + + throw; + } + } + + /// <summary> + /// Determines whether a given HTTP response constitutes an XRDS document. + /// </summary> + /// <param name="response">The response to test.</param> + /// <returns> + /// <c>true</c> if the response constains an XRDS document; otherwise, <c>false</c>. + /// </returns> + private static bool IsXrdsDocument(CachedDirectWebResponse response) { + if (response.ContentType == null) { + return false; + } + + if (response.ContentType.MediaType == ContentTypes.Xrds) { + return true; + } + + if (response.ContentType.MediaType == ContentTypes.Xml) { + // This COULD be an XRDS document with an imprecise content-type. + response.ResponseStream.Seek(0, SeekOrigin.Begin); + XmlReader reader = XmlReader.Create(response.ResponseStream); + while (reader.Read() && reader.NodeType != XmlNodeType.Element) { + // intentionally blank + } + if (reader.NamespaceURI == XrdsNode.XrdsNamespace && reader.Name == "XRDS") { + return true; + } + } + + return false; + } + } +} diff --git a/src/DotNetOpenAuth.OpenIdInfoCard.UI/DotNetOpenAuth.OpenIdInfoCard.UI.csproj b/src/DotNetOpenAuth.OpenIdInfoCard.UI/DotNetOpenAuth.OpenIdInfoCard.UI.csproj new file mode 100644 index 0000000..e0a5267 --- /dev/null +++ b/src/DotNetOpenAuth.OpenIdInfoCard.UI/DotNetOpenAuth.OpenIdInfoCard.UI.csproj @@ -0,0 +1,68 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), EnlistmentInfo.props))\EnlistmentInfo.props" Condition=" '$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), EnlistmentInfo.props))' != '' " /> + <PropertyGroup> + <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> + <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> + </PropertyGroup> + <Import Project="$(ProjectRoot)tools\DotNetOpenAuth.props" /> + <PropertyGroup> + <SchemaVersion>2.0</SchemaVersion> + <ProjectGuid>{3A8347E8-59A5-4092-8842-95C75D7D2F36}</ProjectGuid> + <OutputType>Library</OutputType> + <AppDesignerFolder>Properties</AppDesignerFolder> + <RootNamespace>DotNetOpenAuth</RootNamespace> + <AssemblyName>DotNetOpenAuth.OpenIdInfoCard.UI</AssemblyName> + </PropertyGroup> + <Import Project="$(ProjectRoot)tools\DotNetOpenAuth.Product.props" /> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> + </PropertyGroup> + <ItemGroup> + <Compile Include="OpenId\RelyingParty\OpenIdInfoCardSelector.cs" /> + <Compile Include="OpenId\RelyingParty\SelectorInfoCardButton.cs" /> + <Compile Include="Properties\AssemblyInfo.cs" /> + </ItemGroup> + <ItemGroup> + <ProjectReference Include="..\DotNetOpenAuth.InfoCard.UI\DotNetOpenAuth.InfoCard.UI.csproj"> + <Project>{E040EB58-B4D2-457B-A023-AE6EF3BD34DE}</Project> + <Name>DotNetOpenAuth.InfoCard.UI</Name> + </ProjectReference> + <ProjectReference Include="..\DotNetOpenAuth.InfoCard\DotNetOpenAuth.InfoCard.csproj"> + <Project>{408D10B8-34BA-4CBD-B7AA-FEB1907ABA4C}</Project> + <Name>DotNetOpenAuth.InfoCard</Name> + </ProjectReference> + <ProjectReference Include="..\DotNetOpenAuth.Core.UI\DotNetOpenAuth.Core.UI.csproj"> + <Project>{173E7B8D-E751-46E2-A133-F72297C0D2F4}</Project> + <Name>DotNetOpenAuth.Core.UI</Name> + </ProjectReference> + <ProjectReference Include="..\DotNetOpenAuth.Core\DotNetOpenAuth.Core.csproj"> + <Project>{60426312-6AE5-4835-8667-37EDEA670222}</Project> + <Name>DotNetOpenAuth.Core</Name> + </ProjectReference> + <ProjectReference Include="..\DotNetOpenAuth.OpenId.RelyingParty.UI\DotNetOpenAuth.OpenId.RelyingParty.UI.csproj"> + <Project>{1ED8D424-F8AB-4050-ACEB-F27F4F909484}</Project> + <Name>DotNetOpenAuth.OpenId.RelyingParty.UI</Name> + </ProjectReference> + <ProjectReference Include="..\DotNetOpenAuth.OpenId.RelyingParty\DotNetOpenAuth.OpenId.RelyingParty.csproj"> + <Project>{F458AB60-BA1C-43D9-8CEF-EC01B50BE87B}</Project> + <Name>DotNetOpenAuth.OpenId.RelyingParty</Name> + </ProjectReference> + <ProjectReference Include="..\DotNetOpenAuth.OpenId.UI\DotNetOpenAuth.OpenId.UI.csproj"> + <Project>{75E13AAE-7D51-4421-ABFD-3F3DC91F576E}</Project> + <Name>DotNetOpenAuth.OpenId.UI</Name> + </ProjectReference> + <ProjectReference Include="..\DotNetOpenAuth.OpenId\DotNetOpenAuth.OpenId.csproj"> + <Project>{3896A32A-E876-4C23-B9B8-78E17D134CD3}</Project> + <Name>DotNetOpenAuth.OpenId</Name> + </ProjectReference> + <ProjectReference Include="..\Org.Mentalis.Security.Cryptography\Org.Mentalis.Security.Cryptography.csproj"> + <Project>{26DC877F-5987-48DD-9DDB-E62F2DE0E150}</Project> + <Name>Org.Mentalis.Security.Cryptography</Name> + </ProjectReference> + </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))' != '' " /> +</Project>
\ No newline at end of file diff --git a/src/DotNetOpenAuth.OpenIdInfoCard.UI/OpenId/RelyingParty/OpenIdInfoCardSelector.cs b/src/DotNetOpenAuth.OpenIdInfoCard.UI/OpenId/RelyingParty/OpenIdInfoCardSelector.cs new file mode 100644 index 0000000..8eb399b --- /dev/null +++ b/src/DotNetOpenAuth.OpenIdInfoCard.UI/OpenId/RelyingParty/OpenIdInfoCardSelector.cs @@ -0,0 +1,125 @@ +//----------------------------------------------------------------------- +// <copyright file="OpenIdInfoCardSelector.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +[assembly: System.Web.UI.WebResource(DotNetOpenAuth.OpenId.RelyingParty.OpenIdSelector.EmbeddedScriptResourceName, "text/javascript")] +[assembly: System.Web.UI.WebResource(DotNetOpenAuth.OpenId.RelyingParty.OpenIdSelector.EmbeddedStylesheetResourceName, "text/css")] + +namespace DotNetOpenAuth.OpenId.RelyingParty { + using System; + using System.Collections.Generic; + using System.Collections.ObjectModel; + using System.ComponentModel; + using System.Diagnostics.Contracts; + using System.Globalization; + using System.IdentityModel.Claims; + using System.Linq; + using System.Text; + using System.Web; + using System.Web.UI; + using System.Web.UI.HtmlControls; + using System.Web.UI.WebControls; + using DotNetOpenAuth.ComponentModel; + using DotNetOpenAuth.InfoCard; + ////using DotNetOpenAuth.InfoCard; + using DotNetOpenAuth.Messaging; + + /// <summary> + /// An ASP.NET control that provides a user-friendly way of logging into a web site using OpenID. + /// </summary> + [ToolboxData("<{0}:OpenIdSelector runat=\"server\"></{0}:OpenIdSelector>")] + public class OpenIdInfoCardSelector : OpenIdSelector { + /// <summary> + /// The InfoCard selector button. + /// </summary> + private SelectorInfoCardButton selectorButton; + + /// <summary> + /// Occurs when an InfoCard has been submitted and decoded. + /// </summary> + public event EventHandler<ReceivedTokenEventArgs> ReceivedToken; + + /// <summary> + /// Occurs when [token processing error]. + /// </summary> + public event EventHandler<TokenProcessingErrorEventArgs> TokenProcessingError; + + /// <summary> + /// Ensures that the child controls have been built, but doesn't set control + /// properties that require executing <see cref="Control.EnsureID"/> in order to avoid + /// certain initialization order problems. + /// </summary> + /// <remarks> + /// We don't just call EnsureChildControls() and then set the property on + /// this.textBox itself because (apparently) setting this property in the ASPX + /// page and thus calling this EnsureID() via EnsureChildControls() this early + /// results in no ID. + /// </remarks> + protected override void EnsureChildControlsAreCreatedSafe() { + if (this.selectorButton == null) { + this.selectorButton = this.Buttons.OfType<SelectorInfoCardButton>().FirstOrDefault(); + if (this.selectorButton != null) { + var selector = this.selectorButton.InfoCardSelector; + selector.ClaimsRequested.Add(new ClaimType { Name = ClaimTypes.PPID }); + selector.ImageSize = InfoCardImageSize.Size60x42; + selector.ReceivedToken += this.InfoCardSelector_ReceivedToken; + selector.TokenProcessingError += this.InfoCardSelector_TokenProcessingError; + this.Controls.Add(selector); + } + } + + base.EnsureChildControlsAreCreatedSafe(); + } + + /// <summary> + /// Fires the <see cref="ReceivedToken"/> event. + /// </summary> + /// <param name="e">The token, if it was decrypted.</param> + protected virtual void OnReceivedToken(ReceivedTokenEventArgs e) { + Contract.Requires(e != null); + ErrorUtilities.VerifyArgumentNotNull(e, "e"); + + var receivedInfoCard = this.ReceivedToken; + if (receivedInfoCard != null) { + receivedInfoCard(this, e); + } + } + + /// <summary> + /// Raises the <see cref="E:TokenProcessingError"/> event. + /// </summary> + /// <param name="e">The <see cref="DotNetOpenAuth.InfoCard.TokenProcessingErrorEventArgs"/> instance containing the event data.</param> + protected virtual void OnTokenProcessingError(TokenProcessingErrorEventArgs e) { + Contract.Requires(e != null); + ErrorUtilities.VerifyArgumentNotNull(e, "e"); + + var tokenProcessingError = this.TokenProcessingError; + if (tokenProcessingError != null) { + tokenProcessingError(this, e); + } + } + + /// <summary> + /// Handles the ReceivedToken event of the infoCardSelector control. + /// </summary> + /// <param name="sender">The source of the event.</param> + /// <param name="e">The <see cref="DotNetOpenAuth.InfoCard.ReceivedTokenEventArgs"/> instance containing the event data.</param> + private void InfoCardSelector_ReceivedToken(object sender, ReceivedTokenEventArgs e) { + this.Page.Response.SetCookie(new HttpCookie("openid_identifier", "infocard") { + Path = this.Page.Request.ApplicationPath, + }); + this.OnReceivedToken(e); + } + + /// <summary> + /// Handles the TokenProcessingError event of the infoCardSelector control. + /// </summary> + /// <param name="sender">The source of the event.</param> + /// <param name="e">The <see cref="DotNetOpenAuth.InfoCard.TokenProcessingErrorEventArgs"/> instance containing the event data.</param> + private void InfoCardSelector_TokenProcessingError(object sender, TokenProcessingErrorEventArgs e) { + this.OnTokenProcessingError(e); + } + } +} diff --git a/src/DotNetOpenAuth.OpenIdInfoCard.UI/OpenId/RelyingParty/SelectorInfoCardButton.cs b/src/DotNetOpenAuth.OpenIdInfoCard.UI/OpenId/RelyingParty/SelectorInfoCardButton.cs new file mode 100644 index 0000000..863c90a --- /dev/null +++ b/src/DotNetOpenAuth.OpenIdInfoCard.UI/OpenId/RelyingParty/SelectorInfoCardButton.cs @@ -0,0 +1,102 @@ +//----------------------------------------------------------------------- +// <copyright file="SelectorInfoCardButton.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.RelyingParty { + using System; + using System.Collections.ObjectModel; + using System.ComponentModel; + using System.Diagnostics.Contracts; + using System.Web.UI; + using DotNetOpenAuth.InfoCard; + + /// <summary> + /// A button that appears in the <see cref="OpenIdSelector"/> control that + /// activates the Information Card selector on the browser, if one is available. + /// </summary> + public class SelectorInfoCardButton : SelectorButton, IDisposable { + /// <summary> + /// The backing field for the <see cref="InfoCardSelector"/> property. + /// </summary> + private InfoCardSelector infoCardSelector; + + /// <summary> + /// Initializes a new instance of the <see cref="SelectorInfoCardButton"/> class. + /// </summary> + public SelectorInfoCardButton() { + Reporting.RecordFeatureUse(this); + } + + /// <summary> + /// Gets or sets the InfoCard selector which may be displayed alongside the OP buttons. + /// </summary> + [PersistenceMode(PersistenceMode.InnerProperty)] + public InfoCardSelector InfoCardSelector { + get { + if (this.infoCardSelector == null) { + this.infoCardSelector = new InfoCardSelector(); + } + + return this.infoCardSelector; + } + + set { + Requires.NotNull(value, "value"); + if (this.infoCardSelector != null) { + Logger.Library.WarnFormat("{0}.InfoCardSelector property is being set multiple times.", GetType().Name); + } + + this.infoCardSelector = value; + } + } + + #region IDisposable Members + + /// <summary> + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// </summary> + public void Dispose() { + this.Dispose(true); + GC.SuppressFinalize(this); + } + + #endregion + + /// <summary> + /// Ensures that this button has been initialized to a valid state. + /// </summary> + internal override void EnsureValid() { + } + + /// <summary> + /// Renders the leading attributes for the LI tag. + /// </summary> + /// <param name="writer">The writer.</param> + protected internal override void RenderLeadingAttributes(HtmlTextWriter writer) { + writer.AddAttribute(HtmlTextWriterAttribute.Class, "infocard"); + } + + /// <summary> + /// Renders the content of the button. + /// </summary> + /// <param name="writer">The writer.</param> + /// <param name="selector">The containing selector control.</param> + protected internal override void RenderButtonContent(HtmlTextWriter writer, OpenIdSelector selector) { + this.InfoCardSelector.RenderControl(writer); + } + + /// <summary> + /// Releases unmanaged and - optionally - managed resources + /// </summary> + /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param> + protected virtual void Dispose(bool disposing) { + if (disposing) { + if (this.infoCardSelector != null) { + this.infoCardSelector.Dispose(); + } + } + } + } +} diff --git a/src/DotNetOpenAuth.OpenIdInfoCard.UI/Properties/AssemblyInfo.cs b/src/DotNetOpenAuth.OpenIdInfoCard.UI/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..8700856 --- /dev/null +++ b/src/DotNetOpenAuth.OpenIdInfoCard.UI/Properties/AssemblyInfo.cs @@ -0,0 +1,84 @@ +//----------------------------------------------------------------------- +// <copyright file="AssemblyInfo.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +// We DON'T put an AssemblyVersionAttribute in here because it is generated in the build. + +using System; +using System.Diagnostics.Contracts; +using System.Net; +using System.Reflection; +using System.Resources; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Security; +using System.Security.Permissions; +using System.Web.UI; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("DotNetOpenAuth")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("DotNetOpenAuth")] +[assembly: AssemblyCopyright("Copyright © 2008")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: NeutralResourcesLanguage("en-US")] +[assembly: CLSCompliant(true)] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("7d73990c-47c0-4256-9f20-a893add9e289")] + +[assembly: ContractVerification(true)] + +#if StrongNameSigned +// See comment at top of this file. We need this so that strong-naming doesn't +// keep this assembly from being useful to shared host (medium trust) web sites. +[assembly: AllowPartiallyTrustedCallers] + +[assembly: InternalsVisibleTo("DotNetOpenAuth.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100AD093C3765257C89A7010E853F2C7C741FF92FA8ACE06D7B8254702CAD5CF99104447F63AB05F8BB6F51CE0D81C8C93D2FCE8C20AAFF7042E721CBA16EAAE98778611DED11C0ABC8900DC5667F99B50A9DADEC24DBD8F2C91E3E8AD300EF64F1B4B9536CEB16FB440AF939F57624A9B486F867807C649AE4830EAB88C6C03998")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.InfoCard, PublicKey=0024000004800000940000000602000000240000525341310004000001000100AD093C3765257C89A7010E853F2C7C741FF92FA8ACE06D7B8254702CAD5CF99104447F63AB05F8BB6F51CE0D81C8C93D2FCE8C20AAFF7042E721CBA16EAAE98778611DED11C0ABC8900DC5667F99B50A9DADEC24DBD8F2C91E3E8AD300EF64F1B4B9536CEB16FB440AF939F57624A9B486F867807C649AE4830EAB88C6C03998")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.InfoCard.UI, PublicKey=0024000004800000940000000602000000240000525341310004000001000100AD093C3765257C89A7010E853F2C7C741FF92FA8ACE06D7B8254702CAD5CF99104447F63AB05F8BB6F51CE0D81C8C93D2FCE8C20AAFF7042E721CBA16EAAE98778611DED11C0ABC8900DC5667F99B50A9DADEC24DBD8F2C91E3E8AD300EF64F1B4B9536CEB16FB440AF939F57624A9B486F867807C649AE4830EAB88C6C03998")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.OpenId, PublicKey=0024000004800000940000000602000000240000525341310004000001000100AD093C3765257C89A7010E853F2C7C741FF92FA8ACE06D7B8254702CAD5CF99104447F63AB05F8BB6F51CE0D81C8C93D2FCE8C20AAFF7042E721CBA16EAAE98778611DED11C0ABC8900DC5667F99B50A9DADEC24DBD8F2C91E3E8AD300EF64F1B4B9536CEB16FB440AF939F57624A9B486F867807C649AE4830EAB88C6C03998")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.OpenId.UI, PublicKey=0024000004800000940000000602000000240000525341310004000001000100AD093C3765257C89A7010E853F2C7C741FF92FA8ACE06D7B8254702CAD5CF99104447F63AB05F8BB6F51CE0D81C8C93D2FCE8C20AAFF7042E721CBA16EAAE98778611DED11C0ABC8900DC5667F99B50A9DADEC24DBD8F2C91E3E8AD300EF64F1B4B9536CEB16FB440AF939F57624A9B486F867807C649AE4830EAB88C6C03998")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.OpenId.RelyingParty, PublicKey=0024000004800000940000000602000000240000525341310004000001000100AD093C3765257C89A7010E853F2C7C741FF92FA8ACE06D7B8254702CAD5CF99104447F63AB05F8BB6F51CE0D81C8C93D2FCE8C20AAFF7042E721CBA16EAAE98778611DED11C0ABC8900DC5667F99B50A9DADEC24DBD8F2C91E3E8AD300EF64F1B4B9536CEB16FB440AF939F57624A9B486F867807C649AE4830EAB88C6C03998")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.OpenId.RelyingParty.UI, PublicKey=0024000004800000940000000602000000240000525341310004000001000100AD093C3765257C89A7010E853F2C7C741FF92FA8ACE06D7B8254702CAD5CF99104447F63AB05F8BB6F51CE0D81C8C93D2FCE8C20AAFF7042E721CBA16EAAE98778611DED11C0ABC8900DC5667F99B50A9DADEC24DBD8F2C91E3E8AD300EF64F1B4B9536CEB16FB440AF939F57624A9B486F867807C649AE4830EAB88C6C03998")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.OpenId.Provider, PublicKey=0024000004800000940000000602000000240000525341310004000001000100AD093C3765257C89A7010E853F2C7C741FF92FA8ACE06D7B8254702CAD5CF99104447F63AB05F8BB6F51CE0D81C8C93D2FCE8C20AAFF7042E721CBA16EAAE98778611DED11C0ABC8900DC5667F99B50A9DADEC24DBD8F2C91E3E8AD300EF64F1B4B9536CEB16FB440AF939F57624A9B486F867807C649AE4830EAB88C6C03998")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.OpenId.Provider.UI, PublicKey=0024000004800000940000000602000000240000525341310004000001000100AD093C3765257C89A7010E853F2C7C741FF92FA8ACE06D7B8254702CAD5CF99104447F63AB05F8BB6F51CE0D81C8C93D2FCE8C20AAFF7042E721CBA16EAAE98778611DED11C0ABC8900DC5667F99B50A9DADEC24DBD8F2C91E3E8AD300EF64F1B4B9536CEB16FB440AF939F57624A9B486F867807C649AE4830EAB88C6C03998")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.OAuth, PublicKey=0024000004800000940000000602000000240000525341310004000001000100AD093C3765257C89A7010E853F2C7C741FF92FA8ACE06D7B8254702CAD5CF99104447F63AB05F8BB6F51CE0D81C8C93D2FCE8C20AAFF7042E721CBA16EAAE98778611DED11C0ABC8900DC5667F99B50A9DADEC24DBD8F2C91E3E8AD300EF64F1B4B9536CEB16FB440AF939F57624A9B486F867807C649AE4830EAB88C6C03998")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.OAuth.Consumer, PublicKey=0024000004800000940000000602000000240000525341310004000001000100AD093C3765257C89A7010E853F2C7C741FF92FA8ACE06D7B8254702CAD5CF99104447F63AB05F8BB6F51CE0D81C8C93D2FCE8C20AAFF7042E721CBA16EAAE98778611DED11C0ABC8900DC5667F99B50A9DADEC24DBD8F2C91E3E8AD300EF64F1B4B9536CEB16FB440AF939F57624A9B486F867807C649AE4830EAB88C6C03998")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.OAuth.ServiceProvider, PublicKey=0024000004800000940000000602000000240000525341310004000001000100AD093C3765257C89A7010E853F2C7C741FF92FA8ACE06D7B8254702CAD5CF99104447F63AB05F8BB6F51CE0D81C8C93D2FCE8C20AAFF7042E721CBA16EAAE98778611DED11C0ABC8900DC5667F99B50A9DADEC24DBD8F2C91E3E8AD300EF64F1B4B9536CEB16FB440AF939F57624A9B486F867807C649AE4830EAB88C6C03998")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.OAuth2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100AD093C3765257C89A7010E853F2C7C741FF92FA8ACE06D7B8254702CAD5CF99104447F63AB05F8BB6F51CE0D81C8C93D2FCE8C20AAFF7042E721CBA16EAAE98778611DED11C0ABC8900DC5667F99B50A9DADEC24DBD8F2C91E3E8AD300EF64F1B4B9536CEB16FB440AF939F57624A9B486F867807C649AE4830EAB88C6C03998")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.OAuth2.AuthorizationServer, PublicKey=0024000004800000940000000602000000240000525341310004000001000100AD093C3765257C89A7010E853F2C7C741FF92FA8ACE06D7B8254702CAD5CF99104447F63AB05F8BB6F51CE0D81C8C93D2FCE8C20AAFF7042E721CBA16EAAE98778611DED11C0ABC8900DC5667F99B50A9DADEC24DBD8F2C91E3E8AD300EF64F1B4B9536CEB16FB440AF939F57624A9B486F867807C649AE4830EAB88C6C03998")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.OAuth2.ResourceServer, PublicKey=0024000004800000940000000602000000240000525341310004000001000100AD093C3765257C89A7010E853F2C7C741FF92FA8ACE06D7B8254702CAD5CF99104447F63AB05F8BB6F51CE0D81C8C93D2FCE8C20AAFF7042E721CBA16EAAE98778611DED11C0ABC8900DC5667F99B50A9DADEC24DBD8F2C91E3E8AD300EF64F1B4B9536CEB16FB440AF939F57624A9B486F867807C649AE4830EAB88C6C03998")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.OAuth2.Client, PublicKey=0024000004800000940000000602000000240000525341310004000001000100AD093C3765257C89A7010E853F2C7C741FF92FA8ACE06D7B8254702CAD5CF99104447F63AB05F8BB6F51CE0D81C8C93D2FCE8C20AAFF7042E721CBA16EAAE98778611DED11C0ABC8900DC5667F99B50A9DADEC24DBD8F2C91E3E8AD300EF64F1B4B9536CEB16FB440AF939F57624A9B486F867807C649AE4830EAB88C6C03998")] +[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")] +#else +[assembly: InternalsVisibleTo("DotNetOpenAuth.Test")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.InfoCard")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.InfoCard.UI")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.OpenId")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.OpenId.UI")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.OpenId.RelyingParty")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.OpenId.RelyingParty.UI")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.OpenId.Provider")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.OpenId.Provider.UI")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.OAuth")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.OAuth.Consumer")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.OAuth.ServiceProvider")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.OAuth2")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.OAuth2.AuthorizationServer")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.OAuth2.ResourceServer")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.OAuth2.Client")] +[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] +#endif diff --git a/src/DotNetOpenAuth.Test/App.config b/src/DotNetOpenAuth.Test/App.config index b3da723..d0eb34e 100644 --- a/src/DotNetOpenAuth.Test/App.config +++ b/src/DotNetOpenAuth.Test/App.config @@ -2,7 +2,12 @@ <configuration> <configSections> <section name="uri" type="System.Configuration.UriSection, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"/> - <section name="dotNetOpenAuth" type="DotNetOpenAuth.Configuration.DotNetOpenAuthSection, DotNetOpenAuth"/> + <sectionGroup name="dotNetOpenAuth" type="DotNetOpenAuth.Configuration.DotNetOpenAuthSection, DotNetOpenAuth.Core"> + <section name="openid" type="DotNetOpenAuth.Configuration.OpenIdElement, DotNetOpenAuth.OpenId" requirePermission="false" allowLocation="true" /> + <section name="oauth" type="DotNetOpenAuth.Configuration.OAuthElement, DotNetOpenAuth.OAuth" requirePermission="false" allowLocation="true" /> + <section name="messaging" type="DotNetOpenAuth.Configuration.MessagingElement, DotNetOpenAuth.Core" requirePermission="false" allowLocation="true" /> + <section name="reporting" type="DotNetOpenAuth.Configuration.ReportingElement, DotNetOpenAuth.Core" requirePermission="false" allowLocation="true" /> + </sectionGroup> </configSections> <!-- The uri section is necessary to turn on .NET 3.5 support for IDN (international domain names), diff --git a/src/DotNetOpenAuth.Test/Configuration/SectionTests.cs b/src/DotNetOpenAuth.Test/Configuration/SectionTests.cs index e423053..0cff4b7 100644 --- a/src/DotNetOpenAuth.Test/Configuration/SectionTests.cs +++ b/src/DotNetOpenAuth.Test/Configuration/SectionTests.cs @@ -15,7 +15,7 @@ namespace DotNetOpenAuth.Test.Configuration { public class SectionTests { [TestCase] public void UntrustedWebRequest() { - var uwr = DotNetOpenAuthSection.Configuration.Messaging.UntrustedWebRequest; + var uwr = DotNetOpenAuthSection.Messaging.UntrustedWebRequest; Assert.AreEqual(TimeSpan.Parse("01:23:45"), uwr.Timeout); Assert.AreEqual(TimeSpan.Parse("01:23:56"), uwr.ReadWriteTimeout); @@ -31,12 +31,12 @@ namespace DotNetOpenAuth.Test.Configuration { [TestCase] public void OpenIdMaxAuthenticationTime() { - Assert.AreEqual(TimeSpan.Parse("00:08:17"), DotNetOpenAuthSection.Configuration.OpenId.MaxAuthenticationTime); + Assert.AreEqual(TimeSpan.Parse("00:08:17"), OpenIdElement.Configuration.MaxAuthenticationTime); } [TestCase] public void OpenIdRelyingParty() { - var rp = DotNetOpenAuthSection.Configuration.OpenId.RelyingParty; + var rp = OpenIdElement.Configuration.RelyingParty; Assert.IsNull(rp.ApplicationStore.CustomType); Assert.AreEqual(ProtocolVersion.V10, rp.SecuritySettings.MinimumRequiredOpenIdVersion); @@ -47,7 +47,7 @@ namespace DotNetOpenAuth.Test.Configuration { [TestCase] public void OpenIdProvider() { - var op = DotNetOpenAuthSection.Configuration.OpenId.Provider; + var op = OpenIdElement.Configuration.Provider; Assert.IsNull(op.ApplicationStore.CustomType); Assert.IsTrue(op.SecuritySettings.ProtectDownlevelReplayAttacks); diff --git a/src/DotNetOpenAuth.Test/CoordinatorBase.cs b/src/DotNetOpenAuth.Test/CoordinatorBase.cs index f25964c..84d0ff1 100644 --- a/src/DotNetOpenAuth.Test/CoordinatorBase.cs +++ b/src/DotNetOpenAuth.Test/CoordinatorBase.cs @@ -18,8 +18,8 @@ namespace DotNetOpenAuth.Test { private Action<T2> party2Action; protected CoordinatorBase(Action<T1> party1Action, Action<T2> party2Action) { - Contract.Requires<ArgumentNullException>(party1Action != null); - Contract.Requires<ArgumentNullException>(party2Action != null); + Requires.NotNull(party1Action, "party1Action"); + Requires.NotNull(party2Action, "party2Action"); this.party1Action = party1Action; this.party2Action = party2Action; diff --git a/src/DotNetOpenAuth.Test/DotNetOpenAuth.Test.csproj b/src/DotNetOpenAuth.Test/DotNetOpenAuth.Test.csproj index 9bae939..46556f7 100644 --- a/src/DotNetOpenAuth.Test/DotNetOpenAuth.Test.csproj +++ b/src/DotNetOpenAuth.Test/DotNetOpenAuth.Test.csproj @@ -5,7 +5,7 @@ <ProjectRoot Condition="'$(ProjectRoot)' == ''">$(MSBuildProjectDirectory)\..\..\</ProjectRoot> <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> - <CodeContractsAssemblyMode>1</CodeContractsAssemblyMode> + <CodeContractsAssemblyMode>0</CodeContractsAssemblyMode> </PropertyGroup> <Import Project="$(ProjectRoot)tools\DotNetOpenAuth.props" /> <PropertyGroup> @@ -44,7 +44,7 @@ <DefineConstants>DEBUG;TRACE</DefineConstants> <ErrorReport>prompt</ErrorReport> <WarningLevel>4</WarningLevel> - <CodeContractsEnableRuntimeChecking>True</CodeContractsEnableRuntimeChecking> + <CodeContractsEnableRuntimeChecking>False</CodeContractsEnableRuntimeChecking> <CodeContractsCustomRewriterAssembly> </CodeContractsCustomRewriterAssembly> <CodeContractsCustomRewriterClass> @@ -75,6 +75,10 @@ <CodeContractsExtraRewriteOptions /> <CodeContractsReferenceAssembly>%28none%29</CodeContractsReferenceAssembly> <AllowUnsafeBlocks>true</AllowUnsafeBlocks> + <CodeContractsRuntimeSkipQuantifiers>False</CodeContractsRuntimeSkipQuantifiers> + <CodeContractsEnumObligations>False</CodeContractsEnumObligations> + <CodeContractsCacheAnalysisResults>False</CodeContractsCacheAnalysisResults> + <CodeContractsAnalysisWarningLevel>0</CodeContractsAnalysisWarningLevel> </PropertyGroup> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> <DebugType>pdbonly</DebugType> @@ -82,12 +86,12 @@ <DefineConstants>TRACE</DefineConstants> <ErrorReport>prompt</ErrorReport> <WarningLevel>4</WarningLevel> - <CodeContractsEnableRuntimeChecking>True</CodeContractsEnableRuntimeChecking> + <CodeContractsEnableRuntimeChecking>False</CodeContractsEnableRuntimeChecking> <CodeContractsCustomRewriterAssembly> </CodeContractsCustomRewriterAssembly> <CodeContractsCustomRewriterClass> </CodeContractsCustomRewriterClass> - <CodeContractsRuntimeCheckingLevel>None</CodeContractsRuntimeCheckingLevel> + <CodeContractsRuntimeCheckingLevel>Full</CodeContractsRuntimeCheckingLevel> <CodeContractsRuntimeOnlyPublicSurface>False</CodeContractsRuntimeOnlyPublicSurface> <CodeContractsRuntimeThrowOnFailure>True</CodeContractsRuntimeThrowOnFailure> <CodeContractsRuntimeCallSiteRequires>False</CodeContractsRuntimeCallSiteRequires> @@ -113,6 +117,10 @@ <CodeContractsExtraRewriteOptions /> <CodeContractsReferenceAssembly>%28none%29</CodeContractsReferenceAssembly> <AllowUnsafeBlocks>true</AllowUnsafeBlocks> + <CodeContractsRuntimeSkipQuantifiers>False</CodeContractsRuntimeSkipQuantifiers> + <CodeContractsEnumObligations>False</CodeContractsEnumObligations> + <CodeContractsCacheAnalysisResults>False</CodeContractsCacheAnalysisResults> + <CodeContractsAnalysisWarningLevel>0</CodeContractsAnalysisWarningLevel> </PropertyGroup> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'CodeAnalysis|AnyCPU' "> <DebugSymbols>true</DebugSymbols> @@ -175,6 +183,7 @@ </Reference> <Reference Include="System.ServiceModel.Web" /> <Reference Include="System.Web" /> + <Reference Include="System.Web.Abstractions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL" Condition=" '$(ClrVersion)' == '2' " /> <Reference Include="System.Xml" /> <Reference Include="System.Xml.Linq"> <RequiredTargetFramework>3.5</RequiredTargetFramework> @@ -210,6 +219,7 @@ <Compile Include="Mocks\CoordinatingChannel.cs" /> <Compile Include="Mocks\CoordinatingHttpRequestInfo.cs" /> <Compile Include="Mocks\CoordinatingOutgoingWebResponse.cs" /> + <Compile Include="Mocks\CoordinatingOAuthConsumerChannel.cs" /> <Compile Include="Mocks\InMemoryTokenManager.cs" /> <Compile Include="Mocks\MockHttpRequest.cs" /> <Compile Include="Mocks\MockIdentifier.cs" /> @@ -315,7 +325,7 @@ <Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Messaging\ResponseTests.cs" /> <Compile Include="OAuth\AppendixScenarios.cs" /> - <Compile Include="Mocks\CoordinatingOAuthChannel.cs" /> + <Compile Include="Mocks\CoordinatingOAuthServiceProviderChannel.cs" /> <Compile Include="OAuth\OAuthCoordinator.cs" /> <Compile Include="TestBase.cs" /> <Compile Include="TestUtilities.cs" /> @@ -323,12 +333,6 @@ <Compile Include="UtilTests.cs" /> </ItemGroup> <ItemGroup> - <ProjectReference Include="..\DotNetOpenAuth\DotNetOpenAuth.csproj"> - <Project>{3191B653-F76D-4C1A-9A5A-347BC3AAAAB7}</Project> - <Name>DotNetOpenAuth</Name> - </ProjectReference> - </ItemGroup> - <ItemGroup> <EmbeddedResource Include="Logging.config" /> </ItemGroup> <ItemGroup> @@ -395,7 +399,89 @@ <ItemGroup> <EmbeddedResource Include="OpenId\Discovery\htmldiscovery\html20provWithBadXrds.html" /> </ItemGroup> + <ItemGroup> + <ProjectReference Include="..\DotNetOpenAuth.InfoCard.UI\DotNetOpenAuth.InfoCard.UI.csproj"> + <Project>{E040EB58-B4D2-457B-A023-AE6EF3BD34DE}</Project> + <Name>DotNetOpenAuth.InfoCard.UI</Name> + </ProjectReference> + <ProjectReference Include="..\DotNetOpenAuth.InfoCard\DotNetOpenAuth.InfoCard.csproj"> + <Project>{408D10B8-34BA-4CBD-B7AA-FEB1907ABA4C}</Project> + <Name>DotNetOpenAuth.InfoCard</Name> + </ProjectReference> + <ProjectReference Include="..\DotNetOpenAuth.Core.UI\DotNetOpenAuth.Core.UI.csproj"> + <Project>{173E7B8D-E751-46E2-A133-F72297C0D2F4}</Project> + <Name>DotNetOpenAuth.Core.UI</Name> + </ProjectReference> + <ProjectReference Include="..\DotNetOpenAuth.Core\DotNetOpenAuth.Core.csproj"> + <Project>{60426312-6AE5-4835-8667-37EDEA670222}</Project> + <Name>DotNetOpenAuth.Core</Name> + </ProjectReference> + <ProjectReference Include="..\DotNetOpenAuth.OAuth.Consumer\DotNetOpenAuth.OAuth.Consumer.csproj"> + <Project>{B202E40D-4663-4A2B-ACDA-865F88FF7CAA}</Project> + <Name>DotNetOpenAuth.OAuth.Consumer</Name> + </ProjectReference> + <ProjectReference Include="..\DotNetOpenAuth.OAuth.ServiceProvider\DotNetOpenAuth.OAuth.ServiceProvider.csproj"> + <Project>{FED1923A-6D70-49B5-A37A-FB744FEC1C86}</Project> + <Name>DotNetOpenAuth.OAuth.ServiceProvider</Name> + </ProjectReference> + <ProjectReference Include="..\DotNetOpenAuth.OAuth2.AuthorizationServer\DotNetOpenAuth.OAuth2.AuthorizationServer.csproj"> + <Project>{99BB7543-EA16-43EE-A7BC-D7A25A3B22F6}</Project> + <Name>DotNetOpenAuth.OAuth2.AuthorizationServer</Name> + </ProjectReference> + <ProjectReference Include="..\DotNetOpenAuth.OAuth2.Client.UI\DotNetOpenAuth.OAuth2.Client.UI.csproj"> + <Project>{ADC2CC8C-541E-4F86-ACB1-DD504A36FA4B}</Project> + <Name>DotNetOpenAuth.OAuth2.Client.UI</Name> + </ProjectReference> + <ProjectReference Include="..\DotNetOpenAuth.OAuth2.Client\DotNetOpenAuth.OAuth2.Client.csproj"> + <Project>{CDEDD439-7F35-4E6E-8605-4E70BDC4CC99}</Project> + <Name>DotNetOpenAuth.OAuth2.Client</Name> + </ProjectReference> + <ProjectReference Include="..\DotNetOpenAuth.OAuth2.ResourceServer\DotNetOpenAuth.OAuth2.ResourceServer.csproj"> + <Project>{A1A3150A-7B0E-4A34-8E35-045296CD3C76}</Project> + <Name>DotNetOpenAuth.OAuth2.ResourceServer</Name> + </ProjectReference> + <ProjectReference Include="..\DotNetOpenAuth.OAuth2\DotNetOpenAuth.OAuth2.csproj"> + <Project>{56459A6C-6BA2-4BAC-A9C0-27E3BD961FA6}</Project> + <Name>DotNetOpenAuth.OAuth2</Name> + </ProjectReference> + <ProjectReference Include="..\DotNetOpenAuth.OAuth\DotNetOpenAuth.OAuth.csproj"> + <Project>{A288FCC8-6FCF-46DA-A45E-5F9281556361}</Project> + <Name>DotNetOpenAuth.OAuth</Name> + </ProjectReference> + <ProjectReference Include="..\DotNetOpenAuth.OpenId.Provider.UI\DotNetOpenAuth.OpenId.Provider.UI.csproj"> + <Project>{9D0F8866-2131-4C2A-BC0E-16FEA5B50828}</Project> + <Name>DotNetOpenAuth.OpenId.Provider.UI</Name> + </ProjectReference> + <ProjectReference Include="..\DotNetOpenAuth.OpenId.Provider\DotNetOpenAuth.OpenId.Provider.csproj"> + <Project>{F8284738-3B5D-4733-A511-38C23F4A763F}</Project> + <Name>DotNetOpenAuth.OpenId.Provider</Name> + </ProjectReference> + <ProjectReference Include="..\DotNetOpenAuth.OpenId.RelyingParty.UI\DotNetOpenAuth.OpenId.RelyingParty.UI.csproj"> + <Project>{1ED8D424-F8AB-4050-ACEB-F27F4F909484}</Project> + <Name>DotNetOpenAuth.OpenId.RelyingParty.UI</Name> + </ProjectReference> + <ProjectReference Include="..\DotNetOpenAuth.OpenId.RelyingParty\DotNetOpenAuth.OpenId.RelyingParty.csproj"> + <Project>{F458AB60-BA1C-43D9-8CEF-EC01B50BE87B}</Project> + <Name>DotNetOpenAuth.OpenId.RelyingParty</Name> + </ProjectReference> + <ProjectReference Include="..\DotNetOpenAuth.OpenId.UI\DotNetOpenAuth.OpenId.UI.csproj"> + <Project>{75E13AAE-7D51-4421-ABFD-3F3DC91F576E}</Project> + <Name>DotNetOpenAuth.OpenId.UI</Name> + </ProjectReference> + <ProjectReference Include="..\DotNetOpenAuth.OpenId\DotNetOpenAuth.OpenId.csproj"> + <Project>{3896A32A-E876-4C23-B9B8-78E17D134CD3}</Project> + <Name>DotNetOpenAuth.OpenId</Name> + </ProjectReference> + <ProjectReference Include="..\Mono.Math\Mono.Math.csproj"> + <Project>{F4CD3C04-6037-4946-B7A5-34BFC96A75D2}</Project> + <Name>Mono.Math</Name> + </ProjectReference> + <ProjectReference Include="..\Org.Mentalis.Security.Cryptography\Org.Mentalis.Security.Cryptography.csproj"> + <Project>{26DC877F-5987-48DD-9DDB-E62F2DE0E150}</Project> + <Name>Org.Mentalis.Security.Cryptography</Name> + </ProjectReference> + </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))' != '' " /> -</Project>
\ No newline at end of file +</Project> diff --git a/src/DotNetOpenAuth.Test/Messaging/Bindings/StandardExpirationBindingElementTests.cs b/src/DotNetOpenAuth.Test/Messaging/Bindings/StandardExpirationBindingElementTests.cs index 84b6654..2c3cae2 100644 --- a/src/DotNetOpenAuth.Test/Messaging/Bindings/StandardExpirationBindingElementTests.cs +++ b/src/DotNetOpenAuth.Test/Messaging/Bindings/StandardExpirationBindingElementTests.cs @@ -35,7 +35,7 @@ namespace DotNetOpenAuth.Test.Messaging.Bindings { [TestCase] public void VerifyFutureTimestampWithinClockSkewIsAccepted() { this.Channel = CreateChannel(MessageProtections.Expiration); - this.ParameterizedReceiveProtectedTest(DateTime.UtcNow + DotNetOpenAuthSection.Configuration.Messaging.MaximumClockSkew - TimeSpan.FromSeconds(1), false); + this.ParameterizedReceiveProtectedTest(DateTime.UtcNow + DotNetOpenAuthSection.Messaging.MaximumClockSkew - TimeSpan.FromSeconds(1), false); } [TestCase, ExpectedException(typeof(ExpiredMessageException))] @@ -47,7 +47,7 @@ namespace DotNetOpenAuth.Test.Messaging.Bindings { [TestCase, ExpectedException(typeof(ProtocolException))] public void VerifyFutureTimestampIsRejected() { this.Channel = CreateChannel(MessageProtections.Expiration); - this.ParameterizedReceiveProtectedTest(DateTime.UtcNow + DotNetOpenAuthSection.Configuration.Messaging.MaximumClockSkew + TimeSpan.FromSeconds(2), false); + this.ParameterizedReceiveProtectedTest(DateTime.UtcNow + DotNetOpenAuthSection.Messaging.MaximumClockSkew + TimeSpan.FromSeconds(2), false); } } } diff --git a/src/DotNetOpenAuth.Test/Messaging/CollectionAssert.cs b/src/DotNetOpenAuth.Test/Messaging/CollectionAssert.cs index 506a6b2..af542db 100644 --- a/src/DotNetOpenAuth.Test/Messaging/CollectionAssert.cs +++ b/src/DotNetOpenAuth.Test/Messaging/CollectionAssert.cs @@ -15,8 +15,8 @@ namespace DotNetOpenAuth.Test.Messaging { internal class CollectionAssert<T> { internal static void AreEquivalent(ICollection<T> expected, ICollection<T> actual) { - Contract.Requires<ArgumentNullException>(expected != null); - Contract.Requires<ArgumentNullException>(actual != null); + Requires.NotNull(expected, "expected"); + Requires.NotNull(actual, "actual"); ICollection expectedNonGeneric = new List<T>(expected); ICollection actualNonGeneric = new List<T>(actual); @@ -24,8 +24,8 @@ namespace DotNetOpenAuth.Test.Messaging { } internal static void AreEquivalentByEquality(ICollection<T> expected, ICollection<T> actual) { - Contract.Requires<ArgumentNullException>(expected != null); - Contract.Requires<ArgumentNullException>(actual != null); + Requires.NotNull(expected, "expected"); + Requires.NotNull(actual, "actual"); Assert.AreEqual(expected.Count, actual.Count); foreach (T value in expected) { @@ -34,7 +34,7 @@ namespace DotNetOpenAuth.Test.Messaging { } internal static void Contains(IEnumerable<T> sequence, T element) { - Contract.Requires<ArgumentNullException>(sequence != null); + Requires.NotNull(sequence, "sequence"); if (!sequence.Contains(element)) { Assert.Fail("Sequence did not include expected element '{0}'.", element); diff --git a/src/DotNetOpenAuth.Test/Messaging/MessagingUtilitiesTests.cs b/src/DotNetOpenAuth.Test/Messaging/MessagingUtilitiesTests.cs index 79751ae..2a683ed 100644 --- a/src/DotNetOpenAuth.Test/Messaging/MessagingUtilitiesTests.cs +++ b/src/DotNetOpenAuth.Test/Messaging/MessagingUtilitiesTests.cs @@ -98,7 +98,7 @@ namespace DotNetOpenAuth.Test.Messaging { [TestCase, ExpectedException(typeof(ArgumentNullException))] public void ApplyHeadersToResponseNullAspNetResponse() { - MessagingUtilities.ApplyHeadersToResponse(new WebHeaderCollection(), (HttpResponse)null); + MessagingUtilities.ApplyHeadersToResponse(new WebHeaderCollection(), (HttpResponseBase)null); } [TestCase, ExpectedException(typeof(ArgumentNullException))] @@ -108,7 +108,7 @@ namespace DotNetOpenAuth.Test.Messaging { [TestCase, ExpectedException(typeof(ArgumentNullException))] public void ApplyHeadersToResponseNullHeaders() { - MessagingUtilities.ApplyHeadersToResponse(null, new HttpResponse(new StringWriter())); + MessagingUtilities.ApplyHeadersToResponse(null, new HttpResponseWrapper(new HttpResponse(new StringWriter()))); } [TestCase] @@ -116,7 +116,7 @@ namespace DotNetOpenAuth.Test.Messaging { var headers = new WebHeaderCollection(); headers[HttpResponseHeader.ContentType] = "application/binary"; - var response = new HttpResponse(new StringWriter()); + var response = new HttpResponseWrapper(new HttpResponse(new StringWriter())); MessagingUtilities.ApplyHeadersToResponse(headers, response); Assert.AreEqual(headers[HttpResponseHeader.ContentType], response.ContentType); diff --git a/src/DotNetOpenAuth.Test/Mocks/CoordinatingChannel.cs b/src/DotNetOpenAuth.Test/Mocks/CoordinatingChannel.cs index 99520a2..4d6f02b 100644 --- a/src/DotNetOpenAuth.Test/Mocks/CoordinatingChannel.cs +++ b/src/DotNetOpenAuth.Test/Mocks/CoordinatingChannel.cs @@ -85,7 +85,7 @@ namespace DotNetOpenAuth.Test.Mocks { /// <param name="outgoingMessageFilter">The outgoing message filter. May be null.</param> internal CoordinatingChannel(Channel wrappedChannel, Action<IProtocolMessage> incomingMessageFilter, Action<IProtocolMessage> outgoingMessageFilter) : base(GetMessageFactory(wrappedChannel), wrappedChannel.BindingElements.ToArray()) { - Contract.Requires<ArgumentNullException>(wrappedChannel != null); + Requires.NotNull(wrappedChannel, "wrappedChannel"); this.wrappedChannel = wrappedChannel; this.incomingMessageFilter = incomingMessageFilter; @@ -219,7 +219,7 @@ namespace DotNetOpenAuth.Test.Mocks { /// channel since their message factory is not used. /// </remarks> protected virtual T CloneSerializedParts<T>(T message) where T : class, IProtocolMessage { - Contract.Requires<ArgumentNullException>(message != null); + Requires.NotNull(message, "message"); IProtocolMessage clonedMessage; var messageAccessor = this.MessageDescriptions.GetAccessor(message); @@ -250,7 +250,7 @@ namespace DotNetOpenAuth.Test.Mocks { } private static IMessageFactory GetMessageFactory(Channel channel) { - Contract.Requires<ArgumentNullException>(channel != null); + Requires.NotNull(channel, "channel"); return channel.MessageFactoryTestHook; } diff --git a/src/DotNetOpenAuth.Test/Mocks/CoordinatingOAuthChannel.cs b/src/DotNetOpenAuth.Test/Mocks/CoordinatingOAuthChannel.cs deleted file mode 100644 index 74e23bd..0000000 --- a/src/DotNetOpenAuth.Test/Mocks/CoordinatingOAuthChannel.cs +++ /dev/null @@ -1,170 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="CoordinatingOAuthChannel.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.Test.Mocks { - using System; - using System.Diagnostics.Contracts; - using System.Threading; - using DotNetOpenAuth.Messaging; - using DotNetOpenAuth.Messaging.Bindings; - using DotNetOpenAuth.OAuth.ChannelElements; - using DotNetOpenAuth.OAuth.Messages; - - /// <summary> - /// A special channel used in test simulations to pass messages directly between two parties. - /// </summary> - internal class CoordinatingOAuthChannel : OAuthChannel { - private EventWaitHandle incomingMessageSignal = new AutoResetEvent(false); - private IProtocolMessage incomingMessage; - private OutgoingWebResponse incomingRawResponse; - - /// <summary> - /// Initializes a new instance of the <see cref="CoordinatingOAuthChannel"/> class for Consumers. - /// </summary> - /// <param name="signingBindingElement">The signing element for the Consumer to use. Null for the Service Provider.</param> - /// <param name="tokenManager">The token manager to use.</param> - /// <param name="securitySettings">The security settings.</param> - internal CoordinatingOAuthChannel(ITamperProtectionChannelBindingElement signingBindingElement, IConsumerTokenManager tokenManager, DotNetOpenAuth.OAuth.ConsumerSecuritySettings securitySettings) - : base( - signingBindingElement, - new NonceMemoryStore(StandardExpirationBindingElement.MaximumMessageAge), - tokenManager, - securitySettings) { - } - - /// <summary> - /// Initializes a new instance of the <see cref="CoordinatingOAuthChannel"/> class for Consumers. - /// </summary> - /// <param name="signingBindingElement">The signing element for the Consumer to use. Null for the Service Provider.</param> - /// <param name="tokenManager">The token manager to use.</param> - /// <param name="securitySettings">The security settings.</param> - internal CoordinatingOAuthChannel(ITamperProtectionChannelBindingElement signingBindingElement, IServiceProviderTokenManager tokenManager, DotNetOpenAuth.OAuth.ServiceProviderSecuritySettings securitySettings) - : base( - signingBindingElement, - new NonceMemoryStore(StandardExpirationBindingElement.MaximumMessageAge), - tokenManager, - securitySettings) { - } - - /// <summary> - /// Gets or sets the coordinating channel used by the other party. - /// </summary> - internal CoordinatingOAuthChannel RemoteChannel { get; set; } - - internal OutgoingWebResponse RequestProtectedResource(AccessProtectedResourceRequest request) { - ((ITamperResistantOAuthMessage)request).HttpMethod = this.GetHttpMethod(((ITamperResistantOAuthMessage)request).HttpMethods); - this.ProcessOutgoingMessage(request); - HttpRequestInfo requestInfo = this.SpoofHttpMethod(request); - TestBase.TestLogger.InfoFormat("Sending protected resource request: {0}", requestInfo.Message); - // Drop the outgoing message in the other channel's in-slot and let them know it's there. - this.RemoteChannel.incomingMessage = requestInfo.Message; - this.RemoteChannel.incomingMessageSignal.Set(); - return this.AwaitIncomingRawResponse(); - } - - internal void SendDirectRawResponse(OutgoingWebResponse response) { - this.RemoteChannel.incomingRawResponse = response; - this.RemoteChannel.incomingMessageSignal.Set(); - } - - protected internal override HttpRequestInfo GetRequestFromContext() { - var directedMessage = (IDirectedProtocolMessage)this.AwaitIncomingMessage(); - return new HttpRequestInfo(directedMessage, directedMessage.HttpMethods); - } - - protected override IProtocolMessage RequestCore(IDirectedProtocolMessage request) { - HttpRequestInfo requestInfo = this.SpoofHttpMethod(request); - // Drop the outgoing message in the other channel's in-slot and let them know it's there. - this.RemoteChannel.incomingMessage = requestInfo.Message; - this.RemoteChannel.incomingMessageSignal.Set(); - // Now wait for a response... - return this.AwaitIncomingMessage(); - } - - protected override OutgoingWebResponse PrepareDirectResponse(IProtocolMessage response) { - this.RemoteChannel.incomingMessage = CloneSerializedParts(response, null); - this.RemoteChannel.incomingMessageSignal.Set(); - return new OutgoingWebResponse(); // not used, but returning null is not allowed - } - - protected override OutgoingWebResponse PrepareIndirectResponse(IDirectedProtocolMessage message) { - // In this mock transport, direct and indirect messages are the same. - return this.PrepareDirectResponse(message); - } - - protected override IDirectedProtocolMessage ReadFromRequestCore(HttpRequestInfo request) { - return request.Message; - } - - /// <summary> - /// Spoof HTTP request information for signing/verification purposes. - /// </summary> - /// <param name="message">The message to add a pretend HTTP method to.</param> - /// <returns>A spoofed HttpRequestInfo that wraps the new message.</returns> - private HttpRequestInfo SpoofHttpMethod(IDirectedProtocolMessage message) { - HttpRequestInfo requestInfo = new HttpRequestInfo(message, message.HttpMethods); - - var signedMessage = message as ITamperResistantOAuthMessage; - if (signedMessage != null) { - string httpMethod = this.GetHttpMethod(signedMessage.HttpMethods); - requestInfo.HttpMethod = httpMethod; - requestInfo.UrlBeforeRewriting = message.Recipient; - signedMessage.HttpMethod = httpMethod; - } - - requestInfo.Message = this.CloneSerializedParts(message, requestInfo); - - return requestInfo; - } - - private IProtocolMessage AwaitIncomingMessage() { - this.incomingMessageSignal.WaitOne(); - IProtocolMessage response = this.incomingMessage; - this.incomingMessage = null; - return response; - } - - private OutgoingWebResponse AwaitIncomingRawResponse() { - this.incomingMessageSignal.WaitOne(); - OutgoingWebResponse response = this.incomingRawResponse; - this.incomingRawResponse = null; - return response; - } - - private T CloneSerializedParts<T>(T message, HttpRequestInfo requestInfo) where T : class, IProtocolMessage { - Contract.Requires<ArgumentNullException>(message != null); - - IProtocolMessage clonedMessage; - var messageAccessor = this.MessageDescriptions.GetAccessor(message); - var fields = messageAccessor.Serialize(); - - MessageReceivingEndpoint recipient = null; - var directedMessage = message as IDirectedProtocolMessage; - var directResponse = message as IDirectResponseProtocolMessage; - if (directedMessage != null && directedMessage.IsRequest()) { - if (directedMessage.Recipient != null) { - recipient = new MessageReceivingEndpoint(directedMessage.Recipient, directedMessage.HttpMethods); - } - - clonedMessage = this.RemoteChannel.MessageFactory.GetNewRequestMessage(recipient, fields); - } else if (directResponse != null && directResponse.IsDirectResponse()) { - clonedMessage = this.RemoteChannel.MessageFactory.GetNewResponseMessage(directResponse.OriginatingRequest, fields); - } else { - throw new InvalidOperationException("Totally expected a message to implement one of the two derived interface types."); - } - - // Fill the cloned message with data. - var clonedMessageAccessor = this.MessageDescriptions.GetAccessor(clonedMessage); - clonedMessageAccessor.Deserialize(fields); - - return (T)clonedMessage; - } - - private string GetHttpMethod(HttpDeliveryMethods methods) { - return (methods & HttpDeliveryMethods.PostRequest) != 0 ? "POST" : "GET"; - } - } -} diff --git a/src/DotNetOpenAuth.Test/Mocks/CoordinatingOAuthConsumerChannel.cs b/src/DotNetOpenAuth.Test/Mocks/CoordinatingOAuthConsumerChannel.cs new file mode 100644 index 0000000..3fcce19 --- /dev/null +++ b/src/DotNetOpenAuth.Test/Mocks/CoordinatingOAuthConsumerChannel.cs @@ -0,0 +1,157 @@ +//----------------------------------------------------------------------- +// <copyright file="CoordinatingOAuthConsumerChannel.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Test.Mocks { + using System; + using System.Diagnostics.Contracts; + using System.Threading; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.Messaging.Bindings; + using DotNetOpenAuth.OAuth.ChannelElements; + using DotNetOpenAuth.OAuth.Messages; + + /// <summary> + /// A special channel used in test simulations to pass messages directly between two parties. + /// </summary> + internal class CoordinatingOAuthConsumerChannel : OAuthConsumerChannel { + private EventWaitHandle incomingMessageSignal = new AutoResetEvent(false); + + /// <summary> + /// Initializes a new instance of the <see cref="CoordinatingOAuthConsumerChannel"/> class. + /// </summary> + /// <param name="signingBindingElement">The signing element for the Consumer to use. Null for the Service Provider.</param> + /// <param name="tokenManager">The token manager to use.</param> + /// <param name="securitySettings">The security settings.</param> + internal CoordinatingOAuthConsumerChannel(ITamperProtectionChannelBindingElement signingBindingElement, IConsumerTokenManager tokenManager, DotNetOpenAuth.OAuth.ConsumerSecuritySettings securitySettings) + : base( + signingBindingElement, + new NonceMemoryStore(StandardExpirationBindingElement.MaximumMessageAge), + tokenManager, + securitySettings) { + } + + internal EventWaitHandle IncomingMessageSignal { + get { return this.incomingMessageSignal; } + } + + internal IProtocolMessage IncomingMessage { get; set; } + + internal OutgoingWebResponse IncomingRawResponse { get; set; } + + /// <summary> + /// Gets or sets the coordinating channel used by the other party. + /// </summary> + internal CoordinatingOAuthServiceProviderChannel RemoteChannel { get; set; } + + internal OutgoingWebResponse RequestProtectedResource(AccessProtectedResourceRequest request) { + ((ITamperResistantOAuthMessage)request).HttpMethod = this.GetHttpMethod(((ITamperResistantOAuthMessage)request).HttpMethods); + this.ProcessOutgoingMessage(request); + HttpRequestInfo requestInfo = this.SpoofHttpMethod(request); + TestBase.TestLogger.InfoFormat("Sending protected resource request: {0}", requestInfo.Message); + // Drop the outgoing message in the other channel's in-slot and let them know it's there. + this.RemoteChannel.IncomingMessage = requestInfo.Message; + this.RemoteChannel.IncomingMessageSignal.Set(); + return this.AwaitIncomingRawResponse(); + } + + protected internal override HttpRequestInfo GetRequestFromContext() { + var directedMessage = (IDirectedProtocolMessage)this.AwaitIncomingMessage(); + return new HttpRequestInfo(directedMessage, directedMessage.HttpMethods); + } + + protected override IProtocolMessage RequestCore(IDirectedProtocolMessage request) { + HttpRequestInfo requestInfo = this.SpoofHttpMethod(request); + // Drop the outgoing message in the other channel's in-slot and let them know it's there. + this.RemoteChannel.IncomingMessage = requestInfo.Message; + this.RemoteChannel.IncomingMessageSignal.Set(); + // Now wait for a response... + return this.AwaitIncomingMessage(); + } + + protected override OutgoingWebResponse PrepareDirectResponse(IProtocolMessage response) { + this.RemoteChannel.IncomingMessage = CloneSerializedParts(response, null); + this.RemoteChannel.IncomingMessageSignal.Set(); + return new OutgoingWebResponse(); // not used, but returning null is not allowed + } + + protected override OutgoingWebResponse PrepareIndirectResponse(IDirectedProtocolMessage message) { + // In this mock transport, direct and indirect messages are the same. + return this.PrepareDirectResponse(message); + } + + protected override IDirectedProtocolMessage ReadFromRequestCore(HttpRequestInfo request) { + return request.Message; + } + + /// <summary> + /// Spoof HTTP request information for signing/verification purposes. + /// </summary> + /// <param name="message">The message to add a pretend HTTP method to.</param> + /// <returns>A spoofed HttpRequestInfo that wraps the new message.</returns> + private HttpRequestInfo SpoofHttpMethod(IDirectedProtocolMessage message) { + HttpRequestInfo requestInfo = new HttpRequestInfo(message, message.HttpMethods); + + var signedMessage = message as ITamperResistantOAuthMessage; + if (signedMessage != null) { + string httpMethod = this.GetHttpMethod(signedMessage.HttpMethods); + requestInfo.HttpMethod = httpMethod; + requestInfo.UrlBeforeRewriting = message.Recipient; + signedMessage.HttpMethod = httpMethod; + } + + requestInfo.Message = this.CloneSerializedParts(message, requestInfo); + + return requestInfo; + } + + private IProtocolMessage AwaitIncomingMessage() { + this.incomingMessageSignal.WaitOne(); + IProtocolMessage response = this.IncomingMessage; + this.IncomingMessage = null; + return response; + } + + private OutgoingWebResponse AwaitIncomingRawResponse() { + this.incomingMessageSignal.WaitOne(); + OutgoingWebResponse response = this.IncomingRawResponse; + this.IncomingRawResponse = null; + return response; + } + + private T CloneSerializedParts<T>(T message, HttpRequestInfo requestInfo) where T : class, IProtocolMessage { + Requires.NotNull(message, "message"); + + IProtocolMessage clonedMessage; + var messageAccessor = this.MessageDescriptions.GetAccessor(message); + var fields = messageAccessor.Serialize(); + + MessageReceivingEndpoint recipient = null; + var directedMessage = message as IDirectedProtocolMessage; + var directResponse = message as IDirectResponseProtocolMessage; + if (directedMessage != null && directedMessage.IsRequest()) { + if (directedMessage.Recipient != null) { + recipient = new MessageReceivingEndpoint(directedMessage.Recipient, directedMessage.HttpMethods); + } + + clonedMessage = this.RemoteChannel.MessageFactoryTestHook.GetNewRequestMessage(recipient, fields); + } else if (directResponse != null && directResponse.IsDirectResponse()) { + clonedMessage = this.RemoteChannel.MessageFactoryTestHook.GetNewResponseMessage(directResponse.OriginatingRequest, fields); + } else { + throw new InvalidOperationException("Totally expected a message to implement one of the two derived interface types."); + } + + // Fill the cloned message with data. + var clonedMessageAccessor = this.MessageDescriptions.GetAccessor(clonedMessage); + clonedMessageAccessor.Deserialize(fields); + + return (T)clonedMessage; + } + + private string GetHttpMethod(HttpDeliveryMethods methods) { + return (methods & HttpDeliveryMethods.PostRequest) != 0 ? "POST" : "GET"; + } + } +} diff --git a/src/DotNetOpenAuth.Test/Mocks/CoordinatingOAuthServiceProviderChannel.cs b/src/DotNetOpenAuth.Test/Mocks/CoordinatingOAuthServiceProviderChannel.cs new file mode 100644 index 0000000..d07f794 --- /dev/null +++ b/src/DotNetOpenAuth.Test/Mocks/CoordinatingOAuthServiceProviderChannel.cs @@ -0,0 +1,163 @@ +//----------------------------------------------------------------------- +// <copyright file="CoordinatingOAuthServiceProviderChannel.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Test.Mocks { + using System; + using System.Diagnostics.Contracts; + using System.Threading; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.Messaging.Bindings; + using DotNetOpenAuth.OAuth.ChannelElements; + using DotNetOpenAuth.OAuth.Messages; + + /// <summary> + /// A special channel used in test simulations to pass messages directly between two parties. + /// </summary> + internal class CoordinatingOAuthServiceProviderChannel : OAuthServiceProviderChannel { + private EventWaitHandle incomingMessageSignal = new AutoResetEvent(false); + + /// <summary> + /// Initializes a new instance of the <see cref="CoordinatingOAuthServiceProviderChannel"/> class. + /// </summary> + /// <param name="signingBindingElement">The signing element for the Consumer to use. Null for the Service Provider.</param> + /// <param name="tokenManager">The token manager to use.</param> + /// <param name="securitySettings">The security settings.</param> + internal CoordinatingOAuthServiceProviderChannel(ITamperProtectionChannelBindingElement signingBindingElement, IServiceProviderTokenManager tokenManager, DotNetOpenAuth.OAuth.ServiceProviderSecuritySettings securitySettings) + : base( + signingBindingElement, + new NonceMemoryStore(StandardExpirationBindingElement.MaximumMessageAge), + tokenManager, + securitySettings, + new OAuthServiceProviderMessageFactory(tokenManager)) { + } + + internal EventWaitHandle IncomingMessageSignal { + get { return this.incomingMessageSignal; } + } + + internal IProtocolMessage IncomingMessage { get; set; } + + internal OutgoingWebResponse IncomingRawResponse { get; set; } + + /// <summary> + /// Gets or sets the coordinating channel used by the other party. + /// </summary> + internal CoordinatingOAuthConsumerChannel RemoteChannel { get; set; } + + internal OutgoingWebResponse RequestProtectedResource(AccessProtectedResourceRequest request) { + ((ITamperResistantOAuthMessage)request).HttpMethod = this.GetHttpMethod(((ITamperResistantOAuthMessage)request).HttpMethods); + this.ProcessOutgoingMessage(request); + HttpRequestInfo requestInfo = this.SpoofHttpMethod(request); + TestBase.TestLogger.InfoFormat("Sending protected resource request: {0}", requestInfo.Message); + // Drop the outgoing message in the other channel's in-slot and let them know it's there. + this.RemoteChannel.IncomingMessage = requestInfo.Message; + this.RemoteChannel.IncomingMessageSignal.Set(); + return this.AwaitIncomingRawResponse(); + } + + internal void SendDirectRawResponse(OutgoingWebResponse response) { + this.RemoteChannel.IncomingRawResponse = response; + this.RemoteChannel.IncomingMessageSignal.Set(); + } + + protected internal override HttpRequestInfo GetRequestFromContext() { + var directedMessage = (IDirectedProtocolMessage)this.AwaitIncomingMessage(); + return new HttpRequestInfo(directedMessage, directedMessage.HttpMethods); + } + + protected override IProtocolMessage RequestCore(IDirectedProtocolMessage request) { + HttpRequestInfo requestInfo = this.SpoofHttpMethod(request); + // Drop the outgoing message in the other channel's in-slot and let them know it's there. + this.RemoteChannel.IncomingMessage = requestInfo.Message; + this.RemoteChannel.IncomingMessageSignal.Set(); + // Now wait for a response... + return this.AwaitIncomingMessage(); + } + + protected override OutgoingWebResponse PrepareDirectResponse(IProtocolMessage response) { + this.RemoteChannel.IncomingMessage = CloneSerializedParts(response, null); + this.RemoteChannel.IncomingMessageSignal.Set(); + return new OutgoingWebResponse(); // not used, but returning null is not allowed + } + + protected override OutgoingWebResponse PrepareIndirectResponse(IDirectedProtocolMessage message) { + // In this mock transport, direct and indirect messages are the same. + return this.PrepareDirectResponse(message); + } + + protected override IDirectedProtocolMessage ReadFromRequestCore(HttpRequestInfo request) { + return request.Message; + } + + /// <summary> + /// Spoof HTTP request information for signing/verification purposes. + /// </summary> + /// <param name="message">The message to add a pretend HTTP method to.</param> + /// <returns>A spoofed HttpRequestInfo that wraps the new message.</returns> + private HttpRequestInfo SpoofHttpMethod(IDirectedProtocolMessage message) { + HttpRequestInfo requestInfo = new HttpRequestInfo(message, message.HttpMethods); + + var signedMessage = message as ITamperResistantOAuthMessage; + if (signedMessage != null) { + string httpMethod = this.GetHttpMethod(signedMessage.HttpMethods); + requestInfo.HttpMethod = httpMethod; + requestInfo.UrlBeforeRewriting = message.Recipient; + signedMessage.HttpMethod = httpMethod; + } + + requestInfo.Message = this.CloneSerializedParts(message, requestInfo); + + return requestInfo; + } + + private IProtocolMessage AwaitIncomingMessage() { + this.IncomingMessageSignal.WaitOne(); + IProtocolMessage response = this.IncomingMessage; + this.IncomingMessage = null; + return response; + } + + private OutgoingWebResponse AwaitIncomingRawResponse() { + this.IncomingMessageSignal.WaitOne(); + OutgoingWebResponse response = this.IncomingRawResponse; + this.IncomingRawResponse = null; + return response; + } + + private T CloneSerializedParts<T>(T message, HttpRequestInfo requestInfo) where T : class, IProtocolMessage { + Requires.NotNull(message, "message"); + + IProtocolMessage clonedMessage; + var messageAccessor = this.MessageDescriptions.GetAccessor(message); + var fields = messageAccessor.Serialize(); + + MessageReceivingEndpoint recipient = null; + var directedMessage = message as IDirectedProtocolMessage; + var directResponse = message as IDirectResponseProtocolMessage; + if (directedMessage != null && directedMessage.IsRequest()) { + if (directedMessage.Recipient != null) { + recipient = new MessageReceivingEndpoint(directedMessage.Recipient, directedMessage.HttpMethods); + } + + clonedMessage = this.RemoteChannel.MessageFactoryTestHook.GetNewRequestMessage(recipient, fields); + } else if (directResponse != null && directResponse.IsDirectResponse()) { + clonedMessage = this.RemoteChannel.MessageFactoryTestHook.GetNewResponseMessage(directResponse.OriginatingRequest, fields); + } else { + throw new InvalidOperationException("Totally expected a message to implement one of the two derived interface types."); + } + + // Fill the cloned message with data. + var clonedMessageAccessor = this.MessageDescriptions.GetAccessor(clonedMessage); + clonedMessageAccessor.Deserialize(fields); + + return (T)clonedMessage; + } + + private string GetHttpMethod(HttpDeliveryMethods methods) { + return (methods & HttpDeliveryMethods.PostRequest) != 0 ? "POST" : "GET"; + } + } +} diff --git a/src/DotNetOpenAuth.Test/Mocks/CoordinatingOutgoingWebResponse.cs b/src/DotNetOpenAuth.Test/Mocks/CoordinatingOutgoingWebResponse.cs index a744053..dc2c29f 100644 --- a/src/DotNetOpenAuth.Test/Mocks/CoordinatingOutgoingWebResponse.cs +++ b/src/DotNetOpenAuth.Test/Mocks/CoordinatingOutgoingWebResponse.cs @@ -22,14 +22,14 @@ namespace DotNetOpenAuth.Test.Mocks { /// <param name="message">The direct response message to send to the remote channel. This message will be cloned.</param> /// <param name="receivingChannel">The receiving channel.</param> internal CoordinatingOutgoingWebResponse(IProtocolMessage message, CoordinatingChannel receivingChannel) { - Contract.Requires<ArgumentNullException>(message != null); - Contract.Requires<ArgumentNullException>(receivingChannel != null); + Requires.NotNull(message, "message"); + Requires.NotNull(receivingChannel, "receivingChannel"); this.receivingChannel = receivingChannel; this.OriginalMessage = message; } - [EditorBrowsable(EditorBrowsableState.Never), Obsolete("Use the Respond method instead, and prepare for execution to continue on this page beyond the call to Respond.")] + [EditorBrowsable(EditorBrowsableState.Never)] public override void Send() { this.Respond(); } diff --git a/src/DotNetOpenAuth.Test/Mocks/MockHttpRequest.cs b/src/DotNetOpenAuth.Test/Mocks/MockHttpRequest.cs index c18ea33..7048107 100644 --- a/src/DotNetOpenAuth.Test/Mocks/MockHttpRequest.cs +++ b/src/DotNetOpenAuth.Test/Mocks/MockHttpRequest.cs @@ -23,7 +23,7 @@ namespace DotNetOpenAuth.Test.Mocks { private readonly Dictionary<Uri, IncomingWebResponse> registeredMockResponses = new Dictionary<Uri, IncomingWebResponse>(); private MockHttpRequest(IDirectWebRequestHandler mockHandler) { - Contract.Requires<ArgumentNullException>(mockHandler != null); + Requires.NotNull(mockHandler, "mockHandler"); this.MockWebRequestHandler = mockHandler; } @@ -42,7 +42,7 @@ namespace DotNetOpenAuth.Test.Mocks { } internal void RegisterMockResponse(IncomingWebResponse response) { - Contract.Requires<ArgumentNullException>(response != null); + Requires.NotNull(response, "response"); if (this.registeredMockResponses.ContainsKey(response.RequestUri)) { Logger.Http.WarnFormat("Mock HTTP response already registered for {0}.", response.RequestUri); } else { @@ -59,9 +59,9 @@ namespace DotNetOpenAuth.Test.Mocks { } internal void RegisterMockResponse(Uri requestUri, Uri responseUri, string contentType, WebHeaderCollection headers, string responseBody) { - Contract.Requires<ArgumentNullException>(requestUri != null); - Contract.Requires<ArgumentNullException>(responseUri != null); - Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(contentType)); + Requires.NotNull(requestUri, "requestUri"); + Requires.NotNull(responseUri, "responseUri"); + Requires.NotNullOrEmpty(contentType, "contentType"); // Set up the redirect if appropriate if (requestUri != responseUri) { @@ -84,7 +84,7 @@ namespace DotNetOpenAuth.Test.Mocks { } internal void RegisterMockXrdsResponse(IdentifierDiscoveryResult endpoint) { - Contract.Requires<ArgumentNullException>(endpoint != null); + Requires.NotNull(endpoint, "endpoint"); string identityUri; if (endpoint.ClaimedIdentifier == endpoint.Protocol.ClaimedIdentifierForOPIdentifier) { @@ -96,7 +96,7 @@ namespace DotNetOpenAuth.Test.Mocks { } internal void RegisterMockXrdsResponse(Uri respondingUri, IEnumerable<IdentifierDiscoveryResult> endpoints) { - Contract.Requires<ArgumentNullException>(endpoints != null); + Requires.NotNull(endpoints, "endpoints"); StringBuilder xrds = new StringBuilder(); xrds.AppendLine(@"<xrds:XRDS xmlns:xrds='xri://$xrds' xmlns:openid='http://openid.net/xmlns/1.0' xmlns='xri://$xrd*($v*2.0)'> diff --git a/src/DotNetOpenAuth.Test/Mocks/MockIdentifier.cs b/src/DotNetOpenAuth.Test/Mocks/MockIdentifier.cs index 9f032b8..fe1cb81 100644 --- a/src/DotNetOpenAuth.Test/Mocks/MockIdentifier.cs +++ b/src/DotNetOpenAuth.Test/Mocks/MockIdentifier.cs @@ -26,9 +26,9 @@ namespace DotNetOpenAuth.Test.Mocks { public MockIdentifier(Identifier wrappedIdentifier, MockHttpRequest mockHttpRequest, IEnumerable<IdentifierDiscoveryResult> endpoints) : base(wrappedIdentifier.OriginalString, false) { - Contract.Requires<ArgumentNullException>(wrappedIdentifier != null); - Contract.Requires<ArgumentNullException>(mockHttpRequest != null); - Contract.Requires<ArgumentNullException>(endpoints != null); + Requires.NotNull(wrappedIdentifier, "wrappedIdentifier"); + Requires.NotNull(mockHttpRequest, "mockHttpRequest"); + Requires.NotNull(endpoints, "endpoints"); this.wrappedIdentifier = wrappedIdentifier; this.endpoints = endpoints; diff --git a/src/DotNetOpenAuth.Test/Mocks/MockRealm.cs b/src/DotNetOpenAuth.Test/Mocks/MockRealm.cs index dd17735..16ea337 100644 --- a/src/DotNetOpenAuth.Test/Mocks/MockRealm.cs +++ b/src/DotNetOpenAuth.Test/Mocks/MockRealm.cs @@ -21,7 +21,7 @@ namespace DotNetOpenAuth.Test.Mocks { /// <param name="relyingPartyDescriptions">The relying party descriptions.</param> internal MockRealm(Realm wrappedRealm, params RelyingPartyEndpointDescription[] relyingPartyDescriptions) : base(wrappedRealm) { - Contract.Requires<ArgumentNullException>(relyingPartyDescriptions != null); + Requires.NotNull(relyingPartyDescriptions, "relyingPartyDescriptions"); this.relyingPartyDescriptions = relyingPartyDescriptions; } diff --git a/src/DotNetOpenAuth.Test/Mocks/TestChannel.cs b/src/DotNetOpenAuth.Test/Mocks/TestChannel.cs index 95fb90a..92fd9c6 100644 --- a/src/DotNetOpenAuth.Test/Mocks/TestChannel.cs +++ b/src/DotNetOpenAuth.Test/Mocks/TestChannel.cs @@ -25,6 +25,21 @@ namespace DotNetOpenAuth.Test.Mocks { : base(messageTypeProvider, bindingElements) { } + /// <summary> + /// Deserializes a dictionary of values into a message. + /// </summary> + /// <param name="fields">The dictionary of values that were read from an HTTP request or response.</param> + /// <param name="recipient">Information about where the message was directed. Null for direct response messages.</param> + /// <returns> + /// The deserialized message, or null if no message could be recognized in the provided data. + /// </returns> + /// <remarks> + /// This internal method exposes Receive directly to unit tests for easier deserialization of custom (possibly malformed) messages. + /// </remarks> + internal new IProtocolMessage Receive(Dictionary<string, string> fields, MessageReceivingEndpoint recipient) { + return base.Receive(fields, recipient); + } + protected override IDictionary<string, string> ReadFromResponseCore(IncomingWebResponse response) { throw new NotImplementedException("ReadFromResponseInternal"); } diff --git a/src/DotNetOpenAuth.Test/OAuth/AppendixScenarios.cs b/src/DotNetOpenAuth.Test/OAuth/AppendixScenarios.cs index 5584a7b..6322fe2 100644 --- a/src/DotNetOpenAuth.Test/OAuth/AppendixScenarios.cs +++ b/src/DotNetOpenAuth.Test/OAuth/AppendixScenarios.cs @@ -37,7 +37,7 @@ namespace DotNetOpenAuth.Test.OAuth { consumer.Channel.PrepareResponse(consumer.PrepareRequestUserAuthorization(new Uri("http://printer.example.com/request_token_ready"), null, null)); // .Send() dropped because this is just a simulation string accessToken = consumer.ProcessUserAuthorization().AccessToken; var photoRequest = consumer.CreateAuthorizingMessage(accessPhotoEndpoint, accessToken); - OutgoingWebResponse protectedPhoto = ((CoordinatingOAuthChannel)consumer.Channel).RequestProtectedResource(photoRequest); + OutgoingWebResponse protectedPhoto = ((CoordinatingOAuthConsumerChannel)consumer.Channel).RequestProtectedResource(photoRequest); Assert.IsNotNull(protectedPhoto); Assert.AreEqual(HttpStatusCode.OK, protectedPhoto.Status); Assert.AreEqual("image/jpeg", protectedPhoto.Headers[HttpResponseHeader.ContentType]); @@ -52,7 +52,7 @@ namespace DotNetOpenAuth.Test.OAuth { var accessRequest = sp.ReadAccessTokenRequest(); sp.Channel.PrepareResponse(sp.PrepareAccessTokenMessage(accessRequest)); // .Send() dropped because this is just a simulation string accessToken = sp.ReadProtectedResourceAuthorization().AccessToken; - ((CoordinatingOAuthChannel)sp.Channel).SendDirectRawResponse(new OutgoingWebResponse { + ((CoordinatingOAuthServiceProviderChannel)sp.Channel).SendDirectRawResponse(new OutgoingWebResponse { ResponseStream = new MemoryStream(new byte[] { 0x33, 0x66 }), Headers = new WebHeaderCollection { { HttpResponseHeader.ContentType, "image/jpeg" }, diff --git a/src/DotNetOpenAuth.Test/OAuth/ChannelElements/OAuthChannelTests.cs b/src/DotNetOpenAuth.Test/OAuth/ChannelElements/OAuthChannelTests.cs index 34cc3a4..64eef0e 100644 --- a/src/DotNetOpenAuth.Test/OAuth/ChannelElements/OAuthChannelTests.cs +++ b/src/DotNetOpenAuth.Test/OAuth/ChannelElements/OAuthChannelTests.cs @@ -27,43 +27,43 @@ namespace DotNetOpenAuth.Test.OAuth.ChannelElements { private TestWebRequestHandler webRequestHandler; private SigningBindingElementBase signingElement; private INonceStore nonceStore; - private DotNetOpenAuth.OAuth.ServiceProviderSecuritySettings serviceProviderSecuritySettings = DotNetOpenAuth.Configuration.DotNetOpenAuthSection.Configuration.OAuth.ServiceProvider.SecuritySettings.CreateSecuritySettings(); - private DotNetOpenAuth.OAuth.ConsumerSecuritySettings consumerSecuritySettings = DotNetOpenAuth.Configuration.DotNetOpenAuthSection.Configuration.OAuth.Consumer.SecuritySettings.CreateSecuritySettings(); + private DotNetOpenAuth.OAuth.ServiceProviderSecuritySettings serviceProviderSecuritySettings = DotNetOpenAuth.Configuration.OAuthElement.Configuration.ServiceProvider.SecuritySettings.CreateSecuritySettings(); + private DotNetOpenAuth.OAuth.ConsumerSecuritySettings consumerSecuritySettings = DotNetOpenAuth.Configuration.OAuthElement.Configuration.Consumer.SecuritySettings.CreateSecuritySettings(); [SetUp] public override void SetUp() { base.SetUp(); this.webRequestHandler = new TestWebRequestHandler(); - this.signingElement = new RsaSha1SigningBindingElement(new InMemoryTokenManager()); + this.signingElement = new RsaSha1ServiceProviderSigningBindingElement(new InMemoryTokenManager()); this.nonceStore = new NonceMemoryStore(StandardExpirationBindingElement.MaximumMessageAge); - this.channel = new OAuthChannel(this.signingElement, this.nonceStore, new InMemoryTokenManager(), this.serviceProviderSecuritySettings, new TestMessageFactory()); + this.channel = new OAuthServiceProviderChannel(this.signingElement, this.nonceStore, new InMemoryTokenManager(), this.serviceProviderSecuritySettings, new TestMessageFactory()); this.channel.WebRequestHandler = this.webRequestHandler; } - [TestCase, ExpectedException(typeof(ArgumentNullException))] + [TestCase, ExpectedException(typeof(ArgumentException))] public void CtorNullSigner() { - new OAuthChannel(null, this.nonceStore, new InMemoryTokenManager(), this.consumerSecuritySettings, new TestMessageFactory()); + new OAuthConsumerChannel(null, this.nonceStore, new InMemoryTokenManager(), this.consumerSecuritySettings, new TestMessageFactory()); } [TestCase, ExpectedException(typeof(ArgumentNullException))] public void CtorNullStore() { - new OAuthChannel(new RsaSha1SigningBindingElement(new InMemoryTokenManager()), null, new InMemoryTokenManager(), this.consumerSecuritySettings, new TestMessageFactory()); + new OAuthConsumerChannel(new RsaSha1ServiceProviderSigningBindingElement(new InMemoryTokenManager()), null, new InMemoryTokenManager(), this.consumerSecuritySettings, new TestMessageFactory()); } [TestCase, ExpectedException(typeof(ArgumentNullException))] public void CtorNullTokenManager() { - new OAuthChannel(new RsaSha1SigningBindingElement(new InMemoryTokenManager()), this.nonceStore, null, this.consumerSecuritySettings, new TestMessageFactory()); + new OAuthConsumerChannel(new RsaSha1ServiceProviderSigningBindingElement(new InMemoryTokenManager()), this.nonceStore, null, this.consumerSecuritySettings, new TestMessageFactory()); } [TestCase] public void CtorSimpleConsumer() { - new OAuthChannel(new RsaSha1SigningBindingElement(new InMemoryTokenManager()), this.nonceStore, (IConsumerTokenManager)new InMemoryTokenManager(), this.consumerSecuritySettings); + new OAuthConsumerChannel(new RsaSha1ServiceProviderSigningBindingElement(new InMemoryTokenManager()), this.nonceStore, (IConsumerTokenManager)new InMemoryTokenManager(), this.consumerSecuritySettings); } [TestCase] public void CtorSimpleServiceProvider() { - new OAuthChannel(new RsaSha1SigningBindingElement(new InMemoryTokenManager()), this.nonceStore, (IServiceProviderTokenManager)new InMemoryTokenManager(), this.serviceProviderSecuritySettings); + new OAuthServiceProviderChannel(new RsaSha1ServiceProviderSigningBindingElement(new InMemoryTokenManager()), this.nonceStore, (IServiceProviderTokenManager)new InMemoryTokenManager(), this.serviceProviderSecuritySettings); } [TestCase] @@ -248,7 +248,7 @@ namespace DotNetOpenAuth.Test.OAuth.ChannelElements { } private static string CreateAuthorizationHeader(IDictionary<string, string> fields) { - Contract.Requires<ArgumentNullException>(fields != null); + Requires.NotNull(fields, "fields"); StringBuilder authorization = new StringBuilder(); authorization.Append("OAuth "); diff --git a/src/DotNetOpenAuth.Test/OAuth/OAuthCoordinator.cs b/src/DotNetOpenAuth.Test/OAuth/OAuthCoordinator.cs index 6bcc583..067b364 100644 --- a/src/DotNetOpenAuth.Test/OAuth/OAuthCoordinator.cs +++ b/src/DotNetOpenAuth.Test/OAuth/OAuthCoordinator.cs @@ -19,8 +19,8 @@ namespace DotNetOpenAuth.Test.OAuth { internal class OAuthCoordinator : CoordinatorBase<WebConsumer, ServiceProvider> { private ConsumerDescription consumerDescription; private ServiceProviderDescription serviceDescription; - private DotNetOpenAuth.OAuth.ServiceProviderSecuritySettings serviceProviderSecuritySettings = DotNetOpenAuth.Configuration.DotNetOpenAuthSection.Configuration.OAuth.ServiceProvider.SecuritySettings.CreateSecuritySettings(); - private DotNetOpenAuth.OAuth.ConsumerSecuritySettings consumerSecuritySettings = DotNetOpenAuth.Configuration.DotNetOpenAuthSection.Configuration.OAuth.Consumer.SecuritySettings.CreateSecuritySettings(); + private DotNetOpenAuth.OAuth.ServiceProviderSecuritySettings serviceProviderSecuritySettings = DotNetOpenAuth.Configuration.OAuthElement.Configuration.ServiceProvider.SecuritySettings.CreateSecuritySettings(); + private DotNetOpenAuth.OAuth.ConsumerSecuritySettings consumerSecuritySettings = DotNetOpenAuth.Configuration.OAuthElement.Configuration.Consumer.SecuritySettings.CreateSecuritySettings(); /// <summary>Initializes a new instance of the <see cref="OAuthCoordinator"/> class.</summary> /// <param name="consumerDescription">The description of the consumer.</param> @@ -29,8 +29,8 @@ namespace DotNetOpenAuth.Test.OAuth { /// <param name="serviceProviderAction">The code path of the Service Provider.</param> internal OAuthCoordinator(ConsumerDescription consumerDescription, ServiceProviderDescription serviceDescription, Action<WebConsumer> consumerAction, Action<ServiceProvider> serviceProviderAction) : base(consumerAction, serviceProviderAction) { - Contract.Requires<ArgumentNullException>(consumerDescription != null); - Contract.Requires<ArgumentNullException>(serviceDescription != null); + Requires.NotNull(consumerDescription, "consumerDescription"); + Requires.NotNull(serviceDescription, "serviceDescription"); this.consumerDescription = consumerDescription; this.serviceDescription = serviceDescription; @@ -52,8 +52,8 @@ namespace DotNetOpenAuth.Test.OAuth { serviceTokenManager.AddConsumer(this.consumerDescription); // Prepare channels that will pass messages directly back and forth. - CoordinatingOAuthChannel consumerChannel = new CoordinatingOAuthChannel(consumerSigningElement, (IConsumerTokenManager)consumerTokenManager, this.consumerSecuritySettings); - CoordinatingOAuthChannel serviceProviderChannel = new CoordinatingOAuthChannel(spSigningElement, (IServiceProviderTokenManager)serviceTokenManager, this.serviceProviderSecuritySettings); + var consumerChannel = new CoordinatingOAuthConsumerChannel(consumerSigningElement, (IConsumerTokenManager)consumerTokenManager, this.consumerSecuritySettings); + var serviceProviderChannel = new CoordinatingOAuthServiceProviderChannel(spSigningElement, (IServiceProviderTokenManager)serviceTokenManager, this.serviceProviderSecuritySettings); consumerChannel.RemoteChannel = serviceProviderChannel; serviceProviderChannel.RemoteChannel = consumerChannel; diff --git a/src/DotNetOpenAuth.Test/OpenId/AuthenticationTests.cs b/src/DotNetOpenAuth.Test/OpenId/AuthenticationTests.cs index 99565ed..a0d833d 100644 --- a/src/DotNetOpenAuth.Test/OpenId/AuthenticationTests.cs +++ b/src/DotNetOpenAuth.Test/OpenId/AuthenticationTests.cs @@ -136,12 +136,12 @@ namespace DotNetOpenAuth.Test.OpenId { } private void ParameterizedAuthenticationTest(Protocol protocol, bool statelessRP, bool sharedAssociation, bool positive, bool immediate, bool tamper) { - Contract.Requires<ArgumentException>(!statelessRP || !sharedAssociation, "The RP cannot be stateless while sharing an association with the OP."); - Contract.Requires<ArgumentException>(positive || !tamper, "Cannot tamper with a negative response."); + Requires.True(!statelessRP || !sharedAssociation, null, "The RP cannot be stateless while sharing an association with the OP."); + Requires.True(positive || !tamper, null, "Cannot tamper with a negative response."); var securitySettings = new ProviderSecuritySettings(); var cryptoKeyStore = new MemoryCryptoKeyStore(); var associationStore = new ProviderAssociationHandleEncoder(cryptoKeyStore); - Association association = sharedAssociation ? HmacShaAssociation.Create(protocol, protocol.Args.SignatureAlgorithm.Best, AssociationRelyingPartyType.Smart, associationStore, securitySettings) : null; + Association association = sharedAssociation ? HmacShaAssociationProvider.Create(protocol, protocol.Args.SignatureAlgorithm.Best, AssociationRelyingPartyType.Smart, associationStore, securitySettings) : null; var coordinator = new OpenIdCoordinator( rp => { var request = new CheckIdRequest(protocol.Version, OPUri, immediate ? AuthenticationRequestMode.Immediate : AuthenticationRequestMode.Setup); diff --git a/src/DotNetOpenAuth.Test/OpenId/ChannelElements/ExtensionsBindingElementTests.cs b/src/DotNetOpenAuth.Test/OpenId/ChannelElements/ExtensionsBindingElementTests.cs index 5f1fc45..a4291d6 100644 --- a/src/DotNetOpenAuth.Test/OpenId/ChannelElements/ExtensionsBindingElementTests.cs +++ b/src/DotNetOpenAuth.Test/OpenId/ChannelElements/ExtensionsBindingElementTests.cs @@ -32,7 +32,7 @@ namespace DotNetOpenAuth.Test.OpenId.ChannelElements { this.factory = new StandardOpenIdExtensionFactory(); this.factory.RegisterExtension(MockOpenIdExtension.Factory); - this.rpElement = new ExtensionsBindingElement(this.factory, new RelyingPartySecuritySettings()); + this.rpElement = new ExtensionsBindingElementRelyingParty(this.factory, new RelyingPartySecuritySettings()); this.rpElement.Channel = new TestChannel(this.MessageDescriptions); this.request = new SignedResponseRequest(Protocol.Default.Version, OpenIdTestBase.OPUri, AuthenticationRequestMode.Immediate); } @@ -52,9 +52,9 @@ namespace DotNetOpenAuth.Test.OpenId.ChannelElements { Assert.AreSame(this.factory, this.rpElement.ExtensionFactory); } - [TestCase, ExpectedException(typeof(ArgumentNullException))] + [TestCase] public void PrepareMessageForSendingNull() { - this.rpElement.ProcessOutgoingMessage(null); + Assert.IsNull(this.rpElement.ProcessOutgoingMessage(null)); } /// <summary> @@ -168,7 +168,7 @@ namespace DotNetOpenAuth.Test.OpenId.ChannelElements { } private static void RegisterMockExtension(Channel channel) { - Contract.Requires<ArgumentNullException>(channel != null); + Requires.NotNull(channel, "channel"); ExtensionTestUtilities.RegisterExtension(channel, MockOpenIdExtension.Factory); } @@ -179,7 +179,7 @@ namespace DotNetOpenAuth.Test.OpenId.ChannelElements { /// <param name="protocol">The protocol to construct the message with.</param> /// <returns>The message ready to send from OP to RP.</returns> private IndirectSignedResponse CreateResponseWithExtensions(Protocol protocol) { - Contract.Requires<ArgumentNullException>(protocol != null); + Requires.NotNull(protocol, "protocol"); IndirectSignedResponse response = new IndirectSignedResponse(protocol.Version, RPUri); response.ProviderEndpoint = OPUri; diff --git a/src/DotNetOpenAuth.Test/OpenId/ChannelElements/OpenIdChannelTests.cs b/src/DotNetOpenAuth.Test/OpenId/ChannelElements/OpenIdChannelTests.cs index 5e0ccf5..059c80e 100644 --- a/src/DotNetOpenAuth.Test/OpenId/ChannelElements/OpenIdChannelTests.cs +++ b/src/DotNetOpenAuth.Test/OpenId/ChannelElements/OpenIdChannelTests.cs @@ -29,7 +29,7 @@ namespace DotNetOpenAuth.Test.OpenId.ChannelElements { [SetUp] public void Setup() { this.webHandler = new Mocks.TestWebRequestHandler(); - this.channel = new OpenIdChannel(new MemoryCryptoKeyStore(), new NonceMemoryStore(maximumMessageAge), new RelyingPartySecuritySettings()); + this.channel = new OpenIdRelyingPartyChannel(new MemoryCryptoKeyStore(), new NonceMemoryStore(maximumMessageAge), new RelyingPartySecuritySettings()); this.channel.WebRequestHandler = this.webHandler; } diff --git a/src/DotNetOpenAuth.Test/OpenId/ChannelElements/SigningBindingElementTests.cs b/src/DotNetOpenAuth.Test/OpenId/ChannelElements/SigningBindingElementTests.cs index e6f3e6e..22714a9 100644 --- a/src/DotNetOpenAuth.Test/OpenId/ChannelElements/SigningBindingElementTests.cs +++ b/src/DotNetOpenAuth.Test/OpenId/ChannelElements/SigningBindingElementTests.cs @@ -6,13 +6,16 @@ namespace DotNetOpenAuth.Test.OpenId.ChannelElements { using System; + using System.Collections.Generic; using System.Linq; + using DotNetOpenAuth.Messaging; using DotNetOpenAuth.Messaging.Bindings; using DotNetOpenAuth.OpenId; using DotNetOpenAuth.OpenId.ChannelElements; using DotNetOpenAuth.OpenId.Messages; using DotNetOpenAuth.OpenId.Provider; using DotNetOpenAuth.Test.Mocks; + using Moq; using NUnit.Framework; [TestFixture] @@ -30,7 +33,7 @@ namespace DotNetOpenAuth.Test.OpenId.ChannelElements { cryptoStore.StoreKey(ProviderAssociationKeyStorage.SharedAssociationBucket, handle, new CryptoKey(associationSecret, DateTime.UtcNow.AddDays(1))); var store = new ProviderAssociationKeyStorage(cryptoStore); - SigningBindingElement signer = new SigningBindingElement(store, settings); + SigningBindingElement signer = new ProviderSigningBindingElement(store, settings); signer.Channel = new TestChannel(this.MessageDescriptions); IndirectSignedResponse message = new IndirectSignedResponse(protocol.Version, new Uri("http://rp")); @@ -48,7 +51,7 @@ namespace DotNetOpenAuth.Test.OpenId.ChannelElements { [TestCase] public void SignedResponsesIncludeExtraDataInSignature() { Protocol protocol = Protocol.Default; - SigningBindingElement sbe = new SigningBindingElement(new ProviderAssociationHandleEncoder(new MemoryCryptoKeyStore()), new ProviderSecuritySettings()); + SigningBindingElement sbe = new ProviderSigningBindingElement(new ProviderAssociationHandleEncoder(new MemoryCryptoKeyStore()), new ProviderSecuritySettings()); sbe.Channel = new TestChannel(this.MessageDescriptions); IndirectSignedResponse response = new IndirectSignedResponse(protocol.Version, RPUri); response.ReturnTo = RPUri; @@ -68,5 +71,37 @@ namespace DotNetOpenAuth.Test.OpenId.ChannelElements { Assert.IsTrue(signedParameters.Contains("somesigned")); Assert.IsFalse(signedParameters.Contains("someunsigned")); } + + /// <summary> + /// Regression test for bug #45 (https://github.com/AArnott/dotnetopenid/issues/45) + /// </summary> + [TestCase, ExpectedException(typeof(ProtocolException))] + public void MissingSignedParameter() { + var cryptoStore = new MemoryCryptoKeyStore(); + byte[] associationSecret = Convert.FromBase64String("rsSwv1zPWfjPRQU80hciu8FPDC+GONAMJQ/AvSo1a2M="); + string handle = "{634477555066085461}{TTYcIg==}{32}"; + cryptoStore.StoreKey(ProviderAssociationKeyStorage.PrivateAssociationBucket, handle, new CryptoKey(associationSecret, DateTime.UtcNow.AddDays(1))); + + var signer = new ProviderSigningBindingElement(new ProviderAssociationKeyStorage(cryptoStore), new ProviderSecuritySettings()); + var testChannel = new TestChannel(new OpenIdProviderMessageFactory()); + signer.Channel = testChannel; + + var buggyRPMessage = new Dictionary<string, string>() { + { "openid.assoc_handle", "{634477555066085461}{TTYcIg==}{32}" }, + { "openid.claimed_id", "https://openid.stackexchange.com/user/f5e91123-e5b4-43c5-871f-5f276c75d31a" }, + { "openid.identity", "https://openid.stackexchange.com/user/f5e91123-e5b4-43c5-871f-5f276c75d31a" }, + { "openid.mode", "check_authentication" }, + { "openid.op_endpoint", "https://openid.stackexchange.com/openid/provider" }, + { "openid.response_nonce", "2011-08-01T00:32:10Zvdyt3efw" }, + { "openid.return_to", "http://openid-consumer.appspot.com/finish?session_id=1543025&janrain_nonce=2011-08-01T00%3A32%3A09ZIPGz7D" }, + { "openid.sig", "b0Rll6Kt1KKBWWBEg/qBvW3sQYtmhOUmpI0/UREBVZ0=" }, + { "openid.signed", "claimed_id,identity,assoc_handle,op_endpoint,return_to,response_nonce,ns.sreg,sreg.email,sreg.fullname" }, + { "openid.sreg.email", "kevin.montrose@stackoverflow.com" }, + { "openid.sreg.fullname", "Kevin K Montrose" }, + }; + var message = (CheckAuthenticationRequest)testChannel.Receive(buggyRPMessage, new MessageReceivingEndpoint(OPUri, HttpDeliveryMethods.PostRequest)); + var originalResponse = new IndirectSignedResponse(message, signer.Channel); + signer.ProcessIncomingMessage(originalResponse); + } } } diff --git a/src/DotNetOpenAuth.Test/OpenId/Extensions/AttributeExchange/AttributeRequestTests.cs b/src/DotNetOpenAuth.Test/OpenId/Extensions/AttributeExchange/AttributeRequestTests.cs index 5cc8ec1..3a7ecd7 100644 --- a/src/DotNetOpenAuth.Test/OpenId/Extensions/AttributeExchange/AttributeRequestTests.cs +++ b/src/DotNetOpenAuth.Test/OpenId/Extensions/AttributeExchange/AttributeRequestTests.cs @@ -25,7 +25,7 @@ namespace DotNetOpenAuth.Test.OpenId.Extensions { new AttributeRequest(string.Empty); } - [TestCase, ExpectedException(typeof(ArgumentException))] + [TestCase, ExpectedException(typeof(ArgumentNullException))] public void CtorNullTypeUri() { new AttributeRequest(null); } diff --git a/src/DotNetOpenAuth.Test/OpenId/Extensions/ExtensionTestUtilities.cs b/src/DotNetOpenAuth.Test/OpenId/Extensions/ExtensionTestUtilities.cs index 0f40bc3..4a78fc1 100644 --- a/src/DotNetOpenAuth.Test/OpenId/Extensions/ExtensionTestUtilities.cs +++ b/src/DotNetOpenAuth.Test/OpenId/Extensions/ExtensionTestUtilities.cs @@ -37,7 +37,7 @@ namespace DotNetOpenAuth.Test.OpenId.Extensions { var securitySettings = new ProviderSecuritySettings(); var cryptoKeyStore = new MemoryCryptoKeyStore(); var associationStore = new ProviderAssociationHandleEncoder(cryptoKeyStore); - Association association = HmacShaAssociation.Create(protocol, protocol.Args.SignatureAlgorithm.Best, AssociationRelyingPartyType.Smart, associationStore, securitySettings); + Association association = HmacShaAssociationProvider.Create(protocol, protocol.Args.SignatureAlgorithm.Best, AssociationRelyingPartyType.Smart, associationStore, securitySettings); var coordinator = new OpenIdCoordinator( rp => { RegisterExtension(rp.Channel, Mocks.MockOpenIdExtension.Factory); @@ -77,7 +77,7 @@ namespace DotNetOpenAuth.Test.OpenId.Extensions { } internal static void RegisterExtension(Channel channel, StandardOpenIdExtensionFactory.CreateDelegate extensionFactory) { - Contract.Requires<ArgumentNullException>(channel != null); + Requires.NotNull(channel, "channel"); var factory = (OpenIdExtensionFactoryAggregator)channel.BindingElements.OfType<ExtensionsBindingElement>().Single().ExtensionFactory; factory.Factories.OfType<StandardOpenIdExtensionFactory>().Single().RegisterExtension(extensionFactory); diff --git a/src/DotNetOpenAuth.Test/OpenId/Extensions/ExtensionsInteropHelperOPTests.cs b/src/DotNetOpenAuth.Test/OpenId/Extensions/ExtensionsInteropHelperOPTests.cs index bcafc41..8d7de0e 100644 --- a/src/DotNetOpenAuth.Test/OpenId/Extensions/ExtensionsInteropHelperOPTests.cs +++ b/src/DotNetOpenAuth.Test/OpenId/Extensions/ExtensionsInteropHelperOPTests.cs @@ -14,6 +14,7 @@ namespace DotNetOpenAuth.Test.OpenId.Extensions { using DotNetOpenAuth.OpenId.Extensions.SimpleRegistration; using DotNetOpenAuth.OpenId.Messages; using DotNetOpenAuth.OpenId.Provider; + using DotNetOpenAuth.OpenId.Provider.Extensions; using NUnit.Framework; [TestFixture] @@ -26,7 +27,7 @@ namespace DotNetOpenAuth.Test.OpenId.Extensions { base.SetUp(); var op = this.CreateProvider(); - var rpRequest = new CheckIdRequest(Protocol.Default.Version, OPUri, DotNetOpenAuth.OpenId.RelyingParty.AuthenticationRequestMode.Setup); + var rpRequest = new CheckIdRequest(Protocol.Default.Version, OPUri, DotNetOpenAuth.OpenId.AuthenticationRequestMode.Setup); rpRequest.ReturnTo = RPUri; this.extensions = rpRequest.Extensions; this.request = new AuthenticationRequest(op, rpRequest); diff --git a/src/DotNetOpenAuth.Test/OpenId/Extensions/ExtensionsInteropHelperRPRequestTests.cs b/src/DotNetOpenAuth.Test/OpenId/Extensions/ExtensionsInteropHelperRPRequestTests.cs index d477e9b..c70f023 100644 --- a/src/DotNetOpenAuth.Test/OpenId/Extensions/ExtensionsInteropHelperRPRequestTests.cs +++ b/src/DotNetOpenAuth.Test/OpenId/Extensions/ExtensionsInteropHelperRPRequestTests.cs @@ -12,7 +12,9 @@ namespace DotNetOpenAuth.Test.OpenId.Extensions { using DotNetOpenAuth.OpenId.Extensions.AttributeExchange; using DotNetOpenAuth.OpenId.Extensions.SimpleRegistration; using DotNetOpenAuth.OpenId.RelyingParty; + using DotNetOpenAuth.OpenId.RelyingParty.Extensions; using NUnit.Framework; + using ExtensionsInteropProviderHelper = DotNetOpenAuth.OpenId.Provider.Extensions.ExtensionsInteropHelper; [TestFixture] public class ExtensionsInteropHelperRPRequestTests : OpenIdTestBase { @@ -76,8 +78,8 @@ namespace DotNetOpenAuth.Test.OpenId.Extensions { ExtensionsInteropHelper.SpreadSregToAX(this.authReq, AXAttributeFormats.AXSchemaOrg | AXAttributeFormats.SchemaOpenIdNet); var ax = this.authReq.AppliedExtensions.OfType<FetchRequest>().Single(); Assert.IsTrue(ax.Attributes.Contains(WellKnownAttributes.Name.Alias)); - Assert.IsTrue(ax.Attributes.Contains(ExtensionsInteropHelper.TransformAXFormatTestHook(WellKnownAttributes.Name.Alias, AXAttributeFormats.SchemaOpenIdNet))); - Assert.IsFalse(ax.Attributes.Contains(ExtensionsInteropHelper.TransformAXFormatTestHook(WellKnownAttributes.Name.Alias, AXAttributeFormats.OpenIdNetSchema))); + Assert.IsTrue(ax.Attributes.Contains(ExtensionsInteropProviderHelper.TransformAXFormatTestHook(WellKnownAttributes.Name.Alias, AXAttributeFormats.SchemaOpenIdNet))); + Assert.IsFalse(ax.Attributes.Contains(ExtensionsInteropProviderHelper.TransformAXFormatTestHook(WellKnownAttributes.Name.Alias, AXAttributeFormats.OpenIdNetSchema))); } /// <summary> @@ -100,7 +102,7 @@ namespace DotNetOpenAuth.Test.OpenId.Extensions { this.InjectAdvertisedTypeUri(WellKnownAttributes.Name.FullName); ExtensionsInteropHelper.SpreadSregToAX(this.authReq, AXAttributeFormats.OpenIdNetSchema); var ax = this.authReq.AppliedExtensions.OfType<FetchRequest>().Single(); - Assert.IsFalse(ax.Attributes.Contains(ExtensionsInteropHelper.TransformAXFormatTestHook(WellKnownAttributes.Contact.Email, AXAttributeFormats.OpenIdNetSchema))); + Assert.IsFalse(ax.Attributes.Contains(ExtensionsInteropProviderHelper.TransformAXFormatTestHook(WellKnownAttributes.Contact.Email, AXAttributeFormats.OpenIdNetSchema))); Assert.IsTrue(ax.Attributes.Contains(WellKnownAttributes.Contact.Email)); } @@ -109,9 +111,9 @@ namespace DotNetOpenAuth.Test.OpenId.Extensions { /// </summary> [TestCase] public void TransformAXFormatTest() { - Assert.AreEqual(WellKnownAttributes.Name.Alias, ExtensionsInteropHelper.TransformAXFormatTestHook(WellKnownAttributes.Name.Alias, AXAttributeFormats.AXSchemaOrg)); - Assert.AreEqual("http://schema.openid.net/namePerson/friendly", ExtensionsInteropHelper.TransformAXFormatTestHook(WellKnownAttributes.Name.Alias, AXAttributeFormats.SchemaOpenIdNet)); - Assert.AreEqual("http://openid.net/schema/namePerson/friendly", ExtensionsInteropHelper.TransformAXFormatTestHook(WellKnownAttributes.Name.Alias, AXAttributeFormats.OpenIdNetSchema)); + Assert.AreEqual(WellKnownAttributes.Name.Alias, ExtensionsInteropProviderHelper.TransformAXFormatTestHook(WellKnownAttributes.Name.Alias, AXAttributeFormats.AXSchemaOrg)); + Assert.AreEqual("http://schema.openid.net/namePerson/friendly", ExtensionsInteropProviderHelper.TransformAXFormatTestHook(WellKnownAttributes.Name.Alias, AXAttributeFormats.SchemaOpenIdNet)); + Assert.AreEqual("http://openid.net/schema/namePerson/friendly", ExtensionsInteropProviderHelper.TransformAXFormatTestHook(WellKnownAttributes.Name.Alias, AXAttributeFormats.OpenIdNetSchema)); } /// <summary> diff --git a/src/DotNetOpenAuth.Test/OpenId/Extensions/ExtensionsInteropHelperRPResponseTests.cs b/src/DotNetOpenAuth.Test/OpenId/Extensions/ExtensionsInteropHelperRPResponseTests.cs index e7db6e6..d8d1529 100644 --- a/src/DotNetOpenAuth.Test/OpenId/Extensions/ExtensionsInteropHelperRPResponseTests.cs +++ b/src/DotNetOpenAuth.Test/OpenId/Extensions/ExtensionsInteropHelperRPResponseTests.cs @@ -13,7 +13,9 @@ namespace DotNetOpenAuth.Test.OpenId.Extensions { using DotNetOpenAuth.OpenId.Extensions.SimpleRegistration; using DotNetOpenAuth.OpenId.Messages; using DotNetOpenAuth.OpenId.RelyingParty; + using DotNetOpenAuth.OpenId.RelyingParty.Extensions; using NUnit.Framework; + using ExtensionsInteropProviderHelper = DotNetOpenAuth.OpenId.Provider.Extensions.ExtensionsInteropHelper; [TestFixture] public class ExtensionsInteropHelperRPResponseTests : OpenIdTestBase { @@ -74,7 +76,7 @@ namespace DotNetOpenAuth.Test.OpenId.Extensions { [TestCase] public void UnifyExtensionsasSregFromSchemaOpenIdNet() { var axInjected = new FetchResponse(); - axInjected.Attributes.Add(ExtensionsInteropHelper.TransformAXFormatTestHook(WellKnownAttributes.Name.Alias, AXAttributeFormats.SchemaOpenIdNet), "nate"); + axInjected.Attributes.Add(ExtensionsInteropProviderHelper.TransformAXFormatTestHook(WellKnownAttributes.Name.Alias, AXAttributeFormats.SchemaOpenIdNet), "nate"); this.extensions.Add(axInjected); var sreg = ExtensionsInteropHelper.UnifyExtensionsAsSreg(this.response, true); Assert.AreEqual("nate", sreg.Nickname); diff --git a/src/DotNetOpenAuth.Test/OpenId/IdentifierTests.cs b/src/DotNetOpenAuth.Test/OpenId/IdentifierTests.cs index 01e2fdc..5d3a15e 100644 --- a/src/DotNetOpenAuth.Test/OpenId/IdentifierTests.cs +++ b/src/DotNetOpenAuth.Test/OpenId/IdentifierTests.cs @@ -75,7 +75,7 @@ namespace DotNetOpenAuth.Test.OpenId { Assert.AreEqual(this.uri, ((UriIdentifier)id).Uri.AbsoluteUri); } - [TestCase, ExpectedException(typeof(ArgumentException))] + [TestCase, ExpectedException(typeof(ArgumentNullException))] public void ParseNull() { Identifier.Parse(null); } diff --git a/src/DotNetOpenAuth.Test/OpenId/Messages/AssociateRequestTests.cs b/src/DotNetOpenAuth.Test/OpenId/Messages/AssociateRequestTests.cs index f3c18d9..2d40f76 100644 --- a/src/DotNetOpenAuth.Test/OpenId/Messages/AssociateRequestTests.cs +++ b/src/DotNetOpenAuth.Test/OpenId/Messages/AssociateRequestTests.cs @@ -85,11 +85,11 @@ namespace DotNetOpenAuth.Test.OpenId.Messages { securitySettings.MinimumHashBitLength = 160; securitySettings.MaximumHashBitLength = 160; ProviderEndpointDescription provider = new ProviderEndpointDescription(OPUri, protocol.Version); - Assert.AreEqual(AssociateRequest.Create(securitySettings, provider).AssociationType, protocol.Args.SignatureAlgorithm.HMAC_SHA1); + Assert.AreEqual(AssociateRequestRelyingParty.Create(securitySettings, provider).AssociationType, protocol.Args.SignatureAlgorithm.HMAC_SHA1); securitySettings.MinimumHashBitLength = 384; securitySettings.MaximumHashBitLength = 384; - Assert.AreEqual(AssociateRequest.Create(securitySettings, provider).AssociationType, protocol.Args.SignatureAlgorithm.HMAC_SHA384); + Assert.AreEqual(AssociateRequestRelyingParty.Create(securitySettings, provider).AssociationType, protocol.Args.SignatureAlgorithm.HMAC_SHA384); } } } diff --git a/src/DotNetOpenAuth.Test/OpenId/Messages/IndirectSignedResponseTests.cs b/src/DotNetOpenAuth.Test/OpenId/Messages/IndirectSignedResponseTests.cs index 406a48b..848c849 100644 --- a/src/DotNetOpenAuth.Test/OpenId/Messages/IndirectSignedResponseTests.cs +++ b/src/DotNetOpenAuth.Test/OpenId/Messages/IndirectSignedResponseTests.cs @@ -125,7 +125,7 @@ namespace DotNetOpenAuth.Test.OpenId.Messages { } } - [TestCase, ExpectedException(typeof(ArgumentException))] + [TestCase, ExpectedException(typeof(ArgumentNullException))] public void GetReturnToArgumentNullKey() { this.response.GetReturnToArgument(null); } diff --git a/src/DotNetOpenAuth.Test/OpenId/OpenIdCoordinator.cs b/src/DotNetOpenAuth.Test/OpenId/OpenIdCoordinator.cs index d4884e8..e5c3c64 100644 --- a/src/DotNetOpenAuth.Test/OpenId/OpenIdCoordinator.cs +++ b/src/DotNetOpenAuth.Test/OpenId/OpenIdCoordinator.cs @@ -37,7 +37,7 @@ namespace DotNetOpenAuth.Test.OpenId { } private static Action<OpenIdRelyingParty> WrapAction(Action<OpenIdRelyingParty> action) { - Contract.Requires<ArgumentNullException>(action != null); + Requires.NotNull(action, "action"); return rp => { action(rp); @@ -46,7 +46,7 @@ namespace DotNetOpenAuth.Test.OpenId { } private static Action<OpenIdProvider> WrapAction(Action<OpenIdProvider> action) { - Contract.Requires<ArgumentNullException>(action != null); + Requires.NotNull(action, "action"); return op => { action(op); diff --git a/src/DotNetOpenAuth.Test/OpenId/OpenIdTestBase.cs b/src/DotNetOpenAuth.Test/OpenId/OpenIdTestBase.cs index 4143a08..4b96d65 100644 --- a/src/DotNetOpenAuth.Test/OpenId/OpenIdTestBase.cs +++ b/src/DotNetOpenAuth.Test/OpenId/OpenIdTestBase.cs @@ -66,8 +66,8 @@ namespace DotNetOpenAuth.Test.OpenId { public override void SetUp() { base.SetUp(); - this.RelyingPartySecuritySettings = DotNetOpenAuthSection.Configuration.OpenId.RelyingParty.SecuritySettings.CreateSecuritySettings(); - this.ProviderSecuritySettings = DotNetOpenAuthSection.Configuration.OpenId.Provider.SecuritySettings.CreateSecuritySettings(); + this.RelyingPartySecuritySettings = OpenIdElement.Configuration.RelyingParty.SecuritySettings.CreateSecuritySettings(); + this.ProviderSecuritySettings = OpenIdElement.Configuration.Provider.SecuritySettings.CreateSecuritySettings(); this.MockResponder = MockHttpRequest.CreateUntrustedMockHttpHandler(); this.RequestHandler = this.MockResponder.MockWebRequestHandler; diff --git a/src/DotNetOpenAuth.Test/OpenId/Provider/AuthenticationRequestTest.cs b/src/DotNetOpenAuth.Test/OpenId/Provider/AuthenticationRequestTest.cs index 39b4355..d124c7a 100644 --- a/src/DotNetOpenAuth.Test/OpenId/Provider/AuthenticationRequestTest.cs +++ b/src/DotNetOpenAuth.Test/OpenId/Provider/AuthenticationRequestTest.cs @@ -22,7 +22,7 @@ namespace DotNetOpenAuth.Test.OpenId.Provider { // Construct a V1 immediate request Protocol protocol = Protocol.V11; OpenIdProvider provider = this.CreateProvider(); - CheckIdRequest immediateRequest = new CheckIdRequest(protocol.Version, OPUri, DotNetOpenAuth.OpenId.RelyingParty.AuthenticationRequestMode.Immediate); + CheckIdRequest immediateRequest = new CheckIdRequest(protocol.Version, OPUri, DotNetOpenAuth.OpenId.AuthenticationRequestMode.Immediate); immediateRequest.Realm = RPRealmUri; immediateRequest.ReturnTo = RPUri; immediateRequest.LocalIdentifier = "http://somebody"; diff --git a/src/DotNetOpenAuth.Test/OpenId/Provider/HostProcessedRequestTests.cs b/src/DotNetOpenAuth.Test/OpenId/Provider/HostProcessedRequestTests.cs index 9bb8095..66b2f53 100644 --- a/src/DotNetOpenAuth.Test/OpenId/Provider/HostProcessedRequestTests.cs +++ b/src/DotNetOpenAuth.Test/OpenId/Provider/HostProcessedRequestTests.cs @@ -24,7 +24,7 @@ namespace DotNetOpenAuth.Test.OpenId.Provider { this.protocol = Protocol.Default; this.provider = this.CreateProvider(); - this.checkIdRequest = new CheckIdRequest(this.protocol.Version, OPUri, DotNetOpenAuth.OpenId.RelyingParty.AuthenticationRequestMode.Setup); + this.checkIdRequest = new CheckIdRequest(this.protocol.Version, OPUri, DotNetOpenAuth.OpenId.AuthenticationRequestMode.Setup); this.checkIdRequest.Realm = RPRealmUri; this.checkIdRequest.ReturnTo = RPUri; this.request = new AuthenticationRequest(this.provider, this.checkIdRequest); @@ -32,14 +32,14 @@ namespace DotNetOpenAuth.Test.OpenId.Provider { [TestCase] public void IsReturnUrlDiscoverableNoResponse() { - Assert.AreEqual(RelyingPartyDiscoveryResult.NoServiceDocument, this.request.IsReturnUrlDiscoverable(this.provider)); + Assert.AreEqual(RelyingPartyDiscoveryResult.NoServiceDocument, this.request.IsReturnUrlDiscoverable(this.provider.Channel.WebRequestHandler)); } [TestCase] public void IsReturnUrlDiscoverableValidResponse() { this.MockResponder.RegisterMockRPDiscovery(); this.request = new AuthenticationRequest(this.provider, this.checkIdRequest); - Assert.AreEqual(RelyingPartyDiscoveryResult.Success, this.request.IsReturnUrlDiscoverable(this.provider)); + Assert.AreEqual(RelyingPartyDiscoveryResult.Success, this.request.IsReturnUrlDiscoverable(this.provider.Channel.WebRequestHandler)); } /// <summary> @@ -50,7 +50,7 @@ namespace DotNetOpenAuth.Test.OpenId.Provider { public void IsReturnUrlDiscoverableNotSsl() { this.provider.SecuritySettings.RequireSsl = true; this.MockResponder.RegisterMockRPDiscovery(); - Assert.AreEqual(RelyingPartyDiscoveryResult.NoServiceDocument, this.request.IsReturnUrlDiscoverable(this.provider)); + Assert.AreEqual(RelyingPartyDiscoveryResult.NoServiceDocument, this.request.IsReturnUrlDiscoverable(this.provider.Channel.WebRequestHandler)); } /// <summary> @@ -65,12 +65,12 @@ namespace DotNetOpenAuth.Test.OpenId.Provider { // Try once with RequireSsl this.provider.SecuritySettings.RequireSsl = true; this.request = new AuthenticationRequest(this.provider, this.checkIdRequest); - Assert.AreEqual(RelyingPartyDiscoveryResult.Success, this.request.IsReturnUrlDiscoverable(this.provider)); + Assert.AreEqual(RelyingPartyDiscoveryResult.Success, this.request.IsReturnUrlDiscoverable(this.provider.Channel.WebRequestHandler)); // And again without RequireSsl this.provider.SecuritySettings.RequireSsl = false; this.request = new AuthenticationRequest(this.provider, this.checkIdRequest); - Assert.AreEqual(RelyingPartyDiscoveryResult.Success, this.request.IsReturnUrlDiscoverable(this.provider)); + Assert.AreEqual(RelyingPartyDiscoveryResult.Success, this.request.IsReturnUrlDiscoverable(this.provider.Channel.WebRequestHandler)); } [TestCase] @@ -79,7 +79,7 @@ namespace DotNetOpenAuth.Test.OpenId.Provider { this.provider.SecuritySettings.RequireSsl = false; // reset for another failure test case this.checkIdRequest.ReturnTo = new Uri("http://somerandom/host"); this.request = new AuthenticationRequest(this.provider, this.checkIdRequest); - Assert.AreEqual(RelyingPartyDiscoveryResult.NoMatchingReturnTo, this.request.IsReturnUrlDiscoverable(this.provider)); + Assert.AreEqual(RelyingPartyDiscoveryResult.NoMatchingReturnTo, this.request.IsReturnUrlDiscoverable(this.provider.Channel.WebRequestHandler)); } } } diff --git a/src/DotNetOpenAuth.Test/OpenId/Provider/OpenIdProviderTests.cs b/src/DotNetOpenAuth.Test/OpenId/Provider/OpenIdProviderTests.cs index c8c3831..98cabe9 100644 --- a/src/DotNetOpenAuth.Test/OpenId/Provider/OpenIdProviderTests.cs +++ b/src/DotNetOpenAuth.Test/OpenId/Provider/OpenIdProviderTests.cs @@ -100,7 +100,7 @@ namespace DotNetOpenAuth.Test.OpenId.Provider { // Test some non-empty request scenario. OpenIdCoordinator coordinator = new OpenIdCoordinator( rp => { - rp.Channel.Request(AssociateRequest.Create(rp.SecuritySettings, providerDescription)); + rp.Channel.Request(AssociateRequestRelyingParty.Create(rp.SecuritySettings, providerDescription)); }, op => { IRequest request = op.GetRequest(); diff --git a/src/DotNetOpenAuth.Test/OpenId/Provider/PerformanceTests.cs b/src/DotNetOpenAuth.Test/OpenId/Provider/PerformanceTests.cs index 365c5c5..1e15bde 100644 --- a/src/DotNetOpenAuth.Test/OpenId/Provider/PerformanceTests.cs +++ b/src/DotNetOpenAuth.Test/OpenId/Provider/PerformanceTests.cs @@ -74,7 +74,7 @@ namespace DotNetOpenAuth.Test.OpenId.Provider { } private void ParameterizedCheckIdTest(Protocol protocol, string assocType) { - Association assoc = HmacShaAssociation.Create( + Association assoc = HmacShaAssociationProvider.Create( protocol, assocType, AssociationRelyingPartyType.Smart, @@ -93,7 +93,7 @@ namespace DotNetOpenAuth.Test.OpenId.Provider { private HttpRequestInfo CreateAssociateRequest(Uri opEndpoint) { var rp = CreateRelyingParty(true); - AssociateRequest associateMessage = AssociateRequest.Create(rp.SecuritySettings, new ProviderEndpointDescription(opEndpoint, Protocol.Default.Version)); + AssociateRequest associateMessage = AssociateRequestRelyingParty.Create(rp.SecuritySettings, new ProviderEndpointDescription(opEndpoint, Protocol.Default.Version)); Channel rpChannel = rp.Channel; MemoryStream ms = new MemoryStream(); StreamWriter mswriter = new StreamWriter(ms); @@ -111,7 +111,7 @@ namespace DotNetOpenAuth.Test.OpenId.Provider { CheckIdRequest checkidMessage = new CheckIdRequest( Protocol.Default.Version, OPUri, - DotNetOpenAuth.OpenId.RelyingParty.AuthenticationRequestMode.Setup); + DotNetOpenAuth.OpenId.AuthenticationRequestMode.Setup); if (sharedAssociation) { checkidMessage.AssociationHandle = SharedAssociationHandle; } diff --git a/src/DotNetOpenAuth.Test/OpenId/RelyingParty/IdentifierDiscoveryResultTests.cs b/src/DotNetOpenAuth.Test/OpenId/RelyingParty/IdentifierDiscoveryResultTests.cs index 896cf57..6d2de71 100644 --- a/src/DotNetOpenAuth.Test/OpenId/RelyingParty/IdentifierDiscoveryResultTests.cs +++ b/src/DotNetOpenAuth.Test/OpenId/RelyingParty/IdentifierDiscoveryResultTests.cs @@ -143,7 +143,7 @@ namespace DotNetOpenAuth.Test.OpenId.RelyingParty { Assert.IsFalse(se.IsTypeUriPresent("http://someother")); } - [TestCase, ExpectedException(typeof(ArgumentException))] + [TestCase, ExpectedException(typeof(ArgumentNullException))] public void IsTypeUriPresentNull() { IdentifierDiscoveryResult se = IdentifierDiscoveryResult.CreateForClaimedIdentifier(this.claimedXri, this.userSuppliedXri, this.localId, new ProviderEndpointDescription(this.providerEndpoint, this.v20TypeUris), this.servicePriority, this.uriPriority); se.IsTypeUriPresent(null); @@ -161,7 +161,7 @@ namespace DotNetOpenAuth.Test.OpenId.RelyingParty { se.IsExtensionSupported((Type)null); } - [TestCase, ExpectedException(typeof(ArgumentException))] + [TestCase, ExpectedException(typeof(ArgumentNullException))] public void IsTypeUriPresentNullString() { var se = IdentifierDiscoveryResult.CreateForProviderIdentifier(OPUri, new ProviderEndpointDescription(OPUri, this.v20TypeUris), null, null); se.IsTypeUriPresent((string)null); diff --git a/src/DotNetOpenAuth.Test/OpenId/UriIdentifierTests.cs b/src/DotNetOpenAuth.Test/OpenId/UriIdentifierTests.cs index e6bed2f..427f890 100644 --- a/src/DotNetOpenAuth.Test/OpenId/UriIdentifierTests.cs +++ b/src/DotNetOpenAuth.Test/OpenId/UriIdentifierTests.cs @@ -31,7 +31,7 @@ namespace DotNetOpenAuth.Test.OpenId { new UriIdentifier((Uri)null); } - [TestCase, ExpectedException(typeof(ArgumentException))] + [TestCase, ExpectedException(typeof(ArgumentNullException))] public void CtorNullString() { new UriIdentifier((string)null); } diff --git a/src/DotNetOpenAuth.Test/OpenId/XriIdentifierTests.cs b/src/DotNetOpenAuth.Test/OpenId/XriIdentifierTests.cs index 0c80821..e2acf34 100644 --- a/src/DotNetOpenAuth.Test/OpenId/XriIdentifierTests.cs +++ b/src/DotNetOpenAuth.Test/OpenId/XriIdentifierTests.cs @@ -22,7 +22,7 @@ namespace DotNetOpenAuth.Test.OpenId { base.SetUp(); } - [TestCase, ExpectedException(typeof(ArgumentException))] + [TestCase, ExpectedException(typeof(ArgumentNullException))] public void CtorNull() { new XriIdentifier(null); } diff --git a/src/DotNetOpenAuth.sln b/src/DotNetOpenAuth.sln index 2fd5b89..505ce7e 100644 --- a/src/DotNetOpenAuth.sln +++ b/src/DotNetOpenAuth.sln @@ -9,7 +9,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution ..\doc\README.Bin.html = ..\doc\README.Bin.html ..\doc\README.html = ..\doc\README.html ..\samples\README.html = ..\samples\README.html - ..\nuget\content\web.config.transform = ..\nuget\content\web.config.transform EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Specs", "Specs", "{CD57219F-24F4-4136-8741-6063D0D7A031}" @@ -32,7 +31,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Samples", "Samples", "{B4C6 EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "OpenID", "OpenID", "{034D5B5B-7D00-4A9D-8AFE-4A476E0575B1}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "OAuth", "OAuth", "{1E2CBAA5-60A3-4AED-912E-541F5753CDC6}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "OAuth2", "OAuth2", "{1E2CBAA5-60A3-4AED-912E-541F5753CDC6}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "InfoCard", "InfoCard", "{8A5CEDB9-7F8A-4BE2-A1B9-97130F453277}" EndProject @@ -40,8 +39,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tools", "Tools", "{E9ED920D EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Project Templates", "Project Templates", "{B9EB8729-4B54-4453-B089-FE6761BA3057}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotNetOpenAuth", "DotNetOpenAuth\DotNetOpenAuth.csproj", "{3191B653-F76D-4C1A-9A5A-347BC3AAAAB7}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotNetOpenAuth.Test", "DotNetOpenAuth.Test\DotNetOpenAuth.Test.csproj", "{4376ECC9-C346-4A99-B13C-FA93C0FBD2C9}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotNetOpenAuth.ApplicationBlock", "..\samples\DotNetOpenAuth.ApplicationBlock\DotNetOpenAuth.ApplicationBlock.csproj", "{AA78D112-D889-414B-A7D4-467B34C7B663}" @@ -49,7 +46,7 @@ EndProject Project("{E24C65DC-7377-472B-9ABA-BC803B73C61A}") = "DotNetOpenAuth.TestWeb", "DotNetOpenAuth.TestWeb\", "{47A84EF7-68C3-4D47-926A-9CCEA6518531}" ProjectSection(WebsiteProperties) = preProject TargetFrameworkMoniker = ".NETFramework,Version%3Dv3.5" - ProjectReferences = "{3191B653-F76D-4C1A-9A5A-347BC3AAAAB7}|DotNetOpenAuth.dll;{4376ECC9-C346-4A99-B13C-FA93C0FBD2C9}|DotNetOpenAuth.Test.dll;" + ProjectReferences = "{4376ECC9-C346-4A99-B13C-FA93C0FBD2C9}|DotNetOpenAuth.Test.dll;{408D10B8-34BA-4CBD-B7AA-FEB1907ABA4C}|DotNetOpenAuth.InfoCard.dll;{60426312-6AE5-4835-8667-37EDEA670222}|DotNetOpenAuth.Core.dll;{A288FCC8-6FCF-46DA-A45E-5F9281556361}|DotNetOpenAuth.OAuth.dll;{3896A32A-E876-4C23-B9B8-78E17D134CD3}|DotNetOpenAuth.OpenId.dll;{56459A6C-6BA2-4BAC-A9C0-27E3BD961FA6}|DotNetOpenAuth.OAuth2.dll;{26DC877F-5987-48DD-9DDB-E62F2DE0E150}|Org.Mentalis.Security.Cryptography.dll;{F4CD3C04-6037-4946-B7A5-34BFC96A75D2}|Mono.Math.dll;{173E7B8D-E751-46E2-A133-F72297C0D2F4}|DotNetOpenAuth.Core.UI.dll;{E040EB58-B4D2-457B-A023-AE6EF3BD34DE}|DotNetOpenAuth.InfoCard.UI.dll;{B202E40D-4663-4A2B-ACDA-865F88FF7CAA}|DotNetOpenAuth.OAuth.Consumer.dll;{FED1923A-6D70-49B5-A37A-FB744FEC1C86}|DotNetOpenAuth.OAuth.ServiceProvider.dll;{99BB7543-EA16-43EE-A7BC-D7A25A3B22F6}|DotNetOpenAuth.OAuth2.AuthorizationServer.dll;{CDEDD439-7F35-4E6E-8605-4E70BDC4CC99}|DotNetOpenAuth.OAuth2.Client.dll;{ADC2CC8C-541E-4F86-ACB1-DD504A36FA4B}|DotNetOpenAuth.OAuth2.Client.UI.dll;{A1A3150A-7B0E-4A34-8E35-045296CD3C76}|DotNetOpenAuth.OAuth2.ResourceServer.dll;{F8284738-3B5D-4733-A511-38C23F4A763F}|DotNetOpenAuth.OpenId.Provider.dll;{F458AB60-BA1C-43D9-8CEF-EC01B50BE87B}|DotNetOpenAuth.OpenId.RelyingParty.dll;{9D0F8866-2131-4C2A-BC0E-16FEA5B50828}|DotNetOpenAuth.OpenId.Provider.UI.dll;{75E13AAE-7D51-4421-ABFD-3F3DC91F576E}|DotNetOpenAuth.OpenId.UI.dll;{1ED8D424-F8AB-4050-ACEB-F27F4F909484}|DotNetOpenAuth.OpenId.RelyingParty.UI.dll;" Debug.AspNetCompiler.VirtualPath = "/DotNetOpenAuth.TestWeb" Debug.AspNetCompiler.PhysicalPath = "DotNetOpenAuth.TestWeb\" Debug.AspNetCompiler.TargetPath = "PrecompiledWeb\DotNetOpenAuth.TestWeb\" @@ -76,7 +73,7 @@ EndProject Project("{E24C65DC-7377-472B-9ABA-BC803B73C61A}") = "InfoCardRelyingParty", "..\samples\InfoCardRelyingParty\", "{6EB90284-BD15-461C-BBF2-131CF55F7C8B}" ProjectSection(WebsiteProperties) = preProject TargetFrameworkMoniker = ".NETFramework,Version%3Dv3.5" - ProjectReferences = "{3191B653-F76D-4C1A-9A5A-347BC3AAAAB7}|DotNetOpenAuth.dll;" + ProjectReferences = "{408D10B8-34BA-4CBD-B7AA-FEB1907ABA4C}|DotNetOpenAuth.InfoCard.dll;{60426312-6AE5-4835-8667-37EDEA670222}|DotNetOpenAuth.Core.dll;{173E7B8D-E751-46E2-A133-F72297C0D2F4}|DotNetOpenAuth.Core.UI.dll;{26DC877F-5987-48DD-9DDB-E62F2DE0E150}|Org.Mentalis.Security.Cryptography.dll;{F4CD3C04-6037-4946-B7A5-34BFC96A75D2}|Mono.Math.dll;{E040EB58-B4D2-457B-A023-AE6EF3BD34DE}|DotNetOpenAuth.InfoCard.UI.dll;" Debug.AspNetCompiler.VirtualPath = "/InfoCardRelyingParty" Debug.AspNetCompiler.PhysicalPath = "..\samples\InfoCardRelyingParty\" Debug.AspNetCompiler.TargetPath = "PrecompiledWeb\InfoCardRelyingParty\" @@ -151,6 +148,60 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OAuthResourceServer", "..\s EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OAuthAuthorizationServer", "..\samples\OAuthAuthorizationServer\OAuthAuthorizationServer.csproj", "{C78E8235-1D46-43EB-A912-80B522C4E9AE}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotNetOpenAuth.Core", "DotNetOpenAuth.Core\DotNetOpenAuth.Core.csproj", "{60426312-6AE5-4835-8667-37EDEA670222}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotNetOpenAuth.OpenId", "DotNetOpenAuth.OpenId\DotNetOpenAuth.OpenId.csproj", "{3896A32A-E876-4C23-B9B8-78E17D134CD3}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotNetOpenAuth.OAuth", "DotNetOpenAuth.OAuth\DotNetOpenAuth.OAuth.csproj", "{A288FCC8-6FCF-46DA-A45E-5F9281556361}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotNetOpenAuth.InfoCard", "DotNetOpenAuth.InfoCard\DotNetOpenAuth.InfoCard.csproj", "{408D10B8-34BA-4CBD-B7AA-FEB1907ABA4C}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotNetOpenAuth.OAuth2", "DotNetOpenAuth.OAuth2\DotNetOpenAuth.OAuth2.csproj", "{56459A6C-6BA2-4BAC-A9C0-27E3BD961FA6}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotNetOpenAuth.OpenId.Provider", "DotNetOpenAuth.OpenId.Provider\DotNetOpenAuth.OpenId.Provider.csproj", "{F8284738-3B5D-4733-A511-38C23F4A763F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotNetOpenAuth.OpenId.RelyingParty", "DotNetOpenAuth.OpenId.RelyingParty\DotNetOpenAuth.OpenId.RelyingParty.csproj", "{F458AB60-BA1C-43D9-8CEF-EC01B50BE87B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Mono.Math", "Mono.Math\Mono.Math.csproj", "{F4CD3C04-6037-4946-B7A5-34BFC96A75D2}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Org.Mentalis.Security.Cryptography", "Org.Mentalis.Security.Cryptography\Org.Mentalis.Security.Cryptography.csproj", "{26DC877F-5987-48DD-9DDB-E62F2DE0E150}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotNetOpenAuth.OpenId.RelyingParty.UI", "DotNetOpenAuth.OpenId.RelyingParty.UI\DotNetOpenAuth.OpenId.RelyingParty.UI.csproj", "{1ED8D424-F8AB-4050-ACEB-F27F4F909484}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotNetOpenAuth.OpenId.Provider.UI", "DotNetOpenAuth.OpenId.Provider.UI\DotNetOpenAuth.OpenId.Provider.UI.csproj", "{9D0F8866-2131-4C2A-BC0E-16FEA5B50828}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotNetOpenAuth.OpenId.UI", "DotNetOpenAuth.OpenId.UI\DotNetOpenAuth.OpenId.UI.csproj", "{75E13AAE-7D51-4421-ABFD-3F3DC91F576E}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotNetOpenAuth.Core.UI", "DotNetOpenAuth.Core.UI\DotNetOpenAuth.Core.UI.csproj", "{173E7B8D-E751-46E2-A133-F72297C0D2F4}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Product", "Product", "{8D4236F7-C49B-49D3-BA71-6B86C9514BDE}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "OpenID", "OpenID", "{C7EF1823-3AA7-477E-8476-28929F5C05D2}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "OAuth", "OAuth", "{9AF74F53-10F5-49A2-B747-87B97CD559D3}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "InfoCard", "InfoCard", "{529B4262-6B5A-4EF9-BD3B-1D29A2597B67}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotNetOpenAuth.InfoCard.UI", "DotNetOpenAuth.InfoCard.UI\DotNetOpenAuth.InfoCard.UI.csproj", "{E040EB58-B4D2-457B-A023-AE6EF3BD34DE}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "OAuth2", "OAuth2", "{238B6BA8-AD99-43C9-B8E2-D2BCE6CE04DC}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotNetOpenAuth.OAuth.Consumer", "DotNetOpenAuth.OAuth.Consumer\DotNetOpenAuth.OAuth.Consumer.csproj", "{B202E40D-4663-4A2B-ACDA-865F88FF7CAA}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotNetOpenAuth.OAuth.ServiceProvider", "DotNetOpenAuth.OAuth.ServiceProvider\DotNetOpenAuth.OAuth.ServiceProvider.csproj", "{FED1923A-6D70-49B5-A37A-FB744FEC1C86}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotNetOpenAuth.OAuth2.AuthorizationServer", "DotNetOpenAuth.OAuth2.AuthorizationServer\DotNetOpenAuth.OAuth2.AuthorizationServer.csproj", "{99BB7543-EA16-43EE-A7BC-D7A25A3B22F6}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotNetOpenAuth.OAuth2.Client", "DotNetOpenAuth.OAuth2.Client\DotNetOpenAuth.OAuth2.Client.csproj", "{CDEDD439-7F35-4E6E-8605-4E70BDC4CC99}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotNetOpenAuth.OAuth2.ResourceServer", "DotNetOpenAuth.OAuth2.ResourceServer\DotNetOpenAuth.OAuth2.ResourceServer.csproj", "{A1A3150A-7B0E-4A34-8E35-045296CD3C76}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotNetOpenAuth.OAuth2.Client.UI", "DotNetOpenAuth.OAuth2.Client.UI\DotNetOpenAuth.OAuth2.Client.UI.csproj", "{ADC2CC8C-541E-4F86-ACB1-DD504A36FA4B}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Combinations", "Combinations", "{57A7DD35-666C-4FA3-9A1B-38961E50CA27}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotNetOpenAuth.OpenIdInfoCard.UI", "DotNetOpenAuth.OpenIdInfoCard.UI\DotNetOpenAuth.OpenIdInfoCard.UI.csproj", "{3A8347E8-59A5-4092-8842-95C75D7D2F36}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution CodeAnalysis|Any CPU = CodeAnalysis|Any CPU @@ -159,14 +210,6 @@ Global ReleaseNoUI|Any CPU = ReleaseNoUI|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {3191B653-F76D-4C1A-9A5A-347BC3AAAAB7}.CodeAnalysis|Any CPU.ActiveCfg = CodeAnalysis|Any CPU - {3191B653-F76D-4C1A-9A5A-347BC3AAAAB7}.CodeAnalysis|Any CPU.Build.0 = CodeAnalysis|Any CPU - {3191B653-F76D-4C1A-9A5A-347BC3AAAAB7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {3191B653-F76D-4C1A-9A5A-347BC3AAAAB7}.Debug|Any CPU.Build.0 = Debug|Any CPU - {3191B653-F76D-4C1A-9A5A-347BC3AAAAB7}.Release|Any CPU.ActiveCfg = Release|Any CPU - {3191B653-F76D-4C1A-9A5A-347BC3AAAAB7}.Release|Any CPU.Build.0 = Release|Any CPU - {3191B653-F76D-4C1A-9A5A-347BC3AAAAB7}.ReleaseNoUI|Any CPU.ActiveCfg = ReleaseNoUI|Any CPU - {3191B653-F76D-4C1A-9A5A-347BC3AAAAB7}.ReleaseNoUI|Any CPU.Build.0 = ReleaseNoUI|Any CPU {4376ECC9-C346-4A99-B13C-FA93C0FBD2C9}.CodeAnalysis|Any CPU.ActiveCfg = CodeAnalysis|Any CPU {4376ECC9-C346-4A99-B13C-FA93C0FBD2C9}.CodeAnalysis|Any CPU.Build.0 = CodeAnalysis|Any CPU {4376ECC9-C346-4A99-B13C-FA93C0FBD2C9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU @@ -318,6 +361,174 @@ Global {C78E8235-1D46-43EB-A912-80B522C4E9AE}.Release|Any CPU.Build.0 = Release|Any CPU {C78E8235-1D46-43EB-A912-80B522C4E9AE}.ReleaseNoUI|Any CPU.ActiveCfg = Release|Any CPU {C78E8235-1D46-43EB-A912-80B522C4E9AE}.ReleaseNoUI|Any CPU.Build.0 = Release|Any CPU + {60426312-6AE5-4835-8667-37EDEA670222}.CodeAnalysis|Any CPU.ActiveCfg = CodeAnalysis|Any CPU + {60426312-6AE5-4835-8667-37EDEA670222}.CodeAnalysis|Any CPU.Build.0 = CodeAnalysis|Any CPU + {60426312-6AE5-4835-8667-37EDEA670222}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {60426312-6AE5-4835-8667-37EDEA670222}.Debug|Any CPU.Build.0 = Debug|Any CPU + {60426312-6AE5-4835-8667-37EDEA670222}.Release|Any CPU.ActiveCfg = Release|Any CPU + {60426312-6AE5-4835-8667-37EDEA670222}.Release|Any CPU.Build.0 = Release|Any CPU + {60426312-6AE5-4835-8667-37EDEA670222}.ReleaseNoUI|Any CPU.ActiveCfg = Release|Any CPU + {60426312-6AE5-4835-8667-37EDEA670222}.ReleaseNoUI|Any CPU.Build.0 = Release|Any CPU + {3896A32A-E876-4C23-B9B8-78E17D134CD3}.CodeAnalysis|Any CPU.ActiveCfg = Debug|Any CPU + {3896A32A-E876-4C23-B9B8-78E17D134CD3}.CodeAnalysis|Any CPU.Build.0 = Debug|Any CPU + {3896A32A-E876-4C23-B9B8-78E17D134CD3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3896A32A-E876-4C23-B9B8-78E17D134CD3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3896A32A-E876-4C23-B9B8-78E17D134CD3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3896A32A-E876-4C23-B9B8-78E17D134CD3}.Release|Any CPU.Build.0 = Release|Any CPU + {3896A32A-E876-4C23-B9B8-78E17D134CD3}.ReleaseNoUI|Any CPU.ActiveCfg = Release|Any CPU + {3896A32A-E876-4C23-B9B8-78E17D134CD3}.ReleaseNoUI|Any CPU.Build.0 = Release|Any CPU + {A288FCC8-6FCF-46DA-A45E-5F9281556361}.CodeAnalysis|Any CPU.ActiveCfg = CodeAnalysis|Any CPU + {A288FCC8-6FCF-46DA-A45E-5F9281556361}.CodeAnalysis|Any CPU.Build.0 = CodeAnalysis|Any CPU + {A288FCC8-6FCF-46DA-A45E-5F9281556361}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A288FCC8-6FCF-46DA-A45E-5F9281556361}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A288FCC8-6FCF-46DA-A45E-5F9281556361}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A288FCC8-6FCF-46DA-A45E-5F9281556361}.Release|Any CPU.Build.0 = Release|Any CPU + {A288FCC8-6FCF-46DA-A45E-5F9281556361}.ReleaseNoUI|Any CPU.ActiveCfg = Release|Any CPU + {A288FCC8-6FCF-46DA-A45E-5F9281556361}.ReleaseNoUI|Any CPU.Build.0 = Release|Any CPU + {408D10B8-34BA-4CBD-B7AA-FEB1907ABA4C}.CodeAnalysis|Any CPU.ActiveCfg = CodeAnalysis|Any CPU + {408D10B8-34BA-4CBD-B7AA-FEB1907ABA4C}.CodeAnalysis|Any CPU.Build.0 = CodeAnalysis|Any CPU + {408D10B8-34BA-4CBD-B7AA-FEB1907ABA4C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {408D10B8-34BA-4CBD-B7AA-FEB1907ABA4C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {408D10B8-34BA-4CBD-B7AA-FEB1907ABA4C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {408D10B8-34BA-4CBD-B7AA-FEB1907ABA4C}.Release|Any CPU.Build.0 = Release|Any CPU + {408D10B8-34BA-4CBD-B7AA-FEB1907ABA4C}.ReleaseNoUI|Any CPU.ActiveCfg = Release|Any CPU + {408D10B8-34BA-4CBD-B7AA-FEB1907ABA4C}.ReleaseNoUI|Any CPU.Build.0 = Release|Any CPU + {56459A6C-6BA2-4BAC-A9C0-27E3BD961FA6}.CodeAnalysis|Any CPU.ActiveCfg = CodeAnalysis|Any CPU + {56459A6C-6BA2-4BAC-A9C0-27E3BD961FA6}.CodeAnalysis|Any CPU.Build.0 = CodeAnalysis|Any CPU + {56459A6C-6BA2-4BAC-A9C0-27E3BD961FA6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {56459A6C-6BA2-4BAC-A9C0-27E3BD961FA6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {56459A6C-6BA2-4BAC-A9C0-27E3BD961FA6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {56459A6C-6BA2-4BAC-A9C0-27E3BD961FA6}.Release|Any CPU.Build.0 = Release|Any CPU + {56459A6C-6BA2-4BAC-A9C0-27E3BD961FA6}.ReleaseNoUI|Any CPU.ActiveCfg = Release|Any CPU + {56459A6C-6BA2-4BAC-A9C0-27E3BD961FA6}.ReleaseNoUI|Any CPU.Build.0 = Release|Any CPU + {F8284738-3B5D-4733-A511-38C23F4A763F}.CodeAnalysis|Any CPU.ActiveCfg = Debug|Any CPU + {F8284738-3B5D-4733-A511-38C23F4A763F}.CodeAnalysis|Any CPU.Build.0 = Debug|Any CPU + {F8284738-3B5D-4733-A511-38C23F4A763F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F8284738-3B5D-4733-A511-38C23F4A763F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F8284738-3B5D-4733-A511-38C23F4A763F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F8284738-3B5D-4733-A511-38C23F4A763F}.Release|Any CPU.Build.0 = Release|Any CPU + {F8284738-3B5D-4733-A511-38C23F4A763F}.ReleaseNoUI|Any CPU.ActiveCfg = Release|Any CPU + {F8284738-3B5D-4733-A511-38C23F4A763F}.ReleaseNoUI|Any CPU.Build.0 = Release|Any CPU + {F458AB60-BA1C-43D9-8CEF-EC01B50BE87B}.CodeAnalysis|Any CPU.ActiveCfg = Debug|Any CPU + {F458AB60-BA1C-43D9-8CEF-EC01B50BE87B}.CodeAnalysis|Any CPU.Build.0 = Debug|Any CPU + {F458AB60-BA1C-43D9-8CEF-EC01B50BE87B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F458AB60-BA1C-43D9-8CEF-EC01B50BE87B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F458AB60-BA1C-43D9-8CEF-EC01B50BE87B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F458AB60-BA1C-43D9-8CEF-EC01B50BE87B}.Release|Any CPU.Build.0 = Release|Any CPU + {F458AB60-BA1C-43D9-8CEF-EC01B50BE87B}.ReleaseNoUI|Any CPU.ActiveCfg = Release|Any CPU + {F458AB60-BA1C-43D9-8CEF-EC01B50BE87B}.ReleaseNoUI|Any CPU.Build.0 = Release|Any CPU + {F4CD3C04-6037-4946-B7A5-34BFC96A75D2}.CodeAnalysis|Any CPU.ActiveCfg = CodeAnalysis|Any CPU + {F4CD3C04-6037-4946-B7A5-34BFC96A75D2}.CodeAnalysis|Any CPU.Build.0 = CodeAnalysis|Any CPU + {F4CD3C04-6037-4946-B7A5-34BFC96A75D2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F4CD3C04-6037-4946-B7A5-34BFC96A75D2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F4CD3C04-6037-4946-B7A5-34BFC96A75D2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F4CD3C04-6037-4946-B7A5-34BFC96A75D2}.Release|Any CPU.Build.0 = Release|Any CPU + {F4CD3C04-6037-4946-B7A5-34BFC96A75D2}.ReleaseNoUI|Any CPU.ActiveCfg = Release|Any CPU + {F4CD3C04-6037-4946-B7A5-34BFC96A75D2}.ReleaseNoUI|Any CPU.Build.0 = Release|Any CPU + {26DC877F-5987-48DD-9DDB-E62F2DE0E150}.CodeAnalysis|Any CPU.ActiveCfg = CodeAnalysis|Any CPU + {26DC877F-5987-48DD-9DDB-E62F2DE0E150}.CodeAnalysis|Any CPU.Build.0 = CodeAnalysis|Any CPU + {26DC877F-5987-48DD-9DDB-E62F2DE0E150}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {26DC877F-5987-48DD-9DDB-E62F2DE0E150}.Debug|Any CPU.Build.0 = Debug|Any CPU + {26DC877F-5987-48DD-9DDB-E62F2DE0E150}.Release|Any CPU.ActiveCfg = Release|Any CPU + {26DC877F-5987-48DD-9DDB-E62F2DE0E150}.Release|Any CPU.Build.0 = Release|Any CPU + {26DC877F-5987-48DD-9DDB-E62F2DE0E150}.ReleaseNoUI|Any CPU.ActiveCfg = Release|Any CPU + {26DC877F-5987-48DD-9DDB-E62F2DE0E150}.ReleaseNoUI|Any CPU.Build.0 = Release|Any CPU + {1ED8D424-F8AB-4050-ACEB-F27F4F909484}.CodeAnalysis|Any CPU.ActiveCfg = CodeAnalysis|Any CPU + {1ED8D424-F8AB-4050-ACEB-F27F4F909484}.CodeAnalysis|Any CPU.Build.0 = CodeAnalysis|Any CPU + {1ED8D424-F8AB-4050-ACEB-F27F4F909484}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1ED8D424-F8AB-4050-ACEB-F27F4F909484}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1ED8D424-F8AB-4050-ACEB-F27F4F909484}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1ED8D424-F8AB-4050-ACEB-F27F4F909484}.Release|Any CPU.Build.0 = Release|Any CPU + {1ED8D424-F8AB-4050-ACEB-F27F4F909484}.ReleaseNoUI|Any CPU.ActiveCfg = Release|Any CPU + {1ED8D424-F8AB-4050-ACEB-F27F4F909484}.ReleaseNoUI|Any CPU.Build.0 = Release|Any CPU + {9D0F8866-2131-4C2A-BC0E-16FEA5B50828}.CodeAnalysis|Any CPU.ActiveCfg = CodeAnalysis|Any CPU + {9D0F8866-2131-4C2A-BC0E-16FEA5B50828}.CodeAnalysis|Any CPU.Build.0 = CodeAnalysis|Any CPU + {9D0F8866-2131-4C2A-BC0E-16FEA5B50828}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9D0F8866-2131-4C2A-BC0E-16FEA5B50828}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9D0F8866-2131-4C2A-BC0E-16FEA5B50828}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9D0F8866-2131-4C2A-BC0E-16FEA5B50828}.Release|Any CPU.Build.0 = Release|Any CPU + {9D0F8866-2131-4C2A-BC0E-16FEA5B50828}.ReleaseNoUI|Any CPU.ActiveCfg = Release|Any CPU + {9D0F8866-2131-4C2A-BC0E-16FEA5B50828}.ReleaseNoUI|Any CPU.Build.0 = Release|Any CPU + {75E13AAE-7D51-4421-ABFD-3F3DC91F576E}.CodeAnalysis|Any CPU.ActiveCfg = CodeAnalysis|Any CPU + {75E13AAE-7D51-4421-ABFD-3F3DC91F576E}.CodeAnalysis|Any CPU.Build.0 = CodeAnalysis|Any CPU + {75E13AAE-7D51-4421-ABFD-3F3DC91F576E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {75E13AAE-7D51-4421-ABFD-3F3DC91F576E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {75E13AAE-7D51-4421-ABFD-3F3DC91F576E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {75E13AAE-7D51-4421-ABFD-3F3DC91F576E}.Release|Any CPU.Build.0 = Release|Any CPU + {75E13AAE-7D51-4421-ABFD-3F3DC91F576E}.ReleaseNoUI|Any CPU.ActiveCfg = Release|Any CPU + {75E13AAE-7D51-4421-ABFD-3F3DC91F576E}.ReleaseNoUI|Any CPU.Build.0 = Release|Any CPU + {173E7B8D-E751-46E2-A133-F72297C0D2F4}.CodeAnalysis|Any CPU.ActiveCfg = CodeAnalysis|Any CPU + {173E7B8D-E751-46E2-A133-F72297C0D2F4}.CodeAnalysis|Any CPU.Build.0 = CodeAnalysis|Any CPU + {173E7B8D-E751-46E2-A133-F72297C0D2F4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {173E7B8D-E751-46E2-A133-F72297C0D2F4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {173E7B8D-E751-46E2-A133-F72297C0D2F4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {173E7B8D-E751-46E2-A133-F72297C0D2F4}.Release|Any CPU.Build.0 = Release|Any CPU + {173E7B8D-E751-46E2-A133-F72297C0D2F4}.ReleaseNoUI|Any CPU.ActiveCfg = Release|Any CPU + {173E7B8D-E751-46E2-A133-F72297C0D2F4}.ReleaseNoUI|Any CPU.Build.0 = Release|Any CPU + {E040EB58-B4D2-457B-A023-AE6EF3BD34DE}.CodeAnalysis|Any CPU.ActiveCfg = CodeAnalysis|Any CPU + {E040EB58-B4D2-457B-A023-AE6EF3BD34DE}.CodeAnalysis|Any CPU.Build.0 = CodeAnalysis|Any CPU + {E040EB58-B4D2-457B-A023-AE6EF3BD34DE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E040EB58-B4D2-457B-A023-AE6EF3BD34DE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E040EB58-B4D2-457B-A023-AE6EF3BD34DE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E040EB58-B4D2-457B-A023-AE6EF3BD34DE}.Release|Any CPU.Build.0 = Release|Any CPU + {E040EB58-B4D2-457B-A023-AE6EF3BD34DE}.ReleaseNoUI|Any CPU.ActiveCfg = Release|Any CPU + {E040EB58-B4D2-457B-A023-AE6EF3BD34DE}.ReleaseNoUI|Any CPU.Build.0 = Release|Any CPU + {B202E40D-4663-4A2B-ACDA-865F88FF7CAA}.CodeAnalysis|Any CPU.ActiveCfg = CodeAnalysis|Any CPU + {B202E40D-4663-4A2B-ACDA-865F88FF7CAA}.CodeAnalysis|Any CPU.Build.0 = CodeAnalysis|Any CPU + {B202E40D-4663-4A2B-ACDA-865F88FF7CAA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B202E40D-4663-4A2B-ACDA-865F88FF7CAA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B202E40D-4663-4A2B-ACDA-865F88FF7CAA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B202E40D-4663-4A2B-ACDA-865F88FF7CAA}.Release|Any CPU.Build.0 = Release|Any CPU + {B202E40D-4663-4A2B-ACDA-865F88FF7CAA}.ReleaseNoUI|Any CPU.ActiveCfg = Release|Any CPU + {B202E40D-4663-4A2B-ACDA-865F88FF7CAA}.ReleaseNoUI|Any CPU.Build.0 = Release|Any CPU + {FED1923A-6D70-49B5-A37A-FB744FEC1C86}.CodeAnalysis|Any CPU.ActiveCfg = CodeAnalysis|Any CPU + {FED1923A-6D70-49B5-A37A-FB744FEC1C86}.CodeAnalysis|Any CPU.Build.0 = CodeAnalysis|Any CPU + {FED1923A-6D70-49B5-A37A-FB744FEC1C86}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FED1923A-6D70-49B5-A37A-FB744FEC1C86}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FED1923A-6D70-49B5-A37A-FB744FEC1C86}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FED1923A-6D70-49B5-A37A-FB744FEC1C86}.Release|Any CPU.Build.0 = Release|Any CPU + {FED1923A-6D70-49B5-A37A-FB744FEC1C86}.ReleaseNoUI|Any CPU.ActiveCfg = Release|Any CPU + {FED1923A-6D70-49B5-A37A-FB744FEC1C86}.ReleaseNoUI|Any CPU.Build.0 = Release|Any CPU + {99BB7543-EA16-43EE-A7BC-D7A25A3B22F6}.CodeAnalysis|Any CPU.ActiveCfg = CodeAnalysis|Any CPU + {99BB7543-EA16-43EE-A7BC-D7A25A3B22F6}.CodeAnalysis|Any CPU.Build.0 = CodeAnalysis|Any CPU + {99BB7543-EA16-43EE-A7BC-D7A25A3B22F6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {99BB7543-EA16-43EE-A7BC-D7A25A3B22F6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {99BB7543-EA16-43EE-A7BC-D7A25A3B22F6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {99BB7543-EA16-43EE-A7BC-D7A25A3B22F6}.Release|Any CPU.Build.0 = Release|Any CPU + {99BB7543-EA16-43EE-A7BC-D7A25A3B22F6}.ReleaseNoUI|Any CPU.ActiveCfg = Release|Any CPU + {99BB7543-EA16-43EE-A7BC-D7A25A3B22F6}.ReleaseNoUI|Any CPU.Build.0 = Release|Any CPU + {CDEDD439-7F35-4E6E-8605-4E70BDC4CC99}.CodeAnalysis|Any CPU.ActiveCfg = CodeAnalysis|Any CPU + {CDEDD439-7F35-4E6E-8605-4E70BDC4CC99}.CodeAnalysis|Any CPU.Build.0 = CodeAnalysis|Any CPU + {CDEDD439-7F35-4E6E-8605-4E70BDC4CC99}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CDEDD439-7F35-4E6E-8605-4E70BDC4CC99}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CDEDD439-7F35-4E6E-8605-4E70BDC4CC99}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CDEDD439-7F35-4E6E-8605-4E70BDC4CC99}.Release|Any CPU.Build.0 = Release|Any CPU + {CDEDD439-7F35-4E6E-8605-4E70BDC4CC99}.ReleaseNoUI|Any CPU.ActiveCfg = Release|Any CPU + {CDEDD439-7F35-4E6E-8605-4E70BDC4CC99}.ReleaseNoUI|Any CPU.Build.0 = Release|Any CPU + {A1A3150A-7B0E-4A34-8E35-045296CD3C76}.CodeAnalysis|Any CPU.ActiveCfg = CodeAnalysis|Any CPU + {A1A3150A-7B0E-4A34-8E35-045296CD3C76}.CodeAnalysis|Any CPU.Build.0 = CodeAnalysis|Any CPU + {A1A3150A-7B0E-4A34-8E35-045296CD3C76}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A1A3150A-7B0E-4A34-8E35-045296CD3C76}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A1A3150A-7B0E-4A34-8E35-045296CD3C76}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A1A3150A-7B0E-4A34-8E35-045296CD3C76}.Release|Any CPU.Build.0 = Release|Any CPU + {A1A3150A-7B0E-4A34-8E35-045296CD3C76}.ReleaseNoUI|Any CPU.ActiveCfg = Release|Any CPU + {A1A3150A-7B0E-4A34-8E35-045296CD3C76}.ReleaseNoUI|Any CPU.Build.0 = Release|Any CPU + {ADC2CC8C-541E-4F86-ACB1-DD504A36FA4B}.CodeAnalysis|Any CPU.ActiveCfg = CodeAnalysis|Any CPU + {ADC2CC8C-541E-4F86-ACB1-DD504A36FA4B}.CodeAnalysis|Any CPU.Build.0 = CodeAnalysis|Any CPU + {ADC2CC8C-541E-4F86-ACB1-DD504A36FA4B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {ADC2CC8C-541E-4F86-ACB1-DD504A36FA4B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {ADC2CC8C-541E-4F86-ACB1-DD504A36FA4B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {ADC2CC8C-541E-4F86-ACB1-DD504A36FA4B}.Release|Any CPU.Build.0 = Release|Any CPU + {ADC2CC8C-541E-4F86-ACB1-DD504A36FA4B}.ReleaseNoUI|Any CPU.ActiveCfg = Release|Any CPU + {ADC2CC8C-541E-4F86-ACB1-DD504A36FA4B}.ReleaseNoUI|Any CPU.Build.0 = Release|Any CPU + {3A8347E8-59A5-4092-8842-95C75D7D2F36}.CodeAnalysis|Any CPU.ActiveCfg = CodeAnalysis|Any CPU + {3A8347E8-59A5-4092-8842-95C75D7D2F36}.CodeAnalysis|Any CPU.Build.0 = CodeAnalysis|Any CPU + {3A8347E8-59A5-4092-8842-95C75D7D2F36}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3A8347E8-59A5-4092-8842-95C75D7D2F36}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3A8347E8-59A5-4092-8842-95C75D7D2F36}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3A8347E8-59A5-4092-8842-95C75D7D2F36}.Release|Any CPU.Build.0 = Release|Any CPU + {3A8347E8-59A5-4092-8842-95C75D7D2F36}.ReleaseNoUI|Any CPU.ActiveCfg = Release|Any CPU + {3A8347E8-59A5-4092-8842-95C75D7D2F36}.ReleaseNoUI|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -346,5 +557,31 @@ Global {17932639-1F50-48AF-B0A5-E2BF832F82CC} = {B9EB8729-4B54-4453-B089-FE6761BA3057} {2B4261AC-25AC-4B8D-B459-1C42B6B1401D} = {B9EB8729-4B54-4453-B089-FE6761BA3057} {152B7BAB-E884-4A59-8067-440971A682B3} = {B9EB8729-4B54-4453-B089-FE6761BA3057} + {C7EF1823-3AA7-477E-8476-28929F5C05D2} = {8D4236F7-C49B-49D3-BA71-6B86C9514BDE} + {9AF74F53-10F5-49A2-B747-87B97CD559D3} = {8D4236F7-C49B-49D3-BA71-6B86C9514BDE} + {529B4262-6B5A-4EF9-BD3B-1D29A2597B67} = {8D4236F7-C49B-49D3-BA71-6B86C9514BDE} + {238B6BA8-AD99-43C9-B8E2-D2BCE6CE04DC} = {8D4236F7-C49B-49D3-BA71-6B86C9514BDE} + {173E7B8D-E751-46E2-A133-F72297C0D2F4} = {8D4236F7-C49B-49D3-BA71-6B86C9514BDE} + {60426312-6AE5-4835-8667-37EDEA670222} = {8D4236F7-C49B-49D3-BA71-6B86C9514BDE} + {57A7DD35-666C-4FA3-9A1B-38961E50CA27} = {8D4236F7-C49B-49D3-BA71-6B86C9514BDE} + {F8284738-3B5D-4733-A511-38C23F4A763F} = {C7EF1823-3AA7-477E-8476-28929F5C05D2} + {F458AB60-BA1C-43D9-8CEF-EC01B50BE87B} = {C7EF1823-3AA7-477E-8476-28929F5C05D2} + {F4CD3C04-6037-4946-B7A5-34BFC96A75D2} = {C7EF1823-3AA7-477E-8476-28929F5C05D2} + {26DC877F-5987-48DD-9DDB-E62F2DE0E150} = {C7EF1823-3AA7-477E-8476-28929F5C05D2} + {1ED8D424-F8AB-4050-ACEB-F27F4F909484} = {C7EF1823-3AA7-477E-8476-28929F5C05D2} + {9D0F8866-2131-4C2A-BC0E-16FEA5B50828} = {C7EF1823-3AA7-477E-8476-28929F5C05D2} + {75E13AAE-7D51-4421-ABFD-3F3DC91F576E} = {C7EF1823-3AA7-477E-8476-28929F5C05D2} + {3896A32A-E876-4C23-B9B8-78E17D134CD3} = {C7EF1823-3AA7-477E-8476-28929F5C05D2} + {A288FCC8-6FCF-46DA-A45E-5F9281556361} = {9AF74F53-10F5-49A2-B747-87B97CD559D3} + {B202E40D-4663-4A2B-ACDA-865F88FF7CAA} = {9AF74F53-10F5-49A2-B747-87B97CD559D3} + {FED1923A-6D70-49B5-A37A-FB744FEC1C86} = {9AF74F53-10F5-49A2-B747-87B97CD559D3} + {408D10B8-34BA-4CBD-B7AA-FEB1907ABA4C} = {529B4262-6B5A-4EF9-BD3B-1D29A2597B67} + {E040EB58-B4D2-457B-A023-AE6EF3BD34DE} = {529B4262-6B5A-4EF9-BD3B-1D29A2597B67} + {56459A6C-6BA2-4BAC-A9C0-27E3BD961FA6} = {238B6BA8-AD99-43C9-B8E2-D2BCE6CE04DC} + {99BB7543-EA16-43EE-A7BC-D7A25A3B22F6} = {238B6BA8-AD99-43C9-B8E2-D2BCE6CE04DC} + {CDEDD439-7F35-4E6E-8605-4E70BDC4CC99} = {238B6BA8-AD99-43C9-B8E2-D2BCE6CE04DC} + {A1A3150A-7B0E-4A34-8E35-045296CD3C76} = {238B6BA8-AD99-43C9-B8E2-D2BCE6CE04DC} + {ADC2CC8C-541E-4F86-ACB1-DD504A36FA4B} = {238B6BA8-AD99-43C9-B8E2-D2BCE6CE04DC} + {3A8347E8-59A5-4092-8842-95C75D7D2F36} = {57A7DD35-666C-4FA3-9A1B-38961E50CA27} EndGlobalSection EndGlobal diff --git a/src/DotNetOpenAuth/ComponentModel/ConverterBase.cs b/src/DotNetOpenAuth/ComponentModel/ConverterBase.cs deleted file mode 100644 index c41ac47..0000000 --- a/src/DotNetOpenAuth/ComponentModel/ConverterBase.cs +++ /dev/null @@ -1,223 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="ConverterBase.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.ComponentModel { - using System; - using System.Collections; - using System.ComponentModel; - using System.ComponentModel.Design.Serialization; - using System.Diagnostics.CodeAnalysis; - using System.Diagnostics.Contracts; - using System.Globalization; -using System.Reflection; - using System.Security; - using System.Security.Permissions; - - /// <summary> - /// A design-time helper to allow Intellisense to aid typing - /// ClaimType URIs. - /// </summary> - /// <typeparam name="T">The strong-type of the property this class is affixed to.</typeparam> - public abstract class ConverterBase<T> : TypeConverter { - /// <summary> - /// A cache of the standard claim types known to the application. - /// </summary> - private StandardValuesCollection standardValues; - - /// <summary> - /// Initializes a new instance of the ConverterBase class. - /// </summary> - protected ConverterBase() { - } - - /// <summary> - /// Gets a cache of the standard values to suggest. - /// </summary> - private StandardValuesCollection StandardValueCache { - get { - if (this.standardValues == null) { - this.standardValues = new StandardValuesCollection(this.GetStandardValuesForCache()); - } - - return this.standardValues; - } - } - - /// <summary> - /// Returns whether this object supports a standard set of values that can be picked from a list, using the specified context. - /// </summary> - /// <param name="context">An <see cref="T:System.ComponentModel.ITypeDescriptorContext"/> that provides a format context.</param> - /// <returns> - /// true if <see cref="M:System.ComponentModel.TypeConverter.GetStandardValues"/> should be called to find a common set of values the object supports; otherwise, false. - /// </returns> - public override bool GetStandardValuesSupported(ITypeDescriptorContext context) { - return this.StandardValueCache.Count > 0; - } - - /// <summary> - /// Returns a collection of standard values for the data type this type converter is designed for when provided with a format context. - /// </summary> - /// <param name="context">An <see cref="T:System.ComponentModel.ITypeDescriptorContext"/> that provides a format context that can be used to extract additional information about the environment from which this converter is invoked. This parameter or properties of this parameter can be null.</param> - /// <returns> - /// A <see cref="T:System.ComponentModel.TypeConverter.StandardValuesCollection"/> that holds a standard set of valid values, or null if the data type does not support a standard set of values. - /// </returns> - public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext context) { - return this.StandardValueCache; - } - - /// <summary> - /// Returns whether the collection of standard values returned from <see cref="M:System.ComponentModel.TypeConverter.GetStandardValues"/> is an exclusive list of possible values, using the specified context. - /// </summary> - /// <param name="context">An <see cref="T:System.ComponentModel.ITypeDescriptorContext"/> that provides a format context.</param> - /// <returns> - /// true if the <see cref="T:System.ComponentModel.TypeConverter.StandardValuesCollection"/> returned from <see cref="M:System.ComponentModel.TypeConverter.GetStandardValues"/> is an exhaustive list of possible values; false if other values are possible. - /// </returns> - public override bool GetStandardValuesExclusive(ITypeDescriptorContext context) { - return false; - } - - /// <summary> - /// Returns whether this converter can convert an object of the given type to the type of this converter, using the specified context. - /// </summary> - /// <param name="context">An <see cref="T:System.ComponentModel.ITypeDescriptorContext"/> that provides a format context.</param> - /// <param name="sourceType">A <see cref="T:System.Type"/> that represents the type you want to convert from.</param> - /// <returns> - /// true if this converter can perform the conversion; otherwise, false. - /// </returns> - public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) { - return sourceType == typeof(string) - || base.CanConvertFrom(context, sourceType); - } - - /// <summary> - /// Returns whether this converter can convert the object to the specified type, using the specified context. - /// </summary> - /// <param name="context">An <see cref="T:System.ComponentModel.ITypeDescriptorContext"/> that provides a format context.</param> - /// <param name="destinationType">A <see cref="T:System.Type"/> that represents the type you want to convert to.</param> - /// <returns> - /// true if this converter can perform the conversion; otherwise, false. - /// </returns> - public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) { - return destinationType == typeof(string) - || destinationType == typeof(InstanceDescriptor) - || base.CanConvertTo(context, destinationType); - } - - /// <summary> - /// Converts the given object to the type of this converter, using the specified context and culture information. - /// </summary> - /// <param name="context">An <see cref="T:System.ComponentModel.ITypeDescriptorContext"/> that provides a format context.</param> - /// <param name="culture">The <see cref="T:System.Globalization.CultureInfo"/> to use as the current culture.</param> - /// <param name="value">The <see cref="T:System.Object"/> to convert.</param> - /// <returns> - /// An <see cref="T:System.Object"/> that represents the converted value. - /// </returns> - /// <exception cref="T:System.NotSupportedException"> - /// The conversion cannot be performed. - /// </exception> - public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) { - string stringValue = value as string; - if (stringValue != null) { - return this.ConvertFrom(stringValue); - } else { - return base.ConvertFrom(context, culture, value); - } - } - - /// <summary> - /// Converts the given value object to the specified type, using the specified context and culture information. - /// </summary> - /// <param name="context">An <see cref="T:System.ComponentModel.ITypeDescriptorContext"/> that provides a format context.</param> - /// <param name="culture">A <see cref="T:System.Globalization.CultureInfo"/>. If null is passed, the current culture is assumed.</param> - /// <param name="value">The <see cref="T:System.Object"/> to convert.</param> - /// <param name="destinationType">The <see cref="T:System.Type"/> to convert the <paramref name="value"/> parameter to.</param> - /// <returns> - /// An <see cref="T:System.Object"/> that represents the converted value. - /// </returns> - /// <exception cref="T:System.ArgumentNullException"> - /// The <paramref name="destinationType"/> parameter is null. - /// </exception> - /// <exception cref="T:System.NotSupportedException"> - /// The conversion cannot be performed. - /// </exception> - [SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", MessageId = "System.Diagnostics.Contracts.__ContractsRuntime.Assume(System.Boolean,System.String,System.String)", Justification = "No localization required.")] - public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType) { - Contract.Assume(destinationType != null, "Missing contract."); - if (destinationType.IsInstanceOfType(value)) { - return value; - } - - T typedValue = (T)value; - if (destinationType == typeof(string)) { - return this.ConvertToString(typedValue); - } else if (destinationType == typeof(InstanceDescriptor)) { - return this.CreateFrom(typedValue); - } else { - return base.ConvertTo(context, culture, value, destinationType); - } - } - - /// <summary> - /// Creates an <see cref="InstanceDescriptor"/> instance, protecting against the LinkDemand. - /// </summary> - /// <param name="memberInfo">The member info.</param> - /// <param name="arguments">The arguments.</param> - /// <returns>A <see cref="InstanceDescriptor"/>, or <c>null</c> if sufficient permissions are unavailable.</returns> - protected static InstanceDescriptor CreateInstanceDescriptor(MemberInfo memberInfo, ICollection arguments) { - try { - return CreateInstanceDescriptorPrivate(memberInfo, arguments); - } catch (SecurityException) { - return null; - } - } - - /// <summary> - /// Gets the standard values to suggest with Intellisense in the designer. - /// </summary> - /// <returns>A collection of the standard values.</returns> - [Pure] - [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "Potentially expensive call.")] - protected virtual ICollection GetStandardValuesForCache() { - Contract.Ensures(Contract.Result<ICollection>() != null); - return new T[0]; - } - - /// <summary> - /// Converts a value from its string representation to its strongly-typed object. - /// </summary> - /// <param name="value">The value.</param> - /// <returns>The strongly-typed object.</returns> - [Pure] - protected abstract T ConvertFrom(string value); - - /// <summary> - /// Creates the reflection instructions for recreating an instance later. - /// </summary> - /// <param name="value">The value to recreate later.</param> - /// <returns>The description of how to recreate an instance.</returns> - [Pure] - protected abstract InstanceDescriptor CreateFrom(T value); - - /// <summary> - /// Converts the strongly-typed value to a string. - /// </summary> - /// <param name="value">The value to convert.</param> - /// <returns>The string representation of the object.</returns> - [Pure] - protected abstract string ConvertToString(T value); - - /// <summary> - /// Creates an <see cref="InstanceDescriptor"/> instance, protecting against the LinkDemand. - /// </summary> - /// <param name="memberInfo">The member info.</param> - /// <param name="arguments">The arguments.</param> - /// <returns>A <see cref="InstanceDescriptor"/>.</returns> - [PermissionSet(SecurityAction.Demand, Name = "FullTrust")] - private static InstanceDescriptor CreateInstanceDescriptorPrivate(MemberInfo memberInfo, ICollection arguments) { - return new InstanceDescriptor(memberInfo, arguments); - } - } -} diff --git a/src/DotNetOpenAuth/ComponentModel/SuggestedStringsConverter.cs b/src/DotNetOpenAuth/ComponentModel/SuggestedStringsConverter.cs deleted file mode 100644 index 864d001..0000000 --- a/src/DotNetOpenAuth/ComponentModel/SuggestedStringsConverter.cs +++ /dev/null @@ -1,91 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="SuggestedStringsConverter.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.ComponentModel { - using System; - using System.Collections; - using System.ComponentModel.Design.Serialization; - using System.Diagnostics.Contracts; - using System.Linq; - using System.Reflection; - - /// <summary> - /// A type that generates suggested strings for Intellisense, - /// but doesn't actually convert between strings and other types. - /// </summary> - [ContractClass(typeof(SuggestedStringsConverterContract))] - public abstract class SuggestedStringsConverter : ConverterBase<string> { - /// <summary> - /// Initializes a new instance of the <see cref="SuggestedStringsConverter"/> class. - /// </summary> - protected SuggestedStringsConverter() { - } - - /// <summary> - /// Gets the type to reflect over for the well known values. - /// </summary> - [Pure] - protected abstract Type WellKnownValuesType { get; } - - /// <summary> - /// Gets the values of public static fields and properties on a given type. - /// </summary> - /// <param name="type">The type to reflect over.</param> - /// <returns>A collection of values.</returns> - internal static ICollection GetStandardValuesForCacheShared(Type type) { - Contract.Requires<ArgumentNullException>(type != null); - Contract.Ensures(Contract.Result<ICollection>() != null); - - var fields = from field in type.GetFields(BindingFlags.Static | BindingFlags.Public) - select field.GetValue(null); - var properties = from prop in type.GetProperties(BindingFlags.Static | BindingFlags.Public) - select prop.GetValue(null, null); - return (fields.Concat(properties)).ToArray(); - } - - /// <summary> - /// Converts a value from its string representation to its strongly-typed object. - /// </summary> - /// <param name="value">The value.</param> - /// <returns>The strongly-typed object.</returns> - [Pure] - protected override string ConvertFrom(string value) { - return value; - } - - /// <summary> - /// Creates the reflection instructions for recreating an instance later. - /// </summary> - /// <param name="value">The value to recreate later.</param> - /// <returns> - /// The description of how to recreate an instance. - /// </returns> - [Pure] - protected override InstanceDescriptor CreateFrom(string value) { - // No implementation necessary since we're only dealing with strings. - throw new NotImplementedException(); - } - - /// <summary> - /// Converts the strongly-typed value to a string. - /// </summary> - /// <param name="value">The value to convert.</param> - /// <returns>The string representation of the object.</returns> - [Pure] - protected override string ConvertToString(string value) { - return value; - } - - /// <summary> - /// Gets the standard values to suggest with Intellisense in the designer. - /// </summary> - /// <returns>A collection of the standard values.</returns> - [Pure] - protected override ICollection GetStandardValuesForCache() { - return GetStandardValuesForCacheShared(this.WellKnownValuesType); - } - } -} diff --git a/src/DotNetOpenAuth/ComponentModel/UriConverter.cs b/src/DotNetOpenAuth/ComponentModel/UriConverter.cs deleted file mode 100644 index 5e7c22b..0000000 --- a/src/DotNetOpenAuth/ComponentModel/UriConverter.cs +++ /dev/null @@ -1,117 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="UriConverter.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.ComponentModel { - using System; - using System.Collections; - using System.ComponentModel; - using System.ComponentModel.Design.Serialization; - using System.Diagnostics.Contracts; - using System.Linq; - using System.Reflection; - - /// <summary> - /// A design-time helper to allow controls to have properties - /// of type <see cref="Uri"/>. - /// </summary> - public class UriConverter : ConverterBase<Uri> { - /// <summary> - /// Initializes a new instance of the UriConverter class. - /// </summary> - protected UriConverter() { - } - - /// <summary> - /// Gets the type to reflect over to extract the well known values. - /// </summary> - protected virtual Type WellKnownValuesType { - get { return null; } - } - - /// <summary> - /// Returns whether the given value object is valid for this type and for the specified context. - /// </summary> - /// <param name="context">An <see cref="T:System.ComponentModel.ITypeDescriptorContext"/> that provides a format context.</param> - /// <param name="value">The <see cref="T:System.Object"/> to test for validity.</param> - /// <returns> - /// true if the specified value is valid for this object; otherwise, false. - /// </returns> - public override bool IsValid(ITypeDescriptorContext context, object value) { - Uri uriValue; - string stringValue; - if ((uriValue = value as Uri) != null) { - return uriValue.IsAbsoluteUri; - } else if ((stringValue = value as string) != null) { - Uri result; - return stringValue.Length == 0 || Uri.TryCreate(stringValue, UriKind.Absolute, out result); - } else { - return false; - } - } - - /// <summary> - /// Converts a value from its string representation to its strongly-typed object. - /// </summary> - /// <param name="value">The value.</param> - /// <returns>The strongly-typed object.</returns> - [Pure] - protected override Uri ConvertFrom(string value) { - return string.IsNullOrEmpty(value) ? null : new Uri(value); - } - - /// <summary> - /// Creates the reflection instructions for recreating an instance later. - /// </summary> - /// <param name="value">The value to recreate later.</param> - /// <returns> - /// The description of how to recreate an instance. - /// </returns> - [Pure] - protected override InstanceDescriptor CreateFrom(Uri value) { - if (value == null) { - return null; - } - - MemberInfo uriCtor = typeof(Uri).GetConstructor(new Type[] { typeof(string) }); - return CreateInstanceDescriptor(uriCtor, new object[] { value.AbsoluteUri }); - } - - /// <summary> - /// Converts the strongly-typed value to a string. - /// </summary> - /// <param name="value">The value to convert.</param> - /// <returns>The string representation of the object.</returns> - [Pure] - protected override string ConvertToString(Uri value) { - if (value == null) { - return null; - } - - return value.AbsoluteUri; - } - - /// <summary> - /// Gets the standard claim type URIs known to the library. - /// </summary> - /// <returns>An array of the standard claim types.</returns> - [Pure] - protected override ICollection GetStandardValuesForCache() { - if (this.WellKnownValuesType != null) { - var fields = from field in this.WellKnownValuesType.GetFields(BindingFlags.Static | BindingFlags.Public) - let value = (string)field.GetValue(null) - where value != null - select new Uri(value); - var properties = from prop in this.WellKnownValuesType.GetProperties(BindingFlags.Static | BindingFlags.Public) - let value = (string)prop.GetValue(null, null) - where value != null - select new Uri(value); - return (fields.Concat(properties)).ToArray(); - } else { - return new Uri[0]; - } - } - } -} diff --git a/src/DotNetOpenAuth/Configuration/DotNetOpenAuth.xsd b/src/DotNetOpenAuth/Configuration/DotNetOpenAuth.xsd deleted file mode 100644 index 065b5ee..0000000 --- a/src/DotNetOpenAuth/Configuration/DotNetOpenAuth.xsd +++ /dev/null @@ -1,968 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" - xmlns:vs="http://schemas.microsoft.com/Visual-Studio-Intellisense" - elementFormDefault="qualified" - attributeFormDefault="unqualified"> - <xs:element name="dotNetOpenAuth"> - <xs:annotation> - <xs:documentation> - Customizations and configuration of DotNetOpenAuth behavior. - </xs:documentation> - </xs:annotation> - <xs:complexType> - <xs:choice minOccurs="0" maxOccurs="unbounded"> - <xs:element name="messaging"> - <xs:annotation> - <xs:documentation> - Options for general messaging protocols, such as whitelist/blacklist hosts and maximum message age. - </xs:documentation> - </xs:annotation> - <xs:complexType> - <xs:choice minOccurs="0" maxOccurs="unbounded"> - <xs:element name="untrustedWebRequest"> - <xs:annotation> - <xs:documentation> - Restrictions and settings to apply to outgoing HTTP requests to hosts that are not - trusted by this web site. Useful for OpenID-supporting hosts because HTTP connections - are initiated based on user input to arbitrary servers. - </xs:documentation> - </xs:annotation> - <xs:complexType> - <xs:choice minOccurs="0" maxOccurs="unbounded"> - <xs:element name="whitelistHosts"> - <xs:annotation> - <xs:documentation> - A set of host names (including domain names) to allow outgoing connections to - that would otherwise not be allowed based on security restrictions. - </xs:documentation> - </xs:annotation> - <xs:complexType> - <xs:choice minOccurs="0" maxOccurs="unbounded"> - <xs:element name="add"> - <xs:complexType> - <xs:attribute name="name" type="xs:string" use="required"> - <xs:annotation> - <xs:documentation> - The host name to trust. For example: "localhost" or "www.mypartners.com". - </xs:documentation> - </xs:annotation> - </xs:attribute> - </xs:complexType> - </xs:element> - <xs:element name="remove"> - <xs:complexType> - <xs:attribute name="name" type="xs:string" use="required"> - <xs:annotation> - <xs:documentation> - The host name to NOT trust. For example: "localhost" or "www.mypartners.com". - </xs:documentation> - </xs:annotation> - </xs:attribute> - </xs:complexType> - </xs:element> - <xs:element name="clear"> - <xs:annotation> - <xs:documentation> - Clears all hosts from the whitelist. - </xs:documentation> - </xs:annotation> - <xs:complexType> - <!--tag is empty--> - </xs:complexType> - </xs:element> - </xs:choice> - </xs:complexType> - </xs:element> - <xs:element name="whitelistHostsRegex"> - <xs:complexType> - <xs:choice minOccurs="0" maxOccurs="unbounded"> - <xs:element name="add"> - <xs:complexType> - <xs:attribute name="name" type="xs:string" use="required" /> - </xs:complexType> - </xs:element> - <xs:element name="remove"> - <xs:complexType> - <xs:attribute name="name" type="xs:string" use="required" /> - </xs:complexType> - </xs:element> - <xs:element name="clear"> - <xs:complexType> - <!--tag is empty--> - </xs:complexType> - </xs:element> - </xs:choice> - </xs:complexType> - </xs:element> - <xs:element name="blacklistHosts"> - <xs:annotation> - <xs:documentation> - A set of host names (including domain names) to disallow outgoing connections to - that would otherwise be allowed based on security restrictions. - </xs:documentation> - </xs:annotation> - <xs:complexType> - <xs:choice minOccurs="0" maxOccurs="unbounded"> - <xs:element name="add"> - <xs:complexType> - <xs:attribute name="name" type="xs:string" use="required"> - <xs:annotation> - <xs:documentation> - The host name known to add to the blacklist. For example: "localhost" or "www.mypartners.com". - </xs:documentation> - </xs:annotation> - </xs:attribute> - </xs:complexType> - </xs:element> - <xs:element name="remove"> - <xs:complexType> - <xs:attribute name="name" type="xs:string" use="required"> - <xs:annotation> - <xs:documentation> - The host name known to remove to the blacklist. For example: "localhost" or "www.mypartners.com". - </xs:documentation> - </xs:annotation> - </xs:attribute> - </xs:complexType> - </xs:element> - <xs:element name="clear"> - <xs:annotation> - <xs:documentation> - Clears all hosts from the blacklist. - </xs:documentation> - </xs:annotation> - <xs:complexType> - <!--tag is empty--> - </xs:complexType> - </xs:element> - </xs:choice> - </xs:complexType> - </xs:element> - <xs:element name="blacklistHostsRegex"> - <xs:complexType> - <xs:choice minOccurs="0" maxOccurs="unbounded"> - <xs:element name="add"> - <xs:complexType> - <xs:attribute name="name" type="xs:string" use="required" /> - </xs:complexType> - </xs:element> - <xs:element name="remove"> - <xs:complexType> - <xs:attribute name="name" type="xs:string" use="required" /> - </xs:complexType> - </xs:element> - <xs:element name="clear"> - <xs:complexType> - <!--tag is empty--> - </xs:complexType> - </xs:element> - </xs:choice> - </xs:complexType> - </xs:element> - </xs:choice> - <xs:attribute name="timeout" type="xs:string"> - <xs:annotation> - <xs:documentation> - The maximum time to allow for an outgoing HTTP request to complete before giving up. - </xs:documentation> - </xs:annotation> - </xs:attribute> - <xs:attribute name="readWriteTimeout" type="xs:string"> - <xs:annotation> - <xs:documentation> - The maximum time to allow for an outgoing HTTP request to either send or receive data before giving up. - </xs:documentation> - </xs:annotation> - </xs:attribute> - <xs:attribute name="maximumBytesToRead" type="xs:int"> - <xs:annotation> - <xs:documentation> - The maximum bytes to read from an untrusted server during an outgoing HTTP request before cutting off the response. - </xs:documentation> - </xs:annotation> - </xs:attribute> - <xs:attribute name="maximumRedirections" type="xs:int"> - <xs:annotation> - <xs:documentation> - The maximum redirection instructions to follow before giving up. - </xs:documentation> - </xs:annotation> - </xs:attribute> - </xs:complexType> - </xs:element> - </xs:choice> - <xs:attribute name="lifetime" type="xs:string"> - <xs:annotation> - <xs:documentation> - The maximum time allowed between a message being sent to when it is received before - it is considered expired. - </xs:documentation> - </xs:annotation> - </xs:attribute> - <xs:attribute name="clockSkew" type="xs:string"> - <xs:annotation> - <xs:documentation> - The maximum time to consider a safe difference in server clocks. - </xs:documentation> - </xs:annotation> - </xs:attribute> - <xs:attribute name="strict" type="xs:boolean" default="true"> - <xs:annotation> - <xs:documentation> - Whether remote parties will be held strictly to the protocol specifications. - Strict will require that remote parties adhere strictly to the specifications, - even when a loose interpretation would not compromise security. - true is a good default because it shakes out interoperability bugs in remote services - so they can be identified and corrected. But some web sites want things to Just Work - more than they want to file bugs against others, so false is the setting for them. - </xs:documentation> - </xs:annotation> - </xs:attribute> - <xs:attribute name="relaxSslRequirements" type="xs:boolean" default="false"> - <xs:annotation> - <xs:documentation> - Whether SSL requirements within the library are disabled/relaxed. - Use for TESTING ONLY. - </xs:documentation> - </xs:annotation> - </xs:attribute> - <xs:attribute name="maximumIndirectMessageUrlLength" type="xs:int" default="2048"> - <xs:annotation> - <xs:documentation> - The maximum allowable size for a 301 Redirect response before we send - a 200 OK response with a scripted form POST with the parameters instead - in order to ensure successfully sending a large payload to another server - that might have a maximum allowable size restriction on its GET request. - </xs:documentation> - </xs:annotation> - </xs:attribute> - <xs:attribute name="privateSecretMaximumAge" type="xs:string" default="28.00:00:00"> - <xs:annotation> - <xs:documentation> - The maximum age of a secret used for private signing or encryption before it is renewed. - </xs:documentation> - </xs:annotation> - </xs:attribute> - </xs:complexType> - </xs:element> - <xs:element name="openid"> - <xs:annotation> - <xs:documentation> - Configuration for OpenID authentication (relying parties and providers). - </xs:documentation> - </xs:annotation> - <xs:complexType> - <xs:choice minOccurs="0" maxOccurs="unbounded"> - <xs:element name="relyingParty"> - <xs:annotation> - <xs:documentation> - Configuration specific for OpenID relying parties. - </xs:documentation> - </xs:annotation> - <xs:complexType> - <xs:choice minOccurs="0" maxOccurs="unbounded"> - <xs:element name="security"> - <xs:annotation> - <xs:documentation> - Security settings that apply to OpenID relying parties. - </xs:documentation> - </xs:annotation> - <xs:complexType> - <xs:choice minOccurs="0" maxOccurs="unbounded"> - <xs:element name="trustedProviders"> - <xs:complexType> - <xs:choice minOccurs="0" maxOccurs="unbounded"> - <xs:element name="add"> - <xs:complexType> - <xs:attribute name="endpoint" type="xs:string" use="required"> - <xs:annotation> - <xs:documentation> - The OpenID Provider Endpoint (aka "OP Endpoint") that this relying party trusts. - </xs:documentation> - </xs:annotation> - </xs:attribute> - </xs:complexType> - </xs:element> - <xs:element name="remove"> - <xs:complexType> - <xs:attribute name="endpoint" type="xs:string" use="required" /> - </xs:complexType> - </xs:element> - <xs:element name="clear"> - <xs:complexType> - <!--tag is empty--> - </xs:complexType> - </xs:element> - </xs:choice> - <xs:attribute name="rejectAssertionsFromUntrustedProviders" type="xs:boolean" default="false"> - <xs:annotation> - <xs:documentation> - A value indicating whether any login attempt coming from an OpenID Provider Endpoint that is not on this - whitelist of trusted OP Endpoints will be rejected. If the trusted providers list is empty and this value - is true, all assertions are rejected. - </xs:documentation> - </xs:annotation> - </xs:attribute> - </xs:complexType> - </xs:element> - </xs:choice> - <xs:attribute name="requireSsl" type="xs:boolean" default="false"> - <xs:annotation> - <xs:documentation> - Restricts OpenID logins to identifiers that use HTTPS throughout the discovery process, - and only uses HTTPS OpenID Provider endpoints. - </xs:documentation> - </xs:annotation> - </xs:attribute> - <xs:attribute name="minimumRequiredOpenIdVersion"> - <xs:annotation> - <xs:documentation> - Optionally restricts interoperability with remote parties that - implement older versions of OpenID. - </xs:documentation> - </xs:annotation> - <xs:simpleType> - <xs:restriction base="xs:NMTOKEN"> - <xs:enumeration value="V10" /> - <xs:enumeration value="V11" /> - <xs:enumeration value="V20" /> - </xs:restriction> - </xs:simpleType> - </xs:attribute> - <xs:attribute name="minimumHashBitLength" type="xs:int"> - <xs:annotation> - <xs:documentation> - Shared associations with OpenID Providers will only be formed or used if they - are willing to form associations equal to or greater than a given level of protection. - </xs:documentation> - </xs:annotation> - </xs:attribute> - <xs:attribute name="maximumHashBitLength" type="xs:int"> - <xs:annotation> - <xs:documentation> - Shared associaitons with OpenID Providers will only be formed or used if they - are willing to form associations equal to or less than a given level of protection. - </xs:documentation> - </xs:annotation> - </xs:attribute> - <xs:attribute name="requireDirectedIdentity" type="xs:boolean"> - <xs:annotation> - <xs:documentation> - Requires that OpenID identifiers upon which authentication requests are created - are to be OP Identifiers. Claimed Identifiers are not allowed. - </xs:documentation> - </xs:annotation> - </xs:attribute> - <xs:attribute name="requireAssociation" type="xs:boolean"> - <xs:annotation> - <xs:documentation> - Requires that the relying party can form a shared association with an - OpenID Provider before creating an authentication request for it. - Note that this does not require that the Provider actually use a - shared association in its response. - </xs:documentation> - </xs:annotation> - </xs:attribute> - <xs:attribute name="rejectUnsolicitedAssertions" type="xs:boolean"> - <xs:annotation> - <xs:documentation> - Requires that users begin their login experience at the relying party - rather than at a Provider or using other forms of unsolicited assertions. - </xs:documentation> - </xs:annotation> - </xs:attribute> - <xs:attribute name="rejectDelegatingIdentifiers" type="xs:boolean"> - <xs:annotation> - <xs:documentation> - Requires that the claimed identifiers used to log into the relying party - be the same ones that are originally issued by the Provider. - </xs:documentation> - </xs:annotation> - </xs:attribute> - <xs:attribute name="ignoreUnsignedExtensions" type="xs:boolean"> - <xs:annotation> - <xs:documentation> - Makes it impossible for the relying party to read authentication response - extensions that are not signed by the Provider. - </xs:documentation> - </xs:annotation> - </xs:attribute> - <xs:attribute name="allowDualPurposeIdentifiers" type="xs:boolean"> - <xs:annotation> - <xs:documentation> - Controls whether identifiers that are both OP Identifiers and Claimed Identifiers - should ever be recognized as claimed identifiers. - </xs:documentation> - </xs:annotation> - </xs:attribute> - <xs:attribute name="allowApproximateIdentifierDiscovery" type="xs:boolean"> - <xs:annotation> - <xs:documentation> - Controls whether certain Claimed Identifiers that exploit - features that .NET does not have the ability to send exact HTTP requests for will - still be allowed by using an approximate HTTP request. - Only impacts hosts running under partial trust. - </xs:documentation> - </xs:annotation> - </xs:attribute> - <xs:attribute name="protectDownlevelReplayAttacks" type="xs:boolean"> - <xs:annotation> - <xs:documentation> - Controls whether the relying party should take special care - to protect users against replay attacks when interoperating with OpenID 1.1 Providers. - </xs:documentation> - </xs:annotation> - </xs:attribute> - </xs:complexType> - </xs:element> - <xs:element name="behaviors"> - <xs:annotation> - <xs:documentation> - Manipulates the set of custom behaviors that are automatically applied - to incoming and outgoing OpenID messages. - </xs:documentation> - </xs:annotation> - <xs:complexType> - <xs:choice minOccurs="0" maxOccurs="unbounded"> - <xs:element name="add"> - <xs:complexType> - <xs:attribute name="type" type="xs:string" use="optional"> - <xs:annotation> - <xs:documentation> - The fully-qualified name of the type that implements the IRelyingPartyBehavior interface. - </xs:documentation> - </xs:annotation> - </xs:attribute> - <xs:attribute name="xaml" type="xs:string" use="optional" /> - </xs:complexType> - </xs:element> - <xs:element name="remove"> - <xs:complexType> - <xs:attribute name="type" type="xs:string" use="required"> - <xs:annotation> - <xs:documentation> - The fully-qualified name of the type that implements the IRelyingPartyBehavior interface. - </xs:documentation> - </xs:annotation> - </xs:attribute> - </xs:complexType> - </xs:element> - <xs:element name="clear"> - <xs:complexType> - <!--tag is empty--> - </xs:complexType> - </xs:element> - </xs:choice> - </xs:complexType> - </xs:element> - <xs:element name="discoveryServices"> - <xs:complexType> - <xs:choice minOccurs="0" maxOccurs="unbounded"> - <xs:element name="add"> - <xs:complexType> - <xs:attribute name="name" type="xs:string" use="required" /> - </xs:complexType> - </xs:element> - <xs:element name="remove"> - <xs:complexType> - <xs:attribute name="name" type="xs:string" use="required" /> - </xs:complexType> - </xs:element> - <xs:element name="clear"> - <xs:complexType> - <!--tag is empty--> - </xs:complexType> - </xs:element> - </xs:choice> - </xs:complexType> - </xs:element> - <xs:element name="store"> - <xs:annotation> - <xs:documentation> - A custom implementation of IRelyingPartyApplicationStore to use by default for new - instances of OpenIdRelyingParty. - </xs:documentation> - </xs:annotation> - <xs:complexType> - <xs:attribute name="type" type="xs:string"> - <xs:annotation> - <xs:documentation> - A fully-qualified type name of the custom implementation of IRelyingPartyApplicationStore. - </xs:documentation> - </xs:annotation> - </xs:attribute> - </xs:complexType> - </xs:element> - </xs:choice> - <xs:attribute name="preserveUserSuppliedIdentifier" type="xs:boolean" default="true"> - <xs:annotation> - <xs:documentation> - Whether "dnoa.userSuppliedIdentifier" is tacked onto the openid.return_to URL in order to preserve what the user typed into the OpenID box. - </xs:documentation> - </xs:annotation> - </xs:attribute> - </xs:complexType> - </xs:element> - <xs:element name="provider"> - <xs:annotation> - <xs:documentation> - Configuration specific for OpenID providers. - </xs:documentation> - </xs:annotation> - <xs:complexType> - <xs:choice minOccurs="0" maxOccurs="unbounded"> - <xs:element name="security"> - <xs:annotation> - <xs:documentation> - Security settings that apply to OpenID providers. - </xs:documentation> - </xs:annotation> - <xs:complexType> - <xs:choice minOccurs="0" maxOccurs="unbounded"> - <xs:element name="associations"> - <xs:annotation> - <xs:documentation> - Sets maximum ages for shared associations of various strengths. - </xs:documentation> - </xs:annotation> - <xs:complexType> - <xs:choice minOccurs="0" maxOccurs="unbounded"> - <xs:element name="add"> - <xs:complexType> - <xs:attribute name="type" type="xs:string" use="required"> - <xs:annotation> - <xs:documentation> - The OpenID association type (i.e. HMAC-SHA1 or HMAC-SHA256) - </xs:documentation> - </xs:annotation> - </xs:attribute> - <xs:attribute name="lifetime" type="xs:string" use="required"> - <xs:annotation> - <xs:documentation> - The lifetime a shared association of this type will be used for. - </xs:documentation> - </xs:annotation> - </xs:attribute> - </xs:complexType> - </xs:element> - <xs:element name="remove"> - <xs:complexType> - <xs:attribute name="type" type="xs:string" use="required"> - <xs:annotation> - <xs:documentation> - The OpenID association type (i.e. HMAC-SHA1 or HMAC-SHA256) - </xs:documentation> - </xs:annotation> - </xs:attribute> - </xs:complexType> - </xs:element> - <xs:element name="clear"> - <xs:complexType> - <!--tag is empty--> - </xs:complexType> - </xs:element> - </xs:choice> - </xs:complexType> - </xs:element> - </xs:choice> - <xs:attribute name="requireSsl" type="xs:boolean" default="false"> - <xs:annotation> - <xs:documentation> - Requires that relying parties' realm URLs be protected by HTTPS, - ensuring that the RP discovery step is not vulnerable to DNS poisoning attacks. - </xs:documentation> - </xs:annotation> - </xs:attribute> - <xs:attribute name="protectDownlevelReplayAttacks" type="xs:boolean"> - <xs:annotation> - <xs:documentation> - Provides automatic security protections to OpenID 1.x relying parties - so security is comparable to OpenID 2.0 relying parties. - </xs:documentation> - </xs:annotation> - </xs:attribute> - <xs:attribute name="encodeAssociationSecretsInHandles" type="xs:boolean" default="true"> - <xs:annotation> - <xs:documentation> - Whether the Provider should ease the burden of storing associations - by encoding their secrets (in signed, encrypted form) into the association handles themselves, storing only - a few rotating, private symmetric keys in the Provider's store instead. - </xs:documentation> - </xs:annotation> - </xs:attribute> - <xs:attribute name="unsolicitedAssertionVerification"> - <xs:annotation> - <xs:documentation> - The level of verification done on a claimed identifier before an unsolicited - assertion for that identifier is issued by this Provider. - </xs:documentation> - </xs:annotation> - <xs:simpleType> - <xs:restriction base="xs:NMTOKEN"> - <xs:enumeration value="RequireSuccess"> - <xs:annotation> - <xs:documentation> - The claimed identifier being asserted must delegate to this Provider - and this must be verifiable by the Provider to send the assertion. - </xs:documentation> - </xs:annotation> - </xs:enumeration> - <xs:enumeration value="LogWarningOnFailure"> - <xs:annotation> - <xs:documentation> - The claimed identifier being asserted is checked for delegation to this Provider - and an warning is logged, but the assertion is allowed to go through. - </xs:documentation> - </xs:annotation> - </xs:enumeration> - <xs:enumeration value="NeverVerify"> - <xs:annotation> - <xs:documentation> - The claimed identifier being asserted is not checked to see that this Provider - has authority to assert its identity. - </xs:documentation> - </xs:annotation> - </xs:enumeration> - </xs:restriction> - </xs:simpleType> - </xs:attribute> - <xs:attribute name="minimumHashBitLength" type="xs:int"> - <xs:annotation> - <xs:documentation> - The minimum shared association strength to form with relying parties. - </xs:documentation> - </xs:annotation> - </xs:attribute> - <xs:attribute name="maximumHashBitLength" type="xs:int"> - <xs:annotation> - <xs:documentation> - The maximum shared association strength to form with relying parties. - </xs:documentation> - </xs:annotation> - </xs:attribute> - </xs:complexType> - </xs:element> - <xs:element name="behaviors"> - <xs:annotation> - <xs:documentation> - Manipulates the set of custom behaviors that are automatically applied - to incoming and outgoing OpenID messages. - </xs:documentation> - </xs:annotation> - <xs:complexType> - <xs:choice minOccurs="0" maxOccurs="unbounded"> - <xs:element name="add"> - <xs:complexType> - <xs:attribute name="type" type="xs:string" use="optional"> - <xs:annotation> - <xs:documentation> - The fully-qualified name of the type that implements the IRelyingPartyBehavior interface. - </xs:documentation> - </xs:annotation> - </xs:attribute> - <xs:attribute name="xaml" type="xs:string" use="optional" /> - </xs:complexType> - </xs:element> - <xs:element name="remove"> - <xs:complexType> - <xs:attribute name="type" type="xs:string" use="required" /> - </xs:complexType> - </xs:element> - <xs:element name="clear"> - <xs:complexType> - <!--tag is empty--> - </xs:complexType> - </xs:element> - </xs:choice> - </xs:complexType> - </xs:element> - <xs:element name="store"> - <xs:annotation> - <xs:documentation> - A custom implementation of IProviderApplicationStore to use by default for new - instances of OpenIdRelyingParty. - </xs:documentation> - </xs:annotation> - <xs:complexType> - <xs:attribute name="type" type="xs:string"> - <xs:annotation> - <xs:documentation> - A fully-qualified type name of the custom implementation of IProviderApplicationStore. - </xs:documentation> - </xs:annotation> - </xs:attribute> - </xs:complexType> - </xs:element> - </xs:choice> - </xs:complexType> - </xs:element> - <xs:element name="extensionFactories"> - <xs:annotation> - <xs:documentation> - Adjusts the list of known OpenID extensions via the registration of extension factories. - </xs:documentation> - </xs:annotation> - <xs:complexType> - <xs:choice minOccurs="0" maxOccurs="unbounded"> - <xs:element name="add"> - <xs:complexType> - <xs:attribute name="type" type="xs:string" use="optional"> - <xs:annotation> - <xs:documentation> - The fully-qualified name of the type that implements IOpenIdExtensionFactory. - </xs:documentation> - </xs:annotation> - </xs:attribute> - <xs:attribute name="xaml" type="xs:string" use="optional" /> - </xs:complexType> - </xs:element> - <xs:element name="remove"> - <xs:complexType> - <xs:attribute name="type" type="xs:string" use="required"> - <xs:annotation> - <xs:documentation> - The fully-qualified name of the type that implements IOpenIdExtensionFactory. - </xs:documentation> - </xs:annotation> - </xs:attribute> - </xs:complexType> - </xs:element> - <xs:element name="clear"> - <xs:complexType> - <!--tag is empty--> - </xs:complexType> - </xs:element> - </xs:choice> - </xs:complexType> - </xs:element> - <xs:element name="xriResolver"> - <xs:annotation> - <xs:documentation> - Controls XRI resolution to XRDS documents. - </xs:documentation> - </xs:annotation> - <xs:complexType> - <xs:attribute name="enabled" type="xs:boolean"> - <xs:annotation> - <xs:documentation> - Controls whether XRI identifiers are allowed at all. - </xs:documentation> - </xs:annotation> - </xs:attribute> - <xs:attribute name="proxy" type="xs:string"> - <xs:annotation> - <xs:documentation> - The XRI proxy resolver to use for obtaining XRDS documents from an XRI. - </xs:documentation> - </xs:annotation> - </xs:attribute> - </xs:complexType> - </xs:element> - </xs:choice> - <xs:attribute name="maxAuthenticationTime" type="xs:string"> - <xs:annotation> - <xs:documentation> - The maximum time a user can take at the Provider while logging in before a relying party considers - the authentication lost. - </xs:documentation> - </xs:annotation> - </xs:attribute> - <xs:attribute name="cacheDiscovery" type="xs:boolean"> - <xs:annotation> - <xs:documentation> - Whether the results of identifier discovery should be cached for a short time to improve performance - on subsequent requests, at the potential risk of reading stale data. - </xs:documentation> - </xs:annotation> - </xs:attribute> - </xs:complexType> - </xs:element> - <xs:element name="oauth"> - <xs:annotation> - <xs:documentation> - Settings for OAuth consumers and service providers. - </xs:documentation> - </xs:annotation> - <xs:complexType> - <xs:choice minOccurs="0" maxOccurs="unbounded"> - <xs:element name="consumer"> - <xs:annotation> - <xs:documentation> - Settings applicable to OAuth Consumers. - </xs:documentation> - </xs:annotation> - <xs:complexType> - <xs:choice minOccurs="0" maxOccurs="unbounded"> - <xs:element name="security"> - <xs:annotation> - <xs:documentation> - Security settings applicable to OAuth Consumers. - </xs:documentation> - </xs:annotation> - <xs:complexType> - - </xs:complexType> - </xs:element> - </xs:choice> - </xs:complexType> - </xs:element> - <xs:element name="serviceProvider"> - <xs:annotation> - <xs:documentation> - Settings applicable to OAuth Service Providers. - </xs:documentation> - </xs:annotation> - <xs:complexType> - <xs:choice minOccurs="0" maxOccurs="unbounded"> - <xs:element name="security"> - <xs:annotation> - <xs:documentation> - Security settings applicable to OAuth Service Providers. - </xs:documentation> - </xs:annotation> - <xs:complexType> - <xs:attribute name="minimumRequiredOAuthVersion" default="V10"> - <xs:annotation> - <xs:documentation> - Optionally restricts interoperability with OAuth consumers that implement - older versions of OAuth. - </xs:documentation> - </xs:annotation> - <xs:simpleType> - <xs:restriction base="xs:NMTOKEN"> - <xs:enumeration value="V10"> - <xs:annotation> - <xs:documentation> - The initial version of OAuth, now known to be vulnerable to certain social engineering attacks. - </xs:documentation> - </xs:annotation> - </xs:enumeration> - <xs:enumeration value="V10a"> - <xs:annotation> - <xs:documentation> - The OAuth version that protects against social engineering attacks by introducing - the oauth_verifier parameter. - </xs:documentation> - </xs:annotation> - </xs:enumeration> - </xs:restriction> - </xs:simpleType> - </xs:attribute> - <xs:attribute name="maxAuthorizationTime" type="xs:string" default="0:05"> - <xs:annotation> - <xs:documentation> - The maximum time allowed for users to authorize a consumer before request tokens expire. - </xs:documentation> - </xs:annotation> - </xs:attribute> - </xs:complexType> - </xs:element> - <xs:element name="store"> - <xs:annotation> - <xs:documentation> - Sets the custom type that implements the INonceStore interface to use for nonce checking. - </xs:documentation> - </xs:annotation> - <xs:complexType> - <xs:attribute name="type" type="xs:string"> - <xs:annotation> - <xs:documentation> - A fully-qualified type name of the custom implementation of INonceStore. - </xs:documentation> - </xs:annotation> - </xs:attribute> - </xs:complexType> - </xs:element> - </xs:choice> - </xs:complexType> - </xs:element> - </xs:choice> - </xs:complexType> - </xs:element> - <xs:element name="reporting"> - <xs:annotation> - <xs:documentation> - Adjusts statistical reports DotNetOpenAuth may send to the library authors to - assist with future development of the library. - </xs:documentation> - </xs:annotation> - <xs:complexType> - <xs:attribute name="enabled" type="xs:boolean"> - <xs:annotation> - <xs:documentation> - Controls whether reporting is active at all or entirely inactive. - Note that even if active, the reports may be more or less empty based - on other settings. - </xs:documentation> - </xs:annotation> - </xs:attribute> - <xs:attribute name="minimumReportingInterval" type="xs:string"> - <xs:annotation> - <xs:documentation> - Controls how frequently reports are collected and transmitted. - </xs:documentation> - </xs:annotation> - </xs:attribute> - <xs:attribute name="minimumFlushInterval" type="xs:string"> - <xs:annotation> - <xs:documentation> - Controls how frequently the statistics that are collected in memory are persisted to disk. - </xs:documentation> - </xs:annotation> - </xs:attribute> - <xs:attribute name="includeFeatureUsage" type="xs:boolean" default="true"> - <xs:annotation> - <xs:documentation> - Whether a list of features in DotNetOpenAuth that are actually used by this host - are included in the report. - </xs:documentation> - </xs:annotation> - </xs:attribute> - <xs:attribute name="includeEventStatistics" type="xs:boolean" default="true"> - <xs:annotation> - <xs:documentation> - Whether a set of counters that track how often certain events (such as an - successful or failed authentication) is included in the report. - </xs:documentation> - </xs:annotation> - </xs:attribute> - <xs:attribute name="includeLocalRequestUris" type="xs:boolean" default="true"> - <xs:annotation> - <xs:documentation> - Whether to include a few of this host's URLs that contain DotNetOpenAuth components. - </xs:documentation> - </xs:annotation> - </xs:attribute> - <xs:attribute name="includeCultures" type="xs:boolean" default="true"> - <xs:annotation> - <xs:documentation> - Whether to include the cultures as set on the user agents of incoming requests to pages - that contain DotNetOpenAuth components. - </xs:documentation> - </xs:annotation> - </xs:attribute> - </xs:complexType> - </xs:element> - <xs:element name="webResourceUrlProvider"> - <xs:annotation> - <xs:documentation> - The type that implements the DotNetOpenAuth.IEmbeddedResourceRetrieval interface - to instantiate for obtaining URLs that fetch embedded resource streams. - Primarily useful when the System.Web.UI.Page class is not used in the ASP.NET pipeline. - </xs:documentation> - </xs:annotation> - <xs:complexType> - <xs:attribute name="type" type="xs:string" use="optional"> - <xs:annotation> - <xs:documentation> - The fully-qualified name of the type that implements the IEmbeddedResourceRetrieval interface. - </xs:documentation> - </xs:annotation> - </xs:attribute> - <xs:attribute name="xaml" type="xs:string" use="optional" /> - </xs:complexType> - </xs:element> - </xs:choice> - </xs:complexType> - </xs:element> -</xs:schema> diff --git a/src/DotNetOpenAuth/Configuration/DotNetOpenAuthSection.cs b/src/DotNetOpenAuth/Configuration/DotNetOpenAuthSection.cs deleted file mode 100644 index 409fca9..0000000 --- a/src/DotNetOpenAuth/Configuration/DotNetOpenAuthSection.cs +++ /dev/null @@ -1,134 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="DotNetOpenAuthSection.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.Configuration { - using System.Configuration; - using System.Diagnostics.Contracts; - - /// <summary> - /// Represents the section in the host's .config file that configures - /// this library's settings. - /// </summary> - [ContractVerification(true)] - public class DotNetOpenAuthSection : ConfigurationSection { - /// <summary> - /// The name of the section under which this library's settings must be found. - /// </summary> - private const string SectionName = "dotNetOpenAuth"; - - /// <summary> - /// The name of the <messaging> sub-element. - /// </summary> - private const string MessagingElementName = "messaging"; - - /// <summary> - /// The name of the <openid> sub-element. - /// </summary> - private const string OpenIdElementName = "openid"; - - /// <summary> - /// The name of the <oauth> sub-element. - /// </summary> - private const string OAuthElementName = "oauth"; - - /// <summary> - /// The name of the <reporting> sub-element. - /// </summary> - private const string ReportingElementName = "reporting"; - - /// <summary> - /// The name of the <webResourceUrlProvider> sub-element. - /// </summary> - private const string WebResourceUrlProviderName = "webResourceUrlProvider"; - - /// <summary> - /// Initializes a new instance of the <see cref="DotNetOpenAuthSection"/> class. - /// </summary> - internal DotNetOpenAuthSection() { - Contract.Assume(this.SectionInformation != null); - this.SectionInformation.AllowLocation = false; - } - - /// <summary> - /// Gets the configuration section from the .config file. - /// </summary> - public static DotNetOpenAuthSection Configuration { - get { - Contract.Ensures(Contract.Result<DotNetOpenAuthSection>() != null); - return (DotNetOpenAuthSection)ConfigurationManager.GetSection(SectionName) ?? new DotNetOpenAuthSection(); - } - } - - /// <summary> - /// Gets or sets the configuration for the messaging framework. - /// </summary> - [ConfigurationProperty(MessagingElementName)] - public MessagingElement Messaging { - get { - Contract.Ensures(Contract.Result<MessagingElement>() != null); - return (MessagingElement)this[MessagingElementName] ?? new MessagingElement(); - } - - set { - this[MessagingElementName] = value; - } - } - - /// <summary> - /// Gets or sets the configuration for OpenID. - /// </summary> - [ConfigurationProperty(OpenIdElementName)] - internal OpenIdElement OpenId { - get { - Contract.Ensures(Contract.Result<OpenIdElement>() != null); - return (OpenIdElement)this[OpenIdElementName] ?? new OpenIdElement(); - } - - set { - this[OpenIdElementName] = value; - } - } - - /// <summary> - /// Gets or sets the configuration for OAuth. - /// </summary> - [ConfigurationProperty(OAuthElementName)] - internal OAuthElement OAuth { - get { - Contract.Ensures(Contract.Result<OAuthElement>() != null); - return (OAuthElement)this[OAuthElementName] ?? new OAuthElement(); - } - - set { - this[OAuthElementName] = value; - } - } - - /// <summary> - /// Gets or sets the configuration for reporting. - /// </summary> - [ConfigurationProperty(ReportingElementName)] - internal ReportingElement Reporting { - get { - Contract.Ensures(Contract.Result<ReportingElement>() != null); - return (ReportingElement)this[ReportingElementName] ?? new ReportingElement(); - } - - set { - this[ReportingElementName] = value; - } - } - - /// <summary> - /// Gets or sets the type to use for obtaining URLs that fetch embedded resource streams. - /// </summary> - [ConfigurationProperty(WebResourceUrlProviderName)] - internal TypeConfigurationElement<IEmbeddedResourceRetrieval> EmbeddedResourceRetrievalProvider { - get { return (TypeConfigurationElement<IEmbeddedResourceRetrieval>)this[WebResourceUrlProviderName] ?? new TypeConfigurationElement<IEmbeddedResourceRetrieval>(); } - set { this[WebResourceUrlProviderName] = value; } - } - } -} diff --git a/src/DotNetOpenAuth/Configuration/MessagingElement.cs b/src/DotNetOpenAuth/Configuration/MessagingElement.cs deleted file mode 100644 index 1c46bcf..0000000 --- a/src/DotNetOpenAuth/Configuration/MessagingElement.cs +++ /dev/null @@ -1,177 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="MessagingElement.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.Configuration { - using System; - using System.Configuration; - using System.Diagnostics.Contracts; - using DotNetOpenAuth.Messaging; - using DotNetOpenAuth.Messaging.Bindings; - - /// <summary> - /// Represents the <messaging> element in the host's .config file. - /// </summary> - [ContractVerification(true)] - public class MessagingElement : ConfigurationElement { - /// <summary> - /// The name of the <untrustedWebRequest> sub-element. - /// </summary> - private const string UntrustedWebRequestElementName = "untrustedWebRequest"; - - /// <summary> - /// The name of the attribute that stores the association's maximum lifetime. - /// </summary> - private const string MaximumMessageLifetimeConfigName = "lifetime"; - - /// <summary> - /// The name of the attribute that stores the maximum allowable clock skew. - /// </summary> - private const string MaximumClockSkewConfigName = "clockSkew"; - - /// <summary> - /// The name of the attribute that indicates whether to disable SSL requirements across the library. - /// </summary> - private const string RelaxSslRequirementsConfigName = "relaxSslRequirements"; - - /// <summary> - /// The name of the attribute that controls whether messaging rules are strictly followed. - /// </summary> - private const string StrictConfigName = "strict"; - - /// <summary> - /// The default value for the <see cref="MaximumIndirectMessageUrlLength"/> property. - /// </summary> - /// <value> - /// 2KB, recommended by OpenID group - /// </value> - private const int DefaultMaximumIndirectMessageUrlLength = 2 * 1024; - - /// <summary> - /// The name of the attribute that controls the maximum length of a URL before it is converted - /// to a POST payload. - /// </summary> - private const string MaximumIndirectMessageUrlLengthConfigName = "maximumIndirectMessageUrlLength"; - - /// <summary> - /// Gets the name of the @privateSecretMaximumAge attribute. - /// </summary> - private const string PrivateSecretMaximumAgeConfigName = "privateSecretMaximumAge"; - - /// <summary> - /// Gets the actual maximum message lifetime that a program should allow. - /// </summary> - /// <value>The sum of the <see cref="MaximumMessageLifetime"/> and - /// <see cref="MaximumClockSkew"/> property values.</value> - public TimeSpan MaximumMessageLifetime { - get { return this.MaximumMessageLifetimeNoSkew + this.MaximumClockSkew; } - } - - /// <summary> - /// Gets or sets the maximum lifetime of a private symmetric secret, - /// that may be used for signing or encryption. - /// </summary> - /// <value>The default value is 28 days (twice the age of the longest association).</value> - [ConfigurationProperty(PrivateSecretMaximumAgeConfigName, DefaultValue = "28.00:00:00")] - public TimeSpan PrivateSecretMaximumAge { - get { return (TimeSpan)this[PrivateSecretMaximumAgeConfigName]; } - set { this[PrivateSecretMaximumAgeConfigName] = value; } - } - - /// <summary> - /// Gets or sets the time between a message's creation and its receipt - /// before it is considered expired. - /// </summary> - /// <value> - /// The default value value is 3 minutes. - /// </value> - /// <remarks> - /// <para>Smaller timespans mean lower tolerance for delays in message delivery. - /// Larger timespans mean more nonces must be stored to provide replay protection.</para> - /// <para>The maximum age a message implementing the - /// <see cref="IExpiringProtocolMessage"/> interface can be before - /// being discarded as too old.</para> - /// <para>This time limit should NOT take into account expected - /// time skew for servers across the Internet. Time skew is added to - /// this value and is controlled by the <see cref="MaximumClockSkew"/> property.</para> - /// </remarks> - [ConfigurationProperty(MaximumMessageLifetimeConfigName, DefaultValue = "00:03:00")] - internal TimeSpan MaximumMessageLifetimeNoSkew { - get { return (TimeSpan)this[MaximumMessageLifetimeConfigName]; } - set { this[MaximumMessageLifetimeConfigName] = value; } - } - - /// <summary> - /// Gets or sets the maximum clock skew. - /// </summary> - /// <value>The default value is 10 minutes.</value> - /// <remarks> - /// <para>Smaller timespans mean lower tolerance for - /// time variance due to server clocks not being synchronized. - /// Larger timespans mean greater chance for replay attacks and - /// larger nonce caches.</para> - /// <para>For example, if a server could conceivably have its - /// clock d = 5 minutes off UTC time, then any two servers could have - /// their clocks disagree by as much as 2*d = 10 minutes. </para> - /// </remarks> - [ConfigurationProperty(MaximumClockSkewConfigName, DefaultValue = "00:10:00")] - internal TimeSpan MaximumClockSkew { - get { return (TimeSpan)this[MaximumClockSkewConfigName]; } - set { this[MaximumClockSkewConfigName] = value; } - } - - /// <summary> - /// Gets or sets a value indicating whether SSL requirements within the library are disabled/relaxed. - /// Use for TESTING ONLY. - /// </summary> - [ConfigurationProperty(RelaxSslRequirementsConfigName, DefaultValue = false)] - internal bool RelaxSslRequirements { - get { return (bool)this[RelaxSslRequirementsConfigName]; } - set { this[RelaxSslRequirementsConfigName] = value; } - } - - /// <summary> - /// Gets or sets a value indicating whether messaging rules are strictly - /// adhered to. - /// </summary> - /// <value><c>true</c> by default.</value> - /// <remarks> - /// Strict will require that remote parties adhere strictly to the specifications, - /// even when a loose interpretation would not compromise security. - /// <c>true</c> is a good default because it shakes out interoperability bugs in remote services - /// so they can be identified and corrected. But some web sites want things to Just Work - /// more than they want to file bugs against others, so <c>false</c> is the setting for them. - /// </remarks> - [ConfigurationProperty(StrictConfigName, DefaultValue = true)] - internal bool Strict { - get { return (bool)this[StrictConfigName]; } - set { this[StrictConfigName] = value; } - } - - /// <summary> - /// Gets or sets the configuration for the <see cref="UntrustedWebRequestHandler"/> class. - /// </summary> - /// <value>The untrusted web request.</value> - [ConfigurationProperty(UntrustedWebRequestElementName)] - internal UntrustedWebRequestElement UntrustedWebRequest { - get { return (UntrustedWebRequestElement)this[UntrustedWebRequestElementName] ?? new UntrustedWebRequestElement(); } - set { this[UntrustedWebRequestElementName] = value; } - } - - /// <summary> - /// Gets or sets the maximum allowable size for a 301 Redirect response before we send - /// a 200 OK response with a scripted form POST with the parameters instead - /// in order to ensure successfully sending a large payload to another server - /// that might have a maximum allowable size restriction on its GET request. - /// </summary> - /// <value>The default value is 2048.</value> - [ConfigurationProperty(MaximumIndirectMessageUrlLengthConfigName, DefaultValue = DefaultMaximumIndirectMessageUrlLength)] - [IntegerValidator(MinValue = 500, MaxValue = 4096)] - internal int MaximumIndirectMessageUrlLength { - get { return (int)this[MaximumIndirectMessageUrlLengthConfigName]; } - set { this[MaximumIndirectMessageUrlLengthConfigName] = value; } - } - } -} diff --git a/src/DotNetOpenAuth/Configuration/OAuthElement.cs b/src/DotNetOpenAuth/Configuration/OAuthElement.cs deleted file mode 100644 index 282bdba..0000000 --- a/src/DotNetOpenAuth/Configuration/OAuthElement.cs +++ /dev/null @@ -1,48 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="OAuthElement.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.Configuration { - using System.Configuration; - - /// <summary> - /// Represents the <oauth> element in the host's .config file. - /// </summary> - internal class OAuthElement : ConfigurationElement { - /// <summary> - /// The name of the <consumer> sub-element. - /// </summary> - private const string ConsumerElementName = "consumer"; - - /// <summary> - /// The name of the <serviceProvider> sub-element. - /// </summary> - private const string ServiceProviderElementName = "serviceProvider"; - - /// <summary> - /// Initializes a new instance of the <see cref="OAuthElement"/> class. - /// </summary> - internal OAuthElement() { - } - - /// <summary> - /// Gets or sets the configuration specific for Consumers. - /// </summary> - [ConfigurationProperty(ConsumerElementName)] - internal OAuthConsumerElement Consumer { - get { return (OAuthConsumerElement)this[ConsumerElementName] ?? new OAuthConsumerElement(); } - set { this[ConsumerElementName] = value; } - } - - /// <summary> - /// Gets or sets the configuration specific for Service Providers. - /// </summary> - [ConfigurationProperty(ServiceProviderElementName)] - internal OAuthServiceProviderElement ServiceProvider { - get { return (OAuthServiceProviderElement)this[ServiceProviderElementName] ?? new OAuthServiceProviderElement(); } - set { this[ServiceProviderElementName] = value; } - } - } -} diff --git a/src/DotNetOpenAuth/Configuration/OpenIdElement.cs b/src/DotNetOpenAuth/Configuration/OpenIdElement.cs deleted file mode 100644 index 69994e6..0000000 --- a/src/DotNetOpenAuth/Configuration/OpenIdElement.cs +++ /dev/null @@ -1,134 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="OpenIdElement.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.Configuration { - using System; - using System.Configuration; - using System.Diagnostics.Contracts; - using DotNetOpenAuth.OpenId.ChannelElements; - using DotNetOpenAuth.OpenId.Messages; - - /// <summary> - /// Represents the <openid> element in the host's .config file. - /// </summary> - [ContractVerification(true)] - internal class OpenIdElement : ConfigurationElement { - /// <summary> - /// The name of the <relyingParty> sub-element. - /// </summary> - private const string RelyingPartyElementName = "relyingParty"; - - /// <summary> - /// The name of the <provider> sub-element. - /// </summary> - private const string ProviderElementName = "provider"; - - /// <summary> - /// The name of the <extensions> sub-element. - /// </summary> - private const string ExtensionFactoriesElementName = "extensionFactories"; - - /// <summary> - /// The name of the <xriResolver> sub-element. - /// </summary> - private const string XriResolverElementName = "xriResolver"; - - /// <summary> - /// The name of the @maxAuthenticationTime attribute. - /// </summary> - private const string MaxAuthenticationTimePropertyName = "maxAuthenticationTime"; - - /// <summary> - /// The name of the @cacheDiscovery attribute. - /// </summary> - private const string CacheDiscoveryPropertyName = "cacheDiscovery"; - - /// <summary> - /// Initializes a new instance of the <see cref="OpenIdElement"/> class. - /// </summary> - internal OpenIdElement() { - } - - /// <summary> - /// Gets or sets the maximum time a user can take to complete authentication. - /// </summary> - /// <remarks> - /// This time limit allows the library to decide how long to cache certain values - /// necessary to complete authentication. The lower the time, the less demand on - /// the server. But too short a time can frustrate the user. - /// </remarks> - [ConfigurationProperty(MaxAuthenticationTimePropertyName, DefaultValue = "0:05")] // 5 minutes - [PositiveTimeSpanValidator] - internal TimeSpan MaxAuthenticationTime { - get { - Contract.Ensures(Contract.Result<TimeSpan>() > TimeSpan.Zero); - TimeSpan result = (TimeSpan)this[MaxAuthenticationTimePropertyName]; - Contract.Assume(result > TimeSpan.Zero); // our PositiveTimeSpanValidator should take care of this - return result; - } - - set { - Contract.Requires<ArgumentOutOfRangeException>(value > TimeSpan.Zero); - this[MaxAuthenticationTimePropertyName] = value; - } - } - - /// <summary> - /// Gets or sets a value indicating whether the results of Identifier discovery - /// should be cached. - /// </summary> - /// <value> - /// Use <c>true</c> to allow identifier discovery to immediately return cached results when available; - /// otherwise, use <c>false</c>.to force fresh results every time at the cost of slightly slower logins. - /// The default value is <c>true</c>. - /// </value> - /// <remarks> - /// When enabled, caching is done according to HTTP standards. - /// </remarks> - [ConfigurationProperty(CacheDiscoveryPropertyName, DefaultValue = true)] - internal bool CacheDiscovery { - get { return (bool)this[CacheDiscoveryPropertyName]; } - set { this[CacheDiscoveryPropertyName] = value; } - } - - /// <summary> - /// Gets or sets the configuration specific for Relying Parties. - /// </summary> - [ConfigurationProperty(RelyingPartyElementName)] - internal OpenIdRelyingPartyElement RelyingParty { - get { return (OpenIdRelyingPartyElement)this[RelyingPartyElementName] ?? new OpenIdRelyingPartyElement(); } - set { this[RelyingPartyElementName] = value; } - } - - /// <summary> - /// Gets or sets the configuration specific for Providers. - /// </summary> - [ConfigurationProperty(ProviderElementName)] - internal OpenIdProviderElement Provider { - get { return (OpenIdProviderElement)this[ProviderElementName] ?? new OpenIdProviderElement(); } - set { this[ProviderElementName] = value; } - } - - /// <summary> - /// Gets or sets the registered OpenID extension factories. - /// </summary> - [ConfigurationProperty(ExtensionFactoriesElementName, IsDefaultCollection = false)] - [ConfigurationCollection(typeof(TypeConfigurationCollection<IOpenIdExtensionFactory>))] - internal TypeConfigurationCollection<IOpenIdExtensionFactory> ExtensionFactories { - get { return (TypeConfigurationCollection<IOpenIdExtensionFactory>)this[ExtensionFactoriesElementName] ?? new TypeConfigurationCollection<IOpenIdExtensionFactory>(); } - set { this[ExtensionFactoriesElementName] = value; } - } - - /// <summary> - /// Gets or sets the configuration for the XRI resolver. - /// </summary> - [ConfigurationProperty(XriResolverElementName)] - internal XriResolverElement XriResolver { - get { return (XriResolverElement)this[XriResolverElementName] ?? new XriResolverElement(); } - set { this[XriResolverElementName] = value; } - } - } -} diff --git a/src/DotNetOpenAuth/Configuration/OpenIdProviderElement.cs b/src/DotNetOpenAuth/Configuration/OpenIdProviderElement.cs deleted file mode 100644 index a594e86..0000000 --- a/src/DotNetOpenAuth/Configuration/OpenIdProviderElement.cs +++ /dev/null @@ -1,67 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="OpenIdProviderElement.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.Configuration { - using System.Configuration; - using System.Diagnostics.Contracts; - using DotNetOpenAuth.OpenId; - using DotNetOpenAuth.OpenId.Provider; - - /// <summary> - /// The section in the .config file that allows customization of OpenID Provider behaviors. - /// </summary> - [ContractVerification(true)] - internal class OpenIdProviderElement : ConfigurationElement { - /// <summary> - /// The name of the security sub-element. - /// </summary> - private const string SecuritySettingsConfigName = "security"; - - /// <summary> - /// Gets the name of the <behaviors> sub-element. - /// </summary> - private const string BehaviorsElementName = "behaviors"; - - /// <summary> - /// The name of the custom store sub-element. - /// </summary> - private const string StoreConfigName = "store"; - - /// <summary> - /// Initializes a new instance of the <see cref="OpenIdProviderElement"/> class. - /// </summary> - public OpenIdProviderElement() { - } - - /// <summary> - /// Gets or sets the security settings. - /// </summary> - [ConfigurationProperty(SecuritySettingsConfigName)] - public OpenIdProviderSecuritySettingsElement SecuritySettings { - get { return (OpenIdProviderSecuritySettingsElement)this[SecuritySettingsConfigName] ?? new OpenIdProviderSecuritySettingsElement(); } - set { this[SecuritySettingsConfigName] = value; } - } - - /// <summary> - /// Gets or sets the special behaviors to apply. - /// </summary> - [ConfigurationProperty(BehaviorsElementName, IsDefaultCollection = false)] - [ConfigurationCollection(typeof(TypeConfigurationCollection<IProviderBehavior>))] - public TypeConfigurationCollection<IProviderBehavior> Behaviors { - get { return (TypeConfigurationCollection<IProviderBehavior>)this[BehaviorsElementName] ?? new TypeConfigurationCollection<IProviderBehavior>(); } - set { this[BehaviorsElementName] = value; } - } - - /// <summary> - /// Gets or sets the type to use for storing application state. - /// </summary> - [ConfigurationProperty(StoreConfigName)] - public TypeConfigurationElement<IOpenIdApplicationStore> ApplicationStore { - get { return (TypeConfigurationElement<IOpenIdApplicationStore>)this[StoreConfigName] ?? new TypeConfigurationElement<IOpenIdApplicationStore>(); } - set { this[StoreConfigName] = value; } - } - } -} diff --git a/src/DotNetOpenAuth/Configuration/OpenIdRelyingPartyElement.cs b/src/DotNetOpenAuth/Configuration/OpenIdRelyingPartyElement.cs deleted file mode 100644 index 17b890f..0000000 --- a/src/DotNetOpenAuth/Configuration/OpenIdRelyingPartyElement.cs +++ /dev/null @@ -1,115 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="OpenIdRelyingPartyElement.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.Configuration { - using System; - using System.Configuration; - using System.Diagnostics.Contracts; - using DotNetOpenAuth.OpenId; - using DotNetOpenAuth.OpenId.RelyingParty; - - /// <summary> - /// The section in the .config file that allows customization of OpenID Relying Party behaviors. - /// </summary> - [ContractVerification(true)] - internal class OpenIdRelyingPartyElement : ConfigurationElement { - /// <summary> - /// The name of the custom store sub-element. - /// </summary> - private const string StoreConfigName = "store"; - - /// <summary> - /// The name of the attribute that specifies whether dnoa.userSuppliedIdentifier is tacked onto the openid.return_to URL. - /// </summary> - private const string PreserveUserSuppliedIdentifierConfigName = "preserveUserSuppliedIdentifier"; - - /// <summary> - /// Gets the name of the security sub-element. - /// </summary> - private const string SecuritySettingsConfigName = "security"; - - /// <summary> - /// The name of the <behaviors> sub-element. - /// </summary> - private const string BehaviorsElementName = "behaviors"; - - /// <summary> - /// The name of the <discoveryServices> sub-element. - /// </summary> - private const string DiscoveryServicesElementName = "discoveryServices"; - - /// <summary> - /// The built-in set of identifier discovery services. - /// </summary> - private static readonly TypeConfigurationCollection<IIdentifierDiscoveryService> defaultDiscoveryServices = new TypeConfigurationCollection<IIdentifierDiscoveryService>(new Type[] { typeof(UriDiscoveryService), typeof(XriDiscoveryProxyService) }); - - /// <summary> - /// Initializes a new instance of the <see cref="OpenIdRelyingPartyElement"/> class. - /// </summary> - public OpenIdRelyingPartyElement() { - } - - /// <summary> - /// Gets or sets a value indicating whether "dnoa.userSuppliedIdentifier" is tacked onto the openid.return_to URL in order to preserve what the user typed into the OpenID box. - /// </summary> - /// <value> - /// The default value is <c>true</c>. - /// </value> - [ConfigurationProperty(PreserveUserSuppliedIdentifierConfigName, DefaultValue = true)] - public bool PreserveUserSuppliedIdentifier { - get { return (bool)this[PreserveUserSuppliedIdentifierConfigName]; } - set { this[PreserveUserSuppliedIdentifierConfigName] = value; } - } - - /// <summary> - /// Gets or sets the security settings. - /// </summary> - [ConfigurationProperty(SecuritySettingsConfigName)] - public OpenIdRelyingPartySecuritySettingsElement SecuritySettings { - get { return (OpenIdRelyingPartySecuritySettingsElement)this[SecuritySettingsConfigName] ?? new OpenIdRelyingPartySecuritySettingsElement(); } - set { this[SecuritySettingsConfigName] = value; } - } - - /// <summary> - /// Gets or sets the special behaviors to apply. - /// </summary> - [ConfigurationProperty(BehaviorsElementName, IsDefaultCollection = false)] - [ConfigurationCollection(typeof(TypeConfigurationCollection<IRelyingPartyBehavior>))] - public TypeConfigurationCollection<IRelyingPartyBehavior> Behaviors { - get { return (TypeConfigurationCollection<IRelyingPartyBehavior>)this[BehaviorsElementName] ?? new TypeConfigurationCollection<IRelyingPartyBehavior>(); } - set { this[BehaviorsElementName] = value; } - } - - /// <summary> - /// Gets or sets the type to use for storing application state. - /// </summary> - [ConfigurationProperty(StoreConfigName)] - public TypeConfigurationElement<IOpenIdApplicationStore> ApplicationStore { - get { return (TypeConfigurationElement<IOpenIdApplicationStore>)this[StoreConfigName] ?? new TypeConfigurationElement<IOpenIdApplicationStore>(); } - set { this[StoreConfigName] = value; } - } - - /// <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(DiscoveryServicesElementName, IsDefaultCollection = false)] - [ConfigurationCollection(typeof(TypeConfigurationCollection<IIdentifierDiscoveryService>))] - internal TypeConfigurationCollection<IIdentifierDiscoveryService> DiscoveryServices { - get { - var configResult = (TypeConfigurationCollection<IIdentifierDiscoveryService>)this[DiscoveryServicesElementName]; - return configResult != null && configResult.Count > 0 ? configResult : defaultDiscoveryServices; - } - - set { - this[DiscoveryServicesElementName] = value; - } - } - } -} diff --git a/src/DotNetOpenAuth/Configuration/OpenIdRelyingPartySecuritySettingsElement.cs b/src/DotNetOpenAuth/Configuration/OpenIdRelyingPartySecuritySettingsElement.cs deleted file mode 100644 index e116f52..0000000 --- a/src/DotNetOpenAuth/Configuration/OpenIdRelyingPartySecuritySettingsElement.cs +++ /dev/null @@ -1,266 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="OpenIdRelyingPartySecuritySettingsElement.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.Configuration { - using System; - using System.Configuration; - using System.Diagnostics.Contracts; - using DotNetOpenAuth.OpenId; - using DotNetOpenAuth.OpenId.RelyingParty; - - /// <summary> - /// Represents the .config file element that allows for setting the security policies of the Relying Party. - /// </summary> - internal class OpenIdRelyingPartySecuritySettingsElement : ConfigurationElement { - /// <summary> - /// Gets the name of the @minimumRequiredOpenIdVersion attribute. - /// </summary> - private const string MinimumRequiredOpenIdVersionConfigName = "minimumRequiredOpenIdVersion"; - - /// <summary> - /// Gets the name of the @minimumHashBitLength attribute. - /// </summary> - private const string MinimumHashBitLengthConfigName = "minimumHashBitLength"; - - /// <summary> - /// Gets the name of the @maximumHashBitLength attribute. - /// </summary> - private const string MaximumHashBitLengthConfigName = "maximumHashBitLength"; - - /// <summary> - /// Gets the name of the @requireSsl attribute. - /// </summary> - private const string RequireSslConfigName = "requireSsl"; - - /// <summary> - /// Gets the name of the @requireDirectedIdentity attribute. - /// </summary> - private const string RequireDirectedIdentityConfigName = "requireDirectedIdentity"; - - /// <summary> - /// Gets the name of the @requireAssociation attribute. - /// </summary> - private const string RequireAssociationConfigName = "requireAssociation"; - - /// <summary> - /// Gets the name of the @rejectUnsolicitedAssertions attribute. - /// </summary> - private const string RejectUnsolicitedAssertionsConfigName = "rejectUnsolicitedAssertions"; - - /// <summary> - /// Gets the name of the @rejectDelegatedIdentifiers attribute. - /// </summary> - private const string RejectDelegatingIdentifiersConfigName = "rejectDelegatingIdentifiers"; - - /// <summary> - /// Gets the name of the @ignoreUnsignedExtensions attribute. - /// </summary> - private const string IgnoreUnsignedExtensionsConfigName = "ignoreUnsignedExtensions"; - - /// <summary> - /// Gets the name of the @allowDualPurposeIdentifiers attribute. - /// </summary> - private const string AllowDualPurposeIdentifiersConfigName = "allowDualPurposeIdentifiers"; - - /// <summary> - /// Gets the name of the @allowApproximateIdentifierDiscovery attribute. - /// </summary> - private const string AllowApproximateIdentifierDiscoveryConfigName = "allowApproximateIdentifierDiscovery"; - - /// <summary> - /// Gets the name of the @protectDownlevelReplayAttacks attribute. - /// </summary> - private const string ProtectDownlevelReplayAttacksConfigName = "protectDownlevelReplayAttacks"; - - /// <summary> - /// The name of the <trustedProviders> sub-element. - /// </summary> - private const string TrustedProvidersElementName = "trustedProviders"; - - /// <summary> - /// Initializes a new instance of the <see cref="OpenIdRelyingPartySecuritySettingsElement"/> class. - /// </summary> - public OpenIdRelyingPartySecuritySettingsElement() { - } - - /// <summary> - /// Gets or sets a value indicating whether all discovery and authentication should require SSL security. - /// </summary> - [ConfigurationProperty(RequireSslConfigName, DefaultValue = false)] - public bool RequireSsl { - get { return (bool)this[RequireSslConfigName]; } - set { this[RequireSslConfigName] = value; } - } - - /// <summary> - /// Gets or sets a value indicating whether only OP Identifiers will be discoverable - /// when creating authentication requests. - /// </summary> - [ConfigurationProperty(RequireDirectedIdentityConfigName, DefaultValue = false)] - public bool RequireDirectedIdentity { - get { return (bool)this[RequireDirectedIdentityConfigName]; } - set { this[RequireDirectedIdentityConfigName] = value; } - } - - /// <summary> - /// Gets or sets a value indicating whether authentication requests - /// will only be created where an association with the Provider can be established. - /// </summary> - [ConfigurationProperty(RequireAssociationConfigName, DefaultValue = false)] - public bool RequireAssociation { - get { return (bool)this[RequireAssociationConfigName]; } - set { this[RequireAssociationConfigName] = value; } - } - - /// <summary> - /// Gets or sets the minimum OpenID version a Provider is required to support in order for this library to interoperate with it. - /// </summary> - /// <remarks> - /// Although the earliest versions of OpenID are supported, for security reasons it may be desirable to require the - /// remote party to support a later version of OpenID. - /// </remarks> - [ConfigurationProperty(MinimumRequiredOpenIdVersionConfigName, DefaultValue = "V10")] - public ProtocolVersion MinimumRequiredOpenIdVersion { - get { return (ProtocolVersion)this[MinimumRequiredOpenIdVersionConfigName]; } - set { this[MinimumRequiredOpenIdVersionConfigName] = value; } - } - - /// <summary> - /// Gets or sets the minimum length of the hash that protects the protocol from hijackers. - /// </summary> - [ConfigurationProperty(MinimumHashBitLengthConfigName, DefaultValue = SecuritySettings.MinimumHashBitLengthDefault)] - public int MinimumHashBitLength { - get { return (int)this[MinimumHashBitLengthConfigName]; } - set { this[MinimumHashBitLengthConfigName] = value; } - } - - /// <summary> - /// Gets or sets the maximum length of the hash that protects the protocol from hijackers. - /// </summary> - [ConfigurationProperty(MaximumHashBitLengthConfigName, DefaultValue = SecuritySettings.MaximumHashBitLengthRPDefault)] - public int MaximumHashBitLength { - get { return (int)this[MaximumHashBitLengthConfigName]; } - set { this[MaximumHashBitLengthConfigName] = value; } - } - - /// <summary> - /// Gets or sets a value indicating whether all unsolicited assertions should be ignored. - /// </summary> - /// <value>The default value is <c>false</c>.</value> - [ConfigurationProperty(RejectUnsolicitedAssertionsConfigName, DefaultValue = false)] - public bool RejectUnsolicitedAssertions { - get { return (bool)this[RejectUnsolicitedAssertionsConfigName]; } - set { this[RejectUnsolicitedAssertionsConfigName] = value; } - } - - /// <summary> - /// Gets or sets a value indicating whether delegating identifiers are refused for authentication. - /// </summary> - /// <value>The default value is <c>false</c>.</value> - /// <remarks> - /// When set to <c>true</c>, login attempts that start at the RP or arrive via unsolicited - /// assertions will be rejected if discovery on the identifier shows that OpenID delegation - /// is used for the identifier. This is useful for an RP that should only accept identifiers - /// directly issued by the Provider that is sending the assertion. - /// </remarks> - [ConfigurationProperty(RejectDelegatingIdentifiersConfigName, DefaultValue = false)] - public bool RejectDelegatingIdentifiers { - get { return (bool)this[RejectDelegatingIdentifiersConfigName]; } - set { this[RejectDelegatingIdentifiersConfigName] = value; } - } - - /// <summary> - /// Gets or sets a value indicating whether unsigned extensions in authentication responses should be ignored. - /// </summary> - /// <value>The default value is <c>false</c>.</value> - /// <remarks> - /// When set to true, the <see cref="IAuthenticationResponse.GetUntrustedExtension"/> methods - /// will not return any extension that was not signed by the Provider. - /// </remarks> - [ConfigurationProperty(IgnoreUnsignedExtensionsConfigName, DefaultValue = false)] - public bool IgnoreUnsignedExtensions { - get { return (bool)this[IgnoreUnsignedExtensionsConfigName]; } - set { this[IgnoreUnsignedExtensionsConfigName] = value; } - } - - /// <summary> - /// Gets or sets a value indicating whether identifiers that are both OP Identifiers and Claimed Identifiers - /// should ever be recognized as claimed identifiers. - /// </summary> - /// <value> - /// The default value is <c>false</c>, per the OpenID 2.0 spec. - /// </value> - [ConfigurationProperty(AllowDualPurposeIdentifiersConfigName, DefaultValue = false)] - public bool AllowDualPurposeIdentifiers { - get { return (bool)this[AllowDualPurposeIdentifiersConfigName]; } - set { this[AllowDualPurposeIdentifiersConfigName] = value; } - } - - /// <summary> - /// Gets or sets a value indicating whether certain Claimed Identifiers that exploit - /// features that .NET does not have the ability to send exact HTTP requests for will - /// still be allowed by using an approximate HTTP request. - /// </summary> - /// <value> - /// The default value is <c>true</c>. - /// </value> - [ConfigurationProperty(AllowApproximateIdentifierDiscoveryConfigName, DefaultValue = true)] - public bool AllowApproximateIdentifierDiscovery { - get { return (bool)this[AllowApproximateIdentifierDiscoveryConfigName]; } - set { this[AllowApproximateIdentifierDiscoveryConfigName] = value; } - } - - /// <summary> - /// Gets or sets a value indicating whether the Relying Party should take special care - /// to protect users against replay attacks when interoperating with OpenID 1.1 Providers. - /// </summary> - [ConfigurationProperty(ProtectDownlevelReplayAttacksConfigName, DefaultValue = RelyingPartySecuritySettings.ProtectDownlevelReplayAttacksDefault)] - public bool ProtectDownlevelReplayAttacks { - get { return (bool)this[ProtectDownlevelReplayAttacksConfigName]; } - set { this[ProtectDownlevelReplayAttacksConfigName] = value; } - } - - /// <summary> - /// Gets or sets the set of trusted OpenID Provider Endpoints. - /// </summary> - [ConfigurationProperty(TrustedProvidersElementName, IsDefaultCollection = false)] - [ConfigurationCollection(typeof(TrustedProviderConfigurationCollection))] - public TrustedProviderConfigurationCollection TrustedProviders { - get { return (TrustedProviderConfigurationCollection)this[TrustedProvidersElementName] ?? new TrustedProviderConfigurationCollection(); } - set { this[TrustedProvidersElementName] = value; } - } - - /// <summary> - /// Initializes a programmatically manipulatable bag of these security settings with the settings from the config file. - /// </summary> - /// <returns>The newly created security settings object.</returns> - public RelyingPartySecuritySettings CreateSecuritySettings() { - Contract.Ensures(Contract.Result<RelyingPartySecuritySettings>() != null); - - RelyingPartySecuritySettings settings = new RelyingPartySecuritySettings(); - settings.RequireSsl = this.RequireSsl; - settings.RequireDirectedIdentity = this.RequireDirectedIdentity; - settings.RequireAssociation = this.RequireAssociation; - settings.MinimumRequiredOpenIdVersion = this.MinimumRequiredOpenIdVersion; - settings.MinimumHashBitLength = this.MinimumHashBitLength; - settings.MaximumHashBitLength = this.MaximumHashBitLength; - settings.PrivateSecretMaximumAge = DotNetOpenAuthSection.Configuration.Messaging.PrivateSecretMaximumAge; - settings.RejectUnsolicitedAssertions = this.RejectUnsolicitedAssertions; - settings.RejectDelegatingIdentifiers = this.RejectDelegatingIdentifiers; - settings.IgnoreUnsignedExtensions = this.IgnoreUnsignedExtensions; - settings.AllowDualPurposeIdentifiers = this.AllowDualPurposeIdentifiers; - settings.AllowApproximateIdentifierDiscovery = this.AllowApproximateIdentifierDiscovery; - settings.ProtectDownlevelReplayAttacks = this.ProtectDownlevelReplayAttacks; - - settings.RejectAssertionsFromUntrustedProviders = this.TrustedProviders.RejectAssertionsFromUntrustedProviders; - foreach (TrustedProviderEndpointConfigurationElement opEndpoint in this.TrustedProviders) { - settings.TrustedProviderEndpoints.Add(opEndpoint.ProviderEndpoint); - } - - return settings; - } - } -} diff --git a/src/DotNetOpenAuth/Configuration/ReportingElement.cs b/src/DotNetOpenAuth/Configuration/ReportingElement.cs deleted file mode 100644 index 2374448..0000000 --- a/src/DotNetOpenAuth/Configuration/ReportingElement.cs +++ /dev/null @@ -1,139 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="ReportingElement.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.Configuration { - using System; - using System.Collections.Generic; - using System.Configuration; - using System.Linq; - using System.Text; - - /// <summary> - /// Represents the <reporting> element in the host's .config file. - /// </summary> - internal class ReportingElement : ConfigurationElement { - /// <summary> - /// The name of the @enabled attribute. - /// </summary> - private const string EnabledAttributeName = "enabled"; - - /// <summary> - /// The name of the @minimumReportingInterval attribute. - /// </summary> - private const string MinimumReportingIntervalAttributeName = "minimumReportingInterval"; - - /// <summary> - /// The name of the @minimumFlushInterval attribute. - /// </summary> - private const string MinimumFlushIntervalAttributeName = "minimumFlushInterval"; - - /// <summary> - /// The name of the @includeFeatureUsage attribute. - /// </summary> - private const string IncludeFeatureUsageAttributeName = "includeFeatureUsage"; - - /// <summary> - /// The name of the @includeEventStatistics attribute. - /// </summary> - private const string IncludeEventStatisticsAttributeName = "includeEventStatistics"; - - /// <summary> - /// The name of the @includeLocalRequestUris attribute. - /// </summary> - private const string IncludeLocalRequestUrisAttributeName = "includeLocalRequestUris"; - - /// <summary> - /// The name of the @includeCultures attribute. - /// </summary> - private const string IncludeCulturesAttributeName = "includeCultures"; - - /// <summary> - /// The default value for the @minimumFlushInterval attribute. - /// </summary> -#if DEBUG - private const string MinimumFlushIntervalDefault = "0"; -#else - private const string MinimumFlushIntervalDefault = "0:15"; -#endif - - /// <summary> - /// Initializes a new instance of the <see cref="ReportingElement"/> class. - /// </summary> - internal ReportingElement() { - } - - /// <summary> - /// Gets or sets a value indicating whether this reporting is enabled. - /// </summary> - /// <value><c>true</c> if enabled; otherwise, <c>false</c>.</value> - [ConfigurationProperty(EnabledAttributeName, DefaultValue = true)] - internal bool Enabled { - get { return (bool)this[EnabledAttributeName]; } - set { this[EnabledAttributeName] = value; } - } - - /// <summary> - /// Gets or sets the maximum frequency that reports will be published. - /// </summary> - [ConfigurationProperty(MinimumReportingIntervalAttributeName, DefaultValue = "1")] // 1 day default - internal TimeSpan MinimumReportingInterval { - get { return (TimeSpan)this[MinimumReportingIntervalAttributeName]; } - set { this[MinimumReportingIntervalAttributeName] = value; } - } - - /// <summary> - /// Gets or sets the maximum frequency the set can be flushed to disk. - /// </summary> - [ConfigurationProperty(MinimumFlushIntervalAttributeName, DefaultValue = MinimumFlushIntervalDefault)] - internal TimeSpan MinimumFlushInterval { - get { return (TimeSpan)this[MinimumFlushIntervalAttributeName]; } - set { this[MinimumFlushIntervalAttributeName] = value; } - } - - /// <summary> - /// Gets or sets a value indicating whether to include a list of library features used in the report. - /// </summary> - /// <value><c>true</c> to include a report of features used; otherwise, <c>false</c>.</value> - [ConfigurationProperty(IncludeFeatureUsageAttributeName, DefaultValue = true)] - internal bool IncludeFeatureUsage { - get { return (bool)this[IncludeFeatureUsageAttributeName]; } - set { this[IncludeFeatureUsageAttributeName] = value; } - } - - /// <summary> - /// Gets or sets a value indicating whether to include statistics of certain events such as - /// authentication success and failure counting, and can include remote endpoint URIs. - /// </summary> - /// <value> - /// <c>true</c> to include event counters in the report; otherwise, <c>false</c>. - /// </value> - [ConfigurationProperty(IncludeEventStatisticsAttributeName, DefaultValue = true)] - internal bool IncludeEventStatistics { - get { return (bool)this[IncludeEventStatisticsAttributeName]; } - set { this[IncludeEventStatisticsAttributeName] = value; } - } - - /// <summary> - /// Gets or sets a value indicating whether to include a few URLs to pages on the hosting - /// web site that host DotNetOpenAuth components. - /// </summary> - [ConfigurationProperty(IncludeLocalRequestUrisAttributeName, DefaultValue = true)] - internal bool IncludeLocalRequestUris { - get { return (bool)this[IncludeLocalRequestUrisAttributeName]; } - set { this[IncludeLocalRequestUrisAttributeName] = value; } - } - - /// <summary> - /// Gets or sets a value indicating whether to include the cultures requested by the user agent - /// on pages that host DotNetOpenAuth components. - /// </summary> - [ConfigurationProperty(IncludeCulturesAttributeName, DefaultValue = true)] - internal bool IncludeCultures { - get { return (bool)this[IncludeCulturesAttributeName]; } - set { this[IncludeCulturesAttributeName] = value; } - } - } -} diff --git a/src/DotNetOpenAuth/Configuration/TrustedProviderConfigurationCollection.cs b/src/DotNetOpenAuth/Configuration/TrustedProviderConfigurationCollection.cs deleted file mode 100644 index f5e62f4..0000000 --- a/src/DotNetOpenAuth/Configuration/TrustedProviderConfigurationCollection.cs +++ /dev/null @@ -1,74 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="TrustedProviderConfigurationCollection.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.Configuration { - using System; - using System.Collections.Generic; - using System.Configuration; - using System.Diagnostics.CodeAnalysis; - using System.Diagnostics.Contracts; - - /// <summary> - /// A configuration collection of trusted OP Endpoints. - /// </summary> - internal class TrustedProviderConfigurationCollection : ConfigurationElementCollection { - /// <summary> - /// The name of the "rejectAssertionsFromUntrustedProviders" element. - /// </summary> - private const string RejectAssertionsFromUntrustedProvidersConfigName = "rejectAssertionsFromUntrustedProviders"; - - /// <summary> - /// Initializes a new instance of the <see cref="TrustedProviderConfigurationCollection"/> class. - /// </summary> - internal TrustedProviderConfigurationCollection() { - } - - /// <summary> - /// Initializes a new instance of the <see cref="TrustedProviderConfigurationCollection"/> class. - /// </summary> - /// <param name="elements">The elements to initialize the collection with.</param> - [SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors", Justification = "Seems unavoidable")] - internal TrustedProviderConfigurationCollection(IEnumerable<TrustedProviderEndpointConfigurationElement> elements) { - Contract.Requires<ArgumentNullException>(elements != null); - - foreach (TrustedProviderEndpointConfigurationElement element in elements) { - this.BaseAdd(element); - } - } - - /// <summary> - /// Gets or sets a value indicating whether any login attempt coming from an OpenID Provider Endpoint that is not on this - /// whitelist of trusted OP Endpoints will be rejected. If the trusted providers list is empty and this value - /// is true, all assertions are rejected. - /// </summary> - [ConfigurationProperty(RejectAssertionsFromUntrustedProvidersConfigName, DefaultValue = false)] - internal bool RejectAssertionsFromUntrustedProviders { - get { return (bool)this[RejectAssertionsFromUntrustedProvidersConfigName]; } - set { this[RejectAssertionsFromUntrustedProvidersConfigName] = value; } - } - - /// <summary> - /// When overridden in a derived class, creates a new <see cref="T:System.Configuration.ConfigurationElement"/>. - /// </summary> - /// <returns> - /// A new <see cref="T:System.Configuration.ConfigurationElement"/>. - /// </returns> - protected override ConfigurationElement CreateNewElement() { - return new TrustedProviderEndpointConfigurationElement(); - } - - /// <summary> - /// Gets the element key for a specified configuration element when overridden in a derived class. - /// </summary> - /// <param name="element">The <see cref="T:System.Configuration.ConfigurationElement"/> to return the key for.</param> - /// <returns> - /// An <see cref="T:System.Object"/> that acts as the key for the specified <see cref="T:System.Configuration.ConfigurationElement"/>. - /// </returns> - protected override object GetElementKey(ConfigurationElement element) { - return ((TrustedProviderEndpointConfigurationElement)element).ProviderEndpoint; - } - } -} diff --git a/src/DotNetOpenAuth/Configuration/TypeConfigurationCollection.cs b/src/DotNetOpenAuth/Configuration/TypeConfigurationCollection.cs deleted file mode 100644 index 000cb6a..0000000 --- a/src/DotNetOpenAuth/Configuration/TypeConfigurationCollection.cs +++ /dev/null @@ -1,76 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="TypeConfigurationCollection.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.Configuration { - using System; - using System.Collections.Generic; - using System.Configuration; - using System.Diagnostics.Contracts; - using System.Linq; - using System.Text; - using DotNetOpenAuth.Messaging; - - /// <summary> - /// A collection of <see cref="TypeConfigurationElement<T>"/>. - /// </summary> - /// <typeparam name="T">The type that all types specified in the elements must derive from.</typeparam> - [ContractVerification(true)] - internal class TypeConfigurationCollection<T> : ConfigurationElementCollection - where T : class { - /// <summary> - /// Initializes a new instance of the TypeConfigurationCollection class. - /// </summary> - internal TypeConfigurationCollection() { - } - - /// <summary> - /// Initializes a new instance of the TypeConfigurationCollection class. - /// </summary> - /// <param name="elements">The elements that should be added to the collection initially.</param> - internal TypeConfigurationCollection(IEnumerable<Type> elements) { - Contract.Requires<ArgumentNullException>(elements != null); - - foreach (Type element in elements) { - this.BaseAdd(new TypeConfigurationElement<T> { TypeName = element.FullName }); - } - } - - /// <summary> - /// Creates instances of all the types listed in the collection. - /// </summary> - /// <param name="allowInternals">if set to <c>true</c> then internal types may be instantiated.</param> - /// <returns>A sequence of instances generated from types in this collection. May be empty, but never null.</returns> - internal IEnumerable<T> CreateInstances(bool allowInternals) { - Contract.Ensures(Contract.Result<IEnumerable<T>>() != null); - return from element in this.Cast<TypeConfigurationElement<T>>() - where !element.IsEmpty - select element.CreateInstance(default(T), allowInternals); - } - - /// <summary> - /// When overridden in a derived class, creates a new <see cref="T:System.Configuration.ConfigurationElement"/>. - /// </summary> - /// <returns> - /// A new <see cref="T:System.Configuration.ConfigurationElement"/>. - /// </returns> - protected override ConfigurationElement CreateNewElement() { - return new TypeConfigurationElement<T>(); - } - - /// <summary> - /// Gets the element key for a specified configuration element when overridden in a derived class. - /// </summary> - /// <param name="element">The <see cref="T:System.Configuration.ConfigurationElement"/> to return the key for.</param> - /// <returns> - /// An <see cref="T:System.Object"/> that acts as the key for the specified <see cref="T:System.Configuration.ConfigurationElement"/>. - /// </returns> - protected override object GetElementKey(ConfigurationElement element) { - Contract.Assume(element != null); // this should be Contract.Requires in base class. - TypeConfigurationElement<T> typedElement = (TypeConfigurationElement<T>)element; - return (!string.IsNullOrEmpty(typedElement.TypeName) ? typedElement.TypeName : typedElement.XamlSource) ?? string.Empty; - } - } -} diff --git a/src/DotNetOpenAuth/Configuration/TypeConfigurationElement.cs b/src/DotNetOpenAuth/Configuration/TypeConfigurationElement.cs deleted file mode 100644 index 0335af5..0000000 --- a/src/DotNetOpenAuth/Configuration/TypeConfigurationElement.cs +++ /dev/null @@ -1,133 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="TypeConfigurationElement.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.Configuration { - using System; - using System.Configuration; - using System.Diagnostics.Contracts; - using System.IO; - using System.Reflection; - using System.Web; - using System.Windows.Markup; - using DotNetOpenAuth.Messaging; - - /// <summary> - /// Represents an element in a .config file that allows the user to provide a @type attribute specifying - /// the full type that provides some service used by this library. - /// </summary> - /// <typeparam name="T">A constraint on the type the user may provide.</typeparam> - internal class TypeConfigurationElement<T> : ConfigurationElement - where T : class { - /// <summary> - /// The name of the attribute whose value is the full name of the type the user is specifying. - /// </summary> - private const string CustomTypeConfigName = "type"; - - /// <summary> - /// The name of the attribute whose value is the path to the XAML file to deserialize to obtain the type. - /// </summary> - private const string XamlReaderSourceConfigName = "xaml"; - - /// <summary> - /// Initializes a new instance of the TypeConfigurationElement class. - /// </summary> - public TypeConfigurationElement() { - } - - /// <summary> - /// Gets or sets the full name of the type. - /// </summary> - /// <value>The full name of the type, such as: "ConsumerPortal.Code.CustomStore, ConsumerPortal".</value> - [ConfigurationProperty(CustomTypeConfigName)] - ////[SubclassTypeValidator(typeof(T))] // this attribute is broken in .NET, I think. - public string TypeName { - get { return (string)this[CustomTypeConfigName]; } - set { this[CustomTypeConfigName] = value; } - } - - /// <summary> - /// Gets or sets the path to the XAML file to deserialize to obtain the instance. - /// </summary> - [ConfigurationProperty(XamlReaderSourceConfigName)] - public string XamlSource { - get { return (string)this[XamlReaderSourceConfigName]; } - set { this[XamlReaderSourceConfigName] = value; } - } - - /// <summary> - /// Gets the type described in the .config file. - /// </summary> - public Type CustomType { - get { return string.IsNullOrEmpty(this.TypeName) ? null : Type.GetType(this.TypeName); } - } - - /// <summary> - /// Gets a value indicating whether this type has no meaningful type to instantiate. - /// </summary> - public bool IsEmpty { - get { return this.CustomType == null && string.IsNullOrEmpty(this.XamlSource); } - } - - /// <summary> - /// Creates an instance of the type described in the .config file. - /// </summary> - /// <param name="defaultValue">The value to return if no type is given in the .config file.</param> - /// <returns>The newly instantiated type.</returns> - public T CreateInstance(T defaultValue) { - Contract.Ensures(Contract.Result<T>() != null || Contract.Result<T>() == defaultValue); - - return this.CreateInstance(defaultValue, false); - } - - /// <summary> - /// Creates an instance of the type described in the .config file. - /// </summary> - /// <param name="defaultValue">The value to return if no type is given in the .config file.</param> - /// <param name="allowInternals">if set to <c>true</c> then internal types may be instantiated.</param> - /// <returns>The newly instantiated type.</returns> - public T CreateInstance(T defaultValue, bool allowInternals) { - Contract.Ensures(Contract.Result<T>() != null || Contract.Result<T>() == defaultValue); - - if (this.CustomType != null) { - if (!allowInternals) { - // Although .NET will usually prevent our instantiating non-public types, - // it will allow our instantiation of internal types within this same assembly. - // But we don't want the host site to be able to do this, so we check ourselves. - ErrorUtilities.VerifyArgument((this.CustomType.Attributes & TypeAttributes.Public) != 0, Strings.ConfigurationTypeMustBePublic, this.CustomType.FullName); - } - return (T)Activator.CreateInstance(this.CustomType); - } else if (!string.IsNullOrEmpty(this.XamlSource)) { - string source = this.XamlSource; - if (source.StartsWith("~/", StringComparison.Ordinal)) { - ErrorUtilities.VerifyHost(HttpContext.Current != null, Strings.ConfigurationXamlReferenceRequiresHttpContext, this.XamlSource); - source = HttpContext.Current.Server.MapPath(source); - } - using (Stream xamlFile = File.OpenRead(source)) { - return CreateInstanceFromXaml(xamlFile); - } - } else { - return defaultValue; - } - } - - /// <summary> - /// Creates the instance from xaml. - /// </summary> - /// <param name="xaml">The stream of xaml to deserialize.</param> - /// <returns>The deserialized object.</returns> - /// <remarks> - /// This exists as its own method to prevent the CLR's JIT compiler from failing - /// to compile the CreateInstance method just because the PresentationFramework.dll - /// may be missing (which it is on some shared web hosts). This way, if the - /// XamlSource attribute is never used, the PresentationFramework.dll never need - /// be present. - /// </remarks> - private static T CreateInstanceFromXaml(Stream xaml) { - Contract.Ensures(Contract.Result<T>() != null); - return (T)XamlReader.Load(xaml); - } - } -} diff --git a/src/DotNetOpenAuth/Configuration/UntrustedWebRequestElement.cs b/src/DotNetOpenAuth/Configuration/UntrustedWebRequestElement.cs deleted file mode 100644 index 89cd435..0000000 --- a/src/DotNetOpenAuth/Configuration/UntrustedWebRequestElement.cs +++ /dev/null @@ -1,140 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="UntrustedWebRequestElement.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.Configuration { - using System; - using System.Configuration; - - /// <summary> - /// Represents the section of a .config file where security policies regarding web requests - /// to user-provided, untrusted servers is controlled. - /// </summary> - internal class UntrustedWebRequestElement : ConfigurationElement { - #region Attribute names - - /// <summary> - /// Gets the name of the @timeout attribute. - /// </summary> - private const string TimeoutConfigName = "timeout"; - - /// <summary> - /// Gets the name of the @readWriteTimeout attribute. - /// </summary> - private const string ReadWriteTimeoutConfigName = "readWriteTimeout"; - - /// <summary> - /// Gets the name of the @maximumBytesToRead attribute. - /// </summary> - private const string MaximumBytesToReadConfigName = "maximumBytesToRead"; - - /// <summary> - /// Gets the name of the @maximumRedirections attribute. - /// </summary> - private const string MaximumRedirectionsConfigName = "maximumRedirections"; - - /// <summary> - /// Gets the name of the @whitelistHosts attribute. - /// </summary> - private const string WhitelistHostsConfigName = "whitelistHosts"; - - /// <summary> - /// Gets the name of the @whitelistHostsRegex attribute. - /// </summary> - private const string WhitelistHostsRegexConfigName = "whitelistHostsRegex"; - - /// <summary> - /// Gets the name of the @blacklistHosts attribute. - /// </summary> - private const string BlacklistHostsConfigName = "blacklistHosts"; - - /// <summary> - /// Gets the name of the @blacklistHostsRegex attribute. - /// </summary> - private const string BlacklistHostsRegexConfigName = "blacklistHostsRegex"; - - #endregion - - /// <summary> - /// Gets or sets the read/write timeout after which an HTTP request will fail. - /// </summary> - [ConfigurationProperty(ReadWriteTimeoutConfigName, DefaultValue = "00:00:01.500")] - [PositiveTimeSpanValidator] - public TimeSpan ReadWriteTimeout { - get { return (TimeSpan)this[ReadWriteTimeoutConfigName]; } - set { this[ReadWriteTimeoutConfigName] = value; } - } - - /// <summary> - /// Gets or sets the timeout after which an HTTP request will fail. - /// </summary> - [ConfigurationProperty(TimeoutConfigName, DefaultValue = "00:00:10")] - [PositiveTimeSpanValidator] - public TimeSpan Timeout { - get { return (TimeSpan)this[TimeoutConfigName]; } - set { this[TimeoutConfigName] = value; } - } - - /// <summary> - /// Gets or sets the maximum bytes to read from an untrusted web server. - /// </summary> - [ConfigurationProperty(MaximumBytesToReadConfigName, DefaultValue = 1024 * 1024)] - [IntegerValidator(MinValue = 2048)] - public int MaximumBytesToRead { - get { return (int)this[MaximumBytesToReadConfigName]; } - set { this[MaximumBytesToReadConfigName] = value; } - } - - /// <summary> - /// Gets or sets the maximum redirections that will be followed before an HTTP request fails. - /// </summary> - [ConfigurationProperty(MaximumRedirectionsConfigName, DefaultValue = 10)] - [IntegerValidator(MinValue = 0)] - public int MaximumRedirections { - get { return (int)this[MaximumRedirectionsConfigName]; } - set { this[MaximumRedirectionsConfigName] = value; } - } - - /// <summary> - /// Gets or sets the collection of hosts on the whitelist. - /// </summary> - [ConfigurationProperty(WhitelistHostsConfigName, IsDefaultCollection = false)] - [ConfigurationCollection(typeof(HostNameOrRegexCollection))] - public HostNameOrRegexCollection WhitelistHosts { - get { return (HostNameOrRegexCollection)this[WhitelistHostsConfigName] ?? new HostNameOrRegexCollection(); } - set { this[WhitelistHostsConfigName] = value; } - } - - /// <summary> - /// Gets or sets the collection of hosts on the blacklist. - /// </summary> - [ConfigurationProperty(BlacklistHostsConfigName, IsDefaultCollection = false)] - [ConfigurationCollection(typeof(HostNameOrRegexCollection))] - public HostNameOrRegexCollection BlacklistHosts { - get { return (HostNameOrRegexCollection)this[BlacklistHostsConfigName] ?? new HostNameOrRegexCollection(); } - set { this[BlacklistHostsConfigName] = value; } - } - - /// <summary> - /// Gets or sets the collection of regular expressions that describe hosts on the whitelist. - /// </summary> - [ConfigurationProperty(WhitelistHostsRegexConfigName, IsDefaultCollection = false)] - [ConfigurationCollection(typeof(HostNameOrRegexCollection))] - public HostNameOrRegexCollection WhitelistHostsRegex { - get { return (HostNameOrRegexCollection)this[WhitelistHostsRegexConfigName] ?? new HostNameOrRegexCollection(); } - set { this[WhitelistHostsRegexConfigName] = value; } - } - - /// <summary> - /// Gets or sets the collection of regular expressions that describe hosts on the blacklist. - /// </summary> - [ConfigurationProperty(BlacklistHostsRegexConfigName, IsDefaultCollection = false)] - [ConfigurationCollection(typeof(HostNameOrRegexCollection))] - public HostNameOrRegexCollection BlacklistHostsRegex { - get { return (HostNameOrRegexCollection)this[BlacklistHostsRegexConfigName] ?? new HostNameOrRegexCollection(); } - set { this[BlacklistHostsRegexConfigName] = value; } - } - } -} diff --git a/src/DotNetOpenAuth/DotNetOpenAuth.csproj b/src/DotNetOpenAuth/DotNetOpenAuth.csproj deleted file mode 100644 index fafd97e..0000000 --- a/src/DotNetOpenAuth/DotNetOpenAuth.csproj +++ /dev/null @@ -1,899 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> - <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), EnlistmentInfo.props))\EnlistmentInfo.props" Condition=" '$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), EnlistmentInfo.props))' != '' " /> - <PropertyGroup> - <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> - <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> - <CodeContractsAssemblyMode>1</CodeContractsAssemblyMode> - </PropertyGroup> - <Import Project="$(ProjectRoot)tools\DotNetOpenAuth.props" /> - <PropertyGroup> - <ProductVersion>9.0.30729</ProductVersion> - <SchemaVersion>2.0</SchemaVersion> - <ProjectGuid>{3191B653-F76D-4C1A-9A5A-347BC3AAAAB7}</ProjectGuid> - <OutputType>Library</OutputType> - <AppDesignerFolder>Properties</AppDesignerFolder> - <RootNamespace>DotNetOpenAuth</RootNamespace> - <AssemblyName>DotNetOpenAuth</AssemblyName> - <AssemblyName Condition=" '$(NoUIControls)' == 'true' ">DotNetOpenAuth.NoUI</AssemblyName> - <FileAlignment>512</FileAlignment> - <StandardCopyright> -Copyright (c) 2009, Andrew Arnott. All rights reserved. -Code licensed under the Ms-PL License: -http://opensource.org/licenses/ms-pl.html -</StandardCopyright> - <FileUpgradeFlags> - </FileUpgradeFlags> - <OldToolsVersion>3.5</OldToolsVersion> - <UpgradeBackupLocation /> - <IsWebBootstrapper>false</IsWebBootstrapper> - <TargetFrameworkProfile /> - <PublishUrl>publish\</PublishUrl> - <Install>true</Install> - <InstallFrom>Disk</InstallFrom> - <UpdateEnabled>false</UpdateEnabled> - <UpdateMode>Foreground</UpdateMode> - <UpdateInterval>7</UpdateInterval> - <UpdateIntervalUnits>Days</UpdateIntervalUnits> - <UpdatePeriodically>false</UpdatePeriodically> - <UpdateRequired>false</UpdateRequired> - <MapFileExtensions>true</MapFileExtensions> - <ApplicationRevision>0</ApplicationRevision> - <ApplicationVersion>1.0.0.%2a</ApplicationVersion> - <UseApplicationTrust>false</UseApplicationTrust> - <BootstrapperEnabled>true</BootstrapperEnabled> - <ApplicationIcon>DotNetOpenAuth.ico</ApplicationIcon> - <DocumentationFile>$(OutputPath)$(AssemblyName).xml</DocumentationFile> - </PropertyGroup> - <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> - <DebugSymbols>true</DebugSymbols> - <DebugType>full</DebugType> - <Optimize>false</Optimize> - <DefineConstants>DEBUG;TRACE</DefineConstants> - <ErrorReport>prompt</ErrorReport> - <WarningLevel>4</WarningLevel> - <AllowUnsafeBlocks>false</AllowUnsafeBlocks> - <RunCodeAnalysis>false</RunCodeAnalysis> - <CodeAnalysisRules> - </CodeAnalysisRules> - <CodeContractsEnableRuntimeChecking>True</CodeContractsEnableRuntimeChecking> - <CodeContractsCustomRewriterAssembly> - </CodeContractsCustomRewriterAssembly> - <CodeContractsCustomRewriterClass> - </CodeContractsCustomRewriterClass> - <CodeContractsRuntimeCheckingLevel>Full</CodeContractsRuntimeCheckingLevel> - <CodeContractsRunCodeAnalysis>False</CodeContractsRunCodeAnalysis> - <CodeContractsBuildReferenceAssembly>True</CodeContractsBuildReferenceAssembly> - <CodeContractsNonNullObligations>True</CodeContractsNonNullObligations> - <CodeContractsBoundsObligations>True</CodeContractsBoundsObligations> - <CodeContractsLibPaths> - </CodeContractsLibPaths> - <CodeContractsPlatformPath> - </CodeContractsPlatformPath> - <CodeContractsExtraAnalysisOptions> - </CodeContractsExtraAnalysisOptions> - <CodeContractsBaseLineFile> - </CodeContractsBaseLineFile> - <CodeContractsUseBaseLine>False</CodeContractsUseBaseLine> - <CodeContractsRunInBackground>True</CodeContractsRunInBackground> - <CodeContractsShowSquigglies>True</CodeContractsShowSquigglies> - <CodeContractsArithmeticObligations>False</CodeContractsArithmeticObligations> - <CodeContractsRuntimeOnlyPublicSurface>False</CodeContractsRuntimeOnlyPublicSurface> - <CodeContractsRuntimeThrowOnFailure>True</CodeContractsRuntimeThrowOnFailure> - <CodeContractsRuntimeCallSiteRequires>False</CodeContractsRuntimeCallSiteRequires> - <CodeContractsEmitXMLDocs>True</CodeContractsEmitXMLDocs> - <CodeContractsRedundantAssumptions>False</CodeContractsRedundantAssumptions> - <CodeContractsReferenceAssembly>Build</CodeContractsReferenceAssembly> - <CodeAnalysisRuleSet>Migrated rules for DotNetOpenAuth.ruleset</CodeAnalysisRuleSet> - <CodeContractsExtraRewriteOptions /> - </PropertyGroup> - <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> - <DebugType>pdbonly</DebugType> - <Optimize>true</Optimize> - <DefineConstants>TRACE</DefineConstants> - <ErrorReport>prompt</ErrorReport> - <WarningLevel>4</WarningLevel> - <AllowUnsafeBlocks>false</AllowUnsafeBlocks> - <RunCodeAnalysis>true</RunCodeAnalysis> - <CodeAnalysisRules> - </CodeAnalysisRules> - <CodeContractsEnableRuntimeChecking>True</CodeContractsEnableRuntimeChecking> - <CodeContractsCustomRewriterAssembly> - </CodeContractsCustomRewriterAssembly> - <CodeContractsCustomRewriterClass> - </CodeContractsCustomRewriterClass> - <CodeContractsRuntimeCheckingLevel>ReleaseRequires</CodeContractsRuntimeCheckingLevel> - <CodeContractsRunCodeAnalysis>False</CodeContractsRunCodeAnalysis> - <CodeContractsBuildReferenceAssembly>True</CodeContractsBuildReferenceAssembly> - <CodeContractsNonNullObligations>False</CodeContractsNonNullObligations> - <CodeContractsBoundsObligations>False</CodeContractsBoundsObligations> - <CodeContractsLibPaths> - </CodeContractsLibPaths> - <CodeContractsPlatformPath> - </CodeContractsPlatformPath> - <CodeContractsExtraAnalysisOptions> - </CodeContractsExtraAnalysisOptions> - <CodeContractsBaseLineFile> - </CodeContractsBaseLineFile> - <CodeContractsUseBaseLine>False</CodeContractsUseBaseLine> - <CodeContractsRunInBackground>True</CodeContractsRunInBackground> - <CodeContractsShowSquigglies>False</CodeContractsShowSquigglies> - <CodeContractsArithmeticObligations>False</CodeContractsArithmeticObligations> - <CodeContractsRuntimeOnlyPublicSurface>False</CodeContractsRuntimeOnlyPublicSurface> - <CodeContractsRuntimeThrowOnFailure>True</CodeContractsRuntimeThrowOnFailure> - <CodeContractsRuntimeCallSiteRequires>False</CodeContractsRuntimeCallSiteRequires> - <CodeContractsEmitXMLDocs>True</CodeContractsEmitXMLDocs> - <CodeContractsRedundantAssumptions>False</CodeContractsRedundantAssumptions> - <CodeContractsReferenceAssembly>Build</CodeContractsReferenceAssembly> - <CodeAnalysisRuleSet>Migrated rules for DotNetOpenAuth.ruleset</CodeAnalysisRuleSet> - <CodeContractsExtraRewriteOptions /> - </PropertyGroup> - <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'ReleaseNoUI|AnyCPU'"> - <DefineConstants>TRACE;NoUIControls</DefineConstants> - <NoUIControls>true</NoUIControls> - <Optimize>true</Optimize> - <NoWarn>;1607</NoWarn> - <DebugType>pdbonly</DebugType> - <PlatformTarget>AnyCPU</PlatformTarget> - <ErrorReport>prompt</ErrorReport> - <CodeContractsEnableRuntimeChecking>True</CodeContractsEnableRuntimeChecking> - <CodeContractsCustomRewriterAssembly> - </CodeContractsCustomRewriterAssembly> - <CodeContractsCustomRewriterClass> - </CodeContractsCustomRewriterClass> - <CodeContractsRuntimeCheckingLevel>ReleaseRequires</CodeContractsRuntimeCheckingLevel> - <CodeContractsRunCodeAnalysis>False</CodeContractsRunCodeAnalysis> - <CodeContractsBuildReferenceAssembly>True</CodeContractsBuildReferenceAssembly> - <CodeContractsNonNullObligations>False</CodeContractsNonNullObligations> - <CodeContractsBoundsObligations>False</CodeContractsBoundsObligations> - <CodeContractsLibPaths> - </CodeContractsLibPaths> - <CodeContractsPlatformPath> - </CodeContractsPlatformPath> - <CodeContractsExtraAnalysisOptions> - </CodeContractsExtraAnalysisOptions> - <CodeContractsBaseLineFile> - </CodeContractsBaseLineFile> - <CodeContractsUseBaseLine>False</CodeContractsUseBaseLine> - <CodeContractsRunInBackground>True</CodeContractsRunInBackground> - <CodeContractsShowSquigglies>False</CodeContractsShowSquigglies> - <CodeContractsArithmeticObligations>False</CodeContractsArithmeticObligations> - <CodeContractsRuntimeOnlyPublicSurface>False</CodeContractsRuntimeOnlyPublicSurface> - <CodeContractsRuntimeThrowOnFailure>True</CodeContractsRuntimeThrowOnFailure> - <CodeContractsRuntimeCallSiteRequires>False</CodeContractsRuntimeCallSiteRequires> - <CodeContractsEmitXMLDocs>True</CodeContractsEmitXMLDocs> - <CodeContractsRedundantAssumptions>False</CodeContractsRedundantAssumptions> - <CodeContractsReferenceAssembly>Build</CodeContractsReferenceAssembly> - <CodeContractsExtraRewriteOptions /> - <CodeAnalysisRuleSet>Migrated rules for DotNetOpenAuth.ruleset</CodeAnalysisRuleSet> - </PropertyGroup> - <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'CodeAnalysis|AnyCPU' "> - <DebugSymbols>true</DebugSymbols> - <DefineConstants>$(DefineConstants);CONTRACTS_FULL;DEBUG;TRACE</DefineConstants> - <DebugType>full</DebugType> - <PlatformTarget>AnyCPU</PlatformTarget> - <CodeAnalysisRules> - </CodeAnalysisRules> - <CodeAnalysisUseTypeNameInSuppression>true</CodeAnalysisUseTypeNameInSuppression> - <CodeAnalysisModuleSuppressionsFile>GlobalSuppressions.cs</CodeAnalysisModuleSuppressionsFile> - <ErrorReport>prompt</ErrorReport> - <CodeContractsEnableRuntimeChecking>True</CodeContractsEnableRuntimeChecking> - <CodeContractsCustomRewriterAssembly> - </CodeContractsCustomRewriterAssembly> - <CodeContractsCustomRewriterClass> - </CodeContractsCustomRewriterClass> - <CodeContractsRuntimeCheckingLevel>Preconditions</CodeContractsRuntimeCheckingLevel> - <CodeContractsRunCodeAnalysis>True</CodeContractsRunCodeAnalysis> - <CodeContractsBuildReferenceAssembly>True</CodeContractsBuildReferenceAssembly> - <CodeContractsNonNullObligations>False</CodeContractsNonNullObligations> - <CodeContractsBoundsObligations>False</CodeContractsBoundsObligations> - <CodeContractsLibPaths> - </CodeContractsLibPaths> - <CodeContractsPlatformPath> - </CodeContractsPlatformPath> - <CodeContractsExtraAnalysisOptions> - </CodeContractsExtraAnalysisOptions> - <CodeContractsBaseLineFile> - </CodeContractsBaseLineFile> - <CodeContractsUseBaseLine>False</CodeContractsUseBaseLine> - <CodeContractsRunInBackground>True</CodeContractsRunInBackground> - <CodeContractsShowSquigglies>True</CodeContractsShowSquigglies> - <RunCodeAnalysis>true</RunCodeAnalysis> - <CodeContractsArithmeticObligations>False</CodeContractsArithmeticObligations> - <CodeContractsRuntimeOnlyPublicSurface>False</CodeContractsRuntimeOnlyPublicSurface> - <CodeContractsRuntimeThrowOnFailure>True</CodeContractsRuntimeThrowOnFailure> - <CodeContractsRuntimeCallSiteRequires>False</CodeContractsRuntimeCallSiteRequires> - <CodeContractsEmitXMLDocs>True</CodeContractsEmitXMLDocs> - <CodeContractsRedundantAssumptions>False</CodeContractsRedundantAssumptions> - <CodeContractsReferenceAssembly>Build</CodeContractsReferenceAssembly> - <CodeAnalysisRuleSet>Migrated rules for DotNetOpenAuth.ruleset</CodeAnalysisRuleSet> - <CodeContractsExtraRewriteOptions /> - </PropertyGroup> - <ItemGroup> - <Reference Include="log4net, Version=1.2.10.0, Culture=neutral, PublicKeyToken=1b44e1d426115821, processorArchitecture=MSIL"> - <SpecificVersion>False</SpecificVersion> - </Reference> - <Reference Include="PresentationFramework"> - <RequiredTargetFramework>3.0</RequiredTargetFramework> - </Reference> - <Reference Include="System" /> - <Reference Include="System.Security" /> - <Reference Include="System.configuration" /> - <Reference Include="System.Core"> - <RequiredTargetFramework>3.5</RequiredTargetFramework> - </Reference> - <Reference Include="System.Data" /> - <Reference Include="System.Drawing" /> - <Reference Include="System.IdentityModel"> - <RequiredTargetFramework>3.0</RequiredTargetFramework> - </Reference> - <Reference Include="System.IdentityModel.Selectors"> - <RequiredTargetFramework>3.0</RequiredTargetFramework> - </Reference> - <Reference Include="System.Runtime.Serialization"> - <RequiredTargetFramework>3.0</RequiredTargetFramework> - </Reference> - <Reference Include="System.ServiceModel"> - <RequiredTargetFramework>3.0</RequiredTargetFramework> - </Reference> - <Reference Include="System.ServiceModel.Web"> - <RequiredTargetFramework>3.5</RequiredTargetFramework> - </Reference> - <Reference Include="System.Web" /> - <Reference Include="System.Web.Abstractions"> - <RequiredTargetFramework>3.5</RequiredTargetFramework> - </Reference> - <Reference Include="System.Web.Extensions"> - <RequiredTargetFramework>3.5</RequiredTargetFramework> - </Reference> - <Reference Include="System.Web.Extensions.Design"> - <RequiredTargetFramework>3.5</RequiredTargetFramework> - </Reference> - <Reference Include="System.Web.Mobile" Condition=" '$(ClrVersion)' != '4' " /> - <Reference Include="System.Web.Routing"> - <RequiredTargetFramework>3.5</RequiredTargetFramework> - </Reference> - <Reference Include="System.Windows.Forms" /> - <Reference Include="System.Xaml" Condition=" '$(ClrVersion)' == '4' " /> - <Reference Include="System.XML" /> - <Reference Include="System.Xml.Linq"> - <RequiredTargetFramework>3.5</RequiredTargetFramework> - </Reference> - <Reference Include="WindowsBase"> - <RequiredTargetFramework>3.0</RequiredTargetFramework> - </Reference> - <Reference Include="System.ComponentModel.DataAnnotations"> - <RequiredTargetFramework>3.5</RequiredTargetFramework> - </Reference> - </ItemGroup> - <ItemGroup Condition=" '$(ClrVersion)' == '4' "> - <Reference Include="System.Web.Mvc, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL" /> - </ItemGroup> - <ItemGroup Condition=" '$(ClrVersion)' != '4' "> - <!-- MVC 2 can run on CLR 2 (it doesn't require CLR 4) but since MVC 2 apps tend to use type forwarding, - it's a more broadly consumable idea to bind against MVC 1 for the library unless we're building on CLR 4, - which will definitely have MVC 2 available. --> - <Reference Include="System.Web.Mvc, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL" /> - </ItemGroup> - <ItemGroup> - <Compile Include="Messaging\Bindings\AsymmetricCryptoKeyStoreWrapper.cs" /> - <Compile Include="ComponentModel\ClaimTypeSuggestions.cs" /> - <Compile Include="ComponentModel\ConverterBase.cs" /> - <Compile Include="ComponentModel\SuggestedStringsConverterContract.cs" /> - <Compile Include="ComponentModel\IssuersSuggestions.cs" /> - <Compile Include="ComponentModel\IdentifierConverter.cs" /> - <Compile Include="ComponentModel\SuggestedStringsConverter.cs" /> - <Compile Include="ComponentModel\UriConverter.cs" /> - <Compile Include="Configuration\AssociationTypeCollection.cs" /> - <Compile Include="Configuration\AssociationTypeElement.cs" /> - <Compile Include="Configuration\DotNetOpenAuthSection.cs" /> - <Compile Include="Configuration\MessagingElement.cs" /> - <Compile Include="Configuration\OAuthConsumerElement.cs" /> - <Compile Include="Configuration\OAuthConsumerSecuritySettingsElement.cs" /> - <Compile Include="Configuration\OAuthElement.cs" /> - <Compile Include="Configuration\OAuthServiceProviderElement.cs" /> - <Compile Include="Configuration\OAuthServiceProviderSecuritySettingsElement.cs" /> - <Compile Include="Configuration\OpenIdElement.cs" /> - <Compile Include="Configuration\OpenIdProviderElement.cs" /> - <Compile Include="Configuration\OpenIdProviderSecuritySettingsElement.cs" /> - <Compile Include="Configuration\OpenIdRelyingPartyElement.cs" /> - <Compile Include="Configuration\OpenIdRelyingPartySecuritySettingsElement.cs" /> - <Compile Include="Configuration\ReportingElement.cs" /> - <Compile Include="Configuration\TrustedProviderConfigurationCollection.cs" /> - <Compile Include="Configuration\TrustedProviderEndpointConfigurationElement.cs" /> - <Compile Include="Configuration\TypeConfigurationCollection.cs" /> - <Compile Include="Configuration\TypeConfigurationElement.cs" /> - <Compile Include="Configuration\UntrustedWebRequestElement.cs" /> - <Compile Include="Configuration\HostNameOrRegexCollection.cs" /> - <Compile Include="Configuration\HostNameElement.cs" /> - <Compile Include="Configuration\XriResolverElement.cs" /> - <Compile Include="Messaging\Bindings\CryptoKey.cs" /> - <Compile Include="Messaging\Bindings\CryptoKeyCollisionException.cs" /> - <Compile Include="Messaging\Bindings\ICryptoKeyStore.cs" /> - <Compile Include="IEmbeddedResourceRetrieval.cs" /> - <Compile Include="InfoCard\ClaimType.cs" /> - <Compile Include="InfoCard\InfoCardImage.cs" /> - <Compile Include="InfoCard\InfoCardStrings.Designer.cs"> - <AutoGen>True</AutoGen> - <DesignTime>True</DesignTime> - <DependentUpon>InfoCardStrings.resx</DependentUpon> - </Compile> - <Compile Include="InfoCard\Token\InformationCardException.cs" /> - <Compile Include="InfoCard\Token\Token.cs" /> - <Compile Include="InfoCard\Token\TokenUtility.cs" /> - <Compile Include="InfoCard\Token\TokenDecryptor.cs" /> - <Compile Include="InfoCard\WellKnownIssuers.cs" /> - <Compile Include="Messaging\Bindings\MemoryCryptoKeyStore.cs" /> - <Compile Include="Messaging\BinaryDataBagFormatter.cs" /> - <Compile Include="Messaging\CachedDirectWebResponse.cs" /> - <Compile Include="Messaging\ChannelContract.cs" /> - <Compile Include="Messaging\DataBagFormatterBase.cs" /> - <Compile Include="Messaging\IHttpIndirectResponse.cs" /> - <Compile Include="Messaging\IMessageOriginalPayload.cs" /> - <Compile Include="Messaging\DirectWebRequestOptions.cs" /> - <Compile Include="Messaging\EnumerableCache.cs" /> - <Compile Include="Messaging\HostErrorException.cs" /> - <Compile Include="Messaging\IHttpDirectResponse.cs" /> - <Compile Include="Messaging\IExtensionMessage.cs" /> - <Compile Include="Messaging\IHttpDirectResponseContract.cs" /> - <Compile Include="Messaging\IMessage.cs" /> - <Compile Include="Messaging\IncomingWebResponse.cs" /> - <Compile Include="Messaging\IDirectResponseProtocolMessage.cs" /> - <Compile Include="Messaging\EmptyDictionary.cs" /> - <Compile Include="Messaging\EmptyEnumerator.cs" /> - <Compile Include="Messaging\EmptyList.cs" /> - <Compile Include="Messaging\ErrorUtilities.cs" /> - <Compile Include="Messaging\IMessageWithEvents.cs" /> - <Compile Include="Messaging\IncomingWebResponseContract.cs" /> - <Compile Include="Messaging\IProtocolMessageWithExtensions.cs" /> - <Compile Include="Messaging\InternalErrorException.cs" /> - <Compile Include="Messaging\IStreamSerializingDataBag.cs" /> - <Compile Include="Messaging\KeyedCollectionDelegate.cs" /> - <Compile Include="Messaging\MultipartPostPart.cs" /> - <Compile Include="Messaging\NetworkDirectWebResponse.cs" /> - <Compile Include="Messaging\OutgoingWebResponseActionResult.cs" /> - <Compile Include="Messaging\Reflection\IMessagePartEncoder.cs" /> - <Compile Include="Messaging\Reflection\IMessagePartNullEncoder.cs" /> - <Compile Include="Messaging\Reflection\IMessagePartOriginalEncoder.cs" /> - <Compile Include="Messaging\Reflection\MessageDescriptionCollection.cs" /> - <Compile Include="Mvc\OpenIdAjaxOptions.cs" /> - <Compile Include="Messaging\StandardMessageFactory.cs" /> - <Compile Include="OAuth2\AuthorizationState.cs" /> - <Compile Include="OAuth2\ChannelElements\AccessRequestBindingElement.cs" /> - <Compile Include="OAuth2\ChannelElements\AccessToken.cs" /> - <Compile Include="OAuth2\ChannelElements\AccessTokenBindingElement.cs" /> - <Compile Include="OAuth2\ChannelElements\AuthorizationDataBag.cs" /> - <Compile Include="OAuth2\ChannelElements\AuthServerBindingElementBase.cs" /> - <Compile Include="OAuth2\ChannelElements\GrantTypeEncoder.cs" /> - <Compile Include="OAuth2\ChannelElements\EndUserAuthorizationResponseTypeEncoder.cs" /> - <Compile Include="Messaging\IDataBagFormatter.cs" /> - <Compile Include="OAuth2\ChannelElements\OAuth2ChannelBase.cs" /> - <Compile Include="OAuth2\ChannelElements\OAuth2ClientChannel.cs" /> - <Compile Include="OAuth2\ChannelElements\ScopeEncoder.cs" /> - <Compile Include="Messaging\UriStyleMessageFormatter.cs" /> - <Compile Include="OAuth2\ChannelElements\IAuthorizationDescription.cs" /> - <Compile Include="OAuth2\ChannelElements\IAuthorizationCarryingRequest.cs" /> - <Compile Include="OAuth2\ChannelElements\OAuth2ResourceServerChannel.cs" /> - <Compile Include="Messaging\StandardMessageFactoryChannel.cs" /> - <Compile Include="OAuth2\ChannelElements\RefreshToken.cs" /> - <Compile Include="Messaging\DataBag.cs" /> - <Compile Include="Messaging\TimestampEncoder.cs" /> - <Compile Include="OAuth2\ChannelElements\AuthorizationCode.cs" /> - <Compile Include="OAuth2\ChannelElements\AuthorizationCodeBindingElement.cs" /> - <Compile Include="OAuth2\ChannelElements\AuthServerAllFlowsBindingElement.cs" /> - <Compile Include="OAuth2\ClientAuthorizationView.cs"> - <SubType>UserControl</SubType> - </Compile> - <Compile Include="OAuth2\ClientAuthorizationView.Designer.cs"> - <DependentUpon>ClientAuthorizationView.cs</DependentUpon> - </Compile> - <Compile Include="OAuth2\IAccessTokenAnalyzer.cs" /> - <Compile Include="OAuth2\IAuthorizationServer.cs" /> - <Compile Include="OAuth2\IAuthorizationState.cs" /> - <Compile Include="OAuth2\IClientAuthorizationTracker.cs" /> - <Compile Include="OAuth2\IConsumerDescription.cs" /> - <Compile Include="OAuth2\Messages\AccessProtectedResourceRequest.cs" /> - <Compile Include="OAuth2\Messages\AccessTokenAuthorizationCodeRequest.cs" /> - <Compile Include="OAuth2\Messages\AccessTokenResourceOwnerPasswordCredentialsRequest.cs" /> - <Compile Include="OAuth2\Messages\AccessTokenRequestBase.cs" /> - <Compile Include="OAuth2\Messages\AccessTokenClientCredentialsRequest.cs" /> - <Compile Include="OAuth2\Messages\AuthenticatedClientRequestBase.cs" /> - <Compile Include="OAuth2\Messages\EndUserAuthorizationSuccessAccessTokenResponse.cs" /> - <Compile Include="OAuth2\Messages\EndUserAuthorizationFailedResponse.cs" /> - <Compile Include="OAuth2\Messages\EndUserAuthorizationSuccessAuthCodeResponse.cs" /> - <Compile Include="OAuth2\Messages\GrantType.cs" /> - <Compile Include="OAuth2\Messages\AccessTokenRefreshRequest.cs" /> - <Compile Include="OAuth2\Messages\EndUserAuthorizationResponseType.cs" /> - <Compile Include="OAuth2\Messages\IAccessTokenRequest.cs" /> - <Compile Include="OAuth2\Messages\IMessageWithClientState.cs" /> - <Compile Include="OAuth2\Messages\ScopedAccessTokenRequest.cs" /> - <Compile Include="OAuth2\Messages\UnauthorizedResponse.cs" /> - <Compile Include="OAuth2\Messages\AccessTokenFailedResponse.cs" /> - <Compile Include="OAuth2\Messages\AccessTokenSuccessResponse.cs" /> - <Compile Include="OAuth2\Messages\EndUserAuthorizationSuccessResponseBase.cs" /> - <Compile Include="OAuth2\ResourceServer.cs" /> - <Compile Include="OAuth2\StandardAccessTokenAnalyzer.cs" /> - <Compile Include="OAuth2\UserAgentClient.cs" /> - <Compile Include="OAuth2\AuthorizationServer.cs" /> - <Compile Include="OAuth2\OAuthUtilities.cs" /> - <Compile Include="OAuth\ChannelElements\ICombinedOpenIdProviderTokenManager.cs" /> - <Compile Include="OAuth\ChannelElements\IConsumerDescription.cs" /> - <Compile Include="OAuth\ChannelElements\IConsumerTokenManager.cs" /> - <Compile Include="OAuth\ChannelElements\IOpenIdOAuthTokenManager.cs" /> - <Compile Include="OAuth\ChannelElements\IServiceProviderAccessToken.cs" /> - <Compile Include="OAuth\ChannelElements\IServiceProviderTokenManager.cs" /> - <Compile Include="OAuth\ChannelElements\OAuthConsumerMessageFactory.cs" /> - <Compile Include="OAuth\ChannelElements\ITokenGenerator.cs" /> - <Compile Include="OAuth\ChannelElements\ITokenManager.cs" /> - <Compile Include="OAuth\ChannelElements\OAuthHttpMethodBindingElement.cs" /> - <Compile Include="OAuth\ChannelElements\OAuthIdentity.cs" /> - <Compile Include="OAuth\ChannelElements\OAuthPrincipal.cs" /> - <Compile Include="OAuth\ChannelElements\PlaintextSigningBindingElement.cs" /> - <Compile Include="OAuth\ChannelElements\HmacSha1SigningBindingElement.cs" /> - <Compile Include="OAuth\ChannelElements\IServiceProviderRequestToken.cs" /> - <Compile Include="OAuth\ChannelElements\SigningBindingElementBaseContract.cs" /> - <Compile Include="OAuth\ChannelElements\SigningBindingElementChain.cs" /> - <Compile Include="OAuth\ChannelElements\StandardTokenGenerator.cs" /> - <Compile Include="OAuth\ChannelElements\TokenType.cs" /> - <Compile Include="OAuth\ChannelElements\UriOrOobEncoding.cs" /> - <Compile Include="OAuth\ChannelElements\TokenHandlingBindingElement.cs" /> - <Compile Include="OAuth\ConsumerBase.cs" /> - <Compile Include="OAuth\ConsumerSecuritySettings.cs" /> - <Compile Include="OAuth\DesktopConsumer.cs" /> - <Compile Include="GlobalSuppressions.cs" /> - <Compile Include="Messaging\IMessageWithBinaryData.cs" /> - <Compile Include="OAuth\Messages\ITokenSecretContainingMessage.cs" /> - <Compile Include="Messaging\ChannelEventArgs.cs" /> - <Compile Include="Messaging\ITamperProtectionChannelBindingElement.cs" /> - <Compile Include="OAuth\OAuthStrings.Designer.cs"> - <AutoGen>True</AutoGen> - <DesignTime>True</DesignTime> - <DependentUpon>OAuthStrings.resx</DependentUpon> - </Compile> - <Compile Include="OAuth\SecuritySettings.cs" /> - <Compile Include="OAuth\ServiceProviderDescription.cs" /> - <Compile Include="OAuth\Messages\ITokenContainingMessage.cs" /> - <Compile Include="OAuth\Messages\SignedMessageBase.cs" /> - <Compile Include="Messaging\Bindings\NonceMemoryStore.cs" /> - <Compile Include="OAuth\ChannelElements\SigningBindingElementBase.cs" /> - <Compile Include="OAuth\ServiceProviderSecuritySettings.cs" /> - <Compile Include="OAuth\VerificationCodeFormat.cs" /> - <Compile Include="OAuth\WebConsumer.cs" /> - <Compile Include="Messaging\IDirectWebRequestHandler.cs" /> - <Compile Include="OAuth\ChannelElements\ITamperResistantOAuthMessage.cs" /> - <Compile Include="OAuth\Messages\MessageBase.cs" /> - <Compile Include="OAuth\Messages\AuthorizedTokenRequest.cs" /> - <Compile Include="Messaging\Bindings\INonceStore.cs" /> - <Compile Include="Messaging\Bindings\StandardReplayProtectionBindingElement.cs" /> - <Compile Include="Messaging\MessagePartAttribute.cs" /> - <Compile Include="Messaging\MessageProtections.cs" /> - <Compile Include="Messaging\IChannelBindingElement.cs" /> - <Compile Include="Messaging\Bindings\ReplayedMessageException.cs" /> - <Compile Include="Messaging\Bindings\ExpiredMessageException.cs" /> - <Compile Include="Messaging\Bindings\InvalidSignatureException.cs" /> - <Compile Include="Messaging\Bindings\IReplayProtectedProtocolMessage.cs" /> - <Compile Include="Messaging\Bindings\IExpiringProtocolMessage.cs" /> - <Compile Include="OAuth\Messages\AccessProtectedResourceRequest.cs" /> - <Compile Include="OAuth\Messages\AuthorizedTokenResponse.cs" /> - <Compile Include="OAuth\Messages\UserAuthorizationResponse.cs" /> - <Compile Include="OAuth\Messages\UserAuthorizationRequest.cs" /> - <Compile Include="OAuth\Messages\UnauthorizedTokenResponse.cs" /> - <Compile Include="Messaging\Channel.cs" /> - <Compile Include="Messaging\HttpRequestInfo.cs" /> - <Compile Include="Messaging\IDirectedProtocolMessage.cs" /> - <Compile Include="Messaging\IMessageFactory.cs" /> - <Compile Include="Messaging\ITamperResistantProtocolMessage.cs" /> - <Compile Include="Messaging\MessageSerializer.cs" /> - <Compile Include="Messaging\MessagingStrings.Designer.cs"> - <AutoGen>True</AutoGen> - <DesignTime>True</DesignTime> - <DependentUpon>MessagingStrings.resx</DependentUpon> - </Compile> - <Compile Include="Messaging\MessagingUtilities.cs" /> - <Compile Include="Messaging\Bindings\StandardExpirationBindingElement.cs" /> - <Compile Include="Messaging\Reflection\ValueMapping.cs" /> - <Compile Include="Messaging\Reflection\MessageDescription.cs" /> - <Compile Include="Messaging\Reflection\MessageDictionary.cs" /> - <Compile Include="Messaging\Reflection\MessagePart.cs" /> - <Compile Include="Messaging\UnprotectedMessageException.cs" /> - <Compile Include="OAuth\ChannelElements\OAuthChannel.cs" /> - <Compile Include="Messaging\OutgoingWebResponse.cs" /> - <Compile Include="Messaging\IProtocolMessage.cs" /> - <Compile Include="Logger.cs" /> - <Compile Include="Loggers\ILog.cs" /> - <Compile Include="Loggers\Log4NetLogger.cs" /> - <Compile Include="Loggers\NoOpLogger.cs" /> - <Compile Include="Loggers\TraceLogger.cs" /> - <Compile Include="Messaging\HttpDeliveryMethods.cs" /> - <Compile Include="Messaging\MessageTransport.cs" /> - <Compile Include="OAuth\ChannelElements\OAuthServiceProviderMessageFactory.cs" /> - <Compile Include="Messaging\ProtocolException.cs" /> - <Compile Include="OpenId\Association.cs" /> - <Compile Include="OpenId\Provider\AssociationDataBag.cs" /> - <Compile Include="OpenId\Provider\IProviderAssociationStore.cs" /> - <Compile Include="OpenId\Provider\ProviderAssociationHandleEncoder.cs" /> - <Compile Include="OpenId\Provider\ProviderAssociationKeyStorage.cs" /> - <Compile Include="OpenId\RelyingParty\CryptoKeyStoreAsRelyingPartyAssociationStore.cs" /> - <Compile Include="OpenId\RelyingParty\IRelyingPartyAssociationStore.cs" /> - <Compile Include="OpenId\RelyingParty\Associations.cs" /> - <Compile Include="OpenId\Behaviors\AXFetchAsSregTransform.cs" /> - <Compile Include="OpenId\Behaviors\BehaviorStrings.Designer.cs"> - <AutoGen>True</AutoGen> - <DesignTime>True</DesignTime> - <DependentUpon>BehaviorStrings.resx</DependentUpon> - </Compile> - <Compile Include="OpenId\Behaviors\PpidGeneration.cs" /> - <Compile Include="OpenId\ChannelElements\BackwardCompatibilityBindingElement.cs" /> - <Compile Include="OpenId\ChannelElements\ExtensionsBindingElement.cs" /> - <Compile Include="OpenId\ChannelElements\IOpenIdExtensionFactory.cs" /> - <Compile Include="OpenId\ChannelElements\ITamperResistantOpenIdMessage.cs" /> - <Compile Include="OpenId\ChannelElements\OriginalStringUriEncoder.cs" /> - <Compile Include="OpenId\ChannelElements\RelyingPartySecurityOptions.cs" /> - <Compile Include="OpenId\ChannelElements\ReturnToNonceBindingElement.cs" /> - <Compile Include="OpenId\ChannelElements\SigningBindingElement.cs" /> - <Compile Include="OpenId\ChannelElements\KeyValueFormEncoding.cs" /> - <Compile Include="OpenId\ChannelElements\OpenIdChannel.cs" /> - <Compile Include="OpenId\ChannelElements\OpenIdMessageFactory.cs" /> - <Compile Include="OpenId\ChannelElements\ReturnToSignatureBindingElement.cs" /> - <Compile Include="OpenId\ChannelElements\SkipSecurityBindingElement.cs" /> - <Compile Include="OpenId\AssociationContract.cs" /> - <Compile Include="OpenId\Extensions\AliasManager.cs" /> - <Compile Include="OpenId\Extensions\AttributeExchange\AttributeRequest.cs" /> - <Compile Include="OpenId\Extensions\AttributeExchange\AttributeValues.cs" /> - <Compile Include="OpenId\Extensions\AttributeExchange\AXAttributeFormats.cs" /> - <Compile Include="OpenId\Extensions\AttributeExchange\AXUtilities.cs" /> - <Compile Include="OpenId\Extensions\AttributeExchange\Constants.cs" /> - <Compile Include="OpenId\Extensions\AttributeExchange\FetchRequest.cs" /> - <Compile Include="OpenId\Extensions\AttributeExchange\FetchResponse.cs" /> - <Compile Include="OpenId\Extensions\AttributeExchange\StoreRequest.cs" /> - <Compile Include="OpenId\Extensions\AttributeExchange\StoreResponse.cs" /> - <Compile Include="OpenId\Extensions\AttributeExchange\WellKnownAttributes.cs" /> - <Compile Include="OpenId\Extensions\ExtensionBase.cs" /> - <Compile Include="OpenId\Extensions\ExtensionArgumentsManager.cs" /> - <Compile Include="OpenId\Extensions\IClientScriptExtensionResponse.cs" /> - <Compile Include="OpenId\Extensions\OAuth\AuthorizationRequest.cs" /> - <Compile Include="OpenId\Extensions\OAuth\AuthorizationApprovedResponse.cs" /> - <Compile Include="OpenId\Extensions\OAuth\Constants.cs" /> - <Compile Include="OpenId\Extensions\OAuth\AuthorizationDeclinedResponse.cs" /> - <Compile Include="OpenId\Extensions\OpenIdExtensionFactoryAggregator.cs" /> - <Compile Include="OpenId\Extensions\StandardOpenIdExtensionFactory.cs" /> - <Compile Include="OpenId\Extensions\ProviderAuthenticationPolicy\AuthenticationPolicies.cs" /> - <Compile Include="OpenId\Extensions\ProviderAuthenticationPolicy\Constants.cs" /> - <Compile Include="OpenId\Extensions\ProviderAuthenticationPolicy\DateTimeEncoder.cs" /> - <Compile Include="OpenId\Extensions\ProviderAuthenticationPolicy\NistAssuranceLevel.cs" /> - <Compile Include="OpenId\Extensions\ProviderAuthenticationPolicy\PapeUtilities.cs" /> - <Compile Include="OpenId\Extensions\ProviderAuthenticationPolicy\PolicyRequest.cs" /> - <Compile Include="OpenId\Extensions\ProviderAuthenticationPolicy\PolicyResponse.cs" /> - <Compile Include="Messaging\TimespanSecondsEncoder.cs" /> - <Compile Include="OpenId\Extensions\SimpleRegistration\ClaimsRequest.cs" /> - <Compile Include="OpenId\Extensions\SimpleRegistration\ClaimsResponse.cs" /> - <Compile Include="OpenId\Extensions\SimpleRegistration\Constants.cs" /> - <Compile Include="OpenId\Extensions\SimpleRegistration\DemandLevel.cs" /> - <Compile Include="OpenId\Extensions\SimpleRegistration\Gender.cs" /> - <Compile Include="OpenId\Extensions\UI\UIConstants.cs" /> - <Compile Include="OpenId\Extensions\UI\UIUtilities.cs" /> - <Compile Include="OpenId\Extensions\UI\UIModes.cs" /> - <Compile Include="OpenId\Extensions\UI\UIRequest.cs" /> - <Compile Include="OpenId\HostMetaDiscoveryService.cs" /> - <Compile Include="OpenId\Identifier.cs" /> - <Compile Include="OpenId\IdentifierContract.cs" /> - <Compile Include="OpenId\Extensions\ExtensionsInteropHelper.cs" /> - <Compile Include="OpenId\Interop\AuthenticationResponseShim.cs" /> - <Compile Include="OpenId\Interop\ClaimsResponseShim.cs" /> - <Compile Include="OpenId\Interop\OpenIdRelyingPartyShim.cs" /> - <Compile Include="OpenId\IIdentifierDiscoveryService.cs" /> - <Compile Include="OpenId\Messages\CheckAuthenticationRequest.cs" /> - <Compile Include="OpenId\Messages\CheckAuthenticationResponse.cs" /> - <Compile Include="OpenId\Messages\CheckIdRequest.cs" /> - <Compile Include="OpenId\Messages\AssociateSuccessfulResponseContract.cs" /> - <Compile Include="OpenId\Messages\IErrorMessage.cs" /> - <Compile Include="OpenId\Messages\IndirectResponseBase.cs" /> - <Compile Include="OpenId\Messages\IndirectSignedResponse.cs" /> - <Compile Include="OpenId\Messages\IOpenIdMessageExtension.cs" /> - <Compile Include="OpenId\Messages\NegativeAssertionResponse.cs" /> - <Compile Include="OpenId\Messages\PositiveAssertionResponse.cs" /> - <Compile Include="OpenId\Messages\SignedResponseRequest.cs" /> - <Compile Include="OpenId\NoDiscoveryIdentifier.cs" /> - <Compile Include="OpenId\OpenIdUtilities.cs" /> - <Compile Include="OpenId\Provider\AssociationRelyingPartyType.cs" /> - <Compile Include="OpenId\Provider\PrivatePersonalIdentifierProviderBase.cs" /> - <Compile Include="OpenId\Provider\AnonymousRequest.cs" /> - <Compile Include="OpenId\Provider\AnonymousRequestEventArgs.cs" /> - <Compile Include="OpenId\Provider\AuthenticationChallengeEventArgs.cs" /> - <Compile Include="OpenId\Provider\AuthenticationRequest.cs" /> - <Compile Include="OpenId\Provider\AutoResponsiveRequest.cs" /> - <Compile Include="OpenId\Provider\HostProcessedRequest.cs" /> - <Compile Include="OpenId\Provider\IAnonymousRequest.cs" /> - <Compile Include="OpenId\Provider\IAuthenticationRequest.cs" /> - <Compile Include="OpenId\Provider\IDirectedIdentityIdentifierProvider.cs" /> - <Compile Include="OpenId\Provider\IHostProcessedRequest.cs" /> - <Compile Include="OpenId\Provider\IErrorReporting.cs" /> - <Compile Include="OpenId\Provider\IProviderBehavior.cs" /> - <Compile Include="OpenId\Provider\IRequest.cs" /> - <Compile Include="OpenId\Provider\ProviderEndpoint.cs" /> - <Compile Include="OpenId\Provider\RelyingPartyDiscoveryResult.cs" /> - <Compile Include="OpenId\Provider\Request.cs" /> - <Compile Include="OpenId\Provider\RequestContract.cs" /> - <Compile Include="OpenId\Provider\StandardProviderApplicationStore.cs" /> - <Compile Include="OpenId\Realm.cs" /> - <Compile Include="OpenId\RelyingPartyDescription.cs" /> - <Compile Include="OpenId\DiffieHellmanUtilities.cs" /> - <Compile Include="OpenId\DiffieHellman\DHKeyGeneration.cs" /> - <Compile Include="OpenId\DiffieHellman\DHParameters.cs" /> - <Compile Include="OpenId\DiffieHellman\DiffieHellman.cs" /> - <Compile Include="OpenId\DiffieHellman\DiffieHellmanManaged.cs" /> - <Compile Include="OpenId\DiffieHellman\mono\BigInteger.cs" /> - <Compile Include="OpenId\DiffieHellman\mono\ConfidenceFactor.cs" /> - <Compile Include="OpenId\DiffieHellman\mono\NextPrimeFinder.cs" /> - <Compile Include="OpenId\DiffieHellman\mono\PrimalityTests.cs" /> - <Compile Include="OpenId\DiffieHellman\mono\PrimeGeneratorBase.cs" /> - <Compile Include="OpenId\DiffieHellman\mono\SequentialSearchPrimeGeneratorBase.cs" /> - <Compile Include="OpenId\HmacShaAssociation.cs" /> - <Compile Include="OpenId\Messages\AssociateUnencryptedRequest.cs" /> - <Compile Include="OpenId\Provider\OpenIdProvider.cs" /> - <Compile Include="OpenId\Messages\AssociateDiffieHellmanRequest.cs" /> - <Compile Include="OpenId\Messages\AssociateDiffieHellmanResponse.cs" /> - <Compile Include="OpenId\Messages\AssociateRequest.cs" /> - <Compile Include="OpenId\Messages\AssociateSuccessfulResponse.cs" /> - <Compile Include="OpenId\Messages\AssociateUnencryptedResponse.cs" /> - <Compile Include="OpenId\Messages\AssociateUnsuccessfulResponse.cs" /> - <Compile Include="OpenId\Messages\IndirectErrorResponse.cs" /> - <Compile Include="OpenId\Messages\DirectErrorResponse.cs" /> - <Compile Include="OpenId\Messages\RequestBase.cs" /> - <Compile Include="OpenId\Messages\DirectResponseBase.cs" /> - <Compile Include="OpenId\RelyingParty\AssociationManager.cs" /> - <Compile Include="OpenId\RelyingParty\AssociationPreference.cs" /> - <Compile Include="OpenId\RelyingParty\AuthenticationRequest.cs" /> - <Compile Include="OpenId\RelyingParty\AuthenticationRequestMode.cs" /> - <Compile Include="OpenId\RelyingParty\DuplicateRequestedHostsComparer.cs" /> - <Compile Include="OpenId\RelyingParty\IProviderEndpoint.cs" /> - <Compile Include="OpenId\RelyingParty\IRelyingPartyBehavior.cs" /> - <Compile Include="OpenId\RelyingParty\IAuthenticationRequestContract.cs" /> - <Compile Include="OpenId\RelyingParty\NegativeAuthenticationResponse.cs" /> - <Compile Include="OpenId\RelyingParty\OpenIdEventArgs.cs" /> - <Compile Include="OpenId\RelyingParty\PopupBehavior.cs" /> - <Compile Include="OpenId\RelyingParty\PositiveAnonymousResponse.cs" /> - <Compile Include="OpenId\RelyingParty\PositiveAuthenticationResponse.cs" /> - <Compile Include="OpenId\RelyingParty\AuthenticationStatus.cs" /> - <Compile Include="OpenId\RelyingParty\FailedAuthenticationResponse.cs" /> - <Compile Include="OpenId\RelyingParty\IAuthenticationRequest.cs" /> - <Compile Include="OpenId\RelyingParty\IAuthenticationResponse.cs" /> - <Compile Include="OpenId\RelyingParty\ISetupRequiredAuthenticationResponse.cs" /> - <Compile Include="OpenId\RelyingParty\OpenIdRelyingParty.cs" /> - <Compile Include="OpenId\OpenIdStrings.Designer.cs"> - <DependentUpon>OpenIdStrings.resx</DependentUpon> - <DesignTime>True</DesignTime> - <AutoGen>True</AutoGen> - </Compile> - <Compile Include="OpenId\Protocol.cs" /> - <Compile Include="OpenId\ProviderEndpointDescription.cs" /> - <Compile Include="OpenId\Provider\ProviderSecuritySettings.cs" /> - <Compile Include="OpenId\IOpenIdApplicationStore.cs" /> - <Compile Include="OpenId\RelyingParty\PositiveAuthenticationResponseSnapshot.cs" /> - <Compile Include="OpenId\RelyingParty\RelyingPartySecuritySettings.cs" /> - <Compile Include="OpenId\IdentifierDiscoveryResult.cs" /> - <Compile Include="OpenId\OpenIdXrdsHelper.cs" /> - <Compile Include="OpenId\RelyingParty\SimpleXrdsProviderEndpoint.cs" /> - <Compile Include="OpenId\RelyingParty\StandardRelyingPartyApplicationStore.cs" /> - <Compile Include="OpenId\Behaviors\GsaIcamProfile.cs" /> - <Compile Include="OpenId\RelyingParty\WellKnownProviders.cs" /> - <Compile Include="OpenId\SecuritySettings.cs" /> - <Compile Include="Messaging\UntrustedWebRequestHandler.cs" /> - <Compile Include="OpenId\UriDiscoveryService.cs" /> - <Compile Include="OpenId\UriIdentifier.cs" /> - <Compile Include="OpenId\XriDiscoveryProxyService.cs" /> - <Compile Include="OpenId\XriIdentifier.cs" /> - <Compile Include="Properties\AssemblyInfo.cs" /> - <Compile Include="OAuth\Messages\UnauthorizedTokenRequest.cs" /> - <Compile Include="OAuth\ChannelElements\RsaSha1SigningBindingElement.cs" /> - <Compile Include="Messaging\StandardWebRequestHandler.cs" /> - <Compile Include="Messaging\MessageReceivingEndpoint.cs" /> - <Compile Include="Reporting.cs" /> - <Compile Include="OAuth2\ChannelElements\OAuth2AuthorizationServerChannel.cs" /> - <Compile Include="OAuth2\ClientBase.cs" /> - <Compile Include="OAuth2\Messages\MessageBase.cs" /> - <Compile Include="OAuth2\Messages\EndUserAuthorizationRequest.cs" /> - <Compile Include="OAuth2\Protocol.cs" /> - <Compile Include="OAuth2\OAuthStrings.Designer.cs"> - <AutoGen>True</AutoGen> - <DesignTime>True</DesignTime> - <DependentUpon>OAuthStrings.resx</DependentUpon> - </Compile> - <Compile Include="OAuth2\AuthorizationServerDescription.cs" /> - <Compile Include="OAuth2\WebServerClient.cs" /> - <Compile Include="Util.cs" /> - <Compile Include="OAuth\Protocol.cs" /> - <Compile Include="OAuth\ServiceProvider.cs" /> - <Compile Include="Strings.Designer.cs"> - <AutoGen>True</AutoGen> - <DesignTime>True</DesignTime> - <DependentUpon>Strings.resx</DependentUpon> - </Compile> - <Compile Include="UriUtil.cs" /> - <Compile Include="Xrds\XrdsStrings.Designer.cs"> - <AutoGen>True</AutoGen> - <DesignTime>True</DesignTime> - <DependentUpon>XrdsStrings.resx</DependentUpon> - </Compile> - <Compile Include="Yadis\ContentTypes.cs" /> - <Compile Include="Yadis\DiscoveryResult.cs" /> - <Compile Include="Yadis\HtmlParser.cs" /> - <Compile Include="Xrds\ServiceElement.cs" /> - <Compile Include="Xrds\TypeElement.cs" /> - <Compile Include="Xrds\UriElement.cs" /> - <Compile Include="Xrds\XrdElement.cs" /> - <Compile Include="Xrds\XrdsDocument.cs" /> - <Compile Include="Xrds\XrdsNode.cs" /> - <Compile Include="Yadis\Yadis.cs" /> - </ItemGroup> - <ItemGroup Condition=" '$(NoUIControls)' != 'true' "> - <Compile Include="OpenId\RelyingParty\OpenIdTextBox.cs" /> - <Compile Include="OpenId\RelyingParty\OpenIdLogin.cs" /> - <Compile Include="OpenId\RelyingParty\OpenIdAjaxRelyingParty.cs" /> - <Compile Include="OpenId\RelyingParty\SelectorButtonContract.cs" /> - <Compile Include="OpenId\RelyingParty\SelectorProviderButton.cs" /> - <Compile Include="OpenId\RelyingParty\SelectorOpenIdButton.cs" /> - <Compile Include="XrdsPublisher.cs" /> - <Compile Include="InfoCard\ReceivingTokenEventArgs.cs" /> - <Compile Include="InfoCard\TokenProcessingErrorEventArgs.cs" /> - <Compile Include="InfoCard\InfoCardSelector.cs" /> - <Compile Include="InfoCard\ReceivedTokenEventArgs.cs" /> - <Compile Include="Mvc\OpenIdHelper.cs" /> - <Compile Include="OpenId\RelyingParty\SelectorButton.cs" /> - <Compile Include="OpenId\Provider\IdentityEndpoint.cs" /> - <Compile Include="OpenId\Provider\IdentityEndpointNormalizationEventArgs.cs" /> - <Compile Include="OpenId\RelyingParty\SelectorInfoCardButton.cs" /> - <Compile Include="OpenId\RelyingParty\OpenIdMobileTextBox.cs" Condition=" '$(ClrVersion)' != '4' " /> - <Compile Include="OpenId\RelyingParty\OpenIdAjaxTextBox.cs" /> - <Compile Include="OpenId\RelyingParty\OpenIdSelector.cs" /> - <Compile Include="OpenId\RelyingParty\OpenIdRelyingPartyAjaxControlBase.cs" /> - <Compile Include="OpenId\RelyingParty\OpenIdButton.cs" /> - <Compile Include="OpenId\RelyingParty\OpenIdRelyingPartyControlBase.cs" /> - </ItemGroup> - <ItemGroup> - <None Include="Configuration\DotNetOpenAuth.xsd" /> - <None Include="Migrated rules for DotNetOpenAuth.ruleset" /> - <None Include="OAuth2\Messages\OAuth 2 Messages.cd" /> - <None Include="OAuth2\OAuth 2 client facades.cd" /> - <None Include="OAuth\ClassDiagram.cd" /> - <None Include="OAuth\Messages\OAuth Messages.cd" /> - <None Include="Messaging\Bindings\Bindings.cd" /> - <None Include="Messaging\Exceptions.cd" /> - <None Include="Messaging\Messaging.cd" /> - <None Include="OpenId\RelyingParty\Controls.cd" /> - <None Include="OpenId\RelyingParty\OpenIdRelyingParty.cd" /> - </ItemGroup> - <ItemGroup> - <EmbeddedResource Include="Messaging\MessagingStrings.resx"> - <Generator>ResXFileCodeGenerator</Generator> - <LastGenOutput>MessagingStrings.Designer.cs</LastGenOutput> - <SubType>Designer</SubType> - </EmbeddedResource> - <EmbeddedResource Include="OAuth2\ClientAuthorizationView.resx"> - <DependentUpon>ClientAuthorizationView.cs</DependentUpon> - </EmbeddedResource> - <EmbeddedResource Include="OAuth\OAuthStrings.resx"> - <Generator>ResXFileCodeGenerator</Generator> - <LastGenOutput>OAuthStrings.Designer.cs</LastGenOutput> - </EmbeddedResource> - <EmbeddedResource Include="OpenId\OpenIdStrings.resx"> - <Generator>ResXFileCodeGenerator</Generator> - <LastGenOutput>OpenIdStrings.Designer.cs</LastGenOutput> - </EmbeddedResource> - <EmbeddedResource Include="Strings.resx"> - <Generator>ResXFileCodeGenerator</Generator> - <LastGenOutput>Strings.Designer.cs</LastGenOutput> - <SubType>Designer</SubType> - </EmbeddedResource> - <EmbeddedResource Include="Xrds\XrdsStrings.resx"> - <Generator>ResXFileCodeGenerator</Generator> - <LastGenOutput>XrdsStrings.Designer.cs</LastGenOutput> - </EmbeddedResource> - </ItemGroup> - <ItemGroup> - <EmbeddedResource Include="OpenId\RelyingParty\openid_login.png" /> - </ItemGroup> - <ItemGroup> - <EmbeddedResource Include="OpenId\RelyingParty\login_failure.png" /> - <EmbeddedResource Include="OpenId\RelyingParty\login_success %28lock%29.png" /> - <EmbeddedResource Include="OpenId\RelyingParty\login_success.png" /> - <EmbeddedResource Include="OpenId\RelyingParty\OpenIdAjaxTextBox.js"> - <Copyright>$(StandardCopyright)</Copyright> - </EmbeddedResource> - <EmbeddedResource Include="OpenId\RelyingParty\spinner.gif" /> - </ItemGroup> - <ItemGroup> - <EmbeddedResource Include="InfoCard\InfoCardStrings.resx"> - <Generator>ResXFileCodeGenerator</Generator> - <LastGenOutput>InfoCardStrings.Designer.cs</LastGenOutput> - </EmbeddedResource> - <EmbeddedResource Include="InfoCard\infocard_114x80.png" /> - <EmbeddedResource Include="InfoCard\infocard_14x10.png" /> - <EmbeddedResource Include="InfoCard\infocard_214x150.png" /> - <EmbeddedResource Include="InfoCard\infocard_23x16.png" /> - <EmbeddedResource Include="InfoCard\infocard_300x210.png" /> - <EmbeddedResource Include="InfoCard\infocard_34x24.png" /> - <EmbeddedResource Include="InfoCard\infocard_365x256.png" /> - <EmbeddedResource Include="InfoCard\infocard_41x29.png" /> - <EmbeddedResource Include="InfoCard\infocard_50x35.png" /> - <EmbeddedResource Include="InfoCard\infocard_60x42.png" /> - <EmbeddedResource Include="InfoCard\infocard_71x50.png" /> - <EmbeddedResource Include="InfoCard\infocard_81x57.png" /> - <EmbeddedResource Include="InfoCard\infocard_92x64.png" /> - <EmbeddedResource Include="InfoCard\SupportingScript.js"> - <Copyright>$(StandardCopyright)</Copyright> - </EmbeddedResource> - </ItemGroup> - <ItemGroup> - <EmbeddedResource Include="OpenId\RelyingParty\OpenIdRelyingPartyControlBase.js"> - <Copyright>$(StandardCopyright)</Copyright> - </EmbeddedResource> - </ItemGroup> - <ItemGroup> - <EmbeddedResource Include="InfoCard\InfoCardStrings.sr.resx" /> - <EmbeddedResource Include="Messaging\MessagingStrings.sr.resx" /> - <EmbeddedResource Include="OAuth\OAuthStrings.sr.resx" /> - <EmbeddedResource Include="OpenId\Behaviors\BehaviorStrings.resx"> - <Generator>ResXFileCodeGenerator</Generator> - <LastGenOutput>BehaviorStrings.Designer.cs</LastGenOutput> - </EmbeddedResource> - <EmbeddedResource Include="OpenId\Behaviors\BehaviorStrings.sr.resx" /> - <EmbeddedResource Include="OpenId\OpenIdStrings.sr.resx" /> - <EmbeddedResource Include="OpenId\RelyingParty\OpenIdRelyingPartyAjaxControlBase.js"> - <Copyright>$(StandardCopyright)</Copyright> - </EmbeddedResource> - <EmbeddedResource Include="OAuth2\OAuthStrings.resx"> - <Generator>ResXFileCodeGenerator</Generator> - <LastGenOutput>OAuthStrings.Designer.cs</LastGenOutput> - </EmbeddedResource> - <EmbeddedResource Include="Strings.sr.resx" /> - <EmbeddedResource Include="Xrds\XrdsStrings.sr.resx" /> - </ItemGroup> - <ItemGroup> - <EmbeddedResource Include="OpenId\RelyingParty\OpenIdAjaxTextBox.css" /> - </ItemGroup> - <ItemGroup> - <EmbeddedResource Include="OpenId\RelyingParty\OpenIdSelector.js" /> - </ItemGroup> - <ItemGroup> - <EmbeddedResource Include="OpenId\RelyingParty\OpenIdSelector.css" /> - </ItemGroup> - <ItemGroup> - <BootstrapperPackage Include="Microsoft.Net.Client.3.5"> - <Visible>False</Visible> - <ProductName>.NET Framework 3.5 SP1 Client Profile</ProductName> - <Install>false</Install> - </BootstrapperPackage> - <BootstrapperPackage Include="Microsoft.Net.Framework.3.5.SP1"> - <Visible>False</Visible> - <ProductName>.NET Framework 3.5 SP1</ProductName> - <Install>true</Install> - </BootstrapperPackage> - <BootstrapperPackage Include="Microsoft.Windows.Installer.3.1"> - <Visible>False</Visible> - <ProductName>Windows Installer 3.1</ProductName> - <Install>true</Install> - </BootstrapperPackage> - <CodeAnalysisDictionary Include="CodeAnalysisDictionary.xml" /> - <Content Include="DotNetOpenAuth.ico" /> - </ItemGroup> - <ItemGroup> - <SignDependsOn Include="BuildUnifiedProduct" /> - <DelaySignedAssemblies Include="$(ILMergeOutputAssembly);
 $(OutputPath)CodeContracts\$(ProductName).Contracts.dll;
 " /> - </ItemGroup> - <PropertyGroup> - <!-- Don't sign the non-unified version of the assembly. --> - <SuppressTargetPathDelaySignedAssembly>true</SuppressTargetPathDelaySignedAssembly> - </PropertyGroup> - <Target Name="BuildUnifiedProduct" DependsOnTargets="Build" Inputs="@(ILMergeInputAssemblies)" Outputs="$(ILMergeOutputAssembly)"> - <PropertyGroup> - <!-- The ILMerge task doesn't properly quote the path. --> - <ILMergeTargetPlatformDirectory Condition=" '$(ClrVersion)' == '4' ">"$(MSBuildProgramFiles32)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.0"</ILMergeTargetPlatformDirectory> - </PropertyGroup> - <MakeDir Directories="$(ILMergeOutputAssemblyDirectory)" /> - <ILMerge ExcludeFile="$(ProjectRoot)ILMergeInternalizeExceptions.txt" InputAssemblies="@(ILMergeInputAssemblies)" OutputFile="$(ILMergeOutputAssembly)" KeyFile="$(PublicKeyFile)" DelaySign="true" ToolPath="$(ProjectRoot)tools\ILMerge" TargetPlatformVersion="$(ClrVersion).0" TargetPlatformDirectory="$(ILMergeTargetPlatformDirectory)" /> - </Target> - <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))' != '' " /> -</Project>
\ No newline at end of file diff --git a/src/DotNetOpenAuth/DotNetOpenAuth.proj b/src/DotNetOpenAuth/DotNetOpenAuth.proj new file mode 100644 index 0000000..0ae9c17 --- /dev/null +++ b/src/DotNetOpenAuth/DotNetOpenAuth.proj @@ -0,0 +1,78 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0" DefaultTargets="BuildUnifiedProduct"> + <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), EnlistmentInfo.props))\EnlistmentInfo.props" Condition=" '$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), EnlistmentInfo.props))' != '' " /> + + <PropertyGroup> + <SuppressBuildTarget>true</SuppressBuildTarget> + <AddContractsAssemblyForDelaySigning>false</AddContractsAssemblyForDelaySigning> + </PropertyGroup> + + <Import Project="$(ProjectRoot)tools\DotNetOpenAuth.props" /> + <Import Project="$(ProjectRoot)tools\DotNetOpenAuth.Product.props" /> + + <UsingTask AssemblyFile="$(ProjectRoot)lib\MSBuild.Community.Tasks.dll" TaskName="ILMerge"/> + + <PropertyGroup> + <TargetPath>$(ILMergeOutputAssembly)</TargetPath> + </PropertyGroup> + + <ItemGroup> + <ProjectReference Include="@(ProductProjects)"> + <PrimaryProductOutput Condition=" '%(MergeIntoUnifiedAssembly)' != 'false' ">true</PrimaryProductOutput> + </ProjectReference> + <SignDependsOn Include="BuildUnifiedProduct" /> + <DelaySignedAssemblies Include="$(ILMergeOutputContractAssembly)" + Condition=" '$(BuildCodeContractsReferenceAssemblies)' == 'true' "/> + </ItemGroup> + + <Target Name="BuildILMergeInputs" DependsOnTargets="ResolveReferences"> + <ItemGroup> + <ILMergeProductInputAssemblies Include="@(ReferencePath)" + Condition=" '%(ReferencePath.PrimaryProductOutput)' == 'true' "/> + <ILMergeInputContractAssemblies Include="@(ILMergeProductInputAssemblies->'%(RootDir)%(Directory)CodeContracts\%(FileName).Contracts%(Extension)')" + Condition=" '%(FileName)' != 'Microsoft.Contracts' "/> + </ItemGroup> + </Target> + + <Target Name="BuildUnifiedContractAssembly" DependsOnTargets="BuildILMergeInputs" + Condition=" '$(BuildCodeContractsReferenceAssemblies)' == 'true' " + Inputs="@(ILMergeInputContractAssemblies)" Outputs="$(ILMergeOutputContractAssembly)"> + + <MakeDir Directories="$(ILMergeOutputContractAssemblyDirectory)" /> + + <Message Text="Merging $(ILMergeOutputContractAssembly)" Importance="high" /> + <ILMerge + ExcludeFile="$(ProjectRoot)ILMergeInternalizeExceptions.txt" + InputAssemblies="@(ILMergeInputContractAssemblies)" + OutputFile="$(ILMergeOutputContractAssembly)" + SearchDirectories="$(OutputPath);@(ILMergeSearchDirectories)" + KeyFile="$(PublicKeyFile)" + DelaySign="true" + ToolPath="$(ProjectRoot)tools\ILMerge" + TargetPlatformVersion="$(ClrVersion).0" + TargetPlatformDirectory="$(ILMergeTargetPlatformDirectory)" /> + </Target> + + <Target Name="BuildUnifiedProductAssembly" DependsOnTargets="BuildILMergeInputs" + Inputs="@(ILMergeProductInputAssemblies);@(ILMergeInputAssemblies)" Outputs="$(ILMergeOutputAssembly);$(ILMergeOutputXmlDocs)"> + <MakeDir Directories="$(ILMergeOutputAssemblyDirectory)" /> + <Message Text="Merging $(ILMergeOutputAssembly)" Importance="high" /> + <ILMerge + ExcludeFile="$(ProjectRoot)ILMergeInternalizeExceptions.txt" + InputAssemblies="@(ILMergeProductInputAssemblies);@(ILMergeInputAssemblies)" + OutputFile="$(ILMergeOutputAssembly)" + SearchDirectories="@(ILMergeSearchDirectories)" + KeyFile="$(PublicKeyFile)" + DelaySign="true" + XmlDocumentation="true" + ToolPath="$(ProjectRoot)tools\ILMerge" + TargetPlatformVersion="$(ClrVersion).0" + TargetPlatformDirectory="$(ILMergeTargetPlatformDirectory)" /> + </Target> + + <Target Name="BuildUnifiedProduct" DependsOnTargets="BuildUnifiedProductAssembly;BuildUnifiedContractAssembly" /> + + <Import Project="$(MSBuildToolsPath)\Microsoft.Common.targets" /> + <Import Project="$(ProjectRoot)tools\DotNetOpenAuth.targets" /> + <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), EnlistmentInfo.targets))\EnlistmentInfo.targets" Condition=" '$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), EnlistmentInfo.targets))' != '' " /> +</Project>
\ No newline at end of file diff --git a/src/DotNetOpenAuth/InfoCard/InfoCardSelector.cs b/src/DotNetOpenAuth/InfoCard/InfoCardSelector.cs deleted file mode 100644 index ae45229..0000000 --- a/src/DotNetOpenAuth/InfoCard/InfoCardSelector.cs +++ /dev/null @@ -1,772 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="InfoCardSelector.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// Certain elements are Copyright (c) 2007 Dominick Baier. -// </copyright> -//----------------------------------------------------------------------- - -[assembly: System.Web.UI.WebResource(DotNetOpenAuth.InfoCard.InfoCardSelector.ScriptResourceName, "text/javascript")] - -namespace DotNetOpenAuth.InfoCard { - using System; - using System.Collections.ObjectModel; - using System.ComponentModel; - using System.Diagnostics.CodeAnalysis; - using System.Diagnostics.Contracts; - using System.Drawing.Design; - using System.Globalization; - using System.Linq; - using System.Text; - using System.Text.RegularExpressions; - using System.Web; - using System.Web.UI; - using System.Web.UI.HtmlControls; - using System.Web.UI.WebControls; - using System.Xml; - using DotNetOpenAuth.Messaging; - - /// <summary> - /// The style to use for NOT displaying a hidden region. - /// </summary> - public enum RenderMode { - /// <summary> - /// A hidden region should be invisible while still occupying space in the page layout. - /// </summary> - Static, - - /// <summary> - /// A hidden region should collapse so that it does not occupy space in the page layout. - /// </summary> - Dynamic - } - - /// <summary> - /// An Information Card selector ASP.NET control. - /// </summary> - [ParseChildren(true, "ClaimsRequested")] - [PersistChildren(false)] - [DefaultEvent("ReceivedToken")] - [ToolboxData("<{0}:InfoCardSelector runat=\"server\"><ClaimsRequested><{0}:ClaimType Name=\"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/privatepersonalidentifier\" /></ClaimsRequested><UnsupportedTemplate><p>Your browser does not support Information Cards.</p></UnsupportedTemplate></{0}:InfoCardSelector>")] - [ContractVerification(true)] - public class InfoCardSelector : CompositeControl, IPostBackEventHandler { - /// <summary> - /// The resource name for getting at the SupportingScript.js embedded manifest stream. - /// </summary> - internal const string ScriptResourceName = "DotNetOpenAuth.InfoCard.SupportingScript.js"; - - #region Property constants - - /// <summary> - /// Default value for the <see cref="RenderMode"/> property. - /// </summary> - private const RenderMode RenderModeDefault = RenderMode.Dynamic; - - /// <summary> - /// Default value for the <see cref="AutoPostBack"/> property. - /// </summary> - private const bool AutoPostBackDefault = true; - - /// <summary> - /// Default value for the <see cref="AutoPopup"/> property. - /// </summary> - private const bool AutoPopupDefault = false; - - /// <summary> - /// Default value for the <see cref="PrivacyUrl"/> property. - /// </summary> - private const string PrivacyUrlDefault = ""; - - /// <summary> - /// Default value for the <see cref="PrivacyVersion"/> property. - /// </summary> - private const string PrivacyVersionDefault = ""; - - /// <summary> - /// Default value for the <see cref="InfoCardImage"/> property. - /// </summary> - private const InfoCardImageSize InfoCardImageDefault = InfoCardImage.DefaultImageSize; - - /// <summary> - /// Default value for the <see cref="IssuerPolicy"/> property. - /// </summary> - private const string IssuerPolicyDefault = ""; - - /// <summary> - /// Default value for the <see cref="Issuer"/> property. - /// </summary> - private const string IssuerDefault = WellKnownIssuers.SelfIssued; - - /// <summary> - /// The default value for the <see cref="TokenType"/> property. - /// </summary> - private const string TokenTypeDefault = "urn:oasis:names:tc:SAML:1.0:assertion"; - - /// <summary> - /// The viewstate key for storing the <see cref="Issuer" /> property. - /// </summary> - private const string IssuerViewStateKey = "Issuer"; - - /// <summary> - /// The viewstate key for storing the <see cref="IssuerPolicy" /> property. - /// </summary> - private const string IssuerPolicyViewStateKey = "IssuerPolicy"; - - /// <summary> - /// The viewstate key for storing the <see cref="AutoPopup" /> property. - /// </summary> - private const string AutoPopupViewStateKey = "AutoPopup"; - - /// <summary> - /// The viewstate key for storing the <see cref="ClaimsRequested" /> property. - /// </summary> - private const string ClaimsRequestedViewStateKey = "ClaimsRequested"; - - /// <summary> - /// The viewstate key for storing the <see cref="TokenType" /> property. - /// </summary> - private const string TokenTypeViewStateKey = "TokenType"; - - /// <summary> - /// The viewstate key for storing the <see cref="PrivacyUrl" /> property. - /// </summary> - private const string PrivacyUrlViewStateKey = "PrivacyUrl"; - - /// <summary> - /// The viewstate key for storing the <see cref="PrivacyVersion" /> property. - /// </summary> - private const string PrivacyVersionViewStateKey = "PrivacyVersion"; - - /// <summary> - /// The viewstate key for storing the <see cref="Audience" /> property. - /// </summary> - private const string AudienceViewStateKey = "Audience"; - - /// <summary> - /// The viewstate key for storing the <see cref="AutoPostBack" /> property. - /// </summary> - private const string AutoPostBackViewStateKey = "AutoPostBack"; - - /// <summary> - /// The viewstate key for storing the <see cref="ImageSize" /> property. - /// </summary> - private const string ImageSizeViewStateKey = "ImageSize"; - - /// <summary> - /// The viewstate key for storing the <see cref="RenderMode" /> property. - /// </summary> - private const string RenderModeViewStateKey = "RenderMode"; - - #endregion - - #region Categories - - /// <summary> - /// The "Behavior" property category. - /// </summary> - private const string BehaviorCategory = "Behavior"; - - /// <summary> - /// The "Appearance" property category. - /// </summary> - private const string AppearanceCategory = "Appearance"; - - /// <summary> - /// The "InfoCard" property category. - /// </summary> - private const string InfoCardCategory = "InfoCard"; - - #endregion - - /// <summary> - /// The panel containing the controls to display if InfoCard is supported in the user agent. - /// </summary> - private Panel infoCardSupportedPanel; - - /// <summary> - /// The panel containing the controls to display if InfoCard is NOT supported in the user agent. - /// </summary> - private Panel infoCardNotSupportedPanel; - - /// <summary> - /// Recalls whether the <see cref="Audience"/> property has been set yet, - /// so its default can be set as soon as possible without overwriting - /// an intentional value. - /// </summary> - private bool audienceSet; - - /// <summary> - /// Initializes a new instance of the <see cref="InfoCardSelector"/> class. - /// </summary> - public InfoCardSelector() { - this.ToolTip = InfoCardStrings.SelectorClickPrompt; - Reporting.RecordFeatureUse(this); - } - - /// <summary> - /// Occurs when an InfoCard has been submitted but not decoded yet. - /// </summary> - [Category(InfoCardCategory)] - public event EventHandler<ReceivingTokenEventArgs> ReceivingToken; - - /// <summary> - /// Occurs when an InfoCard has been submitted and decoded. - /// </summary> - [Category(InfoCardCategory)] - public event EventHandler<ReceivedTokenEventArgs> ReceivedToken; - - /// <summary> - /// Occurs when an InfoCard token is submitted but an error occurs in processing. - /// </summary> - [Category(InfoCardCategory)] - public event EventHandler<TokenProcessingErrorEventArgs> TokenProcessingError; - - #region Properties - - /// <summary> - /// Gets the set of claims that are requested from the Information Card. - /// </summary> - [Description("Specifies the required and optional claims.")] - [PersistenceMode(PersistenceMode.InnerProperty), Category(InfoCardCategory)] - public Collection<ClaimType> ClaimsRequested { - get { - Contract.Ensures(Contract.Result<Collection<ClaimType>>() != null); - if (this.ViewState[ClaimsRequestedViewStateKey] == null) { - var claims = new Collection<ClaimType>(); - this.ViewState[ClaimsRequestedViewStateKey] = claims; - return claims; - } else { - return (Collection<ClaimType>)this.ViewState[ClaimsRequestedViewStateKey]; - } - } - } - - /// <summary> - /// Gets or sets the issuer URI. - /// </summary> - [Description("When receiving managed cards, this is the only Issuer whose cards will be accepted.")] - [Category(InfoCardCategory), DefaultValue(IssuerDefault)] - [TypeConverter(typeof(ComponentModel.IssuersSuggestions))] - public string Issuer { - get { return (string)this.ViewState[IssuerViewStateKey] ?? IssuerDefault; } - set { this.ViewState[IssuerViewStateKey] = value; } - } - - /// <summary> - /// Gets or sets the issuer policy URI. - /// </summary> - [Description("Specifies the URI of the issuer MEX endpoint")] - [Category(InfoCardCategory), DefaultValue(IssuerPolicyDefault)] - public string IssuerPolicy { - get { return (string)this.ViewState[IssuerPolicyViewStateKey] ?? IssuerPolicyDefault; } - set { this.ViewState[IssuerPolicyViewStateKey] = value; } - } - - /// <summary> - /// Gets or sets the URL to this site's privacy policy. - /// </summary> - [Description("The URL to this site's privacy policy.")] - [Category(InfoCardCategory), DefaultValue(PrivacyUrlDefault)] - [SuppressMessage("Microsoft.Usage", "CA1806:DoNotIgnoreMethodResults", MessageId = "System.Uri", Justification = "We construct a Uri to validate the format of the string.")] - [SuppressMessage("Microsoft.Usage", "CA2234:PassSystemUriObjectsInsteadOfStrings", Justification = "That overload is NOT the same.")] - [SuppressMessage("Microsoft.Design", "CA1056:UriPropertiesShouldNotBeStrings", Justification = "This can take ~/ paths.")] - public string PrivacyUrl { - get { - return (string)this.ViewState[PrivacyUrlViewStateKey] ?? PrivacyUrlDefault; - } - - set { - ErrorUtilities.VerifyOperation(string.IsNullOrEmpty(value) || this.Page == null || this.DesignMode || (HttpContext.Current != null && HttpContext.Current.Request != null), MessagingStrings.HttpContextRequired); - if (!string.IsNullOrEmpty(value)) { - if (this.Page != null && !this.DesignMode) { - // Validate new value by trying to construct a Uri based on it. - new Uri(new HttpRequestInfo(HttpContext.Current.Request).UrlBeforeRewriting, this.Page.ResolveUrl(value)); // throws an exception on failure. - } else { - // We can't fully test it, but it should start with either ~/ or a protocol. - if (Regex.IsMatch(value, @"^https?://")) { - new Uri(value); // make sure it's fully-qualified, but ignore wildcards - } else if (value.StartsWith("~/", StringComparison.Ordinal)) { - // this is valid too - } else { - throw new UriFormatException(); - } - } - } - - this.ViewState[PrivacyUrlViewStateKey] = value; - } - } - - /// <summary> - /// Gets or sets the version of the privacy policy file. - /// </summary> - [Description("Specifies the version of the privacy policy file")] - [Category(InfoCardCategory), DefaultValue(PrivacyVersionDefault)] - public string PrivacyVersion { - get { return (string)this.ViewState[PrivacyVersionViewStateKey] ?? PrivacyVersionDefault; } - set { this.ViewState[PrivacyVersionViewStateKey] = value; } - } - - /// <summary> - /// Gets or sets the URI that must be found for the SAML token's intended audience - /// in order for the token to be processed. - /// </summary> - /// <value>Typically the URI of the page hosting the control, or <c>null</c> to disable audience verification.</value> - /// <remarks> - /// Disabling audience verification introduces a security risk - /// because tokens can be redirected to allow access to unintended resources. - /// </remarks> - [Description("Specifies the URI that must be found for the SAML token's intended audience.")] - [Bindable(true), Category(InfoCardCategory)] - [TypeConverter(typeof(ComponentModel.UriConverter))] - [UrlProperty, Editor("System.Web.UI.Design.UrlEditor, System.Design, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", typeof(UITypeEditor))] - public Uri Audience { - get { - return (Uri)this.ViewState[AudienceViewStateKey]; - } - - set { - this.ViewState[AudienceViewStateKey] = value; - this.audienceSet = true; - } - } - - /// <summary> - /// Gets or sets a value indicating whether a postback will automatically - /// be invoked when the user selects an Information Card. - /// </summary> - [Description("Specifies if the pages automatically posts back after the user has selected a card")] - [Category(BehaviorCategory), DefaultValue(AutoPostBackDefault)] - public bool AutoPostBack { - get { return (bool)(this.ViewState[AutoPostBackViewStateKey] ?? AutoPostBackDefault); } - set { this.ViewState[AutoPostBackViewStateKey] = value; } - } - - /// <summary> - /// Gets or sets the size of the standard InfoCard image to display. - /// </summary> - /// <value>The default size is 114x80.</value> - [Description("The size of the InfoCard image to use. Defaults to 114x80.")] - [DefaultValue(InfoCardImageDefault), Category(AppearanceCategory)] - public InfoCardImageSize ImageSize { - get { return (InfoCardImageSize)(this.ViewState[ImageSizeViewStateKey] ?? InfoCardImageDefault); } - set { this.ViewState[ImageSizeViewStateKey] = value; } - } - - /// <summary> - /// Gets or sets the template to display when the user agent lacks - /// an Information Card selector. - /// </summary> - [Browsable(false), DefaultValue("")] - [PersistenceMode(PersistenceMode.InnerProperty), TemplateContainer(typeof(InfoCardSelector))] - public virtual ITemplate UnsupportedTemplate { get; set; } - - /// <summary> - /// Gets or sets a value indicating whether a hidden region (either - /// the unsupported or supported InfoCard HTML) - /// collapses or merely becomes invisible when it is not to be displayed. - /// </summary> - [Description("Whether the hidden region collapses or merely becomes invisible.")] - [Category(AppearanceCategory), DefaultValue(RenderModeDefault)] - public RenderMode RenderMode { - get { return (RenderMode)(this.ViewState[RenderModeViewStateKey] ?? RenderModeDefault); } - set { this.ViewState[RenderModeViewStateKey] = value; } - } - - /// <summary> - /// Gets or sets a value indicating whether the identity selector will be triggered at page load. - /// </summary> - [Description("Controls whether the InfoCard selector automatically appears when the page is loaded.")] - [Category(BehaviorCategory), DefaultValue(AutoPopupDefault)] - public bool AutoPopup { - get { return (bool)(this.ViewState[AutoPopupViewStateKey] ?? AutoPopupDefault); } - set { this.ViewState[AutoPopupViewStateKey] = value; } - } - - #endregion - - /// <summary> - /// Gets the name of the hidden field that is used to transport the token back to the server. - /// </summary> - private string HiddenFieldName { - get { return this.ClientID + "_tokenxml"; } - } - - /// <summary> - /// Gets the id of the OBJECT tag that creates the InfoCard Selector. - /// </summary> - private string SelectorObjectId { - get { return this.ClientID + "_cs"; } - } - - /// <summary> - /// Gets the XML token, which will be encrypted if it was received over SSL. - /// </summary> - private string TokenXml { - get { return this.Page.Request.Form[this.HiddenFieldName]; } - } - - /// <summary> - /// Gets or sets the type of token the page is prepared to receive. - /// </summary> - [Description("Specifies the token type. Defaults to SAML 1.0")] - [DefaultValue(TokenTypeDefault), Category(InfoCardCategory)] - private string TokenType { - get { return (string)this.ViewState[TokenTypeViewStateKey] ?? TokenTypeDefault; } - set { this.ViewState[TokenTypeViewStateKey] = value; } - } - - /// <summary> - /// When implemented by a class, enables a server control to process an event raised when a form is posted to the server. - /// </summary> - /// <param name="eventArgument">A <see cref="T:System.String"/> that represents an optional event argument to be passed to the event handler.</param> - void IPostBackEventHandler.RaisePostBackEvent(string eventArgument) { - this.RaisePostBackEvent(eventArgument); - } - - /// <summary> - /// When implemented by a class, enables a server control to process an event raised when a form is posted to the server. - /// </summary> - /// <param name="eventArgument">A <see cref="T:System.String"/> that represents an optional event argument to be passed to the event handler.</param> - [SuppressMessage("Microsoft.Design", "CA1030:UseEventsWhereAppropriate", Justification = "Predefined signature.")] - protected virtual void RaisePostBackEvent(string eventArgument) { - if (!string.IsNullOrEmpty(this.TokenXml)) { - try { - ReceivingTokenEventArgs receivingArgs = this.OnReceivingToken(this.TokenXml); - - if (!receivingArgs.Cancel) { - try { - Token token = Token.Read(this.TokenXml, this.Audience, receivingArgs.DecryptingTokens); - this.OnReceivedToken(token); - } catch (InformationCardException ex) { - this.OnTokenProcessingError(this.TokenXml, ex); - } - } - } catch (XmlException ex) { - this.OnTokenProcessingError(this.TokenXml, ex); - } - } - } - - /// <summary> - /// Fires the <see cref="ReceivingToken"/> event. - /// </summary> - /// <param name="tokenXml">The token XML, prior to any processing.</param> - /// <returns>The event arguments sent to the event handlers.</returns> - [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "decryptor", Justification = "By design")] - protected virtual ReceivingTokenEventArgs OnReceivingToken(string tokenXml) { - Contract.Requires<ArgumentNullException>(tokenXml != null); - - var args = new ReceivingTokenEventArgs(tokenXml); - var receivingToken = this.ReceivingToken; - if (receivingToken != null) { - receivingToken(this, args); - } - - return args; - } - - /// <summary> - /// Fires the <see cref="ReceivedToken"/> event. - /// </summary> - /// <param name="token">The token, if it was decrypted.</param> - protected virtual void OnReceivedToken(Token token) { - Contract.Requires<ArgumentNullException>(token != null); - - var receivedInfoCard = this.ReceivedToken; - if (receivedInfoCard != null) { - receivedInfoCard(this, new ReceivedTokenEventArgs(token)); - } - } - - /// <summary> - /// Fires the <see cref="TokenProcessingError"/> event. - /// </summary> - /// <param name="unprocessedToken">The unprocessed token.</param> - /// <param name="ex">The exception generated while processing the token.</param> - protected virtual void OnTokenProcessingError(string unprocessedToken, Exception ex) { - Contract.Requires<ArgumentNullException>(unprocessedToken != null); - Contract.Requires<ArgumentNullException>(ex != null); - - var tokenProcessingError = this.TokenProcessingError; - if (tokenProcessingError != null) { - TokenProcessingErrorEventArgs args = new TokenProcessingErrorEventArgs(unprocessedToken, ex); - tokenProcessingError(this, args); - } - } - - /// <summary> - /// Raises the <see cref="E:System.Web.UI.Control.Init"/> event. - /// </summary> - /// <param name="e">An <see cref="T:System.EventArgs"/> object that contains the event data.</param> - protected override void OnInit(EventArgs e) { - // Give a default for the Audience property that allows for - // the aspx page to have preset it, and ViewState - // to initialize it (even to null) after this. - if (!this.audienceSet && !this.DesignMode) { - this.Audience = this.Page.Request.Url; - } - - base.OnInit(e); - this.Page.LoadComplete += delegate { this.EnsureChildControls(); }; - } - - /// <summary> - /// Called by the ASP.NET page framework to notify server controls that use composition-based implementation to create any child controls they contain in preparation for posting back or rendering. - /// </summary> - protected override void CreateChildControls() { - base.CreateChildControls(); - - this.Page.ClientScript.RegisterHiddenField(this.HiddenFieldName, ""); - - this.Controls.Add(this.infoCardSupportedPanel = this.CreateInfoCardSupportedPanel()); - this.Controls.Add(this.infoCardNotSupportedPanel = this.CreateInfoCardUnsupportedPanel()); - - this.RenderSupportingScript(); - } - - /// <summary> - /// Raises the <see cref="E:System.Web.UI.Control.PreRender"/> event. - /// </summary> - /// <param name="e">An <see cref="T:System.EventArgs"/> object that contains the event data.</param> - protected override void OnPreRender(EventArgs e) { - base.OnPreRender(e); - - if (!this.DesignMode) { - // The Cardspace selector will display an ugly error to the user if - // the privacy URL is present but the privacy version is not. - ErrorUtilities.VerifyOperation(string.IsNullOrEmpty(this.PrivacyUrl) || !string.IsNullOrEmpty(this.PrivacyVersion), InfoCardStrings.PrivacyVersionRequiredWithPrivacyUrl); - } - - this.RegisterInfoCardSelectorObjectScript(); - } - - /// <summary> - /// Creates a control that renders to <Param Name="{0}" Value="{1}" /> - /// </summary> - /// <param name="name">The parameter name.</param> - /// <param name="value">The parameter value.</param> - /// <returns>The control that renders to the Param tag.</returns> - private static string CreateParamJs(string name, string value) { - Contract.Ensures(Contract.Result<string>() != null); - string scriptFormat = @" objp = document.createElement('param'); - objp.name = {0}; - objp.value = {1}; - obj.appendChild(objp); -"; - return string.Format( - CultureInfo.InvariantCulture, - scriptFormat, - MessagingUtilities.GetSafeJavascriptValue(name), - MessagingUtilities.GetSafeJavascriptValue(value)); - } - - /// <summary> - /// Creates the panel whose contents are displayed to the user - /// on a user agent that has an Information Card selector. - /// </summary> - /// <returns>The Panel control</returns> - [Pure] - private Panel CreateInfoCardSupportedPanel() { - Contract.Ensures(Contract.Result<Panel>() != null); - - Panel supportedPanel = new Panel(); - - try { - if (!this.DesignMode) { - // At the user agent, assume InfoCard is not supported until - // the JavaScript discovers otherwise and reveals this panel. - supportedPanel.Style[HtmlTextWriterStyle.Display] = "none"; - } - - supportedPanel.Controls.Add(this.CreateInfoCardImage()); - - // trigger the selector at page load? - if (this.AutoPopup && !this.Page.IsPostBack) { - this.Page.ClientScript.RegisterStartupScript( - typeof(InfoCardSelector), - "selector_load_trigger", - this.GetInfoCardSelectorActivationScript(true), - true); - } - return supportedPanel; - } catch { - supportedPanel.Dispose(); - throw; - } - } - - /// <summary> - /// Gets the InfoCard selector activation script. - /// </summary> - /// <param name="alwaysPostback">Whether a postback should always immediately follow the selector, even if <see cref="AutoPostBack"/> is <c>false</c>.</param> - /// <returns>The javascript to inject into the surrounding context.</returns> - private string GetInfoCardSelectorActivationScript(bool alwaysPostback) { - // generate call do __doPostback - PostBackOptions options = new PostBackOptions(this); - string postback = string.Empty; - if (alwaysPostback || this.AutoPostBack) { - postback = this.Page.ClientScript.GetPostBackEventReference(options) + ";"; - } - - // generate the onclick script for the image - string invokeScript = string.Format( - CultureInfo.InvariantCulture, - @"if (document.infoCard.activate('{0}', '{1}')) {{ {2} }}", - this.SelectorObjectId, - this.HiddenFieldName, - postback); - - return invokeScript; - } - - /// <summary> - /// Creates the panel whose contents are displayed to the user - /// on a user agent that does not have an Information Card selector. - /// </summary> - /// <returns>The Panel control.</returns> - [Pure] - private Panel CreateInfoCardUnsupportedPanel() { - Contract.Ensures(Contract.Result<Panel>() != null); - - Panel unsupportedPanel = new Panel(); - try { - if (this.UnsupportedTemplate != null) { - this.UnsupportedTemplate.InstantiateIn(unsupportedPanel); - } - return unsupportedPanel; - } catch { - unsupportedPanel.Dispose(); - throw; - } - } - - /// <summary> - /// Adds the javascript that adds the info card selector <object> HTML tag to the page. - /// </summary> - [Pure] - private void RegisterInfoCardSelectorObjectScript() { - string scriptFormat = @"{{ - var obj = document.createElement('object'); - obj.type = 'application/x-informationcard'; - obj.id = {0}; - obj.style.display = 'none'; -"; - StringBuilder script = new StringBuilder(); - script.AppendFormat( - CultureInfo.InvariantCulture, - scriptFormat, - MessagingUtilities.GetSafeJavascriptValue(this.ClientID + "_cs")); - - if (!string.IsNullOrEmpty(this.Issuer)) { - script.AppendLine(CreateParamJs("issuer", this.Issuer)); - } - - if (!string.IsNullOrEmpty(this.IssuerPolicy)) { - script.AppendLine(CreateParamJs("issuerPolicy", this.IssuerPolicy)); - } - - if (!string.IsNullOrEmpty(this.TokenType)) { - script.AppendLine(CreateParamJs("tokenType", this.TokenType)); - } - - string requiredClaims, optionalClaims; - this.GetRequestedClaims(out requiredClaims, out optionalClaims); - ErrorUtilities.VerifyArgument(!string.IsNullOrEmpty(requiredClaims) || !string.IsNullOrEmpty(optionalClaims), InfoCardStrings.EmptyClaimListNotAllowed); - if (!string.IsNullOrEmpty(requiredClaims)) { - script.AppendLine(CreateParamJs("requiredClaims", requiredClaims)); - } - if (!string.IsNullOrEmpty(optionalClaims)) { - script.AppendLine(CreateParamJs("optionalClaims", optionalClaims)); - } - - if (!string.IsNullOrEmpty(this.PrivacyUrl)) { - string privacyUrl = this.DesignMode ? this.PrivacyUrl : new Uri(Page.Request.Url, Page.ResolveUrl(this.PrivacyUrl)).AbsoluteUri; - script.AppendLine(CreateParamJs("privacyUrl", privacyUrl)); - } - - if (!string.IsNullOrEmpty(this.PrivacyVersion)) { - script.AppendLine(CreateParamJs("privacyVersion", this.PrivacyVersion)); - } - - script.AppendLine(@"if (document.infoCard.isSupported()) { document.write(obj.outerHTML); } -}"); - - this.Page.ClientScript.RegisterClientScriptBlock(typeof(InfoCardSelector), this.ClientID + "tag", script.ToString(), true); - } - - /// <summary> - /// Creates the info card clickable image. - /// </summary> - /// <returns>An Image object.</returns> - [Pure] - private Image CreateInfoCardImage() { - // add clickable image - Image image = new Image(); - try { - image.ImageUrl = this.Page.ClientScript.GetWebResourceUrl(typeof(InfoCardSelector), InfoCardImage.GetImageManifestResourceStreamName(this.ImageSize)); - image.AlternateText = InfoCardStrings.SelectorClickPrompt; - image.ToolTip = this.ToolTip; - image.Style[HtmlTextWriterStyle.Cursor] = "hand"; - - image.Attributes["onclick"] = this.GetInfoCardSelectorActivationScript(false); - return image; - } catch { - image.Dispose(); - throw; - } - } - - /// <summary> - /// Compiles lists of requested/required claims that should accompany - /// any submitted Information Card. - /// </summary> - /// <param name="required">A space-delimited list of claim type URIs for claims that must be included in a submitted Information Card.</param> - /// <param name="optional">A space-delimited list of claim type URIs for claims that may optionally be included in a submitted Information Card.</param> - [Pure] - private void GetRequestedClaims(out string required, out string optional) { - Contract.Requires<InvalidOperationException>(this.ClaimsRequested != null); - Contract.Ensures(Contract.ValueAtReturn<string>(out required) != null); - Contract.Ensures(Contract.ValueAtReturn<string>(out optional) != null); - - var nonEmptyClaimTypes = this.ClaimsRequested.Where(c => c.Name != null); - - var optionalClaims = from claim in nonEmptyClaimTypes - where claim.IsOptional - select claim.Name; - var requiredClaims = from claim in nonEmptyClaimTypes - where !claim.IsOptional - select claim.Name; - - string[] requiredClaimsArray = requiredClaims.ToArray(); - string[] optionalClaimsArray = optionalClaims.ToArray(); - required = string.Join(" ", requiredClaimsArray); - optional = string.Join(" ", optionalClaimsArray); - Contract.Assume(required != null); - Contract.Assume(optional != null); - } - - /// <summary> - /// Adds Javascript snippets to the page to help the Information Card selector do its work, - /// or to downgrade gracefully if the user agent lacks an Information Card selector. - /// </summary> - private void RenderSupportingScript() { - Contract.Requires<InvalidOperationException>(this.infoCardSupportedPanel != null); - - this.Page.ClientScript.RegisterClientScriptResource(typeof(InfoCardSelector), ScriptResourceName); - - if (this.RenderMode == RenderMode.Static) { - this.Page.ClientScript.RegisterStartupScript( - typeof(InfoCardSelector), - "SelectorSupportingScript_" + this.ClientID, - string.Format(CultureInfo.InvariantCulture, "document.infoCard.checkStatic('{0}', '{1}');", this.infoCardSupportedPanel.ClientID, this.infoCardNotSupportedPanel.ClientID), - true); - } else if (RenderMode == RenderMode.Dynamic) { - this.Page.ClientScript.RegisterStartupScript( - typeof(InfoCardSelector), - "SelectorSupportingScript_" + this.ClientID, - string.Format(CultureInfo.InvariantCulture, "document.infoCard.checkDynamic('{0}', '{1}');", this.infoCardSupportedPanel.ClientID, this.infoCardNotSupportedPanel.ClientID), - true); - } - } - } -} diff --git a/src/DotNetOpenAuth/InfoCard/ReceivingTokenEventArgs.cs b/src/DotNetOpenAuth/InfoCard/ReceivingTokenEventArgs.cs deleted file mode 100644 index 3dd892a..0000000 --- a/src/DotNetOpenAuth/InfoCard/ReceivingTokenEventArgs.cs +++ /dev/null @@ -1,100 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="ReceivingTokenEventArgs.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.InfoCard { - using System; - using System.Collections.Generic; - using System.Diagnostics.CodeAnalysis; - using System.Diagnostics.Contracts; - using System.IdentityModel.Tokens; - using System.Security.Cryptography.X509Certificates; - - /// <summary> - /// Arguments for the <see cref="InfoCardSelector.ReceivingToken"/> event. - /// </summary> - public class ReceivingTokenEventArgs : EventArgs { - /// <summary> - /// Initializes a new instance of the <see cref="ReceivingTokenEventArgs"/> class. - /// </summary> - /// <param name="tokenXml">The raw token XML, prior to any decryption.</param> - internal ReceivingTokenEventArgs(string tokenXml) { - Contract.Requires<ArgumentNullException>(tokenXml != null); - - this.TokenXml = tokenXml; - this.IsEncrypted = Token.IsEncrypted(this.TokenXml); - this.DecryptingTokens = new List<SecurityToken>(); - } - - /// <summary> - /// Gets a value indicating whether the token is encrypted. - /// </summary> - /// <value> - /// <c>true</c> if the token is encrypted; otherwise, <c>false</c>. - /// </value> - public bool IsEncrypted { get; private set; } - - /// <summary> - /// Gets the raw token XML, prior to any decryption. - /// </summary> - public string TokenXml { get; private set; } - - /// <summary> - /// Gets or sets a value indicating whether processing - /// this token should be canceled. - /// </summary> - /// <value><c>true</c> if cancel; otherwise, <c>false</c>.</value> - /// <remarks> - /// If set the <c>true</c>, the <see cref="InfoCardSelector.ReceivedToken"/> - /// event will never be fired. - /// </remarks> - public bool Cancel { get; set; } - - /// <summary> - /// Gets a list where security tokens such as X.509 certificates may be - /// added to be used for token decryption. - /// </summary> - internal IList<SecurityToken> DecryptingTokens { get; private set; } - - /// <summary> - /// Adds a security token that may be used to decrypt the incoming token. - /// </summary> - /// <param name="securityToken">The security token.</param> - public void AddDecryptingToken(SecurityToken securityToken) { - Contract.Requires<ArgumentNullException>(securityToken != null); - this.DecryptingTokens.Add(securityToken); - } - - /// <summary> - /// Adds an X.509 certificate with a private key that may be used to decrypt the incoming token. - /// </summary> - /// <param name="certificate">The certificate.</param> - [SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "False positive")] - public void AddDecryptingToken(X509Certificate2 certificate) { - Contract.Requires<ArgumentNullException>(certificate != null); - Contract.Requires<ArgumentException>(certificate.HasPrivateKey); - var cert = new X509SecurityToken(certificate); - try { - this.AddDecryptingToken(cert); - } catch { - cert.Dispose(); - throw; - } - } - -#if CONTRACTS_FULL - /// <summary> - /// Verifies conditions that should be true for any valid state of this object. - /// </summary> - [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Called by code contracts.")] - [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called by code contracts.")] - [ContractInvariantMethod] - private void ObjectInvariant() { - Contract.Invariant(this.TokenXml != null); - Contract.Invariant(this.DecryptingTokens != null); - } -#endif - } -} diff --git a/src/DotNetOpenAuth/InfoCard/Token/Token.cs b/src/DotNetOpenAuth/InfoCard/Token/Token.cs deleted file mode 100644 index 3b6f573..0000000 --- a/src/DotNetOpenAuth/InfoCard/Token/Token.cs +++ /dev/null @@ -1,269 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="Token.cs" company="Andrew Arnott, Microsoft Corporation"> -// Copyright (c) Andrew Arnott, Microsoft Corporation. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.InfoCard { - using System; - using System.Collections.Generic; - using System.Diagnostics.CodeAnalysis; - using System.Diagnostics.Contracts; - using System.IdentityModel.Claims; - using System.IdentityModel.Policy; - using System.IdentityModel.Tokens; - using System.IO; - using System.Linq; - using System.Text; - using System.Xml; - using System.Xml.XPath; - using DotNetOpenAuth.Messaging; - - /// <summary> - /// The decrypted token that was submitted as an Information Card. - /// </summary> - [ContractVerification(true)] - public class Token { - /// <summary> - /// Backing field for the <see cref="Claims"/> property. - /// </summary> - private IDictionary<string, string> claims; - - /// <summary> - /// Backing field for the <see cref="UniqueId"/> property. - /// </summary> - private string uniqueId; - - /// <summary> - /// Initializes a new instance of the <see cref="Token"/> class. - /// </summary> - /// <param name="tokenXml">Xml token, which may be encrypted.</param> - /// <param name="audience">The audience. May be <c>null</c> to avoid audience checking.</param> - /// <param name="decryptor">The decryptor to use to decrypt the token, if necessary..</param> - /// <exception cref="InformationCardException">Thrown for any problem decoding or decrypting the token.</exception> - [SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times", Justification = "Not a problem for this type."), SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "False positive")] - private Token(string tokenXml, Uri audience, TokenDecryptor decryptor) { - Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(tokenXml)); - Contract.Requires<ArgumentException>(decryptor != null || !IsEncrypted(tokenXml)); - Contract.Ensures(this.AuthorizationContext != null); - - byte[] decryptedBytes; - string decryptedString; - - using (StringReader xmlReader = new StringReader(tokenXml)) { - using (XmlReader tokenReader = XmlReader.Create(xmlReader)) { - Contract.Assume(tokenReader != null); // BCL contract should say XmlReader.Create result != null - if (IsEncrypted(tokenReader)) { - Logger.InfoCard.DebugFormat("Incoming SAML token, before decryption: {0}", tokenXml); - decryptedBytes = decryptor.DecryptToken(tokenReader); - decryptedString = Encoding.UTF8.GetString(decryptedBytes); - Contract.Assume(decryptedString != null); // BCL contracts should be enhanced here - } else { - decryptedBytes = Encoding.UTF8.GetBytes(tokenXml); - decryptedString = tokenXml; - } - } - } - - var stringReader = new StringReader(decryptedString); - try { - this.Xml = new XPathDocument(stringReader).CreateNavigator(); - } catch { - stringReader.Dispose(); - throw; - } - - Logger.InfoCard.DebugFormat("Incoming SAML token, after any decryption: {0}", this.Xml.InnerXml); - this.AuthorizationContext = TokenUtility.AuthenticateToken(this.Xml.ReadSubtree(), audience); - } - - /// <summary> - /// Gets the AuthorizationContext behind this token. - /// </summary> - public AuthorizationContext AuthorizationContext { get; private set; } - - /// <summary> - /// Gets the the decrypted token XML. - /// </summary> - public XPathNavigator Xml { get; private set; } - - /// <summary> - /// Gets the UniqueID of this token, usable as a stable username that the user - /// has already verified belongs to him/her. - /// </summary> - /// <remarks> - /// By default, this uses the PPID and the Issuer's Public Key and hashes them - /// together to generate a UniqueID. - /// </remarks> - public string UniqueId { - get { - if (string.IsNullOrEmpty(this.uniqueId)) { - this.uniqueId = TokenUtility.GetUniqueName(this.AuthorizationContext); - } - - return this.uniqueId; - } - } - - /// <summary> - /// Gets the hash of the card issuer's public key. - /// </summary> - public string IssuerPubKeyHash { - get { return TokenUtility.GetIssuerPubKeyHash(this.AuthorizationContext); } - } - - /// <summary> - /// Gets the Site Specific ID that the user sees in the Identity Selector. - /// </summary> - public string SiteSpecificId { - get { - Contract.Requires<InvalidOperationException>(this.Claims.ContainsKey(ClaimTypes.PPID) && !string.IsNullOrEmpty(this.Claims[ClaimTypes.PPID])); - string ppidValue; - ErrorUtilities.VerifyOperation(this.Claims.TryGetValue(ClaimTypes.PPID, out ppidValue) && ppidValue != null, InfoCardStrings.PpidClaimRequired); - return TokenUtility.CalculateSiteSpecificID(ppidValue); - } - } - - /// <summary> - /// Gets the claims in all the claimsets as a dictionary of strings. - /// </summary> - public IDictionary<string, string> Claims { - get { - if (this.claims == null) { - this.claims = this.GetFlattenedClaims(); - } - - return this.claims; - } - } - - /// <summary> - /// Deserializes an XML document into a token. - /// </summary> - /// <param name="tokenXml">The token XML.</param> - /// <returns>The deserialized token.</returns> - public static Token Read(string tokenXml) { - Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(tokenXml)); - return Read(tokenXml, (Uri)null); - } - - /// <summary> - /// Deserializes an XML document into a token. - /// </summary> - /// <param name="tokenXml">The token XML.</param> - /// <param name="audience">The URI that this token must have been crafted to be sent to. Use <c>null</c> to accept any intended audience.</param> - /// <returns>The deserialized token.</returns> - public static Token Read(string tokenXml, Uri audience) { - Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(tokenXml)); - return Read(tokenXml, audience, Enumerable.Empty<SecurityToken>()); - } - - /// <summary> - /// Deserializes an XML document into a token. - /// </summary> - /// <param name="tokenXml">The token XML.</param> - /// <param name="decryptionTokens">Any X.509 certificates that may be used to decrypt the token, if necessary.</param> - /// <returns>The deserialized token.</returns> - public static Token Read(string tokenXml, IEnumerable<SecurityToken> decryptionTokens) { - Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(tokenXml)); - Contract.Requires<ArgumentNullException>(decryptionTokens != null); - return Read(tokenXml, null, decryptionTokens); - } - - /// <summary> - /// Deserializes an XML document into a token. - /// </summary> - /// <param name="tokenXml">The token XML.</param> - /// <param name="audience">The URI that this token must have been crafted to be sent to. Use <c>null</c> to accept any intended audience.</param> - /// <param name="decryptionTokens">Any X.509 certificates that may be used to decrypt the token, if necessary.</param> - /// <returns>The deserialized token.</returns> - public static Token Read(string tokenXml, Uri audience, IEnumerable<SecurityToken> decryptionTokens) { - Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(tokenXml)); - Contract.Requires<ArgumentNullException>(decryptionTokens != null); - Contract.Ensures(Contract.Result<Token>() != null); - - TokenDecryptor decryptor = null; - - if (IsEncrypted(tokenXml)) { - decryptor = new TokenDecryptor(); - decryptor.Tokens.AddRange(decryptionTokens); - } - - return new Token(tokenXml, audience, decryptor); - } - - /// <summary> - /// Determines whether the specified token XML is encrypted. - /// </summary> - /// <param name="tokenXml">The token XML.</param> - /// <returns> - /// <c>true</c> if the specified token XML is encrypted; otherwise, <c>false</c>. - /// </returns> - [SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "False positive"), Pure] - internal static bool IsEncrypted(string tokenXml) { - Contract.Requires<ArgumentNullException>(tokenXml != null); - - var stringReader = new StringReader(tokenXml); - XmlReader tokenReader; - try { - tokenReader = XmlReader.Create(stringReader); - } catch { - stringReader.Dispose(); - throw; - } - - try { - Contract.Assume(tokenReader != null); // CC missing for XmlReader.Create - return IsEncrypted(tokenReader); - } catch { - IDisposable disposableReader = tokenReader; - disposableReader.Dispose(); - throw; - } - } - - /// <summary> - /// Determines whether the specified token XML is encrypted. - /// </summary> - /// <param name="tokenXmlReader">The token XML.</param> - /// <returns> - /// <c>true</c> if the specified token XML is encrypted; otherwise, <c>false</c>. - /// </returns> - private static bool IsEncrypted(XmlReader tokenXmlReader) { - Contract.Requires<ArgumentNullException>(tokenXmlReader != null); - return tokenXmlReader.IsStartElement(TokenDecryptor.XmlEncryptionStrings.EncryptedData, TokenDecryptor.XmlEncryptionStrings.Namespace); - } - -#if CONTRACTS_FULL - /// <summary> - /// Verifies conditions that should be true for any valid state of this object. - /// </summary> - [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Called by code contracts.")] - [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called by code contracts.")] - [ContractInvariantMethod] - private void ObjectInvariant() { - Contract.Invariant(this.AuthorizationContext != null); - } -#endif - - /// <summary> - /// Flattens the claims into a dictionary - /// </summary> - /// <returns>A dictionary of claim type URIs and claim values.</returns> - [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "Expensive call.")] - [Pure] - private IDictionary<string, string> GetFlattenedClaims() { - var flattenedClaims = new Dictionary<string, string>(); - - foreach (ClaimSet set in this.AuthorizationContext.ClaimSets) { - foreach (Claim claim in set) { - if (claim.Right == Rights.PossessProperty) { - flattenedClaims.Add(claim.ClaimType, TokenUtility.GetResourceValue(claim)); - } - } - } - - return flattenedClaims; - } - } -} diff --git a/src/DotNetOpenAuth/InfoCard/Token/TokenDecryptor.cs b/src/DotNetOpenAuth/InfoCard/Token/TokenDecryptor.cs deleted file mode 100644 index 9424480..0000000 --- a/src/DotNetOpenAuth/InfoCard/Token/TokenDecryptor.cs +++ /dev/null @@ -1,210 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="TokenDecryptor.cs" company="Microsoft Corporation"> -// Copyright (c) Microsoft Corporation. All rights reserved. -// </copyright> -// <license> -// Microsoft Public License (Ms-PL). -// See http://www.microsoft.com/opensource/licenses.mspx#Ms-PL -// </license> -// <author>This file was subsequently modified by Andrew Arnott.</author> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.InfoCard { - using System; - using System.Collections.Generic; - using System.Diagnostics.CodeAnalysis; - using System.Diagnostics.Contracts; - using System.IdentityModel.Selectors; - using System.IdentityModel.Tokens; - using System.Linq; - using System.Security.Cryptography; - using System.Security.Cryptography.X509Certificates; - using System.ServiceModel.Security; - using System.Xml; - using DotNetOpenAuth.Messaging; - - /// <summary> - /// A utility class for decrypting InfoCard tokens. - /// </summary> - [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Decryptor", Justification = "By design")] - internal class TokenDecryptor { - /// <summary> - /// Backing field for the <see cref="Tokens"/> property. - /// </summary> - private List<SecurityToken> tokens; - - /// <summary> - /// Initializes a new instance of the <see cref="TokenDecryptor"/> class. - /// </summary> - internal TokenDecryptor() { - this.tokens = new List<SecurityToken>(); - StoreName storeName = StoreName.My; - StoreLocation storeLocation = StoreLocation.LocalMachine; - this.AddDecryptionCertificates(storeName, storeLocation); - } - - /// <summary> - /// Gets a list of possible decryption certificates, from the store/location set - /// </summary> - /// <remarks> - /// Defaults to localmachine:my (same place SSL certs are) - /// </remarks> - internal List<SecurityToken> Tokens { - get { return this.tokens; } - } - - /// <summary> - /// Adds a certificate to the list of certificates to decrypt with. - /// </summary> - /// <param name="certificate">The x509 cert to use for decryption</param> - internal void AddDecryptionCertificate(X509Certificate2 certificate) { - this.Tokens.Add(new X509SecurityToken(certificate)); - } - - /// <summary> - /// Adds a certificate to the list of certificates to decrypt with. - /// </summary> - /// <param name="storeName">store name of the certificate</param> - /// <param name="storeLocation">store location</param> - /// <param name="thumbprint">thumbprint of the cert to use</param> - internal void AddDecryptionCertificate(StoreName storeName, StoreLocation storeLocation, string thumbprint) { - this.AddDecryptionCertificates( - storeName, - storeLocation, - store => store.Find(X509FindType.FindByThumbprint, thumbprint, true)); - } - - /// <summary> - /// Adds a store of certificates to the list of certificates to decrypt with. - /// </summary> - /// <param name="storeName">store name of the certificates</param> - /// <param name="storeLocation">store location</param> - internal void AddDecryptionCertificates(StoreName storeName, StoreLocation storeLocation) { - this.AddDecryptionCertificates(storeName, storeLocation, store => store); - } - - /// <summary> - /// Decrpyts a security token from an XML EncryptedData - /// </summary> - /// <param name="reader">The encrypted token XML reader.</param> - /// <returns>A byte array of the contents of the encrypted token</returns> - internal byte[] DecryptToken(XmlReader reader) { - Contract.Requires<ArgumentNullException>(reader != null); - Contract.Ensures(Contract.Result<byte[]>() != null); - - byte[] securityTokenData; - string encryptionAlgorithm; - SecurityKeyIdentifier keyIdentifier; - bool isEmptyElement; - - ErrorUtilities.VerifyInternal(reader.IsStartElement(XmlEncryptionStrings.EncryptedData, XmlEncryptionStrings.Namespace), "Expected encrypted token starting XML element was not found."); - reader.Read(); // get started - - // if it's not an encryption method, something is dreadfully wrong. - ErrorUtilities.VerifyInfoCard(reader.IsStartElement(XmlEncryptionStrings.EncryptionMethod, XmlEncryptionStrings.Namespace), InfoCardStrings.EncryptionAlgorithmNotFound); - - // Looks good, let's grab the alg. - isEmptyElement = reader.IsEmptyElement; - encryptionAlgorithm = reader.GetAttribute(XmlEncryptionStrings.Algorithm); - reader.Read(); - - if (!isEmptyElement) { - while (reader.IsStartElement()) { - reader.Skip(); - } - reader.ReadEndElement(); - } - - // get the key identifier - keyIdentifier = WSSecurityTokenSerializer.DefaultInstance.ReadKeyIdentifier(reader); - - // resolve the symmetric key - SymmetricSecurityKey decryptingKey = (SymmetricSecurityKey)SecurityTokenResolver.CreateDefaultSecurityTokenResolver(this.tokens.AsReadOnly(), false).ResolveSecurityKey(keyIdentifier[0]); - SymmetricAlgorithm algorithm = decryptingKey.GetSymmetricAlgorithm(encryptionAlgorithm); - - // dig for the security token data itself. - reader.ReadStartElement(XmlEncryptionStrings.CipherData, XmlEncryptionStrings.Namespace); - reader.ReadStartElement(XmlEncryptionStrings.CipherValue, XmlEncryptionStrings.Namespace); - securityTokenData = Convert.FromBase64String(reader.ReadString()); - reader.ReadEndElement(); // CipherValue - reader.ReadEndElement(); // CipherData - reader.ReadEndElement(); // EncryptedData - - // decrypto-magic! - int blockSizeBytes = algorithm.BlockSize / 8; - byte[] iv = new byte[blockSizeBytes]; - Buffer.BlockCopy(securityTokenData, 0, iv, 0, iv.Length); - algorithm.Padding = PaddingMode.ISO10126; - algorithm.Mode = CipherMode.CBC; - ICryptoTransform decrTransform = algorithm.CreateDecryptor(algorithm.Key, iv); - byte[] plainText = decrTransform.TransformFinalBlock(securityTokenData, iv.Length, securityTokenData.Length - iv.Length); - decrTransform.Dispose(); - - return plainText; - } - -#if CONTRACTS_FULL - /// <summary> - /// Verifies conditions that should be true for any valid state of this object. - /// </summary> - [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Called by code contracts.")] - [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called by code contracts.")] - [ContractInvariantMethod] - private void ObjectInvariant() { - Contract.Invariant(this.Tokens != null); - } -#endif - - /// <summary> - /// Adds a store of certificates to the list of certificates to decrypt with. - /// </summary> - /// <param name="storeName">store name of the certificates</param> - /// <param name="storeLocation">store location</param> - /// <param name="filter">A filter to on the certificates to add.</param> - private void AddDecryptionCertificates(StoreName storeName, StoreLocation storeLocation, Func<X509Certificate2Collection, X509Certificate2Collection> filter) { - X509Store store = new X509Store(storeName, storeLocation); - store.Open(OpenFlags.ReadOnly); - - this.tokens.AddRange((from cert in filter(store.Certificates).Cast<X509Certificate2>() - where cert.HasPrivateKey - select new X509SecurityToken(cert)).Cast<SecurityToken>()); - - store.Close(); - } - - /// <summary> - /// A set of strings used in parsing the XML token. - /// </summary> - internal static class XmlEncryptionStrings { - /// <summary> - /// The "http://www.w3.org/2001/04/xmlenc#" value. - /// </summary> - internal const string Namespace = "http://www.w3.org/2001/04/xmlenc#"; - - /// <summary> - /// The "EncryptionMethod" value. - /// </summary> - internal const string EncryptionMethod = "EncryptionMethod"; - - /// <summary> - /// The "CipherValue" value. - /// </summary> - internal const string CipherValue = "CipherValue"; - - /// <summary> - /// The "Algorithm" value. - /// </summary> - internal const string Algorithm = "Algorithm"; - - /// <summary> - /// The "EncryptedData" value. - /// </summary> - internal const string EncryptedData = "EncryptedData"; - - /// <summary> - /// The "CipherData" value. - /// </summary> - internal const string CipherData = "CipherData"; - } - } -}
\ No newline at end of file diff --git a/src/DotNetOpenAuth/InfoCard/Token/TokenUtility.cs b/src/DotNetOpenAuth/InfoCard/Token/TokenUtility.cs deleted file mode 100644 index 4ac871a..0000000 --- a/src/DotNetOpenAuth/InfoCard/Token/TokenUtility.cs +++ /dev/null @@ -1,297 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="TokenUtility.cs" company="Microsoft Corporation"> -// Copyright (c) Microsoft Corporation. All rights reserved. -// </copyright> -// <license> -// Microsoft Public License (Ms-PL). -// See http://www.microsoft.com/opensource/licenses.mspx#Ms-PL -// </license> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.InfoCard { - using System; - using System.Collections.Generic; - using System.Configuration; - using System.Diagnostics.Contracts; - using System.IdentityModel.Claims; - using System.IdentityModel.Policy; - using System.IdentityModel.Selectors; - using System.IdentityModel.Tokens; - using System.IO; - using System.Linq; - using System.Net.Mail; - using System.Security.Cryptography; - using System.Security.Principal; - using System.ServiceModel.Security; - using System.Text; - using System.Xml; - using DotNetOpenAuth.Messaging; - - /// <summary> - /// Tools for reading InfoCard tokens. - /// </summary> - internal static class TokenUtility { - /// <summary> - /// Gets the maximum amount the token can be out of sync with time. - /// </summary> - internal static TimeSpan MaximumClockSkew { - get { return DotNetOpenAuth.Configuration.DotNetOpenAuthSection.Configuration.Messaging.MaximumClockSkew; } - } - - /// <summary> - /// Token Authentication. Translates the decrypted data into a AuthContext. - /// </summary> - /// <param name="reader">The token XML reader.</param> - /// <param name="audience">The audience that the token must be scoped for. - /// Use <c>null</c> to indicate any audience is acceptable.</param> - /// <returns> - /// The authorization context carried by the token. - /// </returns> - internal static AuthorizationContext AuthenticateToken(XmlReader reader, Uri audience) { - Contract.Ensures(Contract.Result<AuthorizationContext>() != null); - - // Extensibility Point: - // in order to accept different token types, you would need to add additional - // code to create an authenticationcontext from the security token. - // This code only supports SamlSecurityToken objects. - SamlSecurityToken token = WSSecurityTokenSerializer.DefaultInstance.ReadToken(reader, null) as SamlSecurityToken; - - if (null == token) { - throw new InformationCardException("Unable to read security token"); - } - - ////if (null != token.SecurityKeys && token.SecurityKeys.Count > 0) - //// throw new InformationCardException("Token Security Keys Exist"); - - if (audience == null) { - Logger.InfoCard.Warn("SAML token Audience checking will be skipped."); - } else { - if (token.Assertion.Conditions != null && - token.Assertion.Conditions.Conditions != null) { - foreach (SamlCondition condition in token.Assertion.Conditions.Conditions) { - SamlAudienceRestrictionCondition audienceCondition = condition as SamlAudienceRestrictionCondition; - - if (audienceCondition != null) { - Logger.InfoCard.DebugFormat("SAML token audience(s): {0}", audienceCondition.Audiences.ToStringDeferred()); - bool match = audienceCondition.Audiences.Contains(audience); - - if (!match && Logger.InfoCard.IsErrorEnabled) { - Logger.InfoCard.ErrorFormat("Expected SAML token audience of {0} but found {1}.", audience.AbsoluteUri, audienceCondition.Audiences.Select(aud => aud.AbsoluteUri).ToStringDeferred()); - } - - // The token is invalid if any condition is not valid. - // An audience restriction condition is valid if any audience - // matches the Relying Party. - ErrorUtilities.VerifyInfoCard(match, InfoCardStrings.AudienceMismatch); - } - } - } - } - var samlAuthenticator = new SamlSecurityTokenAuthenticator( - new List<SecurityTokenAuthenticator>( - new SecurityTokenAuthenticator[] { - new RsaSecurityTokenAuthenticator(), - new X509SecurityTokenAuthenticator(), - }), - MaximumClockSkew); - - return AuthorizationContext.CreateDefaultAuthorizationContext(samlAuthenticator.ValidateToken(token)); - } - - /// <summary> - /// Translates claims to strings - /// </summary> - /// <param name="claim">Claim to translate to a string</param> - /// <returns>The string representation of a claim's value.</returns> - internal static string GetResourceValue(Claim claim) { - string strClaim = claim.Resource as string; - if (!string.IsNullOrEmpty(strClaim)) { - return strClaim; - } - - IdentityReference reference = claim.Resource as IdentityReference; - if (null != reference) { - return reference.Value; - } - - ICspAsymmetricAlgorithm rsa = claim.Resource as ICspAsymmetricAlgorithm; - if (null != rsa) { - using (SHA256 sha = new SHA256Managed()) { - return Convert.ToBase64String(sha.ComputeHash(rsa.ExportCspBlob(false))); - } - } - - MailAddress mail = claim.Resource as MailAddress; - if (null != mail) { - return mail.ToString(); - } - - byte[] bufferValue = claim.Resource as byte[]; - if (null != bufferValue) { - return Convert.ToBase64String(bufferValue); - } - - return claim.Resource.ToString(); - } - - /// <summary> - /// Generates a UniqueID based off the Issuer's key - /// </summary> - /// <param name="authzContext">the Authorization Context</param> - /// <returns>the hash of the internal key of the issuer</returns> - internal static string GetIssuerPubKeyHash(AuthorizationContext authzContext) { - foreach (ClaimSet cs in authzContext.ClaimSets) { - Claim currentIssuerClaim = GetUniqueRsaClaim(cs.Issuer); - - if (currentIssuerClaim != null) { - RSA rsa = currentIssuerClaim.Resource as RSA; - if (null == rsa) { - return null; - } - - return ComputeCombinedId(rsa, ""); - } - } - - return null; - } - - /// <summary> - /// Generates a UniqueID based off the Issuer's key and the PPID. - /// </summary> - /// <param name="authzContext">The Authorization Context</param> - /// <returns>A unique ID for this user at this web site.</returns> - internal static string GetUniqueName(AuthorizationContext authzContext) { - Contract.Requires<ArgumentNullException>(authzContext != null); - - Claim uniqueIssuerClaim = null; - Claim uniqueUserClaim = null; - - foreach (ClaimSet cs in authzContext.ClaimSets) { - Claim currentIssuerClaim = GetUniqueRsaClaim(cs.Issuer); - - foreach (Claim c in cs.FindClaims(ClaimTypes.PPID, Rights.PossessProperty)) { - if (null == currentIssuerClaim) { - // Found a claim in a ClaimSet with no RSA issuer. - return null; - } - - if (null == uniqueUserClaim) { - uniqueUserClaim = c; - uniqueIssuerClaim = currentIssuerClaim; - } else if (!uniqueIssuerClaim.Equals(currentIssuerClaim)) { - // Found two of the desired claims with different - // issuers. No unique name. - return null; - } else if (!uniqueUserClaim.Equals(c)) { - // Found two of the desired claims with different - // values. No unique name. - return null; - } - } - } - - // No claim of the desired type was found - if (null == uniqueUserClaim) { - return null; - } - - // Unexpected resource type - string claimValue = uniqueUserClaim.Resource as string; - if (null == claimValue) { - return null; - } - - // Unexpected resource type for RSA - RSA rsa = uniqueIssuerClaim.Resource as RSA; - if (null == rsa) { - return null; - } - - return ComputeCombinedId(rsa, claimValue); - } - - /// <summary> - /// Generates the Site Specific ID to match the one in the Identity Selector. - /// </summary> - /// <value>The ID displayed by the Identity Selector.</value> - /// <param name="ppid">The personal private identifier.</param> - /// <returns>A string containing the XXX-XXXX-XXX cosmetic value.</returns> - internal static string CalculateSiteSpecificID(string ppid) { - Contract.Requires<ArgumentNullException>(ppid != null); - Contract.Ensures(!string.IsNullOrEmpty(Contract.Result<string>())); - - int callSignChars = 10; - char[] charMap = "QL23456789ABCDEFGHJKMNPRSTUVWXYZ".ToCharArray(); - int charMapLength = charMap.Length; - - byte[] raw = Convert.FromBase64String(ppid); - using (HashAlgorithm hasher = SHA1.Create()) { - raw = hasher.ComputeHash(raw); - } - - StringBuilder callSign = new StringBuilder(); - - for (int i = 0; i < callSignChars; i++) { - // after char 3 and char 7, place a dash - if (i == 3 || i == 7) { - callSign.Append('-'); - } - callSign.Append(charMap[raw[i] % charMapLength]); - } - return callSign.ToString(); - } - - /// <summary> - /// Gets the Unique RSA Claim from the SAML token. - /// </summary> - /// <param name="cs">the claimset which contains the claim</param> - /// <returns>a RSA claim</returns> - private static Claim GetUniqueRsaClaim(ClaimSet cs) { - Contract.Requires<ArgumentNullException>(cs != null); - - Claim rsa = null; - - foreach (Claim c in cs.FindClaims(ClaimTypes.Rsa, Rights.PossessProperty)) { - if (null == rsa) { - rsa = c; - } else if (!rsa.Equals(c)) { - // Found two non-equal RSA claims - return null; - } - } - return rsa; - } - - /// <summary> - /// Does the actual calculation of a combined ID from a value and an RSA key. - /// </summary> - /// <param name="issuerKey">The key of the issuer of the token</param> - /// <param name="claimValue">the claim value to hash with.</param> - /// <returns>A base64 representation of the combined ID.</returns> - private static string ComputeCombinedId(RSA issuerKey, string claimValue) { - Contract.Requires<ArgumentNullException>(issuerKey != null); - Contract.Requires<ArgumentNullException>(claimValue != null); - Contract.Ensures(Contract.Result<string>() != null); - - int nameLength = Encoding.UTF8.GetByteCount(claimValue); - RSAParameters rsaParams = issuerKey.ExportParameters(false); - byte[] shaInput; - byte[] shaOutput; - - int i = 0; - shaInput = new byte[rsaParams.Modulus.Length + rsaParams.Exponent.Length + nameLength]; - rsaParams.Modulus.CopyTo(shaInput, i); - i += rsaParams.Modulus.Length; - rsaParams.Exponent.CopyTo(shaInput, i); - i += rsaParams.Exponent.Length; - i += Encoding.UTF8.GetBytes(claimValue, 0, claimValue.Length, shaInput, i); - - using (SHA256 sha = SHA256.Create()) { - shaOutput = sha.ComputeHash(shaInput); - } - - return Convert.ToBase64String(shaOutput); - } - } -} diff --git a/src/DotNetOpenAuth/InfoCard/TokenProcessingErrorEventArgs.cs b/src/DotNetOpenAuth/InfoCard/TokenProcessingErrorEventArgs.cs deleted file mode 100644 index 0f17b63..0000000 --- a/src/DotNetOpenAuth/InfoCard/TokenProcessingErrorEventArgs.cs +++ /dev/null @@ -1,50 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="TokenProcessingErrorEventArgs.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- -namespace DotNetOpenAuth.InfoCard { - using System; - using System.Diagnostics.CodeAnalysis; - using System.Diagnostics.Contracts; - - /// <summary> - /// Arguments for the <see cref="InfoCardSelector.TokenProcessingError"/> event. - /// </summary> - public class TokenProcessingErrorEventArgs : EventArgs { - /// <summary> - /// Initializes a new instance of the <see cref="TokenProcessingErrorEventArgs"/> class. - /// </summary> - /// <param name="tokenXml">The token XML.</param> - /// <param name="exception">The exception.</param> - internal TokenProcessingErrorEventArgs(string tokenXml, Exception exception) { - Contract.Requires<ArgumentNullException>(tokenXml != null); - Contract.Requires<ArgumentNullException>(exception != null); - this.TokenXml = tokenXml; - this.Exception = exception; - } - - /// <summary> - /// Gets the raw token XML. - /// </summary> - public string TokenXml { get; private set; } - - /// <summary> - /// Gets the exception that was generated while processing the token. - /// </summary> - public Exception Exception { get; private set; } - -#if CONTRACTS_FULL - /// <summary> - /// Verifies conditions that should be true for any valid state of this object. - /// </summary> - [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Called by code contracts.")] - [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called by code contracts.")] - [ContractInvariantMethod] - private void ObjectInvariant() { - Contract.Invariant(this.TokenXml != null); - Contract.Invariant(this.Exception != null); - } -#endif - } -} diff --git a/src/DotNetOpenAuth/Logger.cs b/src/DotNetOpenAuth/Logger.cs deleted file mode 100644 index 48007ed..0000000 --- a/src/DotNetOpenAuth/Logger.cs +++ /dev/null @@ -1,184 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="Logger.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth { - using System; - using System.Diagnostics.Contracts; - using System.Globalization; - using DotNetOpenAuth.Loggers; - using DotNetOpenAuth.Messaging; - using log4net.Core; - - /// <summary> - /// A general logger for the entire DotNetOpenAuth library. - /// </summary> - /// <remarks> - /// Because this logger is intended for use with non-localized strings, the - /// overloads that take <see cref="CultureInfo"/> have been removed, and - /// <see cref="CultureInfo.InvariantCulture"/> is used implicitly. - /// </remarks> - internal static partial class Logger { - #region Category-specific loggers - - /// <summary> - /// The <see cref="ILog"/> instance that is to be used - /// by this static Logger for the duration of the appdomain. - /// </summary> - private static readonly ILog library = CreateWithBanner("DotNetOpenAuth"); - - /// <summary> - /// Backing field for the <see cref="Yadis"/> property. - /// </summary> - private static readonly ILog yadis = Create("DotNetOpenAuth.Yadis"); - - /// <summary> - /// Backing field for the <see cref="Messaging"/> property. - /// </summary> - private static readonly ILog messaging = Create("DotNetOpenAuth.Messaging"); - - /// <summary> - /// Backing field for the <see cref="Channel"/> property. - /// </summary> - private static readonly ILog channel = Create("DotNetOpenAuth.Messaging.Channel"); - - /// <summary> - /// Backing field for the <see cref="Bindings"/> property. - /// </summary> - private static readonly ILog bindings = Create("DotNetOpenAuth.Messaging.Bindings"); - - /// <summary> - /// Backing field for the <see cref="Signatures"/> property. - /// </summary> - private static readonly ILog signatures = Create("DotNetOpenAuth.Messaging.Bindings.Signatures"); - - /// <summary> - /// Backing field for the <see cref="Http"/> property. - /// </summary> - private static readonly ILog http = Create("DotNetOpenAuth.Http"); - - /// <summary> - /// Backing field for the <see cref="Controls"/> property. - /// </summary> - private static readonly ILog controls = Create("DotNetOpenAuth.Controls"); - - /// <summary> - /// Backing field for the <see cref="OpenId"/> property. - /// </summary> - private static readonly ILog openId = Create("DotNetOpenAuth.OpenId"); - - /// <summary> - /// Backing field for the <see cref="OAuth"/> property. - /// </summary> - private static readonly ILog oauth = Create("DotNetOpenAuth.OAuth"); - - /// <summary> - /// Backing field for the <see cref="InfoCard"/> property. - /// </summary> - private static readonly ILog infocard = Create("DotNetOpenAuth.InfoCard"); - - /// <summary> - /// Gets the logger for general library logging. - /// </summary> - internal static ILog Library { get { return library; } } - - /// <summary> - /// Gets the logger for service discovery and selection events. - /// </summary> - internal static ILog Yadis { get { return yadis; } } - - /// <summary> - /// Gets the logger for Messaging events. - /// </summary> - internal static ILog Messaging { get { return messaging; } } - - /// <summary> - /// Gets the logger for Channel events. - /// </summary> - internal static ILog Channel { get { return channel; } } - - /// <summary> - /// Gets the logger for binding elements and binding-element related events on the channel. - /// </summary> - internal static ILog Bindings { get { return bindings; } } - - /// <summary> - /// Gets the logger specifically used for logging verbose text on everything about the signing process. - /// </summary> - internal static ILog Signatures { get { return signatures; } } - - /// <summary> - /// Gets the logger for HTTP-level events. - /// </summary> - internal static ILog Http { get { return http; } } - - /// <summary> - /// Gets the logger for events logged by ASP.NET controls. - /// </summary> - internal static ILog Controls { get { return controls; } } - - /// <summary> - /// Gets the logger for high-level OpenID events. - /// </summary> - internal static ILog OpenId { get { return openId; } } - - /// <summary> - /// Gets the logger for high-level OAuth events. - /// </summary> - internal static ILog OAuth { get { return oauth; } } - - /// <summary> - /// Gets the logger for high-level InfoCard events. - /// </summary> - internal static ILog InfoCard { get { return infocard; } } - - #endregion - - /// <summary> - /// Creates an additional logger on demand for a subsection of the application. - /// </summary> - /// <param name="name">A name that will be included in the log file.</param> - /// <returns>The <see cref="ILog"/> instance created with the given name.</returns> - internal static ILog Create(string name) { - Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(name)); - return InitializeFacade(name); - } - - /// <summary> - /// Creates the main logger for the library, and emits an INFO message - /// that is the name and version of the library. - /// </summary> - /// <param name="name">A name that will be included in the log file.</param> - /// <returns>The <see cref="ILog"/> instance created with the given name.</returns> - internal static ILog CreateWithBanner(string name) { - Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(name)); - ILog log = Create(name); - log.Info(Util.LibraryVersion); - return log; - } - - /// <summary> - /// Creates an additional logger on demand for a subsection of the application. - /// </summary> - /// <param name="type">A type whose full name that will be included in the log file.</param> - /// <returns>The <see cref="ILog"/> instance created with the given type name.</returns> - internal static ILog Create(Type type) { - Contract.Requires<ArgumentNullException>(type != null); - - return Create(type.FullName); - } - - /// <summary> - /// Discovers the presence of Log4net.dll and other logging mechanisms - /// and returns the best available logger. - /// </summary> - /// <param name="name">The name of the log to initialize.</param> - /// <returns>The <see cref="ILog"/> instance of the logger to use.</returns> - private static ILog InitializeFacade(string name) { - ILog result = Log4NetLogger.Initialize(name) ?? TraceLogger.Initialize(name) ?? NoOpLogger.Initialize(); - return result; - } - } -} diff --git a/src/DotNetOpenAuth/Messaging/BinaryDataBagFormatter.cs b/src/DotNetOpenAuth/Messaging/BinaryDataBagFormatter.cs deleted file mode 100644 index 591adc3..0000000 --- a/src/DotNetOpenAuth/Messaging/BinaryDataBagFormatter.cs +++ /dev/null @@ -1,80 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="BinaryDataBagFormatter.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.Messaging { - using System; - using System.Collections.Generic; - using System.Diagnostics.Contracts; - using System.IO; - using System.Linq; - using System.Security.Cryptography; - using System.Text; - using DotNetOpenAuth.Messaging.Bindings; - - /// <summary> - /// A compact binary <see cref="DataBag"/> serialization class. - /// </summary> - /// <typeparam name="T">The <see cref="DataBag"/>-derived type to serialize/deserialize.</typeparam> - internal class BinaryDataBagFormatter<T> : DataBagFormatterBase<T> where T : DataBag, IStreamSerializingDataBag, new() { - /// <summary> - /// Initializes a new instance of the <see cref="BinaryDataBagFormatter<T>"/> class. - /// </summary> - /// <param name="signingKey">The crypto service provider with the asymmetric key to use for signing or verifying the token.</param> - /// <param name="encryptingKey">The crypto service provider with the asymmetric key to use for encrypting or decrypting the token.</param> - /// <param name="compressed">A value indicating whether the data in this instance will be GZip'd.</param> - /// <param name="maximumAge">The maximum age of a token that can be decoded; useful only when <paramref name="decodeOnceOnly"/> is <c>true</c>.</param> - /// <param name="decodeOnceOnly">The nonce store to use to ensure that this instance is only decoded once.</param> - protected internal BinaryDataBagFormatter(RSACryptoServiceProvider signingKey = null, RSACryptoServiceProvider encryptingKey = null, bool compressed = false, TimeSpan? maximumAge = null, INonceStore decodeOnceOnly = null) - : base(signingKey, encryptingKey, compressed, maximumAge, decodeOnceOnly) { - } - - /// <summary> - /// Initializes a new instance of the <see cref="BinaryDataBagFormatter<T>"/> class. - /// </summary> - /// <param name="cryptoKeyStore">The crypto key store used when signing or encrypting.</param> - /// <param name="bucket">The bucket in which symmetric keys are stored for signing/encrypting data.</param> - /// <param name="signed">A value indicating whether the data in this instance will be protected against tampering.</param> - /// <param name="encrypted">A value indicating whether the data in this instance will be protected against eavesdropping.</param> - /// <param name="compressed">A value indicating whether the data in this instance will be GZip'd.</param> - /// <param name="minimumAge">The minimum age.</param> - /// <param name="maximumAge">The maximum age of a token that can be decoded; useful only when <paramref name="decodeOnceOnly"/> is <c>true</c>.</param> - /// <param name="decodeOnceOnly">The nonce store to use to ensure that this instance is only decoded once.</param> - protected internal BinaryDataBagFormatter(ICryptoKeyStore cryptoKeyStore = null, string bucket = null, bool signed = false, bool encrypted = false, bool compressed = false, TimeSpan? minimumAge = null, TimeSpan? maximumAge = null, INonceStore decodeOnceOnly = null) - : base(cryptoKeyStore, bucket, signed, encrypted, compressed, minimumAge, maximumAge, decodeOnceOnly) { - Contract.Requires<ArgumentException>((cryptoKeyStore != null && bucket != null) || (!signed && !encrypted)); - } - - /// <summary> - /// Serializes the <see cref="DataBag"/> instance to a buffer. - /// </summary> - /// <param name="message">The message.</param> - /// <returns>The buffer containing the serialized data.</returns> - protected override byte[] SerializeCore(T message) { - using (var stream = new MemoryStream()) { - message.Serialize(stream); - return stream.ToArray(); - } - } - - /// <summary> - /// Deserializes the <see cref="DataBag"/> instance from a buffer. - /// </summary> - /// <param name="message">The message instance to initialize with data from the buffer.</param> - /// <param name="data">The data buffer.</param> - protected override void DeserializeCore(T message, byte[] data) { - using (var stream = new MemoryStream(data)) { - message.Deserialize(stream); - } - - // Perform basic validation on message that the MessageSerializer would have normally performed. - var messageDescription = MessageDescriptions.Get(message); - var dictionary = messageDescription.GetDictionary(message); - messageDescription.EnsureMessagePartsPassBasicValidation(dictionary); - IMessage m = message; - m.EnsureValidMessage(); - } - } -} diff --git a/src/DotNetOpenAuth/Messaging/Bindings/AsymmetricCryptoKeyStoreWrapper.cs b/src/DotNetOpenAuth/Messaging/Bindings/AsymmetricCryptoKeyStoreWrapper.cs deleted file mode 100644 index 0f2ac05..0000000 --- a/src/DotNetOpenAuth/Messaging/Bindings/AsymmetricCryptoKeyStoreWrapper.cs +++ /dev/null @@ -1,163 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="AsymmetricCryptoKeyStoreWrapper.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.Messaging.Bindings { - using System; - using System.Collections.Generic; - using System.Diagnostics.CodeAnalysis; - using System.Diagnostics.Contracts; - using System.Linq; - using System.Security.Cryptography; - using System.Text; - using DotNetOpenAuth.Messaging; - - /// <summary> - /// Provides RSA encryption of symmetric keys to protect them from a theft of - /// the persistent store. - /// </summary> - public class AsymmetricCryptoKeyStoreWrapper : ICryptoKeyStore { - /// <summary> - /// The persistent store for asymmetrically encrypted symmetric keys. - /// </summary> - private readonly ICryptoKeyStore dataStore; - - /// <summary> - /// The memory cache of decrypted keys. - /// </summary> - private readonly MemoryCryptoKeyStore cache = new MemoryCryptoKeyStore(); - - /// <summary> - /// The asymmetric algorithm to use encrypting/decrypting the symmetric keys. - /// </summary> - private readonly RSACryptoServiceProvider asymmetricCrypto; - - /// <summary> - /// Initializes a new instance of the <see cref="AsymmetricCryptoKeyStoreWrapper"/> class. - /// </summary> - /// <param name="dataStore">The data store.</param> - /// <param name="asymmetricCrypto">The asymmetric protection to apply to symmetric keys. Must include the private key.</param> - public AsymmetricCryptoKeyStoreWrapper(ICryptoKeyStore dataStore, RSACryptoServiceProvider asymmetricCrypto) { - Contract.Requires<ArgumentNullException>(dataStore != null); - Contract.Requires<ArgumentNullException>(asymmetricCrypto != null); - Contract.Requires<ArgumentException>(!asymmetricCrypto.PublicOnly); - this.dataStore = dataStore; - this.asymmetricCrypto = asymmetricCrypto; - } - - /// <summary> - /// Gets the key in a given bucket and handle. - /// </summary> - /// <param name="bucket">The bucket name. Case sensitive.</param> - /// <param name="handle">The key handle. Case sensitive.</param> - /// <returns> - /// The cryptographic key, or <c>null</c> if no matching key was found. - /// </returns> - public CryptoKey GetKey(string bucket, string handle) { - var key = this.dataStore.GetKey(bucket, handle); - return this.Decrypt(bucket, handle, key); - } - - /// <summary> - /// Gets a sequence of existing keys within a given bucket. - /// </summary> - /// <param name="bucket">The bucket name. Case sensitive.</param> - /// <returns> - /// A sequence of handles and keys, ordered by descending <see cref="CryptoKey.ExpiresUtc"/>. - /// </returns> - public IEnumerable<KeyValuePair<string, CryptoKey>> GetKeys(string bucket) { - return this.dataStore.GetKeys(bucket) - .Select(pair => new KeyValuePair<string, CryptoKey>(pair.Key, this.Decrypt(bucket, pair.Key, pair.Value))); - } - - /// <summary> - /// Stores a cryptographic key. - /// </summary> - /// <param name="bucket">The name of the bucket to store the key in. Case sensitive.</param> - /// <param name="handle">The handle to the key, unique within the bucket. Case sensitive.</param> - /// <param name="decryptedCryptoKey">The key to store.</param> - [SuppressMessage("Microsoft.Naming", "CA1725:ParameterNamesShouldMatchBaseDeclaration", MessageId = "2#", Justification = "Helps readability because multiple keys are involved.")] - public void StoreKey(string bucket, string handle, CryptoKey decryptedCryptoKey) { - byte[] encryptedKey = this.asymmetricCrypto.Encrypt(decryptedCryptoKey.Key, true); - var encryptedCryptoKey = new CryptoKey(encryptedKey, decryptedCryptoKey.ExpiresUtc); - this.dataStore.StoreKey(bucket, handle, encryptedCryptoKey); - - this.cache.StoreKey(bucket, handle, new CachedCryptoKey(encryptedCryptoKey, decryptedCryptoKey)); - } - - /// <summary> - /// Removes the key. - /// </summary> - /// <param name="bucket">The bucket name. Case sensitive.</param> - /// <param name="handle">The key handle. Case sensitive.</param> - public void RemoveKey(string bucket, string handle) { - this.dataStore.RemoveKey(bucket, handle); - this.cache.RemoveKey(bucket, handle); - } - - /// <summary> - /// Decrypts the specified key. - /// </summary> - /// <param name="bucket">The bucket.</param> - /// <param name="handle">The handle.</param> - /// <param name="encryptedCryptoKey">The encrypted key.</param> - /// <returns> - /// The decrypted key. - /// </returns> - [SuppressMessage("Microsoft.Naming", "CA1725:ParameterNamesShouldMatchBaseDeclaration", MessageId = "2#", Justification = "Helps readability because multiple keys are involved.")] - private CryptoKey Decrypt(string bucket, string handle, CryptoKey encryptedCryptoKey) { - if (encryptedCryptoKey == null) { - return null; - } - - // Avoid the asymmetric decryption if possible by looking up whether we have that in our cache. - CachedCryptoKey cached = (CachedCryptoKey)this.cache.GetKey(bucket, handle); - if (cached != null && MessagingUtilities.AreEquivalent(cached.EncryptedKey, encryptedCryptoKey.Key)) { - return cached; - } - - byte[] decryptedKey = this.asymmetricCrypto.Decrypt(encryptedCryptoKey.Key, true); - var decryptedCryptoKey = new CryptoKey(decryptedKey, encryptedCryptoKey.ExpiresUtc); - - // Store the decrypted version in the cache to save time next time. - this.cache.StoreKey(bucket, handle, new CachedCryptoKey(encryptedCryptoKey, decryptedCryptoKey)); - - return decryptedCryptoKey; - } - - /// <summary> - /// An encrypted key and its decrypted equivalent. - /// </summary> - private class CachedCryptoKey : CryptoKey { - /// <summary> - /// Initializes a new instance of the <see cref="CachedCryptoKey"/> class. - /// </summary> - /// <param name="encrypted">The encrypted key.</param> - /// <param name="decrypted">The decrypted key.</param> - internal CachedCryptoKey(CryptoKey encrypted, CryptoKey decrypted) - : base(decrypted.Key, decrypted.ExpiresUtc) { - Contract.Requires(encrypted != null); - Contract.Requires(decrypted != null); - Contract.Requires(encrypted.ExpiresUtc == decrypted.ExpiresUtc); - - this.EncryptedKey = encrypted.Key; - } - - /// <summary> - /// Gets the encrypted key. - /// </summary> - internal byte[] EncryptedKey { get; private set; } - - /// <summary> - /// Invariant conditions. - /// </summary> - [ContractInvariantMethod] - [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Required for code contracts.")] - private void ObjectInvariant() { - Contract.Invariant(this.EncryptedKey != null); - } - } - } -} diff --git a/src/DotNetOpenAuth/Messaging/Bindings/CryptoKey.cs b/src/DotNetOpenAuth/Messaging/Bindings/CryptoKey.cs deleted file mode 100644 index cd10199..0000000 --- a/src/DotNetOpenAuth/Messaging/Bindings/CryptoKey.cs +++ /dev/null @@ -1,93 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="CryptoKey.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.Messaging.Bindings { - using System; - using System.Collections.Generic; - using System.Diagnostics.CodeAnalysis; - using System.Diagnostics.Contracts; - using System.Linq; - using System.Text; - using DotNetOpenAuth.Messaging; - - /// <summary> - /// A cryptographic key and metadata concerning it. - /// </summary> - public class CryptoKey { - /// <summary> - /// Backing field for the <see cref="Key"/> property. - /// </summary> - private readonly byte[] key; - - /// <summary> - /// Backing field for the <see cref="ExpiresUtc"/> property. - /// </summary> - private readonly DateTime expiresUtc; - - /// <summary> - /// Initializes a new instance of the <see cref="CryptoKey"/> class. - /// </summary> - /// <param name="key">The cryptographic key.</param> - /// <param name="expiresUtc">The expires UTC.</param> - public CryptoKey(byte[] key, DateTime expiresUtc) { - Contract.Requires<ArgumentNullException>(key != null); - Contract.Requires<ArgumentException>(expiresUtc.Kind == DateTimeKind.Utc); - this.key = key; - this.expiresUtc = expiresUtc; - } - - /// <summary> - /// Gets the key. - /// </summary> - [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification = "It's a buffer")] - public byte[] Key { - get { - Contract.Ensures(Contract.Result<byte[]>() != null); - return this.key; - } - } - - /// <summary> - /// Gets the expiration date of this key (UTC time). - /// </summary> - public DateTime ExpiresUtc { - get { - Contract.Ensures(Contract.Result<DateTime>().Kind == DateTimeKind.Utc); - return this.expiresUtc; - } - } - - /// <summary> - /// Determines whether the specified <see cref="System.Object"/> is equal to this instance. - /// </summary> - /// <param name="obj">The <see cref="System.Object"/> to compare with this instance.</param> - /// <returns> - /// <c>true</c> if the specified <see cref="System.Object"/> is equal to this instance; otherwise, <c>false</c>. - /// </returns> - /// <exception cref="T:System.NullReferenceException"> - /// The <paramref name="obj"/> parameter is null. - /// </exception> - public override bool Equals(object obj) { - var other = obj as CryptoKey; - if (other == null) { - return false; - } - - return this.ExpiresUtc == other.ExpiresUtc - && MessagingUtilities.AreEquivalent(this.Key, other.Key); - } - - /// <summary> - /// Returns a hash code for this instance. - /// </summary> - /// <returns> - /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. - /// </returns> - public override int GetHashCode() { - return this.ExpiresUtc.GetHashCode(); - } - } -} diff --git a/src/DotNetOpenAuth/Messaging/Bindings/CryptoKeyCollisionException.cs b/src/DotNetOpenAuth/Messaging/Bindings/CryptoKeyCollisionException.cs deleted file mode 100644 index 11634ac..0000000 --- a/src/DotNetOpenAuth/Messaging/Bindings/CryptoKeyCollisionException.cs +++ /dev/null @@ -1,68 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="CryptoKeyCollisionException.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.Messaging.Bindings { - using System; - using System.Diagnostics.CodeAnalysis; - using System.Security.Permissions; - - /// <summary> - /// Thrown by a hosting application or web site when a cryptographic key is created with a - /// bucket and handle that conflicts with a previously stored and unexpired key. - /// </summary> - [SuppressMessage("Microsoft.Design", "CA1032:ImplementStandardExceptionConstructors", Justification = "Specialized exception has no need of a message parameter.")] - [Serializable] - public class CryptoKeyCollisionException : ArgumentException { - /// <summary> - /// Initializes a new instance of the <see cref="CryptoKeyCollisionException"/> class. - /// </summary> - public CryptoKeyCollisionException() { - } - - /// <summary> - /// Initializes a new instance of the <see cref="CryptoKeyCollisionException"/> class. - /// </summary> - /// <param name="inner">The inner exception to include.</param> - public CryptoKeyCollisionException(Exception inner) : base(null, inner) { - } - - /// <summary> - /// Initializes a new instance of the <see cref="CryptoKeyCollisionException"/> class. - /// </summary> - /// <param name="info">The <see cref="System.Runtime.Serialization.SerializationInfo"/> - /// that holds the serialized object data about the exception being thrown.</param> - /// <param name="context">The System.Runtime.Serialization.StreamingContext - /// that contains contextual information about the source or destination.</param> - protected CryptoKeyCollisionException( - System.Runtime.Serialization.SerializationInfo info, - System.Runtime.Serialization.StreamingContext context) - : base(info, context) { - throw new NotImplementedException(); - } - - /// <summary> - /// When overridden in a derived class, sets the <see cref="T:System.Runtime.Serialization.SerializationInfo"/> with information about the exception. - /// </summary> - /// <param name="info">The <see cref="T:System.Runtime.Serialization.SerializationInfo"/> that holds the serialized object data about the exception being thrown.</param> - /// <param name="context">The <see cref="T:System.Runtime.Serialization.StreamingContext"/> that contains contextual information about the source or destination.</param> - /// <exception cref="T:System.ArgumentNullException"> - /// The <paramref name="info"/> parameter is a null reference (Nothing in Visual Basic). - /// </exception> - /// <PermissionSet> - /// <IPermission class="System.Security.Permissions.FileIOPermission, mscorlib, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Read="*AllFiles*" PathDiscovery="*AllFiles*"/> - /// <IPermission class="System.Security.Permissions.SecurityPermission, mscorlib, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Flags="SerializationFormatter"/> - /// </PermissionSet> -#if CLR4 - [SecurityCritical] -#else - [SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.SerializationFormatter)] -#endif - public override void GetObjectData(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) { - base.GetObjectData(info, context); - throw new NotImplementedException(); - } - } -} diff --git a/src/DotNetOpenAuth/Messaging/Bindings/ExpiredMessageException.cs b/src/DotNetOpenAuth/Messaging/Bindings/ExpiredMessageException.cs deleted file mode 100644 index cfe7f6c..0000000 --- a/src/DotNetOpenAuth/Messaging/Bindings/ExpiredMessageException.cs +++ /dev/null @@ -1,40 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="ExpiredMessageException.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.Messaging.Bindings { - using System; - using System.Diagnostics.Contracts; - using System.Globalization; - - /// <summary> - /// An exception thrown when a message is received that exceeds the maximum message age limit. - /// </summary> - [Serializable] - internal class ExpiredMessageException : ProtocolException { - /// <summary> - /// Initializes a new instance of the <see cref="ExpiredMessageException"/> class. - /// </summary> - /// <param name="utcExpirationDate">The date the message expired.</param> - /// <param name="faultedMessage">The expired message.</param> - public ExpiredMessageException(DateTime utcExpirationDate, IProtocolMessage faultedMessage) - : base(string.Format(CultureInfo.CurrentCulture, MessagingStrings.ExpiredMessage, utcExpirationDate.ToLocalTime(), DateTime.Now), faultedMessage) { - Contract.Requires<ArgumentException>(utcExpirationDate.Kind == DateTimeKind.Utc); - Contract.Requires<ArgumentNullException>(faultedMessage != null); - } - - /// <summary> - /// Initializes a new instance of the <see cref="ExpiredMessageException"/> class. - /// </summary> - /// <param name="info">The <see cref="System.Runtime.Serialization.SerializationInfo"/> - /// that holds the serialized object data about the exception being thrown.</param> - /// <param name="context">The System.Runtime.Serialization.StreamingContext - /// that contains contextual information about the source or destination.</param> - protected ExpiredMessageException( - System.Runtime.Serialization.SerializationInfo info, - System.Runtime.Serialization.StreamingContext context) - : base(info, context) { } - } -} diff --git a/src/DotNetOpenAuth/Messaging/Bindings/ICryptoKeyStore.cs b/src/DotNetOpenAuth/Messaging/Bindings/ICryptoKeyStore.cs deleted file mode 100644 index 815c488..0000000 --- a/src/DotNetOpenAuth/Messaging/Bindings/ICryptoKeyStore.cs +++ /dev/null @@ -1,118 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="ICryptoKeyStore.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.Messaging.Bindings { - using System; - using System.Collections.Generic; - using System.Diagnostics.CodeAnalysis; - using System.Diagnostics.Contracts; - using System.Linq; - using System.Text; - using DotNetOpenAuth.Messaging; - - /// <summary> - /// A persistent store for rotating symmetric cryptographic keys. - /// </summary> - /// <remarks> - /// Implementations should persist it in such a way that the keys are shared across all servers - /// on a web farm, where applicable. - /// The store should consider protecting the persistent store against theft resulting in the loss - /// of the confidentiality of the keys. One possible mitigation is to asymmetrically encrypt - /// each key using a certificate installed in the server's certificate store. - /// </remarks> - [ContractClass(typeof(ICryptoKeyStoreContract))] - public interface ICryptoKeyStore { - /// <summary> - /// Gets the key in a given bucket and handle. - /// </summary> - /// <param name="bucket">The bucket name. Case sensitive.</param> - /// <param name="handle">The key handle. Case sensitive.</param> - /// <returns>The cryptographic key, or <c>null</c> if no matching key was found.</returns> - CryptoKey GetKey(string bucket, string handle); - - /// <summary> - /// Gets a sequence of existing keys within a given bucket. - /// </summary> - /// <param name="bucket">The bucket name. Case sensitive.</param> - /// <returns>A sequence of handles and keys, ordered by descending <see cref="CryptoKey.ExpiresUtc"/>.</returns> - [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "Important for scalability")] - IEnumerable<KeyValuePair<string, CryptoKey>> GetKeys(string bucket); - - /// <summary> - /// Stores a cryptographic key. - /// </summary> - /// <param name="bucket">The name of the bucket to store the key in. Case sensitive.</param> - /// <param name="handle">The handle to the key, unique within the bucket. Case sensitive.</param> - /// <param name="key">The key to store.</param> - /// <exception cref="CryptoKeyCollisionException">Thrown in the event of a conflict with an existing key in the same bucket and with the same handle.</exception> - void StoreKey(string bucket, string handle, CryptoKey key); - - /// <summary> - /// Removes the key. - /// </summary> - /// <param name="bucket">The bucket name. Case sensitive.</param> - /// <param name="handle">The key handle. Case sensitive.</param> - void RemoveKey(string bucket, string handle); - } - - /// <summary> - /// Code contract for the <see cref="ICryptoKeyStore"/> interface. - /// </summary> - [ContractClassFor(typeof(ICryptoKeyStore))] - internal abstract class ICryptoKeyStoreContract : ICryptoKeyStore { - /// <summary> - /// Gets the key in a given bucket and handle. - /// </summary> - /// <param name="bucket">The bucket name. Case sensitive.</param> - /// <param name="handle">The key handle. Case sensitive.</param> - /// <returns> - /// The cryptographic key, or <c>null</c> if no matching key was found. - /// </returns> - CryptoKey ICryptoKeyStore.GetKey(string bucket, string handle) { - Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(bucket)); - Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(handle)); - throw new NotImplementedException(); - } - - /// <summary> - /// Gets a sequence of existing keys within a given bucket. - /// </summary> - /// <param name="bucket">The bucket name. Case sensitive.</param> - /// <returns> - /// A sequence of handles and keys, ordered by descending <see cref="CryptoKey.ExpiresUtc"/>. - /// </returns> - IEnumerable<KeyValuePair<string, CryptoKey>> ICryptoKeyStore.GetKeys(string bucket) { - Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(bucket)); - Contract.Ensures(Contract.Result<IEnumerable<KeyValuePair<string, CryptoKey>>>() != null); - throw new NotImplementedException(); - } - - /// <summary> - /// Stores a cryptographic key. - /// </summary> - /// <param name="bucket">The name of the bucket to store the key in. Case sensitive.</param> - /// <param name="handle">The handle to the key, unique within the bucket. Case sensitive.</param> - /// <param name="key">The key to store.</param> - /// <exception cref="CryptoKeyCollisionException">Thrown in the event of a conflict with an existing key in the same bucket and with the same handle.</exception> - void ICryptoKeyStore.StoreKey(string bucket, string handle, CryptoKey key) { - Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(bucket)); - Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(handle)); - Contract.Requires<ArgumentNullException>(key != null); - throw new NotImplementedException(); - } - - /// <summary> - /// Removes the key. - /// </summary> - /// <param name="bucket">The bucket name. Case sensitive.</param> - /// <param name="handle">The key handle. Case sensitive.</param> - void ICryptoKeyStore.RemoveKey(string bucket, string handle) { - Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(bucket)); - Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(handle)); - throw new NotImplementedException(); - } - } -} diff --git a/src/DotNetOpenAuth/Messaging/Bindings/INonceStore.cs b/src/DotNetOpenAuth/Messaging/Bindings/INonceStore.cs deleted file mode 100644 index 6b6e2e1..0000000 --- a/src/DotNetOpenAuth/Messaging/Bindings/INonceStore.cs +++ /dev/null @@ -1,39 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="INonceStore.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.Messaging.Bindings { - using System; - - /// <summary> - /// Describes the contract a nonce store must fulfill. - /// </summary> - public interface INonceStore { - /// <summary> - /// Stores a given nonce and timestamp. - /// </summary> - /// <param name="context">The context, or namespace, within which the - /// <paramref name="nonce"/> must be unique. - /// The context SHOULD be treated as case-sensitive. - /// The value will never be <c>null</c> but may be the empty string.</param> - /// <param name="nonce">A series of random characters.</param> - /// <param name="timestampUtc">The UTC timestamp that together with the nonce string make it unique - /// within the given <paramref name="context"/>. - /// The timestamp may also be used by the data store to clear out old nonces.</param> - /// <returns> - /// True if the context+nonce+timestamp (combination) was not previously in the database. - /// False if the nonce was stored previously with the same timestamp and context. - /// </returns> - /// <remarks> - /// The nonce must be stored for no less than the maximum time window a message may - /// be processed within before being discarded as an expired message. - /// This maximum message age can be looked up via the - /// <see cref="DotNetOpenAuth.Configuration.MessagingElement.MaximumMessageLifetime"/> - /// property, accessible via the <see cref="DotNetOpenAuth.Configuration.DotNetOpenAuthSection.Configuration"/> - /// property. - /// </remarks> - bool StoreNonce(string context, string nonce, DateTime timestampUtc); - } -} diff --git a/src/DotNetOpenAuth/Messaging/Bindings/StandardExpirationBindingElement.cs b/src/DotNetOpenAuth/Messaging/Bindings/StandardExpirationBindingElement.cs deleted file mode 100644 index 4396c16..0000000 --- a/src/DotNetOpenAuth/Messaging/Bindings/StandardExpirationBindingElement.cs +++ /dev/null @@ -1,107 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="StandardExpirationBindingElement.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.Messaging.Bindings { - using System; - using DotNetOpenAuth.Configuration; - - /// <summary> - /// A message expiration enforcing binding element that supports messages - /// implementing the <see cref="IExpiringProtocolMessage"/> interface. - /// </summary> - internal class StandardExpirationBindingElement : IChannelBindingElement { - /// <summary> - /// Initializes a new instance of the <see cref="StandardExpirationBindingElement"/> class. - /// </summary> - internal StandardExpirationBindingElement() { - } - - #region IChannelBindingElement Properties - - /// <summary> - /// Gets the protection offered by this binding element. - /// </summary> - /// <value><see cref="MessageProtections.Expiration"/></value> - MessageProtections IChannelBindingElement.Protection { - get { return MessageProtections.Expiration; } - } - - /// <summary> - /// Gets or sets the channel that this binding element belongs to. - /// </summary> - public Channel Channel { get; set; } - - #endregion - - /// <summary> - /// Gets the maximum age a message implementing the - /// <see cref="IExpiringProtocolMessage"/> interface can be before - /// being discarded as too old. - /// </summary> - protected internal static TimeSpan MaximumMessageAge { - get { return Configuration.DotNetOpenAuthSection.Configuration.Messaging.MaximumMessageLifetime; } - } - - #region IChannelBindingElement Methods - - /// <summary> - /// Sets the timestamp on an outgoing message. - /// </summary> - /// <param name="message">The outgoing message.</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> - public MessageProtections? ProcessOutgoingMessage(IProtocolMessage message) { - IExpiringProtocolMessage expiringMessage = message as IExpiringProtocolMessage; - if (expiringMessage != null) { - expiringMessage.UtcCreationDate = DateTime.UtcNow; - return MessageProtections.Expiration; - } - - return null; - } - - /// <summary> - /// Reads the timestamp on a message and throws an exception if the message is too old. - /// </summary> - /// <param name="message">The incoming message.</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="ExpiredMessageException">Thrown if the given message has already expired.</exception> - /// <exception cref="ProtocolException"> - /// Thrown when the binding element rules indicate that this message is invalid and should - /// NOT be processed. - /// </exception> - public MessageProtections? ProcessIncomingMessage(IProtocolMessage message) { - IExpiringProtocolMessage expiringMessage = message as IExpiringProtocolMessage; - if (expiringMessage != null) { - // Yes the UtcCreationDate is supposed to always be in UTC already, - // but just in case a given message failed to guarantee that, we do it here. - DateTime creationDate = expiringMessage.UtcCreationDate.ToUniversalTimeSafe(); - DateTime expirationDate = creationDate + MaximumMessageAge; - if (expirationDate < DateTime.UtcNow) { - throw new ExpiredMessageException(expirationDate, expiringMessage); - } - - // Mitigate HMAC attacks (just guessing the signature until they get it) by - // disallowing post-dated messages. - ErrorUtilities.VerifyProtocol( - creationDate <= DateTime.UtcNow + DotNetOpenAuthSection.Configuration.Messaging.MaximumClockSkew, - MessagingStrings.MessageTimestampInFuture, - creationDate); - - return MessageProtections.Expiration; - } - - return null; - } - - #endregion - } -} diff --git a/src/DotNetOpenAuth/Messaging/Bindings/StandardReplayProtectionBindingElement.cs b/src/DotNetOpenAuth/Messaging/Bindings/StandardReplayProtectionBindingElement.cs deleted file mode 100644 index bb56cfd..0000000 --- a/src/DotNetOpenAuth/Messaging/Bindings/StandardReplayProtectionBindingElement.cs +++ /dev/null @@ -1,148 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="StandardReplayProtectionBindingElement.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.Messaging.Bindings { - using System; - using System.Diagnostics; - using System.Diagnostics.Contracts; - - /// <summary> - /// A binding element that checks/verifies a nonce message part. - /// </summary> - internal class StandardReplayProtectionBindingElement : IChannelBindingElement { - /// <summary> - /// These are the characters that may be chosen from when forming a random nonce. - /// </summary> - private const string AllowedCharacters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; - - /// <summary> - /// The persistent store for nonces received. - /// </summary> - private INonceStore nonceStore; - - /// <summary> - /// The length of generated nonces. - /// </summary> - private int nonceLength = 8; - - /// <summary> - /// Initializes a new instance of the <see cref="StandardReplayProtectionBindingElement"/> class. - /// </summary> - /// <param name="nonceStore">The store where nonces will be persisted and checked.</param> - internal StandardReplayProtectionBindingElement(INonceStore nonceStore) - : this(nonceStore, false) { - } - - /// <summary> - /// Initializes a new instance of the <see cref="StandardReplayProtectionBindingElement"/> class. - /// </summary> - /// <param name="nonceStore">The store where nonces will be persisted and checked.</param> - /// <param name="allowEmptyNonces">A value indicating whether zero-length nonces will be allowed.</param> - internal StandardReplayProtectionBindingElement(INonceStore nonceStore, bool allowEmptyNonces) { - Contract.Requires<ArgumentNullException>(nonceStore != null); - - this.nonceStore = nonceStore; - this.AllowZeroLengthNonce = allowEmptyNonces; - } - - #region IChannelBindingElement Properties - - /// <summary> - /// Gets the protection that this binding element provides messages. - /// </summary> - public MessageProtections Protection { - get { return MessageProtections.ReplayProtection; } - } - - /// <summary> - /// Gets or sets the channel that this binding element belongs to. - /// </summary> - public Channel Channel { get; set; } - - #endregion - - /// <summary> - /// Gets or sets the strength of the nonce, which is measured by the number of - /// nonces that could theoretically be generated. - /// </summary> - /// <remarks> - /// The strength of the nonce is equal to the number of characters that might appear - /// in the nonce to the power of the length of the nonce. - /// </remarks> - internal double NonceStrength { - get { - return Math.Pow(AllowedCharacters.Length, this.nonceLength); - } - - set { - value = Math.Max(value, AllowedCharacters.Length); - this.nonceLength = (int)Math.Log(value, AllowedCharacters.Length); - Debug.Assert(this.nonceLength > 0, "Nonce length calculated to be below 1!"); - } - } - - /// <summary> - /// Gets or sets a value indicating whether empty nonces are allowed. - /// </summary> - /// <value>Default is <c>false</c>.</value> - internal bool AllowZeroLengthNonce { get; set; } - - #region IChannelBindingElement Methods - - /// <summary> - /// Applies a nonce to the message. - /// </summary> - /// <param name="message">The message to apply replay protection to.</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> - public MessageProtections? ProcessOutgoingMessage(IProtocolMessage message) { - IReplayProtectedProtocolMessage nonceMessage = message as IReplayProtectedProtocolMessage; - if (nonceMessage != null) { - nonceMessage.Nonce = this.GenerateUniqueFragment(); - return MessageProtections.ReplayProtection; - } - - return null; - } - - /// <summary> - /// Verifies that the nonce in an incoming message has not been seen before. - /// </summary> - /// <param name="message">The incoming message.</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="ReplayedMessageException">Thrown when the nonce check revealed a replayed message.</exception> - public MessageProtections? ProcessIncomingMessage(IProtocolMessage message) { - IReplayProtectedProtocolMessage nonceMessage = message as IReplayProtectedProtocolMessage; - if (nonceMessage != null && nonceMessage.Nonce != null) { - ErrorUtilities.VerifyProtocol(nonceMessage.Nonce.Length > 0 || this.AllowZeroLengthNonce, MessagingStrings.InvalidNonceReceived); - - if (!this.nonceStore.StoreNonce(nonceMessage.NonceContext, nonceMessage.Nonce, nonceMessage.UtcCreationDate)) { - Logger.OpenId.ErrorFormat("Replayed nonce detected ({0} {1}). Rejecting message.", nonceMessage.Nonce, nonceMessage.UtcCreationDate); - throw new ReplayedMessageException(message); - } - - return MessageProtections.ReplayProtection; - } - - return null; - } - - #endregion - - /// <summary> - /// Generates a string of random characters for use as a nonce. - /// </summary> - /// <returns>The nonce string.</returns> - private string GenerateUniqueFragment() { - return MessagingUtilities.GetRandomString(this.nonceLength, AllowedCharacters); - } - } -} diff --git a/src/DotNetOpenAuth/Messaging/CachedDirectWebResponse.cs b/src/DotNetOpenAuth/Messaging/CachedDirectWebResponse.cs deleted file mode 100644 index 111d636..0000000 --- a/src/DotNetOpenAuth/Messaging/CachedDirectWebResponse.cs +++ /dev/null @@ -1,184 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="CachedDirectWebResponse.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.Messaging { - using System; - using System.Diagnostics; - using System.Diagnostics.CodeAnalysis; - using System.Diagnostics.Contracts; - using System.IO; - using System.Net; - using System.Text; - - /// <summary> - /// Cached details on the response from a direct web request to a remote party. - /// </summary> - [ContractVerification(true)] - [DebuggerDisplay("{Status} {ContentType.MediaType}, length: {ResponseStream.Length}")] - internal class CachedDirectWebResponse : IncomingWebResponse { - /// <summary> - /// A seekable, repeatable response stream. - /// </summary> - private MemoryStream responseStream; - - /// <summary> - /// Initializes a new instance of the <see cref="CachedDirectWebResponse"/> class. - /// </summary> - internal CachedDirectWebResponse() { - } - - /// <summary> - /// Initializes a new instance of the <see cref="CachedDirectWebResponse"/> class. - /// </summary> - /// <param name="requestUri">The request URI.</param> - /// <param name="response">The response.</param> - /// <param name="maximumBytesToRead">The maximum bytes to read.</param> - internal CachedDirectWebResponse(Uri requestUri, HttpWebResponse response, int maximumBytesToRead) - : base(requestUri, response) { - Contract.Requires<ArgumentNullException>(requestUri != null); - Contract.Requires<ArgumentNullException>(response != null); - this.responseStream = CacheNetworkStreamAndClose(response, maximumBytesToRead); - - // BUGBUG: if the response was exactly maximumBytesToRead, we'll incorrectly believe it was truncated. - this.ResponseTruncated = this.responseStream.Length == maximumBytesToRead; - } - - /// <summary> - /// Initializes a new instance of the <see cref="CachedDirectWebResponse"/> class. - /// </summary> - /// <param name="requestUri">The request URI.</param> - /// <param name="responseUri">The final URI to respond to the request.</param> - /// <param name="headers">The headers.</param> - /// <param name="statusCode">The status code.</param> - /// <param name="contentType">Type of the content.</param> - /// <param name="contentEncoding">The content encoding.</param> - /// <param name="responseStream">The response stream.</param> - internal CachedDirectWebResponse(Uri requestUri, Uri responseUri, WebHeaderCollection headers, HttpStatusCode statusCode, string contentType, string contentEncoding, MemoryStream responseStream) - : base(requestUri, responseUri, headers, statusCode, contentType, contentEncoding) { - Contract.Requires<ArgumentNullException>(requestUri != null); - Contract.Requires<ArgumentNullException>(responseStream != null); - this.responseStream = responseStream; - } - - /// <summary> - /// Gets a value indicating whether the cached response stream was - /// truncated to a maximum allowable length. - /// </summary> - public bool ResponseTruncated { get; private set; } - - /// <summary> - /// Gets the body of the HTTP response. - /// </summary> - public override Stream ResponseStream { - get { return this.responseStream; } - } - - /// <summary> - /// Gets or sets the cached response stream. - /// </summary> - internal MemoryStream CachedResponseStream { - get { return this.responseStream; } - set { this.responseStream = value; } - } - - /// <summary> - /// Creates a text reader for the response stream. - /// </summary> - /// <returns>The text reader, initialized for the proper encoding.</returns> - public override StreamReader GetResponseReader() { - this.ResponseStream.Seek(0, SeekOrigin.Begin); - string contentEncoding = this.Headers[HttpResponseHeader.ContentEncoding]; - Encoding encoding = null; - if (!string.IsNullOrEmpty(contentEncoding)) { - try { - encoding = Encoding.GetEncoding(contentEncoding); - } catch (ArgumentException ex) { - Logger.Messaging.ErrorFormat("Encoding.GetEncoding(\"{0}\") threw ArgumentException: {1}", contentEncoding, ex); - } - } - - return encoding != null ? new StreamReader(this.ResponseStream, encoding) : new StreamReader(this.ResponseStream); - } - - /// <summary> - /// Gets the body of the response as a string. - /// </summary> - /// <returns>The entire body of the response.</returns> - internal string GetResponseString() { - if (this.ResponseStream != null) { - string value = this.GetResponseReader().ReadToEnd(); - this.ResponseStream.Seek(0, SeekOrigin.Begin); - return value; - } else { - return null; - } - } - - /// <summary> - /// Gets an offline snapshot version of this instance. - /// </summary> - /// <param name="maximumBytesToCache">The maximum bytes from the response stream to cache.</param> - /// <returns>A snapshot version of this instance.</returns> - /// <remarks> - /// If this instance is a <see cref="NetworkDirectWebResponse"/> creating a snapshot - /// will automatically close and dispose of the underlying response stream. - /// If this instance is a <see cref="CachedDirectWebResponse"/>, the result will - /// be the self same instance. - /// </remarks> - internal override CachedDirectWebResponse GetSnapshot(int maximumBytesToCache) { - return this; - } - - /// <summary> - /// Sets the response to some string, encoded as UTF-8. - /// </summary> - /// <param name="body">The string to set the response to.</param> - internal void SetResponse(string body) { - if (body == null) { - this.responseStream = null; - return; - } - - Encoding encoding = Encoding.UTF8; - this.Headers[HttpResponseHeader.ContentEncoding] = encoding.HeaderName; - this.responseStream = new MemoryStream(); - StreamWriter writer = new StreamWriter(this.ResponseStream, encoding); - writer.Write(body); - writer.Flush(); - this.ResponseStream.Seek(0, SeekOrigin.Begin); - } - - /// <summary> - /// Caches the network stream and closes it if it is open. - /// </summary> - /// <param name="response">The response whose stream is to be cloned.</param> - /// <param name="maximumBytesToRead">The maximum bytes to cache.</param> - /// <returns>The seekable Stream instance that contains a copy of what was returned in the HTTP response.</returns> - [SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", MessageId = "System.Diagnostics.Contracts.__ContractsRuntime.Assume(System.Boolean,System.String,System.String)", Justification = "No localization required.")] - private static MemoryStream CacheNetworkStreamAndClose(HttpWebResponse response, int maximumBytesToRead) { - Contract.Requires<ArgumentNullException>(response != null); - Contract.Ensures(Contract.Result<MemoryStream>() != null); - - // Now read and cache the network stream - Stream networkStream = response.GetResponseStream(); - MemoryStream cachedStream = new MemoryStream(response.ContentLength < 0 ? 4 * 1024 : Math.Min((int)response.ContentLength, maximumBytesToRead)); - try { - Contract.Assume(networkStream.CanRead, "HttpWebResponse.GetResponseStream() always returns a readable stream."); // CC missing - Contract.Assume(cachedStream.CanWrite, "This is a MemoryStream -- it's always writable."); // CC missing - networkStream.CopyTo(cachedStream); - cachedStream.Seek(0, SeekOrigin.Begin); - - networkStream.Dispose(); - response.Close(); - - return cachedStream; - } catch { - cachedStream.Dispose(); - throw; - } - } - } -} diff --git a/src/DotNetOpenAuth/Messaging/Channel.cs b/src/DotNetOpenAuth/Messaging/Channel.cs deleted file mode 100644 index 872fc1c..0000000 --- a/src/DotNetOpenAuth/Messaging/Channel.cs +++ /dev/null @@ -1,1406 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="Channel.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.Messaging { - using System; - using System.Collections.Generic; - using System.Collections.ObjectModel; - using System.ComponentModel; - using System.Diagnostics; - using System.Diagnostics.CodeAnalysis; - using System.Diagnostics.Contracts; - using System.Globalization; - using System.IO; - using System.Linq; - using System.Net; - using System.Net.Cache; - using System.Net.Mime; - using System.Runtime.Serialization.Json; - using System.Text; - using System.Threading; - using System.Web; - using System.Xml; - using DotNetOpenAuth.Messaging.Reflection; - - /// <summary> - /// Manages sending direct messages to a remote party and receiving responses. - /// </summary> - [SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Justification = "Unavoidable.")] - [ContractVerification(true)] - [ContractClass(typeof(ChannelContract))] - public abstract class Channel : IDisposable { - /// <summary> - /// The encoding to use when writing out POST entity strings. - /// </summary> - internal static readonly Encoding PostEntityEncoding = new UTF8Encoding(false); - - /// <summary> - /// The content-type used on HTTP POST requests where the POST entity is a - /// URL-encoded series of key=value pairs. - /// </summary> - protected internal const string HttpFormUrlEncoded = "application/x-www-form-urlencoded"; - - /// <summary> - /// The content-type used for JSON serialized objects. - /// </summary> - protected internal const string JsonEncoded = "application/json"; - - /// <summary> - /// The "text/javascript" content-type that some servers return instead of the standard <see cref="JsonEncoded"/> one. - /// </summary> - protected internal const string JsonTextEncoded = "text/javascript"; - - /// <summary> - /// The content-type for plain text. - /// </summary> - [SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "PlainText", Justification = "Not 'Plaintext' in the crypographic sense.")] - protected internal const string PlainTextEncoded = "text/plain"; - - /// <summary> - /// The content-type used on HTTP POST requests where the POST entity is a - /// URL-encoded series of key=value pairs. - /// This includes the <see cref="PostEntityEncoding"/> character encoding. - /// </summary> - protected internal static readonly ContentType HttpFormUrlEncodedContentType = new ContentType(HttpFormUrlEncoded) { CharSet = PostEntityEncoding.WebName }; - - /// <summary> - /// The HTML that should be returned to the user agent as part of a 301 Redirect. - /// </summary> - /// <value>A string that should be used as the first argument to String.Format, where the {0} should be replaced with the URL to redirect to.</value> - private const string RedirectResponseBodyFormat = @"<html><head><title>Object moved</title></head><body> -<h2>Object moved to <a href=""{0}"">here</a>.</h2> -</body></html>"; - - /// <summary> - /// A list of binding elements in the order they must be applied to outgoing messages. - /// </summary> - [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private readonly List<IChannelBindingElement> outgoingBindingElements = new List<IChannelBindingElement>(); - - /// <summary> - /// A list of binding elements in the order they must be applied to incoming messages. - /// </summary> - private readonly List<IChannelBindingElement> incomingBindingElements = new List<IChannelBindingElement>(); - - /// <summary> - /// The template for indirect messages that require form POST to forward through the user agent. - /// </summary> - /// <remarks> - /// We are intentionally using " instead of the html single quote ' below because - /// the HtmlEncode'd values that we inject will only escape the double quote, so - /// only the double-quote used around these values is safe. - /// </remarks> - private const string IndirectMessageFormPostFormat = @" -<html> -<head> -</head> -<body onload=""document.body.style.display = 'none'; var btn = document.getElementById('submit_button'); btn.disabled = true; btn.value = 'Login in progress'; document.getElementById('openid_message').submit()""> -<form id=""openid_message"" action=""{0}"" method=""post"" accept-charset=""UTF-8"" enctype=""application/x-www-form-urlencoded"" onSubmit=""var btn = document.getElementById('submit_button'); btn.disabled = true; btn.value = 'Login in progress'; return true;""> -{1} - <input id=""submit_button"" type=""submit"" value=""Continue"" /> -</form> -</body> -</html> -"; - - /// <summary> - /// The default cache of message descriptions to use unless they are customized. - /// </summary> - /// <remarks> - /// This is a perf optimization, so that we don't reflect over every message type - /// every time a channel is constructed. - /// </remarks> - private static MessageDescriptionCollection defaultMessageDescriptions = new MessageDescriptionCollection(); - - /// <summary> - /// A cache of reflected message types that may be sent or received on this channel. - /// </summary> - private MessageDescriptionCollection messageDescriptions = defaultMessageDescriptions; - - /// <summary> - /// A tool that can figure out what kind of message is being received - /// so it can be deserialized. - /// </summary> - private IMessageFactory messageTypeProvider; - - /// <summary> - /// Backing store for the <see cref="CachePolicy"/> property. - /// </summary> - private RequestCachePolicy cachePolicy = new HttpRequestCachePolicy(HttpRequestCacheLevel.NoCacheNoStore); - - /// <summary> - /// Backing field for the <see cref="MaximumIndirectMessageUrlLength"/> property. - /// </summary> - private int maximumIndirectMessageUrlLength = Configuration.DotNetOpenAuthSection.Configuration.Messaging.MaximumIndirectMessageUrlLength; - - /// <summary> - /// Initializes a new instance of the <see cref="Channel"/> class. - /// </summary> - /// <param name="messageTypeProvider"> - /// A class prepared to analyze incoming messages and indicate what concrete - /// message types can deserialize from it. - /// </param> - /// <param name="bindingElements">The binding elements to use in sending and receiving messages.</param> - protected Channel(IMessageFactory messageTypeProvider, params IChannelBindingElement[] bindingElements) { - Contract.Requires<ArgumentNullException>(messageTypeProvider != null); - - this.messageTypeProvider = messageTypeProvider; - this.WebRequestHandler = new StandardWebRequestHandler(); - this.XmlDictionaryReaderQuotas = new XmlDictionaryReaderQuotas { - MaxArrayLength = 1, - MaxDepth = 2, - MaxBytesPerRead = 8 * 1024, - MaxStringContentLength = 16 * 1024, - }; - - this.outgoingBindingElements = new List<IChannelBindingElement>(ValidateAndPrepareBindingElements(bindingElements)); - this.incomingBindingElements = new List<IChannelBindingElement>(this.outgoingBindingElements); - this.incomingBindingElements.Reverse(); - - foreach (var element in this.outgoingBindingElements) { - element.Channel = this; - } - } - - /// <summary> - /// An event fired whenever a message is about to be encoded and sent. - /// </summary> - internal event EventHandler<ChannelEventArgs> Sending; - - /// <summary> - /// Gets or sets an instance to a <see cref="IDirectWebRequestHandler"/> that will be used when - /// submitting HTTP requests and waiting for responses. - /// </summary> - /// <remarks> - /// This defaults to a straightforward implementation, but can be set - /// to a mock object for testing purposes. - /// </remarks> - public IDirectWebRequestHandler WebRequestHandler { get; set; } - - /// <summary> - /// Gets or sets the maximum allowable size for a 301 Redirect response before we send - /// a 200 OK response with a scripted form POST with the parameters instead - /// in order to ensure successfully sending a large payload to another server - /// that might have a maximum allowable size restriction on its GET request. - /// </summary> - /// <value>The default value is 2048.</value> - public int MaximumIndirectMessageUrlLength { - get { - return this.maximumIndirectMessageUrlLength; - } - - set { - Contract.Requires<ArgumentOutOfRangeException>(value >= 500 && value <= 4096); - this.maximumIndirectMessageUrlLength = value; - } - } - - /// <summary> - /// Gets or sets the message descriptions. - /// </summary> - internal virtual MessageDescriptionCollection MessageDescriptions { - get { - return this.messageDescriptions; - } - - set { - Contract.Requires<ArgumentNullException>(value != null); - this.messageDescriptions = value; - } - } - - /// <summary> - /// Gets a tool that can figure out what kind of message is being received - /// so it can be deserialized. - /// </summary> - internal IMessageFactory MessageFactoryTestHook { - get { return this.MessageFactory; } - } - - /// <summary> - /// Gets the binding elements used by this channel, in no particular guaranteed order. - /// </summary> - protected internal ReadOnlyCollection<IChannelBindingElement> BindingElements { - get { - Contract.Ensures(Contract.Result<ReadOnlyCollection<IChannelBindingElement>>() != null); - var result = this.outgoingBindingElements.AsReadOnly(); - Contract.Assume(result != null); // should be an implicit BCL contract - return result; - } - } - - /// <summary> - /// Gets the binding elements used by this channel, in the order applied to outgoing messages. - /// </summary> - protected internal ReadOnlyCollection<IChannelBindingElement> OutgoingBindingElements { - get { return this.outgoingBindingElements.AsReadOnly(); } - } - - /// <summary> - /// Gets the binding elements used by this channel, in the order applied to incoming messages. - /// </summary> - protected internal ReadOnlyCollection<IChannelBindingElement> IncomingBindingElements { - get { - Contract.Ensures(Contract.Result<ReadOnlyCollection<IChannelBindingElement>>().All(be => be.Channel != null)); - Contract.Ensures(Contract.Result<ReadOnlyCollection<IChannelBindingElement>>().All(be => be != null)); - return this.incomingBindingElements.AsReadOnly(); - } - } - - /// <summary> - /// Gets or sets a value indicating whether this instance is disposed. - /// </summary> - /// <value> - /// <c>true</c> if this instance is disposed; otherwise, <c>false</c>. - /// </value> - protected internal bool IsDisposed { get; set; } - - /// <summary> - /// Gets or sets a tool that can figure out what kind of message is being received - /// so it can be deserialized. - /// </summary> - protected virtual IMessageFactory MessageFactory { - get { return this.messageTypeProvider; } - set { this.messageTypeProvider = value; } - } - - /// <summary> - /// Gets or sets the cache policy to use for direct message requests. - /// </summary> - /// <value>Default is <see cref="HttpRequestCacheLevel.NoCacheNoStore"/>.</value> - protected RequestCachePolicy CachePolicy { - get { - return this.cachePolicy; - } - - set { - Contract.Requires<ArgumentNullException>(value != null); - this.cachePolicy = value; - } - } - - /// <summary> - /// Gets or sets the XML dictionary reader quotas. - /// </summary> - /// <value>The XML dictionary reader quotas.</value> - protected virtual XmlDictionaryReaderQuotas XmlDictionaryReaderQuotas { get; set; } - - /// <summary> - /// Sends an indirect message (either a request or response) - /// or direct message response for transmission to a remote party - /// and ends execution on the current page or handler. - /// </summary> - /// <param name="message">The one-way message to send</param> - /// <exception cref="ThreadAbortException">Thrown by ASP.NET in order to prevent additional data from the page being sent to the client and corrupting the response.</exception> - /// <remarks> - /// Requires an HttpContext.Current context. - /// </remarks> - [EditorBrowsable(EditorBrowsableState.Never)] - public void Send(IProtocolMessage message) { - Contract.Requires<InvalidOperationException>(HttpContext.Current != null, MessagingStrings.CurrentHttpContextRequired); - Contract.Requires<ArgumentNullException>(message != null); - this.PrepareResponse(message).Respond(HttpContext.Current, true); - } - - /// <summary> - /// Sends an indirect message (either a request or response) - /// or direct message response for transmission to a remote party - /// and skips most of the remaining ASP.NET request handling pipeline. - /// Not safe to call from ASP.NET web forms. - /// </summary> - /// <param name="message">The one-way message to send</param> - /// <remarks> - /// Requires an HttpContext.Current context. - /// This call is not safe to make from an ASP.NET web form (.aspx file or code-behind) because - /// ASP.NET will render HTML after the protocol message has been sent, which will corrupt the response. - /// Use the <see cref="Send"/> method instead for web forms. - /// </remarks> - public void Respond(IProtocolMessage message) { - Contract.Requires<InvalidOperationException>(HttpContext.Current != null, MessagingStrings.CurrentHttpContextRequired); - Contract.Requires<ArgumentNullException>(message != null); - this.PrepareResponse(message).Respond(); - } - - /// <summary> - /// Prepares an indirect message (either a request or response) - /// or direct message response for transmission to a remote party. - /// </summary> - /// <param name="message">The one-way message to send</param> - /// <returns>The pending user agent redirect based message to be sent as an HttpResponse.</returns> - public OutgoingWebResponse PrepareResponse(IProtocolMessage message) { - Contract.Requires<ArgumentNullException>(message != null); - Contract.Ensures(Contract.Result<OutgoingWebResponse>() != null); - - this.ProcessOutgoingMessage(message); - Logger.Channel.DebugFormat("Sending message: {0}", message.GetType().Name); - - OutgoingWebResponse result; - switch (message.Transport) { - case MessageTransport.Direct: - // This is a response to a direct message. - result = this.PrepareDirectResponse(message); - break; - case MessageTransport.Indirect: - var directedMessage = message as IDirectedProtocolMessage; - ErrorUtilities.VerifyArgumentNamed( - directedMessage != null, - "message", - MessagingStrings.IndirectMessagesMustImplementIDirectedProtocolMessage, - typeof(IDirectedProtocolMessage).FullName); - ErrorUtilities.VerifyArgumentNamed( - directedMessage.Recipient != null, - "message", - MessagingStrings.DirectedMessageMissingRecipient); - result = this.PrepareIndirectResponse(directedMessage); - break; - default: - throw ErrorUtilities.ThrowArgumentNamed( - "message", - MessagingStrings.UnrecognizedEnumValue, - "Transport", - message.Transport); - } - - // Apply caching policy to any response. We want to disable all caching because in auth* protocols, - // caching can be utilized in identity spoofing attacks. - result.Headers[HttpResponseHeader.CacheControl] = "no-cache, no-store, max-age=0, must-revalidate"; - result.Headers[HttpResponseHeader.Pragma] = "no-cache"; - - return result; - } - - /// <summary> - /// Gets the protocol message embedded in the given HTTP request, if present. - /// </summary> - /// <returns>The deserialized message, if one is found. Null otherwise.</returns> - /// <remarks> - /// Requires an HttpContext.Current context. - /// </remarks> - /// <exception cref="InvalidOperationException">Thrown when <see cref="HttpContext.Current"/> is null.</exception> - public IDirectedProtocolMessage ReadFromRequest() { - return this.ReadFromRequest(this.GetRequestFromContext()); - } - - /// <summary> - /// Gets the protocol message embedded in the given HTTP request, if present. - /// </summary> - /// <typeparam name="TRequest">The expected type of the message to be received.</typeparam> - /// <param name="request">The deserialized message, if one is found. Null otherwise.</param> - /// <returns>True if the expected message was recognized and deserialized. False otherwise.</returns> - /// <remarks> - /// Requires an HttpContext.Current context. - /// </remarks> - /// <exception cref="InvalidOperationException">Thrown when <see cref="HttpContext.Current"/> is null.</exception> - /// <exception cref="ProtocolException">Thrown when a request message of an unexpected type is received.</exception> - public bool TryReadFromRequest<TRequest>(out TRequest request) - where TRequest : class, IProtocolMessage { - return TryReadFromRequest<TRequest>(this.GetRequestFromContext(), out request); - } - - /// <summary> - /// Gets the protocol message embedded in the given HTTP request, if present. - /// </summary> - /// <typeparam name="TRequest">The expected type of the message to be received.</typeparam> - /// <param name="httpRequest">The request to search for an embedded message.</param> - /// <param name="request">The deserialized message, if one is found. Null otherwise.</param> - /// <returns>True if the expected message was recognized and deserialized. False otherwise.</returns> - /// <exception cref="InvalidOperationException">Thrown when <see cref="HttpContext.Current"/> is null.</exception> - /// <exception cref="ProtocolException">Thrown when a request message of an unexpected type is received.</exception> - public bool TryReadFromRequest<TRequest>(HttpRequestInfo httpRequest, out TRequest request) - where TRequest : class, IProtocolMessage { - Contract.Requires<ArgumentNullException>(httpRequest != null); - Contract.Ensures(Contract.Result<bool>() == (Contract.ValueAtReturn<TRequest>(out request) != null)); - - IProtocolMessage untypedRequest = this.ReadFromRequest(httpRequest); - if (untypedRequest == null) { - request = null; - return false; - } - - request = untypedRequest as TRequest; - ErrorUtilities.VerifyProtocol(request != null, MessagingStrings.UnexpectedMessageReceived, typeof(TRequest), untypedRequest.GetType()); - - return true; - } - - /// <summary> - /// Gets the protocol message embedded in the current HTTP request. - /// </summary> - /// <typeparam name="TRequest">The expected type of the message to be received.</typeparam> - /// <returns>The deserialized message. Never null.</returns> - /// <remarks> - /// Requires an HttpContext.Current context. - /// </remarks> - /// <exception cref="InvalidOperationException">Thrown when <see cref="HttpContext.Current"/> is null.</exception> - /// <exception cref="ProtocolException">Thrown if the expected message was not recognized in the response.</exception> - [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "This returns and verifies the appropriate message type.")] - public TRequest ReadFromRequest<TRequest>() - where TRequest : class, IProtocolMessage { - return this.ReadFromRequest<TRequest>(this.GetRequestFromContext()); - } - - /// <summary> - /// Gets the protocol message embedded in the given HTTP request. - /// </summary> - /// <typeparam name="TRequest">The expected type of the message to be received.</typeparam> - /// <param name="httpRequest">The request to search for an embedded message.</param> - /// <returns>The deserialized message. Never null.</returns> - /// <exception cref="ProtocolException">Thrown if the expected message was not recognized in the response.</exception> - [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "This returns and verifies the appropriate message type.")] - public TRequest ReadFromRequest<TRequest>(HttpRequestInfo httpRequest) - where TRequest : class, IProtocolMessage { - Contract.Requires<ArgumentNullException>(httpRequest != null); - TRequest request; - if (this.TryReadFromRequest<TRequest>(httpRequest, out request)) { - return request; - } else { - throw ErrorUtilities.ThrowProtocol(MessagingStrings.ExpectedMessageNotReceived, typeof(TRequest)); - } - } - - /// <summary> - /// Gets the protocol message that may be embedded in the given HTTP request. - /// </summary> - /// <param name="httpRequest">The request to search for an embedded message.</param> - /// <returns>The deserialized message, if one is found. Null otherwise.</returns> - public IDirectedProtocolMessage ReadFromRequest(HttpRequestInfo httpRequest) { - Contract.Requires<ArgumentNullException>(httpRequest != null); - - if (Logger.Channel.IsInfoEnabled && httpRequest.UrlBeforeRewriting != null) { - Logger.Channel.InfoFormat("Scanning incoming request for messages: {0}", httpRequest.UrlBeforeRewriting.AbsoluteUri); - } - IDirectedProtocolMessage requestMessage = this.ReadFromRequestCore(httpRequest); - if (requestMessage != null) { - Logger.Channel.DebugFormat("Incoming request received: {0}", requestMessage.GetType().Name); - this.ProcessIncomingMessage(requestMessage); - } - - return requestMessage; - } - - /// <summary> - /// Sends a direct message to a remote party and waits for the response. - /// </summary> - /// <typeparam name="TResponse">The expected type of the message to be received.</typeparam> - /// <param name="requestMessage">The message to send.</param> - /// <returns>The remote party's response.</returns> - /// <exception cref="ProtocolException"> - /// Thrown if no message is recognized in the response - /// or an unexpected type of message is received. - /// </exception> - [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "This returns and verifies the appropriate message type.")] - public TResponse Request<TResponse>(IDirectedProtocolMessage requestMessage) - where TResponse : class, IProtocolMessage { - Contract.Requires<ArgumentNullException>(requestMessage != null); - Contract.Ensures(Contract.Result<TResponse>() != null); - - IProtocolMessage response = this.Request(requestMessage); - ErrorUtilities.VerifyProtocol(response != null, MessagingStrings.ExpectedMessageNotReceived, typeof(TResponse)); - - var expectedResponse = response as TResponse; - ErrorUtilities.VerifyProtocol(expectedResponse != null, MessagingStrings.UnexpectedMessageReceived, typeof(TResponse), response.GetType()); - - return expectedResponse; - } - - /// <summary> - /// Sends a direct message to a remote party and waits for the response. - /// </summary> - /// <param name="requestMessage">The message to send.</param> - /// <returns>The remote party's response. Guaranteed to never be null.</returns> - /// <exception cref="ProtocolException">Thrown if the response does not include a protocol message.</exception> - public IProtocolMessage Request(IDirectedProtocolMessage requestMessage) { - Contract.Requires<ArgumentNullException>(requestMessage != null); - - this.ProcessOutgoingMessage(requestMessage); - Logger.Channel.DebugFormat("Sending {0} request.", requestMessage.GetType().Name); - var responseMessage = this.RequestCore(requestMessage); - ErrorUtilities.VerifyProtocol(responseMessage != null, MessagingStrings.ExpectedMessageNotReceived, typeof(IProtocolMessage).Name); - - Logger.Channel.DebugFormat("Received {0} response.", responseMessage.GetType().Name); - this.ProcessIncomingMessage(responseMessage); - - return responseMessage; - } - - #region IDisposable Members - - /// <summary> - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. - /// </summary> - public void Dispose() { - this.Dispose(true); - GC.SuppressFinalize(this); - } - - #endregion - - /// <summary> - /// Verifies the integrity and applicability of an incoming message. - /// </summary> - /// <param name="message">The message just received.</param> - /// <exception cref="ProtocolException"> - /// Thrown when the message is somehow invalid. - /// This can be due to tampering, replay attack or expiration, among other things. - /// </exception> - internal void ProcessIncomingMessageTestHook(IProtocolMessage message) { - this.ProcessIncomingMessage(message); - } - - /// <summary> - /// Prepares an HTTP request that carries a given message. - /// </summary> - /// <param name="request">The message to send.</param> - /// <returns>The <see cref="HttpWebRequest"/> prepared to send the request.</returns> - /// <remarks> - /// This method must be overridden by a derived class, unless the <see cref="RequestCore"/> method - /// is overridden and does not require this method. - /// </remarks> - internal HttpWebRequest CreateHttpRequestTestHook(IDirectedProtocolMessage request) { - return this.CreateHttpRequest(request); - } - - /// <summary> - /// Queues a message for sending in the response stream where the fields - /// are sent in the response stream in querystring style. - /// </summary> - /// <param name="response">The message to send as a response.</param> - /// <returns>The pending user agent redirect based message to be sent as an HttpResponse.</returns> - /// <remarks> - /// This method implements spec OAuth V1.0 section 5.3. - /// </remarks> - internal OutgoingWebResponse PrepareDirectResponseTestHook(IProtocolMessage response) { - return this.PrepareDirectResponse(response); - } - - /// <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> - internal IDictionary<string, string> ReadFromResponseCoreTestHook(IncomingWebResponse response) { - return this.ReadFromResponseCore(response); - } - - /// <remarks> - /// This method should NOT be called by derived types - /// except when sending ONE WAY request messages. - /// </remarks> - /// <summary> - /// Prepares a message for transmit by applying signatures, nonces, etc. - /// </summary> - /// <param name="message">The message to prepare for sending.</param> - internal void ProcessOutgoingMessageTestHook(IProtocolMessage message) { - this.ProcessOutgoingMessage(message); - } - - /// <summary> - /// Gets the current HTTP request being processed. - /// </summary> - /// <returns>The HttpRequestInfo for the current request.</returns> - /// <remarks> - /// Requires an <see cref="HttpContext.Current"/> context. - /// </remarks> - /// <exception cref="InvalidOperationException">Thrown if <see cref="HttpContext.Current">HttpContext.Current</see> == <c>null</c>.</exception> - [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "Costly call should not be a property.")] - protected internal virtual HttpRequestInfo GetRequestFromContext() { - Contract.Requires<InvalidOperationException>(HttpContext.Current != null && HttpContext.Current.Request != null, MessagingStrings.HttpContextRequired); - Contract.Ensures(Contract.Result<HttpRequestInfo>() != null); - Contract.Ensures(Contract.Result<HttpRequestInfo>().Url != null); - Contract.Ensures(Contract.Result<HttpRequestInfo>().RawUrl != null); - Contract.Ensures(Contract.Result<HttpRequestInfo>().UrlBeforeRewriting != null); - - Contract.Assume(HttpContext.Current.Request.Url != null); - Contract.Assume(HttpContext.Current.Request.RawUrl != null); - return new HttpRequestInfo(HttpContext.Current.Request); - } - - /// <summary> - /// Checks whether a given HTTP method is expected to include an entity body in its request. - /// </summary> - /// <param name="httpMethod">The HTTP method.</param> - /// <returns><c>true</c> if the HTTP method is supposed to have an entity; <c>false</c> otherwise.</returns> - protected static bool HttpMethodHasEntity(string httpMethod) { - if (string.Equals(httpMethod, "GET", StringComparison.Ordinal) || - string.Equals(httpMethod, "HEAD", StringComparison.Ordinal) || - string.Equals(httpMethod, "DELETE", StringComparison.Ordinal)) { - return false; - } else if (string.Equals(httpMethod, "POST", StringComparison.Ordinal) || - string.Equals(httpMethod, "PUT", StringComparison.Ordinal)) { - return true; - } else { - throw ErrorUtilities.ThrowArgumentNamed("httpMethod", MessagingStrings.UnsupportedHttpVerb, httpMethod); - } - } - - /// <summary> - /// Releases unmanaged and - optionally - managed resources - /// </summary> - /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param> - protected virtual void Dispose(bool disposing) { - if (disposing) { - // Call dispose on any binding elements that need it. - foreach (IDisposable bindingElement in this.BindingElements.OfType<IDisposable>()) { - bindingElement.Dispose(); - } - - this.IsDisposed = true; - } - } - - /// <summary> - /// Fires the <see cref="Sending"/> event. - /// </summary> - /// <param name="message">The message about to be encoded and sent.</param> - protected virtual void OnSending(IProtocolMessage message) { - Contract.Requires<ArgumentNullException>(message != null); - - var sending = this.Sending; - if (sending != null) { - sending(this, new ChannelEventArgs(message)); - } - } - - /// <summary> - /// Gets the direct response of a direct HTTP request. - /// </summary> - /// <param name="webRequest">The web request.</param> - /// <returns>The response to the web request.</returns> - /// <exception cref="ProtocolException">Thrown on network or protocol errors.</exception> - protected virtual IncomingWebResponse GetDirectResponse(HttpWebRequest webRequest) { - Contract.Requires<ArgumentNullException>(webRequest != null); - return this.WebRequestHandler.GetResponse(webRequest); - } - - /// <summary> - /// Submits a direct request message to some remote party and blocks waiting for an immediately reply. - /// </summary> - /// <param name="request">The request message.</param> - /// <returns>The response message, or null if the response did not carry a message.</returns> - /// <remarks> - /// Typically a deriving channel will override <see cref="CreateHttpRequest"/> to customize this method's - /// behavior. However in non-HTTP frameworks, such as unit test mocks, it may be appropriate to override - /// this method to eliminate all use of an HTTP transport. - /// </remarks> - protected virtual IProtocolMessage RequestCore(IDirectedProtocolMessage request) { - Contract.Requires<ArgumentNullException>(request != null); - Contract.Requires<ArgumentException>(request.Recipient != null, MessagingStrings.DirectedMessageMissingRecipient); - - HttpWebRequest webRequest = this.CreateHttpRequest(request); - IDictionary<string, string> responseFields; - IDirectResponseProtocolMessage responseMessage; - - using (IncomingWebResponse response = this.GetDirectResponse(webRequest)) { - if (response.ResponseStream == null) { - return null; - } - - responseFields = this.ReadFromResponseCore(response); - if (responseFields == null) { - return null; - } - - responseMessage = this.MessageFactory.GetNewResponseMessage(request, responseFields); - if (responseMessage == null) { - return null; - } - - this.OnReceivingDirectResponse(response, responseMessage); - } - - var messageAccessor = this.MessageDescriptions.GetAccessor(responseMessage); - messageAccessor.Deserialize(responseFields); - - return responseMessage; - } - - /// <summary> - /// Called when receiving a direct response message, before deserialization begins. - /// </summary> - /// <param name="response">The HTTP direct response.</param> - /// <param name="message">The newly instantiated message, prior to deserialization.</param> - protected virtual void OnReceivingDirectResponse(IncomingWebResponse response, IDirectResponseProtocolMessage message) { - } - - /// <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 virtual IDirectedProtocolMessage ReadFromRequestCore(HttpRequestInfo request) { - Contract.Requires<ArgumentNullException>(request != null); - - Logger.Channel.DebugFormat("Incoming HTTP request: {0} {1}", request.HttpMethod, request.UrlBeforeRewriting.AbsoluteUri); - - // Search Form data first, and if nothing is there search the QueryString - Contract.Assume(request.Form != null && request.QueryStringBeforeRewriting != null); - var fields = request.Form.ToDictionary(); - if (fields.Count == 0 && request.HttpMethod != "POST") { // OpenID 2.0 section 4.1.2 - fields = request.QueryStringBeforeRewriting.ToDictionary(); - } - - MessageReceivingEndpoint recipient; - try { - recipient = request.GetRecipient(); - } catch (ArgumentException ex) { - Logger.Messaging.WarnFormat("Unrecognized HTTP request: {0}", ex); - return null; - } - - return (IDirectedProtocolMessage)this.Receive(fields, recipient); - } - - /// <summary> - /// Deserializes a dictionary of values into a message. - /// </summary> - /// <param name="fields">The dictionary of values that were read from an HTTP request or response.</param> - /// <param name="recipient">Information about where the message was directed. Null for direct response messages.</param> - /// <returns>The deserialized message, or null if no message could be recognized in the provided data.</returns> - protected virtual IProtocolMessage Receive(Dictionary<string, string> fields, MessageReceivingEndpoint recipient) { - Contract.Requires<ArgumentNullException>(fields != null); - - this.FilterReceivedFields(fields); - IProtocolMessage message = this.MessageFactory.GetNewRequestMessage(recipient, fields); - - // If there was no data, or we couldn't recognize it as a message, abort. - if (message == null) { - return null; - } - - // Ensure that the message came in using an allowed HTTP verb for this message type. - var directedMessage = message as IDirectedProtocolMessage; - ErrorUtilities.VerifyProtocol(recipient == null || (directedMessage != null && (recipient.AllowedMethods & directedMessage.HttpMethods) != 0), MessagingStrings.UnsupportedHttpVerbForMessageType, message.GetType().Name, recipient.AllowedMethods); - - // We have a message! Assemble it. - var messageAccessor = this.MessageDescriptions.GetAccessor(message); - messageAccessor.Deserialize(fields); - - return message; - } - - /// <summary> - /// Queues an indirect message for transmittal via the user agent. - /// </summary> - /// <param name="message">The message to send.</param> - /// <returns>The pending user agent redirect based message to be sent as an HttpResponse.</returns> - protected virtual OutgoingWebResponse PrepareIndirectResponse(IDirectedProtocolMessage message) { - Contract.Requires<ArgumentNullException>(message != null); - Contract.Requires<ArgumentException>(message.Recipient != null, MessagingStrings.DirectedMessageMissingRecipient); - Contract.Requires<ArgumentException>((message.HttpMethods & (HttpDeliveryMethods.GetRequest | HttpDeliveryMethods.PostRequest)) != 0); - Contract.Ensures(Contract.Result<OutgoingWebResponse>() != null); - - Contract.Assert(message != null && message.Recipient != null); - var messageAccessor = this.MessageDescriptions.GetAccessor(message); - Contract.Assert(message != null && message.Recipient != null); - var fields = messageAccessor.Serialize(); - - OutgoingWebResponse response = null; - bool tooLargeForGet = false; - if ((message.HttpMethods & HttpDeliveryMethods.GetRequest) == HttpDeliveryMethods.GetRequest) { - bool payloadInFragment = false; - var httpIndirect = message as IHttpIndirectResponse; - if (httpIndirect != null) { - payloadInFragment = httpIndirect.Include301RedirectPayloadInFragment; - } - - // First try creating a 301 redirect, and fallback to a form POST - // if the message is too big. - response = this.Create301RedirectResponse(message, fields, payloadInFragment); - tooLargeForGet = response.Headers[HttpResponseHeader.Location].Length > this.MaximumIndirectMessageUrlLength; - } - - // Make sure that if the message is too large for GET that POST is allowed. - if (tooLargeForGet) { - ErrorUtilities.VerifyProtocol( - (message.HttpMethods & HttpDeliveryMethods.PostRequest) == HttpDeliveryMethods.PostRequest, - "Message too large for a HTTP GET, and HTTP POST is not allowed for this message type."); - } - - // If GET didn't work out, for whatever reason... - if (response == null || tooLargeForGet) { - response = this.CreateFormPostResponse(message, fields); - } - - return response; - } - - /// <summary> - /// Encodes an HTTP response that will instruct the user agent to forward a message to - /// some remote third party using a 301 Redirect GET method. - /// </summary> - /// <param name="message">The message to forward.</param> - /// <param name="fields">The pre-serialized fields from the message.</param> - /// <param name="payloadInFragment">if set to <c>true</c> the redirect will contain the message payload in the #fragment portion of the URL rather than the ?querystring.</param> - /// <returns>The encoded HTTP response.</returns> - [Pure] - protected virtual OutgoingWebResponse Create301RedirectResponse(IDirectedProtocolMessage message, IDictionary<string, string> fields, bool payloadInFragment = false) { - Contract.Requires<ArgumentNullException>(message != null); - Contract.Requires<ArgumentException>(message.Recipient != null, MessagingStrings.DirectedMessageMissingRecipient); - Contract.Requires<ArgumentNullException>(fields != null); - Contract.Ensures(Contract.Result<OutgoingWebResponse>() != null); - - // As part of this redirect, we include an HTML body in order to get passed some proxy filters - // such as WebSense. - WebHeaderCollection headers = new WebHeaderCollection(); - UriBuilder builder = new UriBuilder(message.Recipient); - if (payloadInFragment) { - builder.AppendFragmentArgs(fields); - } else { - builder.AppendQueryArgs(fields); - } - - headers.Add(HttpResponseHeader.Location, builder.Uri.AbsoluteUri); - headers.Add(HttpResponseHeader.ContentType, "text/html; charset=utf-8"); - Logger.Http.DebugFormat("Redirecting to {0}", builder.Uri.AbsoluteUri); - OutgoingWebResponse response = new OutgoingWebResponse { - Status = HttpStatusCode.Redirect, - Headers = headers, - Body = string.Format(CultureInfo.InvariantCulture, RedirectResponseBodyFormat, builder.Uri.AbsoluteUri), - OriginalMessage = message - }; - - return response; - } - - /// <summary> - /// Encodes an HTTP response that will instruct the user agent to forward a message to - /// some remote third party using a form POST method. - /// </summary> - /// <param name="message">The message to forward.</param> - /// <param name="fields">The pre-serialized fields from the message.</param> - /// <returns>The encoded HTTP response.</returns> - protected virtual OutgoingWebResponse CreateFormPostResponse(IDirectedProtocolMessage message, IDictionary<string, string> fields) { - Contract.Requires<ArgumentNullException>(message != null); - Contract.Requires<ArgumentException>(message.Recipient != null, MessagingStrings.DirectedMessageMissingRecipient); - Contract.Requires<ArgumentNullException>(fields != null); - Contract.Ensures(Contract.Result<OutgoingWebResponse>() != null); - - WebHeaderCollection headers = new WebHeaderCollection(); - headers.Add(HttpResponseHeader.ContentType, "text/html"); - using (StringWriter bodyWriter = new StringWriter(CultureInfo.InvariantCulture)) { - StringBuilder hiddenFields = new StringBuilder(); - foreach (var field in fields) { - hiddenFields.AppendFormat( - "\t<input type=\"hidden\" name=\"{0}\" value=\"{1}\" />\r\n", - HttpUtility.HtmlEncode(field.Key), - HttpUtility.HtmlEncode(field.Value)); - } - bodyWriter.WriteLine( - IndirectMessageFormPostFormat, - HttpUtility.HtmlEncode(message.Recipient.AbsoluteUri), - hiddenFields); - bodyWriter.Flush(); - OutgoingWebResponse response = new OutgoingWebResponse { - Status = HttpStatusCode.OK, - Headers = headers, - Body = bodyWriter.ToString(), - OriginalMessage = message - }; - - return response; - } - } - - /// <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 abstract IDictionary<string, string> ReadFromResponseCore(IncomingWebResponse response); - - /// <summary> - /// Prepares an HTTP request that carries a given message. - /// </summary> - /// <param name="request">The message to send.</param> - /// <returns>The <see cref="HttpWebRequest"/> prepared to send the request.</returns> - /// <remarks> - /// This method must be overridden by a derived class, unless the <see cref="Channel.RequestCore"/> method - /// is overridden and does not require this method. - /// </remarks> - protected virtual HttpWebRequest CreateHttpRequest(IDirectedProtocolMessage request) { - Contract.Requires<ArgumentNullException>(request != null); - Contract.Requires<ArgumentException>(request.Recipient != null, MessagingStrings.DirectedMessageMissingRecipient); - Contract.Ensures(Contract.Result<HttpWebRequest>() != null); - throw new NotImplementedException(); - } - - /// <summary> - /// Queues a message for sending in the response stream where the fields - /// are sent in the response stream in querystring style. - /// </summary> - /// <param name="response">The message to send as a response.</param> - /// <returns>The pending user agent redirect based message to be sent as an HttpResponse.</returns> - /// <remarks> - /// This method implements spec OAuth V1.0 section 5.3. - /// </remarks> - protected abstract OutgoingWebResponse PrepareDirectResponse(IProtocolMessage response); - - /// <summary> - /// Serializes the given message as a JSON string. - /// </summary> - /// <param name="message">The message to serialize.</param> - /// <returns>A JSON string.</returns> - [SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times", Justification = "This Dispose is safe.")] - protected virtual string SerializeAsJson(IMessage message) { - Contract.Requires<ArgumentNullException>(message != null); - - MessageDictionary messageDictionary = this.MessageDescriptions.GetAccessor(message); - using (var memoryStream = new MemoryStream()) { - using (var jsonWriter = JsonReaderWriterFactory.CreateJsonWriter(memoryStream, Encoding.UTF8)) { - MessageSerializer.Serialize(messageDictionary, jsonWriter); - jsonWriter.Flush(); - } - - string json = Encoding.UTF8.GetString(memoryStream.ToArray()); - return json; - } - } - - /// <summary> - /// Deserializes from flat data from a JSON object. - /// </summary> - /// <param name="json">A JSON string.</param> - /// <returns>The simple "key":"value" pairs from a JSON-encoded object.</returns> - protected virtual IDictionary<string, string> DeserializeFromJson(string json) { - Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(json)); - - var dictionary = new Dictionary<string, string>(); - using (var jsonReader = JsonReaderWriterFactory.CreateJsonReader(Encoding.UTF8.GetBytes(json), this.XmlDictionaryReaderQuotas)) { - MessageSerializer.DeserializeJsonAsFlatDictionary(dictionary, jsonReader); - } - return dictionary; - } - - /// <summary> - /// Prepares a message for transmit by applying signatures, nonces, etc. - /// </summary> - /// <param name="message">The message to prepare for sending.</param> - /// <remarks> - /// This method should NOT be called by derived types - /// except when sending ONE WAY request messages. - /// </remarks> - protected void ProcessOutgoingMessage(IProtocolMessage message) { - Contract.Requires<ArgumentNullException>(message != null); - - Logger.Channel.DebugFormat("Preparing to send {0} ({1}) message.", message.GetType().Name, message.Version); - this.OnSending(message); - - // Give the message a chance to do custom serialization. - IMessageWithEvents eventedMessage = message as IMessageWithEvents; - if (eventedMessage != null) { - eventedMessage.OnSending(); - } - - MessageProtections appliedProtection = MessageProtections.None; - foreach (IChannelBindingElement bindingElement in this.outgoingBindingElements) { - Contract.Assume(bindingElement.Channel != null); - MessageProtections? elementProtection = bindingElement.ProcessOutgoingMessage(message); - if (elementProtection.HasValue) { - Logger.Bindings.DebugFormat("Binding element {0} applied to message.", bindingElement.GetType().FullName); - - // Ensure that only one protection binding element applies to this message - // for each protection type. - ErrorUtilities.VerifyProtocol((appliedProtection & elementProtection.Value) == 0, MessagingStrings.TooManyBindingsOfferingSameProtection, elementProtection.Value); - appliedProtection |= elementProtection.Value; - } else { - Logger.Bindings.DebugFormat("Binding element {0} did not apply to message.", bindingElement.GetType().FullName); - } - } - - // Ensure that the message's protection requirements have been satisfied. - if ((message.RequiredProtection & appliedProtection) != message.RequiredProtection) { - throw new UnprotectedMessageException(message, appliedProtection); - } - - this.EnsureValidMessageParts(message); - message.EnsureValidMessage(); - - if (Logger.Channel.IsInfoEnabled) { - var directedMessage = message as IDirectedProtocolMessage; - string recipient = (directedMessage != null && directedMessage.Recipient != null) ? directedMessage.Recipient.AbsoluteUri : "<response>"; - var messageAccessor = this.MessageDescriptions.GetAccessor(message); - Logger.Channel.InfoFormat( - "Prepared outgoing {0} ({1}) message for {2}: {3}{4}", - message.GetType().Name, - message.Version, - recipient, - Environment.NewLine, - messageAccessor.ToStringDeferred()); - } - } - - /// <summary> - /// Prepares to send a request to the Service Provider as the query string in a GET request. - /// </summary> - /// <param name="requestMessage">The message to be transmitted to the ServiceProvider.</param> - /// <returns>The web request ready to send.</returns> - /// <remarks> - /// This method is simply a standard HTTP Get request with the message parts serialized to the query string. - /// This method satisfies OAuth 1.0 section 5.2, item #3. - /// </remarks> - protected virtual HttpWebRequest InitializeRequestAsGet(IDirectedProtocolMessage requestMessage) { - Contract.Requires<ArgumentNullException>(requestMessage != null); - Contract.Requires<ArgumentException>(requestMessage.Recipient != null, MessagingStrings.DirectedMessageMissingRecipient); - - var messageAccessor = this.MessageDescriptions.GetAccessor(requestMessage); - var fields = messageAccessor.Serialize(); - - UriBuilder builder = new UriBuilder(requestMessage.Recipient); - MessagingUtilities.AppendQueryArgs(builder, fields); - HttpWebRequest httpRequest = (HttpWebRequest)WebRequest.Create(builder.Uri); - - return httpRequest; - } - - /// <summary> - /// Prepares to send a request to the Service Provider as the query string in a HEAD request. - /// </summary> - /// <param name="requestMessage">The message to be transmitted to the ServiceProvider.</param> - /// <returns>The web request ready to send.</returns> - /// <remarks> - /// This method is simply a standard HTTP HEAD request with the message parts serialized to the query string. - /// This method satisfies OAuth 1.0 section 5.2, item #3. - /// </remarks> - protected virtual HttpWebRequest InitializeRequestAsHead(IDirectedProtocolMessage requestMessage) { - Contract.Requires<ArgumentNullException>(requestMessage != null); - Contract.Requires<ArgumentException>(requestMessage.Recipient != null, MessagingStrings.DirectedMessageMissingRecipient); - - HttpWebRequest request = this.InitializeRequestAsGet(requestMessage); - request.Method = "HEAD"; - return request; - } - - /// <summary> - /// Prepares to send a request to the Service Provider as the payload of a POST request. - /// </summary> - /// <param name="requestMessage">The message to be transmitted to the ServiceProvider.</param> - /// <returns>The web request ready to send.</returns> - /// <remarks> - /// This method is simply a standard HTTP POST request with the message parts serialized to the POST entity - /// with the application/x-www-form-urlencoded content type - /// This method satisfies OAuth 1.0 section 5.2, item #2 and OpenID 2.0 section 4.1.2. - /// </remarks> - protected virtual HttpWebRequest InitializeRequestAsPost(IDirectedProtocolMessage requestMessage) { - Contract.Requires<ArgumentNullException>(requestMessage != null); - Contract.Ensures(Contract.Result<HttpWebRequest>() != null); - - var messageAccessor = this.MessageDescriptions.GetAccessor(requestMessage); - var fields = messageAccessor.Serialize(); - - var httpRequest = (HttpWebRequest)WebRequest.Create(requestMessage.Recipient); - httpRequest.CachePolicy = this.CachePolicy; - httpRequest.Method = "POST"; - - var requestMessageWithBinaryData = requestMessage as IMessageWithBinaryData; - if (requestMessageWithBinaryData != null && requestMessageWithBinaryData.SendAsMultipart) { - var multiPartFields = new List<MultipartPostPart>(requestMessageWithBinaryData.BinaryData); - - // When sending multi-part, all data gets send as multi-part -- even the non-binary data. - multiPartFields.AddRange(fields.Select(field => MultipartPostPart.CreateFormPart(field.Key, field.Value))); - this.SendParametersInEntityAsMultipart(httpRequest, multiPartFields); - } else { - ErrorUtilities.VerifyProtocol(requestMessageWithBinaryData == null || requestMessageWithBinaryData.BinaryData.Count == 0, MessagingStrings.BinaryDataRequiresMultipart); - this.SendParametersInEntity(httpRequest, fields); - } - - return httpRequest; - } - - /// <summary> - /// Prepares to send a request to the Service Provider as the query string in a PUT request. - /// </summary> - /// <param name="requestMessage">The message to be transmitted to the ServiceProvider.</param> - /// <returns>The web request ready to send.</returns> - /// <remarks> - /// This method is simply a standard HTTP PUT request with the message parts serialized to the query string. - /// </remarks> - protected virtual HttpWebRequest InitializeRequestAsPut(IDirectedProtocolMessage requestMessage) { - Contract.Requires<ArgumentNullException>(requestMessage != null); - Contract.Ensures(Contract.Result<HttpWebRequest>() != null); - - HttpWebRequest request = this.InitializeRequestAsGet(requestMessage); - request.Method = "PUT"; - return request; - } - - /// <summary> - /// Prepares to send a request to the Service Provider as the query string in a DELETE request. - /// </summary> - /// <param name="requestMessage">The message to be transmitted to the ServiceProvider.</param> - /// <returns>The web request ready to send.</returns> - /// <remarks> - /// This method is simply a standard HTTP DELETE request with the message parts serialized to the query string. - /// </remarks> - protected virtual HttpWebRequest InitializeRequestAsDelete(IDirectedProtocolMessage requestMessage) { - Contract.Requires<ArgumentNullException>(requestMessage != null); - Contract.Ensures(Contract.Result<HttpWebRequest>() != null); - - HttpWebRequest request = this.InitializeRequestAsGet(requestMessage); - request.Method = "DELETE"; - return request; - } - - /// <summary> - /// Sends the given parameters in the entity stream of an HTTP request. - /// </summary> - /// <param name="httpRequest">The HTTP request.</param> - /// <param name="fields">The parameters to send.</param> - /// <remarks> - /// This method calls <see cref="HttpWebRequest.GetRequestStream()"/> and closes - /// the request stream, but does not call <see cref="HttpWebRequest.GetResponse"/>. - /// </remarks> - protected void SendParametersInEntity(HttpWebRequest httpRequest, IDictionary<string, string> fields) { - Contract.Requires<ArgumentNullException>(httpRequest != null); - Contract.Requires<ArgumentNullException>(fields != null); - - string requestBody = MessagingUtilities.CreateQueryString(fields); - byte[] requestBytes = PostEntityEncoding.GetBytes(requestBody); - httpRequest.ContentType = HttpFormUrlEncodedContentType.ToString(); - httpRequest.ContentLength = requestBytes.Length; - Stream requestStream = this.WebRequestHandler.GetRequestStream(httpRequest); - try { - requestStream.Write(requestBytes, 0, requestBytes.Length); - } finally { - // We need to be sure to close the request stream... - // unless it is a MemoryStream, which is a clue that we're in - // a mock stream situation and closing it would preclude reading it later. - if (!(requestStream is MemoryStream)) { - requestStream.Dispose(); - } - } - } - - /// <summary> - /// Sends the given parameters in the entity stream of an HTTP request in multi-part format. - /// </summary> - /// <param name="httpRequest">The HTTP request.</param> - /// <param name="fields">The parameters to send.</param> - /// <remarks> - /// This method calls <see cref="HttpWebRequest.GetRequestStream()"/> and closes - /// the request stream, but does not call <see cref="HttpWebRequest.GetResponse"/>. - /// </remarks> - protected void SendParametersInEntityAsMultipart(HttpWebRequest httpRequest, IEnumerable<MultipartPostPart> fields) { - httpRequest.PostMultipartNoGetResponse(this.WebRequestHandler, fields); - } - - /// <summary> - /// Verifies the integrity and applicability of an incoming message. - /// </summary> - /// <param name="message">The message just received.</param> - /// <exception cref="ProtocolException"> - /// Thrown when the message is somehow invalid. - /// This can be due to tampering, replay attack or expiration, among other things. - /// </exception> - protected virtual void ProcessIncomingMessage(IProtocolMessage message) { - Contract.Requires<ArgumentNullException>(message != null); - - if (Logger.Channel.IsInfoEnabled) { - var messageAccessor = this.MessageDescriptions.GetAccessor(message, true); - Logger.Channel.InfoFormat( - "Processing incoming {0} ({1}) message:{2}{3}", - message.GetType().Name, - message.Version, - Environment.NewLine, - messageAccessor.ToStringDeferred()); - } - - MessageProtections appliedProtection = MessageProtections.None; - foreach (IChannelBindingElement bindingElement in this.IncomingBindingElements) { - Contract.Assume(bindingElement.Channel != null); // CC bug: this.IncomingBindingElements ensures this... why must we assume it here? - MessageProtections? elementProtection = bindingElement.ProcessIncomingMessage(message); - if (elementProtection.HasValue) { - Logger.Bindings.DebugFormat("Binding element {0} applied to message.", bindingElement.GetType().FullName); - - // Ensure that only one protection binding element applies to this message - // for each protection type. - if ((appliedProtection & elementProtection.Value) != 0) { - // It turns out that this MAY not be a fatal error condition. - // But it may indicate a problem. - // Specifically, when this RP uses OpenID 1.x to talk to an OP, and both invent - // their own replay protection for OpenID 1.x, and the OP happens to reuse - // openid.response_nonce, then this RP may consider both the RP's own nonce and - // the OP's nonce and "apply" replay protection twice. This actually isn't a problem. - Logger.Bindings.WarnFormat(MessagingStrings.TooManyBindingsOfferingSameProtection, elementProtection.Value); - } - - appliedProtection |= elementProtection.Value; - } else { - Logger.Bindings.DebugFormat("Binding element {0} did not apply to message.", bindingElement.GetType().FullName); - } - } - - // Ensure that the message's protection requirements have been satisfied. - if ((message.RequiredProtection & appliedProtection) != message.RequiredProtection) { - throw new UnprotectedMessageException(message, appliedProtection); - } - - // Give the message a chance to do custom serialization. - IMessageWithEvents eventedMessage = message as IMessageWithEvents; - if (eventedMessage != null) { - eventedMessage.OnReceiving(); - } - - if (Logger.Channel.IsDebugEnabled) { - var messageAccessor = this.MessageDescriptions.GetAccessor(message); - Logger.Channel.DebugFormat( - "After binding element processing, the received {0} ({1}) message is: {2}{3}", - message.GetType().Name, - message.Version, - Environment.NewLine, - messageAccessor.ToStringDeferred()); - } - - // We do NOT verify that all required message parts are present here... the - // message deserializer did for us. It would be too late to do it here since - // they might look initialized by the time we have an IProtocolMessage instance. - message.EnsureValidMessage(); - } - - /// <summary> - /// Allows preprocessing and validation of message data before an appropriate message type is - /// selected or deserialized. - /// </summary> - /// <param name="fields">The received message data.</param> - protected virtual void FilterReceivedFields(IDictionary<string, string> fields) { - } - - /// <summary> - /// Customizes the binding element order for outgoing and incoming messages. - /// </summary> - /// <param name="outgoingOrder">The outgoing order.</param> - /// <param name="incomingOrder">The incoming order.</param> - /// <remarks> - /// No binding elements can be added or removed from the channel using this method. - /// Only a customized order is allowed. - /// </remarks> - /// <exception cref="ArgumentException">Thrown if a binding element is new or missing in one of the ordered lists.</exception> - protected void CustomizeBindingElementOrder(IEnumerable<IChannelBindingElement> outgoingOrder, IEnumerable<IChannelBindingElement> incomingOrder) { - Contract.Requires<ArgumentNullException>(outgoingOrder != null); - Contract.Requires<ArgumentNullException>(incomingOrder != null); - ErrorUtilities.VerifyArgument(this.IsBindingElementOrderValid(outgoingOrder), MessagingStrings.InvalidCustomBindingElementOrder); - ErrorUtilities.VerifyArgument(this.IsBindingElementOrderValid(incomingOrder), MessagingStrings.InvalidCustomBindingElementOrder); - - this.outgoingBindingElements.Clear(); - this.outgoingBindingElements.AddRange(outgoingOrder); - this.incomingBindingElements.Clear(); - this.incomingBindingElements.AddRange(incomingOrder); - } - - /// <summary> - /// Ensures a consistent and secure set of binding elements and - /// sorts them as necessary for a valid sequence of operations. - /// </summary> - /// <param name="elements">The binding elements provided to the channel.</param> - /// <returns>The properly ordered list of elements.</returns> - /// <exception cref="ProtocolException">Thrown when the binding elements are incomplete or inconsistent with each other.</exception> - private static IEnumerable<IChannelBindingElement> ValidateAndPrepareBindingElements(IEnumerable<IChannelBindingElement> elements) { - Contract.Requires<ArgumentException>(elements == null || elements.All(e => e != null)); - Contract.Ensures(Contract.Result<IEnumerable<IChannelBindingElement>>() != null); - if (elements == null) { - return new IChannelBindingElement[0]; - } - - // Filter the elements between the mere transforming ones and the protection ones. - var transformationElements = new List<IChannelBindingElement>( - elements.Where(element => element.Protection == MessageProtections.None)); - var protectionElements = new List<IChannelBindingElement>( - elements.Where(element => element.Protection != MessageProtections.None)); - - bool wasLastProtectionPresent = true; - foreach (MessageProtections protectionKind in Enum.GetValues(typeof(MessageProtections))) { - if (protectionKind == MessageProtections.None) { - continue; - } - - int countProtectionsOfThisKind = protectionElements.Count(element => (element.Protection & protectionKind) == protectionKind); - - // Each protection binding element is backed by the presence of its dependent protection(s). - ErrorUtilities.VerifyProtocol(!(countProtectionsOfThisKind > 0 && !wasLastProtectionPresent), MessagingStrings.RequiredProtectionMissing, protectionKind); - - wasLastProtectionPresent = countProtectionsOfThisKind > 0; - } - - // Put the binding elements in order so they are correctly applied to outgoing messages. - // Start with the transforming (non-protecting) binding elements first and preserve their original order. - var orderedList = new List<IChannelBindingElement>(transformationElements); - - // Now sort the protection binding elements among themselves and add them to the list. - orderedList.AddRange(protectionElements.OrderBy(element => element.Protection, BindingElementOutgoingMessageApplicationOrder)); - return orderedList; - } - - /// <summary> - /// Puts binding elements in their correct outgoing message processing order. - /// </summary> - /// <param name="protection1">The first protection type to compare.</param> - /// <param name="protection2">The second protection type to compare.</param> - /// <returns> - /// -1 if <paramref name="protection1"/> should be applied to an outgoing message before <paramref name="protection2"/>. - /// 1 if <paramref name="protection2"/> should be applied to an outgoing message before <paramref name="protection1"/>. - /// 0 if it doesn't matter. - /// </returns> - private static int BindingElementOutgoingMessageApplicationOrder(MessageProtections protection1, MessageProtections protection2) { - ErrorUtilities.VerifyInternal(protection1 != MessageProtections.None || protection2 != MessageProtections.None, "This comparison function should only be used to compare protection binding elements. Otherwise we change the order of user-defined message transformations."); - - // Now put the protection ones in the right order. - return -((int)protection1).CompareTo((int)protection2); // descending flag ordinal order - } - -#if CONTRACTS_FULL - /// <summary> - /// Verifies conditions that should be true for any valid state of this object. - /// </summary> - [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Called by code contracts.")] - [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called by code contracts.")] - [ContractInvariantMethod] - private void ObjectInvariant() { - Contract.Invariant(this.MessageDescriptions != null); - } -#endif - - /// <summary> - /// Verifies that all required message parts are initialized to values - /// prior to sending the message to a remote party. - /// </summary> - /// <param name="message">The message to verify.</param> - /// <exception cref="ProtocolException"> - /// Thrown when any required message part does not have a value. - /// </exception> - private void EnsureValidMessageParts(IProtocolMessage message) { - Contract.Requires<ArgumentNullException>(message != null); - MessageDictionary dictionary = this.MessageDescriptions.GetAccessor(message); - MessageDescription description = this.MessageDescriptions.Get(message); - description.EnsureMessagePartsPassBasicValidation(dictionary); - } - - /// <summary> - /// Determines whether a given ordered list of binding elements includes every - /// binding element in this channel exactly once. - /// </summary> - /// <param name="order">The list of binding elements to test.</param> - /// <returns> - /// <c>true</c> if the given list is a valid description of a binding element ordering; otherwise, <c>false</c>. - /// </returns> - [Pure] - private bool IsBindingElementOrderValid(IEnumerable<IChannelBindingElement> order) { - Contract.Requires<ArgumentNullException>(order != null); - - // Check that the same number of binding elements are defined. - if (order.Count() != this.OutgoingBindingElements.Count) { - return false; - } - - // Check that every binding element appears exactly once. - if (order.Any(el => !this.OutgoingBindingElements.Contains(el))) { - return false; - } - - return true; - } - } -} diff --git a/src/DotNetOpenAuth/Messaging/ChannelContract.cs b/src/DotNetOpenAuth/Messaging/ChannelContract.cs deleted file mode 100644 index 9b85d28..0000000 --- a/src/DotNetOpenAuth/Messaging/ChannelContract.cs +++ /dev/null @@ -1,54 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="ChannelContract.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.Messaging { - using System; - using System.Collections.Generic; - using System.Diagnostics.Contracts; - - /// <summary> - /// Code contract for the <see cref="Channel"/> class. - /// </summary> - [ContractClassFor(typeof(Channel))] - internal abstract class ChannelContract : Channel { - /// <summary> - /// Prevents a default instance of the ChannelContract class from being created. - /// </summary> - private ChannelContract() - : base(null, null) { - } - - /// <summary> - /// Gets the protocol message that may be in the given HTTP response. - /// </summary> - /// <param name="response">The response that is anticipated to contain an protocol message.</param> - /// <returns> - /// The deserialized message parts, if found. Null otherwise. - /// </returns> - /// <exception cref="ProtocolException">Thrown when the response is not valid.</exception> - protected override IDictionary<string, string> ReadFromResponseCore(IncomingWebResponse response) { - Contract.Requires<ArgumentNullException>(response != null); - throw new NotImplementedException(); - } - - /// <summary> - /// Queues a message for sending in the response stream where the fields - /// are sent in the response stream in querystring style. - /// </summary> - /// <param name="response">The message to send as a response.</param> - /// <returns> - /// The pending user agent redirect based message to be sent as an HttpResponse. - /// </returns> - /// <remarks> - /// This method implements spec V1.0 section 5.3. - /// </remarks> - protected override OutgoingWebResponse PrepareDirectResponse(IProtocolMessage response) { - Contract.Requires<ArgumentNullException>(response != null); - Contract.Ensures(Contract.Result<OutgoingWebResponse>() != null); - throw new NotImplementedException(); - } - } -} diff --git a/src/DotNetOpenAuth/Messaging/ChannelEventArgs.cs b/src/DotNetOpenAuth/Messaging/ChannelEventArgs.cs deleted file mode 100644 index 1e71bf2..0000000 --- a/src/DotNetOpenAuth/Messaging/ChannelEventArgs.cs +++ /dev/null @@ -1,30 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="ChannelEventArgs.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.Messaging { - using System; - using System.Diagnostics.Contracts; - - /// <summary> - /// The data packet sent with Channel events. - /// </summary> - public class ChannelEventArgs : EventArgs { - /// <summary> - /// Initializes a new instance of the <see cref="ChannelEventArgs"/> class. - /// </summary> - /// <param name="message">The message behind the fired event..</param> - internal ChannelEventArgs(IProtocolMessage message) { - Contract.Requires<ArgumentNullException>(message != null); - - this.Message = message; - } - - /// <summary> - /// Gets the message that caused the event to fire. - /// </summary> - public IProtocolMessage Message { get; private set; } - } -} diff --git a/src/DotNetOpenAuth/Messaging/DataBagFormatterBase.cs b/src/DotNetOpenAuth/Messaging/DataBagFormatterBase.cs deleted file mode 100644 index 9cb63e6..0000000 --- a/src/DotNetOpenAuth/Messaging/DataBagFormatterBase.cs +++ /dev/null @@ -1,354 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="DataBagFormatterBase.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.Messaging { - using System; - using System.Collections.Generic; - using System.Diagnostics.Contracts; - using System.IO; - using System.Linq; - using System.Security.Cryptography; - using System.Text; - using System.Web; - using DotNetOpenAuth.Messaging; - using DotNetOpenAuth.Messaging.Bindings; - using DotNetOpenAuth.Messaging.Reflection; - - /// <summary> - /// A serializer for <see cref="DataBag"/>-derived types - /// </summary> - /// <typeparam name="T">The DataBag-derived type that is to be serialized/deserialized.</typeparam> - internal abstract class DataBagFormatterBase<T> : IDataBagFormatter<T> where T : DataBag, new() { - /// <summary> - /// The message description cache to use for data bag types. - /// </summary> - protected static readonly MessageDescriptionCollection MessageDescriptions = new MessageDescriptionCollection(); - - /// <summary> - /// The length of the nonce to include in tokens that can be decoded once only. - /// </summary> - private const int NonceLength = 6; - - /// <summary> - /// The minimum allowable lifetime for the key used to encrypt/decrypt or sign this databag. - /// </summary> - private readonly TimeSpan minimumAge = TimeSpan.FromDays(1); - - /// <summary> - /// The symmetric key store with the secret used for signing/encryption of verification codes and refresh tokens. - /// </summary> - private readonly ICryptoKeyStore cryptoKeyStore; - - /// <summary> - /// The bucket for symmetric keys. - /// </summary> - private readonly string cryptoKeyBucket; - - /// <summary> - /// The crypto to use for signing access tokens. - /// </summary> - private readonly RSACryptoServiceProvider asymmetricSigning; - - /// <summary> - /// The crypto to use for encrypting access tokens. - /// </summary> - private readonly RSACryptoServiceProvider asymmetricEncrypting; - - /// <summary> - /// A value indicating whether the data in this instance will be protected against tampering. - /// </summary> - private readonly bool signed; - - /// <summary> - /// The nonce store to use to ensure that this instance is only decoded once. - /// </summary> - private readonly INonceStore decodeOnceOnly; - - /// <summary> - /// The maximum age of a token that can be decoded; useful only when <see cref="decodeOnceOnly"/> is <c>true</c>. - /// </summary> - private readonly TimeSpan? maximumAge; - - /// <summary> - /// A value indicating whether the data in this instance will be protected against eavesdropping. - /// </summary> - private readonly bool encrypted; - - /// <summary> - /// A value indicating whether the data in this instance will be GZip'd. - /// </summary> - private readonly bool compressed; - - /// <summary> - /// Initializes a new instance of the <see cref="DataBagFormatterBase<T>"/> class. - /// </summary> - /// <param name="signingKey">The crypto service provider with the asymmetric key to use for signing or verifying the token.</param> - /// <param name="encryptingKey">The crypto service provider with the asymmetric key to use for encrypting or decrypting the token.</param> - /// <param name="compressed">A value indicating whether the data in this instance will be GZip'd.</param> - /// <param name="maximumAge">The maximum age of a token that can be decoded; useful only when <paramref name="decodeOnceOnly"/> is <c>true</c>.</param> - /// <param name="decodeOnceOnly">The nonce store to use to ensure that this instance is only decoded once.</param> - protected DataBagFormatterBase(RSACryptoServiceProvider signingKey = null, RSACryptoServiceProvider encryptingKey = null, bool compressed = false, TimeSpan? maximumAge = null, INonceStore decodeOnceOnly = null) - : this(signingKey != null, encryptingKey != null, compressed, maximumAge, decodeOnceOnly) { - this.asymmetricSigning = signingKey; - this.asymmetricEncrypting = encryptingKey; - } - - /// <summary> - /// Initializes a new instance of the <see cref="DataBagFormatterBase<T>"/> class. - /// </summary> - /// <param name="cryptoKeyStore">The crypto key store used when signing or encrypting.</param> - /// <param name="bucket">The bucket in which symmetric keys are stored for signing/encrypting data.</param> - /// <param name="signed">A value indicating whether the data in this instance will be protected against tampering.</param> - /// <param name="encrypted">A value indicating whether the data in this instance will be protected against eavesdropping.</param> - /// <param name="compressed">A value indicating whether the data in this instance will be GZip'd.</param> - /// <param name="minimumAge">The required minimum lifespan within which this token must be decodable and verifiable; useful only when <paramref name="signed"/> and/or <paramref name="encrypted"/> is true.</param> - /// <param name="maximumAge">The maximum age of a token that can be decoded; useful only when <paramref name="decodeOnceOnly"/> is <c>true</c>.</param> - /// <param name="decodeOnceOnly">The nonce store to use to ensure that this instance is only decoded once.</param> - protected DataBagFormatterBase(ICryptoKeyStore cryptoKeyStore = null, string bucket = null, bool signed = false, bool encrypted = false, bool compressed = false, TimeSpan? minimumAge = null, TimeSpan? maximumAge = null, INonceStore decodeOnceOnly = null) - : this(signed, encrypted, compressed, maximumAge, decodeOnceOnly) { - Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(bucket) || cryptoKeyStore == null); - Contract.Requires<ArgumentException>(cryptoKeyStore != null || (!signed && !encrypted)); - - this.cryptoKeyStore = cryptoKeyStore; - this.cryptoKeyBucket = bucket; - if (minimumAge.HasValue) { - this.minimumAge = minimumAge.Value; - } - } - - /// <summary> - /// Initializes a new instance of the <see cref="DataBagFormatterBase<T>"/> class. - /// </summary> - /// <param name="signed">A value indicating whether the data in this instance will be protected against tampering.</param> - /// <param name="encrypted">A value indicating whether the data in this instance will be protected against eavesdropping.</param> - /// <param name="compressed">A value indicating whether the data in this instance will be GZip'd.</param> - /// <param name="maximumAge">The maximum age of a token that can be decoded; useful only when <paramref name="decodeOnceOnly"/> is <c>true</c>.</param> - /// <param name="decodeOnceOnly">The nonce store to use to ensure that this instance is only decoded once.</param> - private DataBagFormatterBase(bool signed = false, bool encrypted = false, bool compressed = false, TimeSpan? maximumAge = null, INonceStore decodeOnceOnly = null) { - Contract.Requires<ArgumentException>(signed || decodeOnceOnly == null); - Contract.Requires<ArgumentException>(maximumAge.HasValue || decodeOnceOnly == null); - - this.signed = signed; - this.maximumAge = maximumAge; - this.decodeOnceOnly = decodeOnceOnly; - this.encrypted = encrypted; - this.compressed = compressed; - } - - /// <summary> - /// Serializes the specified message, including compression, encryption, signing, and nonce handling where applicable. - /// </summary> - /// <param name="message">The message to serialize. Must not be null.</param> - /// <returns>A non-null, non-empty value.</returns> - public string Serialize(T message) { - message.UtcCreationDate = DateTime.UtcNow; - - if (this.decodeOnceOnly != null) { - message.Nonce = MessagingUtilities.GetNonCryptoRandomData(NonceLength); - } - - byte[] encoded = this.SerializeCore(message); - - if (this.compressed) { - encoded = MessagingUtilities.Compress(encoded); - } - - string symmetricSecretHandle = null; - if (this.encrypted) { - encoded = this.Encrypt(encoded, out symmetricSecretHandle); - } - - if (this.signed) { - message.Signature = this.CalculateSignature(encoded, symmetricSecretHandle); - } - - int capacity = this.signed ? 4 + message.Signature.Length + 4 + encoded.Length : encoded.Length; - using (var finalStream = new MemoryStream(capacity)) { - var writer = new BinaryWriter(finalStream); - if (this.signed) { - writer.WriteBuffer(message.Signature); - } - - writer.WriteBuffer(encoded); - writer.Flush(); - - string payload = MessagingUtilities.ConvertToBase64WebSafeString(finalStream.ToArray()); - string result = payload; - if (symmetricSecretHandle != null && (this.signed || this.encrypted)) { - result = MessagingUtilities.CombineKeyHandleAndPayload(symmetricSecretHandle, payload); - } - - return result; - } - } - - /// <summary> - /// Deserializes a <see cref="DataBag"/>, including decompression, decryption, signature and nonce validation where applicable. - /// </summary> - /// <param name="containingMessage">The message that contains the <see cref="DataBag"/> serialized value. Must not be nulll.</param> - /// <param name="value">The serialized form of the <see cref="DataBag"/> to deserialize. Must not be null or empty.</param> - /// <returns>The deserialized value. Never null.</returns> - public T Deserialize(IProtocolMessage containingMessage, string value) { - string symmetricSecretHandle = null; - if (this.encrypted && this.cryptoKeyStore != null) { - string valueWithoutHandle; - MessagingUtilities.ExtractKeyHandleAndPayload(containingMessage, "<TODO>", value, out symmetricSecretHandle, out valueWithoutHandle); - value = valueWithoutHandle; - } - - var message = new T { ContainingMessage = containingMessage }; - byte[] data = MessagingUtilities.FromBase64WebSafeString(value); - - byte[] signature = null; - if (this.signed) { - using (var dataStream = new MemoryStream(data)) { - var dataReader = new BinaryReader(dataStream); - signature = dataReader.ReadBuffer(); - data = dataReader.ReadBuffer(); - } - - // Verify that the verification code was issued by message authorization server. - ErrorUtilities.VerifyProtocol(this.IsSignatureValid(data, signature, symmetricSecretHandle), MessagingStrings.SignatureInvalid); - } - - if (this.encrypted) { - data = this.Decrypt(data, symmetricSecretHandle); - } - - if (this.compressed) { - data = MessagingUtilities.Decompress(data); - } - - this.DeserializeCore(message, data); - message.Signature = signature; // TODO: we don't really need this any more, do we? - - if (this.maximumAge.HasValue) { - // Has message verification code expired? - DateTime expirationDate = message.UtcCreationDate + this.maximumAge.Value; - if (expirationDate < DateTime.UtcNow) { - throw new ExpiredMessageException(expirationDate, containingMessage); - } - } - - // Has message verification code already been used to obtain an access/refresh token? - if (this.decodeOnceOnly != null) { - ErrorUtilities.VerifyInternal(this.maximumAge.HasValue, "Oops! How can we validate a nonce without a maximum message age?"); - string context = "{" + GetType().FullName + "}"; - if (!this.decodeOnceOnly.StoreNonce(context, Convert.ToBase64String(message.Nonce), message.UtcCreationDate)) { - Logger.OpenId.ErrorFormat("Replayed nonce detected ({0} {1}). Rejecting message.", message.Nonce, message.UtcCreationDate); - throw new ReplayedMessageException(containingMessage); - } - } - - ((IMessage)message).EnsureValidMessage(); - - return message; - } - - /// <summary> - /// Serializes the <see cref="DataBag"/> instance to a buffer. - /// </summary> - /// <param name="message">The message.</param> - /// <returns>The buffer containing the serialized data.</returns> - protected abstract byte[] SerializeCore(T message); - - /// <summary> - /// Deserializes the <see cref="DataBag"/> instance from a buffer. - /// </summary> - /// <param name="message">The message instance to initialize with data from the buffer.</param> - /// <param name="data">The data buffer.</param> - protected abstract void DeserializeCore(T message, byte[] data); - - /// <summary> - /// Determines whether the signature on this instance is valid. - /// </summary> - /// <param name="signedData">The signed data.</param> - /// <param name="signature">The signature.</param> - /// <param name="symmetricSecretHandle">The symmetric secret handle. <c>null</c> when using an asymmetric algorithm.</param> - /// <returns> - /// <c>true</c> if the signature is valid; otherwise, <c>false</c>. - /// </returns> - private bool IsSignatureValid(byte[] signedData, byte[] signature, string symmetricSecretHandle) { - Contract.Requires<ArgumentNullException>(signedData != null); - Contract.Requires<ArgumentNullException>(signature != null); - - if (this.asymmetricSigning != null) { - using (var hasher = new SHA1CryptoServiceProvider()) { - return this.asymmetricSigning.VerifyData(signedData, hasher, signature); - } - } else { - return MessagingUtilities.AreEquivalentConstantTime(signature, this.CalculateSignature(signedData, symmetricSecretHandle)); - } - } - - /// <summary> - /// Calculates the signature for the data in this verification code. - /// </summary> - /// <param name="bytesToSign">The bytes to sign.</param> - /// <param name="symmetricSecretHandle">The symmetric secret handle. <c>null</c> when using an asymmetric algorithm.</param> - /// <returns> - /// The calculated signature. - /// </returns> - private byte[] CalculateSignature(byte[] bytesToSign, string symmetricSecretHandle) { - Contract.Requires<ArgumentNullException>(bytesToSign != null); - Contract.Requires<InvalidOperationException>(this.asymmetricSigning != null || this.cryptoKeyStore != null); - Contract.Ensures(Contract.Result<byte[]>() != null); - - if (this.asymmetricSigning != null) { - using (var hasher = new SHA1CryptoServiceProvider()) { - return this.asymmetricSigning.SignData(bytesToSign, hasher); - } - } else { - var key = this.cryptoKeyStore.GetKey(this.cryptoKeyBucket, symmetricSecretHandle); - ErrorUtilities.VerifyProtocol(key != null, "Missing decryption key."); - using (var symmetricHasher = new HMACSHA256(key.Key)) { - return symmetricHasher.ComputeHash(bytesToSign); - } - } - } - - /// <summary> - /// Encrypts the specified value using either the symmetric or asymmetric encryption algorithm as appropriate. - /// </summary> - /// <param name="value">The value.</param> - /// <param name="symmetricSecretHandle">Receives the symmetric secret handle. <c>null</c> when using an asymmetric algorithm.</param> - /// <returns> - /// The encrypted value. - /// </returns> - private byte[] Encrypt(byte[] value, out string symmetricSecretHandle) { - Contract.Requires<InvalidOperationException>(this.asymmetricEncrypting != null || this.cryptoKeyStore != null); - - if (this.asymmetricEncrypting != null) { - symmetricSecretHandle = null; - return this.asymmetricEncrypting.EncryptWithRandomSymmetricKey(value); - } else { - var cryptoKey = this.cryptoKeyStore.GetCurrentKey(this.cryptoKeyBucket, this.minimumAge); - symmetricSecretHandle = cryptoKey.Key; - return MessagingUtilities.Encrypt(value, cryptoKey.Value.Key); - } - } - - /// <summary> - /// Decrypts the specified value using either the symmetric or asymmetric encryption algorithm as appropriate. - /// </summary> - /// <param name="value">The value.</param> - /// <param name="symmetricSecretHandle">The symmetric secret handle. <c>null</c> when using an asymmetric algorithm.</param> - /// <returns> - /// The decrypted value. - /// </returns> - private byte[] Decrypt(byte[] value, string symmetricSecretHandle) { - Contract.Requires<InvalidOperationException>(this.asymmetricEncrypting != null || symmetricSecretHandle != null); - - if (this.asymmetricEncrypting != null) { - return this.asymmetricEncrypting.DecryptWithRandomSymmetricKey(value); - } else { - var key = this.cryptoKeyStore.GetKey(this.cryptoKeyBucket, symmetricSecretHandle); - ErrorUtilities.VerifyProtocol(key != null, "Missing decryption key."); - return MessagingUtilities.Decrypt(value, key.Key); - } - } - } -} diff --git a/src/DotNetOpenAuth/Messaging/EnumerableCache.cs b/src/DotNetOpenAuth/Messaging/EnumerableCache.cs deleted file mode 100644 index be65bca..0000000 --- a/src/DotNetOpenAuth/Messaging/EnumerableCache.cs +++ /dev/null @@ -1,243 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="EnumerableCache.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// This code is released under the Microsoft Public License (Ms-PL). -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.Messaging { - using System; - using System.Collections; - using System.Collections.Generic; - using System.Diagnostics.Contracts; - - /// <summary> - /// Extension methods for <see cref="IEnumerable<T>"/> types. - /// </summary> - public static class EnumerableCacheExtensions { - /// <summary> - /// Caches the results of enumerating over a given object so that subsequence enumerations - /// don't require interacting with the object a second time. - /// </summary> - /// <typeparam name="T">The type of element found in the enumeration.</typeparam> - /// <param name="sequence">The enumerable object.</param> - /// <returns> - /// Either a new enumerable object that caches enumerated results, or the original, <paramref name="sequence"/> - /// object if no caching is necessary to avoid additional CPU work. - /// </returns> - /// <remarks> - /// <para>This is designed for use on the results of generator methods (the ones with <c>yield return</c> in them) - /// so that only those elements in the sequence that are needed are ever generated, while not requiring - /// regeneration of elements that are enumerated over multiple times.</para> - /// <para>This can be a huge performance gain if enumerating multiple times over an expensive generator method.</para> - /// <para>Some enumerable types such as collections, lists, and already-cached generators do not require - /// any (additional) caching, and this method will simply return those objects rather than caching them - /// to avoid double-caching.</para> - /// </remarks> - public static IEnumerable<T> CacheGeneratedResults<T>(this IEnumerable<T> sequence) { - Contract.Requires<ArgumentNullException>(sequence != null); - - // Don't create a cache for types that don't need it. - if (sequence is IList<T> || - sequence is ICollection<T> || - sequence is Array || - sequence is EnumerableCache<T>) { - return sequence; - } - - return new EnumerableCache<T>(sequence); - } - - /// <summary> - /// A wrapper for <see cref="IEnumerable<T>"/> types and returns a caching <see cref="IEnumerator<T>"/> - /// from its <see cref="IEnumerable<T>.GetEnumerator"/> method. - /// </summary> - /// <typeparam name="T">The type of element in the sequence.</typeparam> - private class EnumerableCache<T> : IEnumerable<T> { - /// <summary> - /// The results from enumeration of the live object that have been collected thus far. - /// </summary> - private List<T> cache; - - /// <summary> - /// The original generator method or other enumerable object whose contents should only be enumerated once. - /// </summary> - private IEnumerable<T> generator; - - /// <summary> - /// The enumerator we're using over the generator method's results. - /// </summary> - private IEnumerator<T> generatorEnumerator; - - /// <summary> - /// The sync object our caching enumerators use when adding a new live generator method result to the cache. - /// </summary> - /// <remarks> - /// Although individual enumerators are not thread-safe, this <see cref="IEnumerable<T>"/> should be - /// thread safe so that multiple enumerators can be created from it and used from different threads. - /// </remarks> - private object generatorLock = new object(); - - /// <summary> - /// Initializes a new instance of the EnumerableCache class. - /// </summary> - /// <param name="generator">The generator.</param> - internal EnumerableCache(IEnumerable<T> generator) { - Contract.Requires<ArgumentNullException>(generator != null); - - this.generator = generator; - } - - #region IEnumerable<T> Members - - /// <summary> - /// Returns an enumerator that iterates through the collection. - /// </summary> - /// <returns> - /// A <see cref="T:System.Collections.Generic.IEnumerator`1"/> that can be used to iterate through the collection. - /// </returns> - public IEnumerator<T> GetEnumerator() { - if (this.generatorEnumerator == null) { - this.cache = new List<T>(); - this.generatorEnumerator = this.generator.GetEnumerator(); - } - - return new EnumeratorCache(this); - } - - #endregion - - #region IEnumerable Members - - /// <summary> - /// Returns an enumerator that iterates through a collection. - /// </summary> - /// <returns> - /// An <see cref="T:System.Collections.IEnumerator"/> object that can be used to iterate through the collection. - /// </returns> - System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { - return this.GetEnumerator(); - } - - #endregion - - /// <summary> - /// An enumerator that uses cached enumeration results whenever they are available, - /// and caches whatever results it has to pull from the original <see cref="IEnumerable<T>"/> object. - /// </summary> - private class EnumeratorCache : IEnumerator<T> { - /// <summary> - /// The parent enumeration wrapper class that stores the cached results. - /// </summary> - private EnumerableCache<T> parent; - - /// <summary> - /// The position of this enumerator in the cached list. - /// </summary> - private int cachePosition = -1; - - /// <summary> - /// Initializes a new instance of the EnumeratorCache class. - /// </summary> - /// <param name="parent">The parent cached enumerable whose GetEnumerator method is calling this constructor.</param> - internal EnumeratorCache(EnumerableCache<T> parent) { - Contract.Requires<ArgumentNullException>(parent != null); - - this.parent = parent; - } - - #region IEnumerator<T> Members - - /// <summary> - /// Gets the element in the collection at the current position of the enumerator. - /// </summary> - /// <returns> - /// The element in the collection at the current position of the enumerator. - /// </returns> - public T Current { - get { - if (this.cachePosition < 0 || this.cachePosition >= this.parent.cache.Count) { - throw new InvalidOperationException(); - } - - return this.parent.cache[this.cachePosition]; - } - } - - #endregion - - #region IEnumerator Properties - - /// <summary> - /// Gets the element in the collection at the current position of the enumerator. - /// </summary> - /// <returns> - /// The element in the collection at the current position of the enumerator. - /// </returns> - object System.Collections.IEnumerator.Current { - get { return this.Current; } - } - - #endregion - - #region IDisposable Members - - /// <summary> - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. - /// </summary> - public void Dispose() { - this.Dispose(true); - GC.SuppressFinalize(this); - } - - #endregion - - #region IEnumerator Methods - - /// <summary> - /// Advances the enumerator to the next element of the collection. - /// </summary> - /// <returns> - /// true if the enumerator was successfully advanced to the next element; false if the enumerator has passed the end of the collection. - /// </returns> - /// <exception cref="T:System.InvalidOperationException"> - /// The collection was modified after the enumerator was created. - /// </exception> - public bool MoveNext() { - this.cachePosition++; - if (this.cachePosition >= this.parent.cache.Count) { - lock (this.parent.generatorLock) { - if (this.parent.generatorEnumerator.MoveNext()) { - this.parent.cache.Add(this.parent.generatorEnumerator.Current); - } else { - return false; - } - } - } - - return true; - } - - /// <summary> - /// Sets the enumerator to its initial position, which is before the first element in the collection. - /// </summary> - /// <exception cref="T:System.InvalidOperationException"> - /// The collection was modified after the enumerator was created. - /// </exception> - public void Reset() { - this.cachePosition = -1; - } - - #endregion - - /// <summary> - /// Releases unmanaged and - optionally - managed resources - /// </summary> - /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param> - protected virtual void Dispose(bool disposing) { - // Nothing to do here. - } - } - } - } -} diff --git a/src/DotNetOpenAuth/Messaging/ErrorUtilities.cs b/src/DotNetOpenAuth/Messaging/ErrorUtilities.cs deleted file mode 100644 index 1807f54..0000000 --- a/src/DotNetOpenAuth/Messaging/ErrorUtilities.cs +++ /dev/null @@ -1,385 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="ErrorUtilities.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.Messaging { - using System; - using System.Diagnostics; - using System.Diagnostics.Contracts; - using System.Globalization; - using System.Web; - - /// <summary> - /// A collection of error checking and reporting methods. - /// </summary> - [ContractVerification(true)] - [Pure] - internal static class ErrorUtilities { - /// <summary> - /// Wraps an exception in a new <see cref="ProtocolException"/>. - /// </summary> - /// <param name="inner">The inner exception to wrap.</param> - /// <param name="errorMessage">The error message for the outer exception.</param> - /// <param name="args">The string formatting arguments, if any.</param> - /// <returns>The newly constructed (unthrown) exception.</returns> - [Pure] - internal static Exception Wrap(Exception inner, string errorMessage, params object[] args) { - Contract.Requires<ArgumentNullException>(args != null); - Contract.Assume(errorMessage != null); - return new ProtocolException(string.Format(CultureInfo.CurrentCulture, errorMessage, args), inner); - } - - /// <summary> - /// Throws an internal error exception. - /// </summary> - /// <param name="errorMessage">The error message.</param> - /// <returns>Nothing. But included here so callers can "throw" this method for C# safety.</returns> - /// <exception cref="InternalErrorException">Always thrown.</exception> - [Pure] - internal static Exception ThrowInternal(string errorMessage) { - // Since internal errors are really bad, take this chance to - // help the developer find the cause by breaking into the - // debugger if one is attached. - if (Debugger.IsAttached) { - Debugger.Break(); - } - - throw new InternalErrorException(errorMessage); - } - - /// <summary> - /// Checks a condition and throws an internal error exception if it evaluates to false. - /// </summary> - /// <param name="condition">The condition to check.</param> - /// <param name="errorMessage">The message to include in the exception, if created.</param> - /// <exception cref="InternalErrorException">Thrown if <paramref name="condition"/> evaluates to <c>false</c>.</exception> - [Pure] - internal static void VerifyInternal(bool condition, string errorMessage) { - Contract.Ensures(condition); - Contract.EnsuresOnThrow<InternalErrorException>(!condition); - if (!condition) { - ThrowInternal(errorMessage); - } - } - - /// <summary> - /// Checks a condition and throws an internal error exception if it evaluates to false. - /// </summary> - /// <param name="condition">The condition to check.</param> - /// <param name="errorMessage">The message to include in the exception, if created.</param> - /// <param name="args">The formatting arguments.</param> - /// <exception cref="InternalErrorException">Thrown if <paramref name="condition"/> evaluates to <c>false</c>.</exception> - [Pure] - internal static void VerifyInternal(bool condition, string errorMessage, params object[] args) { - Contract.Requires<ArgumentNullException>(args != null); - Contract.Ensures(condition); - Contract.EnsuresOnThrow<InternalErrorException>(!condition); - Contract.Assume(errorMessage != null); - if (!condition) { - errorMessage = string.Format(CultureInfo.CurrentCulture, errorMessage, args); - throw new InternalErrorException(errorMessage); - } - } - - /// <summary> - /// Checks a condition and throws an <see cref="InvalidOperationException"/> if it evaluates to false. - /// </summary> - /// <param name="condition">The condition to check.</param> - /// <param name="errorMessage">The message to include in the exception, if created.</param> - /// <exception cref="InvalidOperationException">Thrown if <paramref name="condition"/> evaluates to <c>false</c>.</exception> - [Pure] - internal static void VerifyOperation(bool condition, string errorMessage) { - Contract.Ensures(condition); - Contract.EnsuresOnThrow<InvalidOperationException>(!condition); - if (!condition) { - throw new InvalidOperationException(errorMessage); - } - } - - /// <summary> - /// Checks a condition and throws a <see cref="NotSupportedException"/> if it evaluates to false. - /// </summary> - /// <param name="condition">The condition to check.</param> - /// <param name="errorMessage">The message to include in the exception, if created.</param> - /// <exception cref="NotSupportedException">Thrown if <paramref name="condition"/> evaluates to <c>false</c>.</exception> - [Pure] - internal static void VerifySupported(bool condition, string errorMessage) { - Contract.Ensures(condition); - Contract.EnsuresOnThrow<NotSupportedException>(!condition); - if (!condition) { - throw new NotSupportedException(errorMessage); - } - } - - /// <summary> - /// Checks a condition and throws a <see cref="NotSupportedException"/> if it evaluates to false. - /// </summary> - /// <param name="condition">The condition to check.</param> - /// <param name="errorMessage">The message to include in the exception, if created.</param> - /// <param name="args">The string formatting arguments for <paramref name="errorMessage"/>.</param> - /// <exception cref="NotSupportedException">Thrown if <paramref name="condition"/> evaluates to <c>false</c>.</exception> - [Pure] - internal static void VerifySupported(bool condition, string errorMessage, params object[] args) { - Contract.Requires<ArgumentNullException>(args != null); - Contract.Ensures(condition); - Contract.EnsuresOnThrow<NotSupportedException>(!condition); - Contract.Assume(errorMessage != null); - if (!condition) { - throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, errorMessage, args)); - } - } - - /// <summary> - /// Checks a condition and throws an <see cref="InvalidOperationException"/> if it evaluates to false. - /// </summary> - /// <param name="condition">The condition to check.</param> - /// <param name="errorMessage">The message to include in the exception, if created.</param> - /// <param name="args">The formatting arguments.</param> - /// <exception cref="InvalidOperationException">Thrown if <paramref name="condition"/> evaluates to <c>false</c>.</exception> - [Pure] - internal static void VerifyOperation(bool condition, string errorMessage, params object[] args) { - Contract.Requires<ArgumentNullException>(args != null); - Contract.Ensures(condition); - Contract.EnsuresOnThrow<InvalidOperationException>(!condition); - Contract.Assume(errorMessage != null); - if (!condition) { - errorMessage = string.Format(CultureInfo.CurrentCulture, errorMessage, args); - throw new InvalidOperationException(errorMessage); - } - } - - /// <summary> - /// Checks a condition and throws an <see cref="InfoCard.InformationCardException"/> - /// if it evaluates to false. - /// </summary> - /// <param name="condition">The condition to check.</param> - /// <param name="errorMessage">The message to include in the exception, if created.</param> - /// <param name="args">The formatting arguments.</param> - /// <exception cref="InfoCard.InformationCardException">Thrown if <paramref name="condition"/> evaluates to <c>false</c>.</exception> - [Pure] - internal static void VerifyInfoCard(bool condition, string errorMessage, params object[] args) { - Contract.Requires<ArgumentNullException>(args != null); - Contract.Ensures(condition); - Contract.EnsuresOnThrow<InfoCard.InformationCardException>(!condition); - Contract.Assume(errorMessage != null); - if (!condition) { - errorMessage = string.Format(CultureInfo.CurrentCulture, errorMessage, args); - throw new InfoCard.InformationCardException(errorMessage); - } - } - - /// <summary> - /// Throws a <see cref="HostErrorException"/> if some <paramref name="condition"/> evaluates to false. - /// </summary> - /// <param name="condition">True to do nothing; false to throw the exception.</param> - /// <param name="errorMessage">The error message for the exception.</param> - /// <param name="args">The string formatting arguments, if any.</param> - /// <exception cref="HostErrorException">Thrown if <paramref name="condition"/> evaluates to <c>false</c>.</exception> - [Pure] - internal static void VerifyHost(bool condition, string errorMessage, params object[] args) { - Contract.Requires<ArgumentNullException>(args != null); - Contract.Ensures(condition); - Contract.EnsuresOnThrow<ProtocolException>(!condition); - Contract.Assume(errorMessage != null); - if (!condition) { - throw new HostErrorException(string.Format(CultureInfo.CurrentCulture, errorMessage, args)); - } - } - - /// <summary> - /// Throws a <see cref="ProtocolException"/> if some <paramref name="condition"/> evaluates to false. - /// </summary> - /// <param name="condition">True to do nothing; false to throw the exception.</param> - /// <param name="faultedMessage">The message being processed that would be responsible for the exception if thrown.</param> - /// <param name="errorMessage">The error message for the exception.</param> - /// <param name="args">The string formatting arguments, if any.</param> - /// <exception cref="ProtocolException">Thrown if <paramref name="condition"/> evaluates to <c>false</c>.</exception> - [Pure] - internal static void VerifyProtocol(bool condition, IProtocolMessage faultedMessage, string errorMessage, params object[] args) { - Contract.Requires<ArgumentNullException>(args != null); - Contract.Requires<ArgumentNullException>(faultedMessage != null); - Contract.Ensures(condition); - Contract.EnsuresOnThrow<ProtocolException>(!condition); - Contract.Assume(errorMessage != null); - if (!condition) { - throw new ProtocolException(string.Format(CultureInfo.CurrentCulture, errorMessage, args), faultedMessage); - } - } - - /// <summary> - /// Throws a <see cref="ProtocolException"/> if some <paramref name="condition"/> evaluates to false. - /// </summary> - /// <param name="condition">True to do nothing; false to throw the exception.</param> - /// <param name="message">The error message for the exception.</param> - /// <param name="args">The string formatting arguments, if any.</param> - /// <exception cref="ProtocolException">Thrown if <paramref name="condition"/> evaluates to <c>false</c>.</exception> - [Pure] - internal static void VerifyProtocol(bool condition, string message, params object[] args) { - Contract.Requires<ArgumentNullException>(args != null); - Contract.Ensures(condition); - Contract.EnsuresOnThrow<ProtocolException>(!condition); - Contract.Assume(message != null); - if (!condition) { - var exception = new ProtocolException(string.Format(CultureInfo.CurrentCulture, message, args)); - if (Logger.Messaging.IsErrorEnabled) { - Logger.Messaging.Error( - string.Format( - CultureInfo.CurrentCulture, - "Protocol error: {0}{1}{2}", - exception.Message, - Environment.NewLine, - new StackTrace())); - } - throw exception; - } - } - - /// <summary> - /// Throws a <see cref="ProtocolException"/>. - /// </summary> - /// <param name="message">The message to set in the exception.</param> - /// <param name="args">The formatting arguments of the message.</param> - /// <returns> - /// An InternalErrorException, which may be "thrown" by the caller in order - /// to satisfy C# rules to show that code will never be reached, but no value - /// actually is ever returned because this method guarantees to throw. - /// </returns> - /// <exception cref="ProtocolException">Always thrown.</exception> - [Pure] - internal static Exception ThrowProtocol(string message, params object[] args) { - Contract.Requires<ArgumentNullException>(args != null); - Contract.Assume(message != null); - VerifyProtocol(false, message, args); - - // we never reach here, but this allows callers to "throw" this method. - return new InternalErrorException(); - } - - /// <summary> - /// Throws a <see cref="FormatException"/>. - /// </summary> - /// <param name="message">The message for the exception.</param> - /// <param name="args">The string formatting arguments for <paramref name="message"/>.</param> - /// <returns>Nothing. It's just here so the caller can throw this method for C# compilation check.</returns> - [Pure] - internal static Exception ThrowFormat(string message, params object[] args) { - Contract.Requires<ArgumentNullException>(args != null); - Contract.Assume(message != null); - throw new FormatException(string.Format(CultureInfo.CurrentCulture, message, args)); - } - - /// <summary> - /// Throws a <see cref="FormatException"/> if some condition is false. - /// </summary> - /// <param name="condition">The expression to evaluate. A value of <c>false</c> will cause the exception to be thrown.</param> - /// <param name="message">The message for the exception.</param> - /// <param name="args">The string formatting arguments for <paramref name="message"/>.</param> - /// <exception cref="FormatException">Thrown when <paramref name="condition"/> is <c>false</c>.</exception> - [Pure] - internal static void VerifyFormat(bool condition, string message, params object[] args) { - Contract.Requires<ArgumentNullException>(args != null); - Contract.Ensures(condition); - Contract.EnsuresOnThrow<FormatException>(!condition); - Contract.Assume(message != null); - if (!condition) { - throw ThrowFormat(message, args); - } - } - - /// <summary> - /// Verifies something about the argument supplied to a method. - /// </summary> - /// <param name="condition">The condition that must evaluate to true to avoid an exception.</param> - /// <param name="message">The message to use in the exception if the condition is false.</param> - /// <param name="args">The string formatting arguments, if any.</param> - /// <exception cref="ArgumentException">Thrown if <paramref name="condition"/> evaluates to <c>false</c>.</exception> - [Pure] - internal static void VerifyArgument(bool condition, string message, params object[] args) { - Contract.Requires<ArgumentNullException>(args != null); - Contract.Ensures(condition); - Contract.EnsuresOnThrow<ArgumentException>(!condition); - Contract.Assume(message != null); - if (!condition) { - throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, message, args)); - } - } - - /// <summary> - /// Throws an <see cref="ArgumentException"/>. - /// </summary> - /// <param name="parameterName">Name of the parameter.</param> - /// <param name="message">The message to use in the exception if the condition is false.</param> - /// <param name="args">The string formatting arguments, if any.</param> - /// <returns>Never returns anything. It always throws.</returns> - [Pure] - internal static Exception ThrowArgumentNamed(string parameterName, string message, params object[] args) { - Contract.Requires<ArgumentNullException>(args != null); - Contract.Assume(message != null); - throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, message, args), parameterName); - } - - /// <summary> - /// Verifies something about the argument supplied to a method. - /// </summary> - /// <param name="condition">The condition that must evaluate to true to avoid an exception.</param> - /// <param name="parameterName">Name of the parameter.</param> - /// <param name="message">The message to use in the exception if the condition is false.</param> - /// <param name="args">The string formatting arguments, if any.</param> - /// <exception cref="ArgumentException">Thrown if <paramref name="condition"/> evaluates to <c>false</c>.</exception> - [Pure] - internal static void VerifyArgumentNamed(bool condition, string parameterName, string message, params object[] args) { - Contract.Requires<ArgumentNullException>(args != null); - Contract.Ensures(condition); - Contract.EnsuresOnThrow<ArgumentException>(!condition); - Contract.Assume(message != null); - if (!condition) { - throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, message, args), parameterName); - } - } - - /// <summary> - /// Verifies that some given value is not null. - /// </summary> - /// <param name="value">The value to check.</param> - /// <param name="paramName">Name of the parameter, which will be used in the <see cref="ArgumentException"/>, if thrown.</param> - /// <exception cref="ArgumentNullException">Thrown if <paramref name="value"/> is null.</exception> - [Pure] - internal static void VerifyArgumentNotNull(object value, string paramName) { - Contract.Ensures(value != null); - Contract.EnsuresOnThrow<ArgumentNullException>(value == null); - if (value == null) { - throw new ArgumentNullException(paramName); - } - } - - /// <summary> - /// Verifies that some string is not null and has non-zero length. - /// </summary> - /// <param name="value">The value to check.</param> - /// <param name="paramName">Name of the parameter, which will be used in the <see cref="ArgumentException"/>, if thrown.</param> - /// <exception cref="ArgumentNullException">Thrown if <paramref name="value"/> is null.</exception> - /// <exception cref="ArgumentException">Thrown if <paramref name="value"/> has zero length.</exception> - [Pure] - internal static void VerifyNonZeroLength(string value, string paramName) { - Contract.Ensures((value != null && value.Length > 0) && !string.IsNullOrEmpty(value)); - Contract.EnsuresOnThrow<ArgumentException>(value == null || value.Length == 0); - VerifyArgumentNotNull(value, paramName); - if (value.Length == 0) { - throw new ArgumentException(MessagingStrings.UnexpectedEmptyString, paramName); - } - } - - /// <summary> - /// Verifies that <see cref="HttpContext.Current"/> != <c>null</c>. - /// </summary> - /// <exception cref="InvalidOperationException">Thrown if <see cref="HttpContext.Current"/> == <c>null</c></exception> - [Pure] - internal static void VerifyHttpContext() { - Contract.Ensures(HttpContext.Current != null); - Contract.Ensures(HttpContext.Current.Request != null); - ErrorUtilities.VerifyOperation(HttpContext.Current != null && HttpContext.Current.Request != null, MessagingStrings.HttpContextRequired); - } - } -} diff --git a/src/DotNetOpenAuth/Messaging/HttpRequestInfo.cs b/src/DotNetOpenAuth/Messaging/HttpRequestInfo.cs deleted file mode 100644 index 6ce87a8..0000000 --- a/src/DotNetOpenAuth/Messaging/HttpRequestInfo.cs +++ /dev/null @@ -1,423 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="HttpRequestInfo.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.Messaging { - using System; - using System.Collections.Specialized; - using System.Diagnostics; - using System.Diagnostics.CodeAnalysis; - using System.Diagnostics.Contracts; - using System.Globalization; - using System.IO; - using System.Net; - using System.Net.Mime; - using System.ServiceModel.Channels; - using System.Web; - - /// <summary> - /// A property store of details of an incoming HTTP request. - /// </summary> - /// <remarks> - /// This serves a very similar purpose to <see cref="HttpRequest"/>, except that - /// ASP.NET does not let us fully initialize that class, so we have to write one - /// of our one. - /// </remarks> - public class HttpRequestInfo { - /// <summary> - /// The key/value pairs found in the entity of a POST request. - /// </summary> - private NameValueCollection form; - - /// <summary> - /// The key/value pairs found in the querystring of the incoming request. - /// </summary> - private NameValueCollection queryString; - - /// <summary> - /// Backing field for the <see cref="QueryStringBeforeRewriting"/> property. - /// </summary> - private NameValueCollection queryStringBeforeRewriting; - - /// <summary> - /// Backing field for the <see cref="Message"/> property. - /// </summary> - private IDirectedProtocolMessage message; - - /// <summary> - /// Initializes a new instance of the <see cref="HttpRequestInfo"/> class. - /// </summary> - /// <param name="request">The ASP.NET structure to copy from.</param> - public HttpRequestInfo(HttpRequest request) { - Contract.Requires<ArgumentNullException>(request != null); - Contract.Ensures(this.HttpMethod == request.HttpMethod); - Contract.Ensures(this.Url == request.Url); - Contract.Ensures(this.RawUrl == request.RawUrl); - Contract.Ensures(this.UrlBeforeRewriting != null); - Contract.Ensures(this.Headers != null); - Contract.Ensures(this.InputStream == request.InputStream); - Contract.Ensures(this.form == request.Form); - Contract.Ensures(this.queryString == request.QueryString); - - this.HttpMethod = request.HttpMethod; - this.Url = request.Url; - this.UrlBeforeRewriting = GetPublicFacingUrl(request); - this.RawUrl = request.RawUrl; - this.Headers = GetHeaderCollection(request.Headers); - this.InputStream = request.InputStream; - - // These values would normally be calculated, but we'll reuse them from - // HttpRequest since they're already calculated, and there's a chance (<g>) - // that ASP.NET does a better job of being comprehensive about gathering - // these as well. - this.form = request.Form; - this.queryString = request.QueryString; - - Reporting.RecordRequestStatistics(this); - } - - /// <summary> - /// Initializes a new instance of the <see cref="HttpRequestInfo"/> class. - /// </summary> - /// <param name="httpMethod">The HTTP method (i.e. GET or POST) of the incoming request.</param> - /// <param name="requestUrl">The URL being requested.</param> - /// <param name="rawUrl">The raw URL that appears immediately following the HTTP verb in the request, - /// before any URL rewriting takes place.</param> - /// <param name="headers">Headers in the HTTP request.</param> - /// <param name="inputStream">The entity stream, if any. (POST requests typically have these). Use <c>null</c> for GET requests.</param> - public HttpRequestInfo(string httpMethod, Uri requestUrl, string rawUrl, WebHeaderCollection headers, Stream inputStream) { - Contract.Requires<ArgumentNullException>(!string.IsNullOrEmpty(httpMethod)); - Contract.Requires<ArgumentNullException>(requestUrl != null); - Contract.Requires<ArgumentNullException>(rawUrl != null); - Contract.Requires<ArgumentNullException>(headers != null); - - this.HttpMethod = httpMethod; - this.Url = requestUrl; - this.UrlBeforeRewriting = requestUrl; - this.RawUrl = rawUrl; - this.Headers = headers; - this.InputStream = inputStream; - - Reporting.RecordRequestStatistics(this); - } - - /// <summary> - /// Initializes a new instance of the <see cref="HttpRequestInfo"/> class. - /// </summary> - /// <param name="listenerRequest">Details on the incoming HTTP request.</param> - public HttpRequestInfo(HttpListenerRequest listenerRequest) { - Contract.Requires<ArgumentNullException>(listenerRequest != null); - - this.HttpMethod = listenerRequest.HttpMethod; - this.Url = listenerRequest.Url; - this.UrlBeforeRewriting = listenerRequest.Url; - this.RawUrl = listenerRequest.RawUrl; - this.Headers = new WebHeaderCollection(); - foreach (string key in listenerRequest.Headers) { - this.Headers[key] = listenerRequest.Headers[key]; - } - - this.InputStream = listenerRequest.InputStream; - - Reporting.RecordRequestStatistics(this); - } - - /// <summary> - /// Initializes a new instance of the <see cref="HttpRequestInfo"/> class. - /// </summary> - /// <param name="request">The WCF incoming request structure to get the HTTP information from.</param> - /// <param name="requestUri">The URI of the service endpoint.</param> - public HttpRequestInfo(HttpRequestMessageProperty request, Uri requestUri) { - Contract.Requires<ArgumentNullException>(request != null); - Contract.Requires<ArgumentNullException>(requestUri != null); - - this.HttpMethod = request.Method; - this.Headers = request.Headers; - this.Url = requestUri; - this.UrlBeforeRewriting = requestUri; - this.RawUrl = MakeUpRawUrlFromUrl(requestUri); - - Reporting.RecordRequestStatistics(this); - } - - /// <summary> - /// Initializes a new instance of the <see cref="HttpRequestInfo"/> class. - /// </summary> - internal HttpRequestInfo() { - Contract.Ensures(this.HttpMethod == "GET"); - Contract.Ensures(this.Headers != null); - - this.HttpMethod = "GET"; - this.Headers = new WebHeaderCollection(); - } - - /// <summary> - /// Initializes a new instance of the <see cref="HttpRequestInfo"/> class. - /// </summary> - /// <param name="request">The HttpWebRequest (that was never used) to copy from.</param> - internal HttpRequestInfo(WebRequest request) { - Contract.Requires<ArgumentNullException>(request != null); - - this.HttpMethod = request.Method; - this.Url = request.RequestUri; - this.UrlBeforeRewriting = request.RequestUri; - this.RawUrl = MakeUpRawUrlFromUrl(request.RequestUri); - this.Headers = GetHeaderCollection(request.Headers); - this.InputStream = null; - - Reporting.RecordRequestStatistics(this); - } - - /// <summary> - /// Initializes a new instance of the <see cref="HttpRequestInfo"/> class. - /// </summary> - /// <param name="message">The message being passed in through a mock transport. May be null.</param> - /// <param name="httpMethod">The HTTP method that the incoming request came in on, whether or not <paramref name="message"/> is null.</param> - internal HttpRequestInfo(IDirectedProtocolMessage message, HttpDeliveryMethods httpMethod) { - this.message = message; - this.HttpMethod = MessagingUtilities.GetHttpVerb(httpMethod); - } - - /// <summary> - /// Gets or sets the message that is being sent over a mock transport (for testing). - /// </summary> - internal virtual IDirectedProtocolMessage Message { - get { return this.message; } - set { this.message = value; } - } - - /// <summary> - /// Gets or sets the verb in the request (i.e. GET, POST, etc.) - /// </summary> - internal string HttpMethod { get; set; } - - /// <summary> - /// Gets or sets the entire URL of the request, after any URL rewriting. - /// </summary> - internal Uri Url { get; set; } - - /// <summary> - /// Gets or sets the raw URL that appears immediately following the HTTP verb in the request, - /// before any URL rewriting takes place. - /// </summary> - internal string RawUrl { get; set; } - - /// <summary> - /// Gets or sets the full public URL used by the remote client to initiate this request, - /// before any URL rewriting and before any changes made by web farm load distributors. - /// </summary> - internal Uri UrlBeforeRewriting { get; set; } - - /// <summary> - /// Gets the query part of the URL (The ? and everything after it), after URL rewriting. - /// </summary> - internal string Query { - get { return this.Url != null ? this.Url.Query : null; } - } - - /// <summary> - /// Gets or sets the collection of headers that came in with the request. - /// </summary> - internal WebHeaderCollection Headers { get; set; } - - /// <summary> - /// Gets or sets the entity, or body of the request, if any. - /// </summary> - internal Stream InputStream { get; set; } - - /// <summary> - /// Gets the key/value pairs found in the entity of a POST request. - /// </summary> - internal NameValueCollection Form { - get { - Contract.Ensures(Contract.Result<NameValueCollection>() != null); - if (this.form == null) { - ContentType contentType = string.IsNullOrEmpty(this.Headers[HttpRequestHeader.ContentType]) ? null : new ContentType(this.Headers[HttpRequestHeader.ContentType]); - if (this.HttpMethod == "POST" && contentType != null && string.Equals(contentType.MediaType, Channel.HttpFormUrlEncoded, StringComparison.Ordinal)) { - StreamReader reader = new StreamReader(this.InputStream); - long originalPosition = 0; - if (this.InputStream.CanSeek) { - originalPosition = this.InputStream.Position; - } - this.form = HttpUtility.ParseQueryString(reader.ReadToEnd()); - if (this.InputStream.CanSeek) { - this.InputStream.Seek(originalPosition, SeekOrigin.Begin); - } - } else { - this.form = new NameValueCollection(); - } - } - - return this.form; - } - } - - /// <summary> - /// Gets the key/value pairs found in the querystring of the incoming request. - /// </summary> - internal NameValueCollection QueryString { - get { - if (this.queryString == null) { - this.queryString = this.Query != null ? HttpUtility.ParseQueryString(this.Query) : new NameValueCollection(); - } - - return this.queryString; - } - } - - /// <summary> - /// Gets the query data from the original request (before any URL rewriting has occurred.) - /// </summary> - /// <returns>A <see cref="NameValueCollection"/> containing all the parameters in the query string.</returns> - internal NameValueCollection QueryStringBeforeRewriting { - get { - if (this.queryStringBeforeRewriting == null) { - // This request URL may have been rewritten by the host site. - // For openid protocol purposes, we really need to look at - // the original query parameters before any rewriting took place. - if (!this.IsUrlRewritten) { - // No rewriting has taken place. - this.queryStringBeforeRewriting = this.QueryString; - } else { - // Rewriting detected! Recover the original request URI. - ErrorUtilities.VerifyInternal(this.UrlBeforeRewriting != null, "UrlBeforeRewriting is null, so the query string cannot be determined."); - this.queryStringBeforeRewriting = HttpUtility.ParseQueryString(this.UrlBeforeRewriting.Query); - } - } - - return this.queryStringBeforeRewriting; - } - } - - /// <summary> - /// Gets a value indicating whether the request's URL was rewritten by ASP.NET - /// or some other module. - /// </summary> - /// <value> - /// <c>true</c> if this request's URL was rewritten; otherwise, <c>false</c>. - /// </value> - internal bool IsUrlRewritten { - get { return this.Url != this.UrlBeforeRewriting; } - } - - /// <summary> - /// Gets the public facing URL for the given incoming HTTP request. - /// </summary> - /// <param name="request">The request.</param> - /// <param name="serverVariables">The server variables to consider part of the request.</param> - /// <returns> - /// The URI that the outside world used to create this request. - /// </returns> - /// <remarks> - /// Although the <paramref name="serverVariables"/> value can be obtained from - /// <see cref="HttpRequest.ServerVariables"/>, it's useful to be able to pass them - /// in so we can simulate injected values from our unit tests since the actual property - /// is a read-only kind of <see cref="NameValueCollection"/>. - /// </remarks> - internal static Uri GetPublicFacingUrl(HttpRequest request, NameValueCollection serverVariables) { - Contract.Requires<ArgumentNullException>(request != null); - Contract.Requires<ArgumentNullException>(serverVariables != null); - - // Due to URL rewriting, cloud computing (i.e. Azure) - // and web farms, etc., we have to be VERY careful about what - // we consider the incoming URL. We want to see the URL as it would - // appear on the public-facing side of the hosting web site. - // HttpRequest.Url gives us the internal URL in a cloud environment, - // So we use a variable that (at least from what I can tell) gives us - // the public URL: - if (serverVariables["HTTP_HOST"] != null) { - ErrorUtilities.VerifySupported(request.Url.Scheme == Uri.UriSchemeHttps || request.Url.Scheme == Uri.UriSchemeHttp, "Only HTTP and HTTPS are supported protocols."); - string scheme = serverVariables["HTTP_X_FORWARDED_PROTO"] ?? request.Url.Scheme; - Uri hostAndPort = new Uri(scheme + Uri.SchemeDelimiter + serverVariables["HTTP_HOST"]); - UriBuilder publicRequestUri = new UriBuilder(request.Url); - publicRequestUri.Scheme = scheme; - publicRequestUri.Host = hostAndPort.Host; - publicRequestUri.Port = hostAndPort.Port; // CC missing Uri.Port contract that's on UriBuilder.Port - return publicRequestUri.Uri; - } else { - // Failover to the method that works for non-web farm enviroments. - // We use Request.Url for the full path to the server, and modify it - // with Request.RawUrl to capture both the cookieless session "directory" if it exists - // and the original path in case URL rewriting is going on. We don't want to be - // fooled by URL rewriting because we're comparing the actual URL with what's in - // the return_to parameter in some cases. - // Response.ApplyAppPathModifier(builder.Path) would have worked for the cookieless - // session, but not the URL rewriting problem. - return new Uri(request.Url, request.RawUrl); - } - } - - /// <summary> - /// Gets the query or form data from the original request (before any URL rewriting has occurred.) - /// </summary> - /// <returns>A set of name=value pairs.</returns> - [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "Expensive call")] - internal NameValueCollection GetQueryOrFormFromContext() { - NameValueCollection query; - if (this.HttpMethod == "GET") { - query = this.QueryStringBeforeRewriting; - } else { - query = this.Form; - } - return query; - } - - /// <summary> - /// Gets the public facing URL for the given incoming HTTP request. - /// </summary> - /// <param name="request">The request.</param> - /// <returns>The URI that the outside world used to create this request.</returns> - private static Uri GetPublicFacingUrl(HttpRequest request) { - Contract.Requires<ArgumentNullException>(request != null); - return GetPublicFacingUrl(request, request.ServerVariables); - } - - /// <summary> - /// Makes up a reasonable guess at the raw URL from the possibly rewritten URL. - /// </summary> - /// <param name="url">A full URL.</param> - /// <returns>A raw URL that might have come in on the HTTP verb.</returns> - private static string MakeUpRawUrlFromUrl(Uri url) { - Contract.Requires<ArgumentNullException>(url != null); - return url.AbsolutePath + url.Query + url.Fragment; - } - - /// <summary> - /// Converts a NameValueCollection to a WebHeaderCollection. - /// </summary> - /// <param name="pairs">The collection a HTTP headers.</param> - /// <returns>A new collection of the given headers.</returns> - private static WebHeaderCollection GetHeaderCollection(NameValueCollection pairs) { - Contract.Requires<ArgumentNullException>(pairs != null); - - WebHeaderCollection headers = new WebHeaderCollection(); - foreach (string key in pairs) { - try { - headers.Add(key, pairs[key]); - } catch (ArgumentException ex) { - Logger.Messaging.WarnFormat( - "{0} thrown when trying to add web header \"{1}: {2}\". {3}", - ex.GetType().Name, - key, - pairs[key], - ex.Message); - } - } - - return headers; - } - -#if CONTRACTS_FULL - /// <summary> - /// Verifies conditions that should be true for any valid state of this object. - /// </summary> - [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Called by code contracts.")] - [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called by code contracts.")] - [ContractInvariantMethod] - private void ObjectInvariant() { - } -#endif - } -} diff --git a/src/DotNetOpenAuth/Messaging/IChannelBindingElement.cs b/src/DotNetOpenAuth/Messaging/IChannelBindingElement.cs deleted file mode 100644 index f502f17..0000000 --- a/src/DotNetOpenAuth/Messaging/IChannelBindingElement.cs +++ /dev/null @@ -1,146 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="IChannelBindingElement.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.Messaging { - using System; - using System.Diagnostics.Contracts; - - /// <summary> - /// An interface that must be implemented by message transforms/validators in order - /// to be included in the channel stack. - /// </summary> - [ContractClass(typeof(IChannelBindingElementContract))] - public interface IChannelBindingElement { - /// <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> - 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> - MessageProtections Protection { get; } - - /// <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> - 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> - MessageProtections? ProcessIncomingMessage(IProtocolMessage message); - } - - /// <summary> - /// Code Contract for the <see cref="IChannelBindingElement"/> interface. - /// </summary> - [ContractClassFor(typeof(IChannelBindingElement))] - internal abstract class IChannelBindingElementContract : IChannelBindingElement { - /// <summary> - /// Prevents a default instance of the <see cref="IChannelBindingElementContract"/> class from being created. - /// </summary> - private IChannelBindingElementContract() { - } - - #region IChannelBindingElement Members - - /// <summary> - /// Gets or sets the channel that this binding element belongs to. - /// </summary> - /// <value></value> - /// <remarks> - /// This property is set by the channel when it is first constructed. - /// </remarks> - Channel IChannelBindingElement.Channel { - get { throw new NotImplementedException(); } - set { throw new NotImplementedException(); } - } - - /// <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> - MessageProtections IChannelBindingElement.Protection { - get { throw new NotImplementedException(); } - } - - /// <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> - MessageProtections? IChannelBindingElement.ProcessOutgoingMessage(IProtocolMessage message) { - Contract.Requires<ArgumentNullException>(message != null); - Contract.Requires<InvalidOperationException>(((IChannelBindingElement)this).Channel != null); - throw new NotImplementedException(); - } - - /// <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> - MessageProtections? IChannelBindingElement.ProcessIncomingMessage(IProtocolMessage message) { - Contract.Requires<ArgumentNullException>(message != null); - Contract.Requires<InvalidOperationException>(((IChannelBindingElement)this).Channel != null); - throw new NotImplementedException(); - } - - #endregion - } -} diff --git a/src/DotNetOpenAuth/Messaging/IDataBagFormatter.cs b/src/DotNetOpenAuth/Messaging/IDataBagFormatter.cs deleted file mode 100644 index c7dcc42..0000000 --- a/src/DotNetOpenAuth/Messaging/IDataBagFormatter.cs +++ /dev/null @@ -1,75 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="IDataBagFormatter.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.Messaging { - using System; - using System.Diagnostics.Contracts; - - /// <summary> - /// A serializer for <see cref="DataBag"/>-derived types - /// </summary> - /// <typeparam name="T">The DataBag-derived type that is to be serialized/deserialized.</typeparam> - [ContractClass(typeof(IDataBagFormatterContract<>))] - internal interface IDataBagFormatter<T> where T : DataBag, new() { - /// <summary> - /// Serializes the specified message. - /// </summary> - /// <param name="message">The message to serialize. Must not be null.</param> - /// <returns>A non-null, non-empty value.</returns> - string Serialize(T message); - - /// <summary> - /// Deserializes a <see cref="DataBag"/>. - /// </summary> - /// <param name="containingMessage">The message that contains the <see cref="DataBag"/> serialized value. Must not be nulll.</param> - /// <param name="data">The serialized form of the <see cref="DataBag"/> to deserialize. Must not be null or empty.</param> - /// <returns>The deserialized value. Never null.</returns> - T Deserialize(IProtocolMessage containingMessage, string data); - } - - /// <summary> - /// Contract class for the IDataBagFormatter interface. - /// </summary> - /// <typeparam name="T">The type of DataBag to serialize.</typeparam> - [ContractClassFor(typeof(IDataBagFormatter<>))] - internal abstract class IDataBagFormatterContract<T> : IDataBagFormatter<T> where T : DataBag, new() { - /// <summary> - /// Prevents a default instance of the <see cref="IDataBagFormatterContract<T>"/> class from being created. - /// </summary> - private IDataBagFormatterContract() { - } - - #region IDataBagFormatter<T> Members - - /// <summary> - /// Serializes the specified message. - /// </summary> - /// <param name="message">The message to serialize. Must not be null.</param> - /// <returns>A non-null, non-empty value.</returns> - string IDataBagFormatter<T>.Serialize(T message) { - Contract.Requires<ArgumentNullException>(message != null); - Contract.Ensures(!String.IsNullOrEmpty(Contract.Result<string>())); - - throw new System.NotImplementedException(); - } - - /// <summary> - /// Deserializes a <see cref="DataBag"/>. - /// </summary> - /// <param name="containingMessage">The message that contains the <see cref="DataBag"/> serialized value. Must not be nulll.</param> - /// <param name="data">The serialized form of the <see cref="DataBag"/> to deserialize. Must not be null or empty.</param> - /// <returns>The deserialized value. Never null.</returns> - T IDataBagFormatter<T>.Deserialize(IProtocolMessage containingMessage, string data) { - Contract.Requires<ArgumentNullException>(containingMessage != null); - Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(data)); - Contract.Ensures(Contract.Result<T>() != null); - - throw new System.NotImplementedException(); - } - - #endregion - } -}
\ No newline at end of file diff --git a/src/DotNetOpenAuth/Messaging/IDirectWebRequestHandler.cs b/src/DotNetOpenAuth/Messaging/IDirectWebRequestHandler.cs deleted file mode 100644 index d47da4a..0000000 --- a/src/DotNetOpenAuth/Messaging/IDirectWebRequestHandler.cs +++ /dev/null @@ -1,223 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="IDirectWebRequestHandler.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.Messaging { - using System; - using System.Collections.Generic; - using System.Diagnostics.Contracts; - using System.IO; - using System.Net; - using DotNetOpenAuth.Messaging; - - /// <summary> - /// A contract for <see cref="HttpWebRequest"/> handling. - /// </summary> - /// <remarks> - /// Implementations of this interface must be thread safe. - /// </remarks> - [ContractClass(typeof(IDirectWebRequestHandlerContract))] - public interface IDirectWebRequestHandler { - /// <summary> - /// Determines whether this instance can support the specified options. - /// </summary> - /// <param name="options">The set of options that might be given in a subsequent web request.</param> - /// <returns> - /// <c>true</c> if this instance can support the specified options; otherwise, <c>false</c>. - /// </returns> - [Pure] - bool CanSupport(DirectWebRequestOptions options); - - /// <summary> - /// Prepares an <see cref="HttpWebRequest"/> that contains an POST entity for sending the entity. - /// </summary> - /// <param name="request">The <see cref="HttpWebRequest"/> that should contain the entity.</param> - /// <returns> - /// The stream the caller should write out the entity data to. - /// </returns> - /// <exception cref="ProtocolException">Thrown for any network error.</exception> - /// <remarks> - /// <para>The caller should have set the <see cref="HttpWebRequest.ContentLength"/> - /// and any other appropriate properties <i>before</i> calling this method. - /// Callers <i>must</i> close and dispose of the request stream when they are done - /// writing to it to avoid taking up the connection too long and causing long waits on - /// subsequent requests.</para> - /// <para>Implementations should catch <see cref="WebException"/> and wrap it in a - /// <see cref="ProtocolException"/> to abstract away the transport and provide - /// a single exception type for hosts to catch.</para> - /// </remarks> - Stream GetRequestStream(HttpWebRequest request); - - /// <summary> - /// Prepares an <see cref="HttpWebRequest"/> that contains an POST entity for sending the entity. - /// </summary> - /// <param name="request">The <see cref="HttpWebRequest"/> that should contain the entity.</param> - /// <param name="options">The options to apply to this web request.</param> - /// <returns> - /// The stream the caller should write out the entity data to. - /// </returns> - /// <exception cref="ProtocolException">Thrown for any network error.</exception> - /// <remarks> - /// <para>The caller should have set the <see cref="HttpWebRequest.ContentLength"/> - /// and any other appropriate properties <i>before</i> calling this method. - /// Callers <i>must</i> close and dispose of the request stream when they are done - /// writing to it to avoid taking up the connection too long and causing long waits on - /// subsequent requests.</para> - /// <para>Implementations should catch <see cref="WebException"/> and wrap it in a - /// <see cref="ProtocolException"/> to abstract away the transport and provide - /// a single exception type for hosts to catch.</para> - /// </remarks> - Stream GetRequestStream(HttpWebRequest request, DirectWebRequestOptions options); - - /// <summary> - /// Processes an <see cref="HttpWebRequest"/> and converts the - /// <see cref="HttpWebResponse"/> to a <see cref="IncomingWebResponse"/> instance. - /// </summary> - /// <param name="request">The <see cref="HttpWebRequest"/> to handle.</param> - /// <returns>An instance of <see cref="IncomingWebResponse"/> describing the response.</returns> - /// <exception cref="ProtocolException">Thrown for any network error.</exception> - /// <remarks> - /// <para>Implementations should catch <see cref="WebException"/> and wrap it in a - /// <see cref="ProtocolException"/> to abstract away the transport and provide - /// a single exception type for hosts to catch. The <see cref="WebException.Response"/> - /// value, if set, should be Closed before throwing.</para> - /// </remarks> - IncomingWebResponse GetResponse(HttpWebRequest request); - - /// <summary> - /// Processes an <see cref="HttpWebRequest"/> and converts the - /// <see cref="HttpWebResponse"/> to a <see cref="IncomingWebResponse"/> instance. - /// </summary> - /// <param name="request">The <see cref="HttpWebRequest"/> to handle.</param> - /// <param name="options">The options to apply to this web request.</param> - /// <returns>An instance of <see cref="IncomingWebResponse"/> describing the response.</returns> - /// <exception cref="ProtocolException">Thrown for any network error.</exception> - /// <remarks> - /// <para>Implementations should catch <see cref="WebException"/> and wrap it in a - /// <see cref="ProtocolException"/> to abstract away the transport and provide - /// a single exception type for hosts to catch. The <see cref="WebException.Response"/> - /// value, if set, should be Closed before throwing.</para> - /// </remarks> - IncomingWebResponse GetResponse(HttpWebRequest request, DirectWebRequestOptions options); - } - - /// <summary> - /// Code contract for the <see cref="IDirectWebRequestHandler"/> type. - /// </summary> - [ContractClassFor(typeof(IDirectWebRequestHandler))] - internal abstract class IDirectWebRequestHandlerContract : IDirectWebRequestHandler { - #region IDirectWebRequestHandler Members - - /// <summary> - /// Determines whether this instance can support the specified options. - /// </summary> - /// <param name="options">The set of options that might be given in a subsequent web request.</param> - /// <returns> - /// <c>true</c> if this instance can support the specified options; otherwise, <c>false</c>. - /// </returns> - bool IDirectWebRequestHandler.CanSupport(DirectWebRequestOptions options) { - throw new System.NotImplementedException(); - } - - /// <summary> - /// Prepares an <see cref="HttpWebRequest"/> that contains an POST entity for sending the entity. - /// </summary> - /// <param name="request">The <see cref="HttpWebRequest"/> that should contain the entity.</param> - /// <returns> - /// The stream the caller should write out the entity data to. - /// </returns> - /// <exception cref="ProtocolException">Thrown for any network error.</exception> - /// <remarks> - /// <para>The caller should have set the <see cref="HttpWebRequest.ContentLength"/> - /// and any other appropriate properties <i>before</i> calling this method. - /// Callers <i>must</i> close and dispose of the request stream when they are done - /// writing to it to avoid taking up the connection too long and causing long waits on - /// subsequent requests.</para> - /// <para>Implementations should catch <see cref="WebException"/> and wrap it in a - /// <see cref="ProtocolException"/> to abstract away the transport and provide - /// a single exception type for hosts to catch.</para> - /// </remarks> - Stream IDirectWebRequestHandler.GetRequestStream(HttpWebRequest request) { - Contract.Requires<ArgumentNullException>(request != null); - throw new System.NotImplementedException(); - } - - /// <summary> - /// Prepares an <see cref="HttpWebRequest"/> that contains an POST entity for sending the entity. - /// </summary> - /// <param name="request">The <see cref="HttpWebRequest"/> that should contain the entity.</param> - /// <param name="options">The options to apply to this web request.</param> - /// <returns> - /// The stream the caller should write out the entity data to. - /// </returns> - /// <exception cref="ProtocolException">Thrown for any network error.</exception> - /// <remarks> - /// <para>The caller should have set the <see cref="HttpWebRequest.ContentLength"/> - /// and any other appropriate properties <i>before</i> calling this method. - /// Callers <i>must</i> close and dispose of the request stream when they are done - /// writing to it to avoid taking up the connection too long and causing long waits on - /// subsequent requests.</para> - /// <para>Implementations should catch <see cref="WebException"/> and wrap it in a - /// <see cref="ProtocolException"/> to abstract away the transport and provide - /// a single exception type for hosts to catch.</para> - /// </remarks> - Stream IDirectWebRequestHandler.GetRequestStream(HttpWebRequest request, DirectWebRequestOptions options) { - Contract.Requires<ArgumentNullException>(request != null); - Contract.Requires<NotSupportedException>(((IDirectWebRequestHandler)this).CanSupport(options), MessagingStrings.DirectWebRequestOptionsNotSupported); - ////ErrorUtilities.VerifySupported(((IDirectWebRequestHandler)this).CanSupport(options), string.Format(MessagingStrings.DirectWebRequestOptionsNotSupported, options, this.GetType().Name)); - throw new System.NotImplementedException(); - } - - /// <summary> - /// Processes an <see cref="HttpWebRequest"/> and converts the - /// <see cref="HttpWebResponse"/> to a <see cref="IncomingWebResponse"/> instance. - /// </summary> - /// <param name="request">The <see cref="HttpWebRequest"/> to handle.</param> - /// <returns> - /// An instance of <see cref="IncomingWebResponse"/> describing the response. - /// </returns> - /// <exception cref="ProtocolException">Thrown for any network error.</exception> - /// <remarks> - /// Implementations should catch <see cref="WebException"/> and wrap it in a - /// <see cref="ProtocolException"/> to abstract away the transport and provide - /// a single exception type for hosts to catch. The <see cref="WebException.Response"/> - /// value, if set, should be Closed before throwing. - /// </remarks> - IncomingWebResponse IDirectWebRequestHandler.GetResponse(HttpWebRequest request) { - Contract.Requires<ArgumentNullException>(request != null); - Contract.Ensures(Contract.Result<IncomingWebResponse>() != null); - Contract.Ensures(Contract.Result<IncomingWebResponse>().ResponseStream != null); - throw new System.NotImplementedException(); - } - - /// <summary> - /// Processes an <see cref="HttpWebRequest"/> and converts the - /// <see cref="HttpWebResponse"/> to a <see cref="IncomingWebResponse"/> instance. - /// </summary> - /// <param name="request">The <see cref="HttpWebRequest"/> to handle.</param> - /// <param name="options">The options to apply to this web request.</param> - /// <returns> - /// An instance of <see cref="IncomingWebResponse"/> describing the response. - /// </returns> - /// <exception cref="ProtocolException">Thrown for any network error.</exception> - /// <remarks> - /// Implementations should catch <see cref="WebException"/> and wrap it in a - /// <see cref="ProtocolException"/> to abstract away the transport and provide - /// a single exception type for hosts to catch. The <see cref="WebException.Response"/> - /// value, if set, should be Closed before throwing. - /// </remarks> - IncomingWebResponse IDirectWebRequestHandler.GetResponse(HttpWebRequest request, DirectWebRequestOptions options) { - Contract.Requires<ArgumentNullException>(request != null); - Contract.Requires<NotSupportedException>(((IDirectWebRequestHandler)this).CanSupport(options), MessagingStrings.DirectWebRequestOptionsNotSupported); - Contract.Ensures(Contract.Result<IncomingWebResponse>() != null); - Contract.Ensures(Contract.Result<IncomingWebResponse>().ResponseStream != null); - - ////ErrorUtilities.VerifySupported(((IDirectWebRequestHandler)this).CanSupport(options), string.Format(MessagingStrings.DirectWebRequestOptionsNotSupported, options, this.GetType().Name)); - throw new System.NotImplementedException(); - } - - #endregion - } -} diff --git a/src/DotNetOpenAuth/Messaging/IMessageFactory.cs b/src/DotNetOpenAuth/Messaging/IMessageFactory.cs deleted file mode 100644 index f9ddf3d..0000000 --- a/src/DotNetOpenAuth/Messaging/IMessageFactory.cs +++ /dev/null @@ -1,93 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="IMessageFactory.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.Messaging { - using System; - using System.Collections.Generic; - using System.Diagnostics.Contracts; - - /// <summary> - /// A tool to analyze an incoming message to figure out what concrete class - /// is designed to deserialize it and instantiates that class. - /// </summary> - [ContractClass(typeof(IMessageFactoryContract))] - public interface IMessageFactory { - /// <summary> - /// Analyzes an incoming request message payload to discover what kind of - /// message is embedded in it and returns the type, or null if no match is found. - /// </summary> - /// <param name="recipient">The intended or actual recipient of the request message.</param> - /// <param name="fields">The name/value pairs that make up the message payload.</param> - /// <returns> - /// A newly instantiated <see cref="IProtocolMessage"/>-derived object that this message can - /// deserialize to. Null if the request isn't recognized as a valid protocol message. - /// </returns> - IDirectedProtocolMessage GetNewRequestMessage(MessageReceivingEndpoint recipient, IDictionary<string, string> fields); - - /// <summary> - /// Analyzes an incoming request message payload to discover what kind of - /// message is embedded in it and returns the type, or null if no match is found. - /// </summary> - /// <param name="request"> - /// The message that was sent as a request that resulted in the response. - /// </param> - /// <param name="fields">The name/value pairs that make up the message payload.</param> - /// <returns> - /// A newly instantiated <see cref="IProtocolMessage"/>-derived object that this message can - /// deserialize to. Null if the request isn't recognized as a valid protocol message. - /// </returns> - IDirectResponseProtocolMessage GetNewResponseMessage(IDirectedProtocolMessage request, IDictionary<string, string> fields); - } - - /// <summary> - /// Code contract for the <see cref="IMessageFactory"/> interface. - /// </summary> - [ContractClassFor(typeof(IMessageFactory))] - internal abstract class IMessageFactoryContract : IMessageFactory { - /// <summary> - /// Prevents a default instance of the <see cref="IMessageFactoryContract"/> class from being created. - /// </summary> - private IMessageFactoryContract() { - } - - #region IMessageFactory Members - - /// <summary> - /// Analyzes an incoming request message payload to discover what kind of - /// message is embedded in it and returns the type, or null if no match is found. - /// </summary> - /// <param name="recipient">The intended or actual recipient of the request message.</param> - /// <param name="fields">The name/value pairs that make up the message payload.</param> - /// <returns> - /// A newly instantiated <see cref="IProtocolMessage"/>-derived object that this message can - /// deserialize to. Null if the request isn't recognized as a valid protocol message. - /// </returns> - IDirectedProtocolMessage IMessageFactory.GetNewRequestMessage(MessageReceivingEndpoint recipient, IDictionary<string, string> fields) { - Contract.Requires<ArgumentNullException>(recipient != null); - Contract.Requires<ArgumentNullException>(fields != null); - - throw new NotImplementedException(); - } - - /// <summary> - /// Analyzes an incoming request message payload to discover what kind of - /// message is embedded in it and returns the type, or null if no match is found. - /// </summary> - /// <param name="request">The message that was sent as a request that resulted in the response.</param> - /// <param name="fields">The name/value pairs that make up the message payload.</param> - /// <returns> - /// A newly instantiated <see cref="IProtocolMessage"/>-derived object that this message can - /// deserialize to. Null if the request isn't recognized as a valid protocol message. - /// </returns> - IDirectResponseProtocolMessage IMessageFactory.GetNewResponseMessage(IDirectedProtocolMessage request, IDictionary<string, string> fields) { - Contract.Requires<ArgumentNullException>(request != null); - Contract.Requires<ArgumentNullException>(fields != null); - throw new NotImplementedException(); - } - - #endregion - } -} diff --git a/src/DotNetOpenAuth/Messaging/IncomingWebResponse.cs b/src/DotNetOpenAuth/Messaging/IncomingWebResponse.cs deleted file mode 100644 index 70b1032..0000000 --- a/src/DotNetOpenAuth/Messaging/IncomingWebResponse.cs +++ /dev/null @@ -1,191 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="IncomingWebResponse.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.Messaging { - using System; - using System.Diagnostics.CodeAnalysis; - using System.Diagnostics.Contracts; - using System.Globalization; - using System.IO; - using System.Net; - using System.Net.Mime; - using System.Text; - - /// <summary> - /// Details on the incoming response from a direct web request to a remote party. - /// </summary> - [ContractVerification(true)] - [ContractClass(typeof(IncomingWebResponseContract))] - public abstract class IncomingWebResponse : IDisposable { - /// <summary> - /// The encoding to use in reading a response that does not declare its own content encoding. - /// </summary> - private const string DefaultContentEncoding = "ISO-8859-1"; - - /// <summary> - /// Initializes a new instance of the <see cref="IncomingWebResponse"/> class. - /// </summary> - protected internal IncomingWebResponse() { - this.Status = HttpStatusCode.OK; - this.Headers = new WebHeaderCollection(); - } - - /// <summary> - /// Initializes a new instance of the <see cref="IncomingWebResponse"/> class. - /// </summary> - /// <param name="requestUri">The original request URI.</param> - /// <param name="response">The response to initialize from. The network stream is used by this class directly.</param> - protected IncomingWebResponse(Uri requestUri, HttpWebResponse response) { - Contract.Requires<ArgumentNullException>(requestUri != null); - Contract.Requires<ArgumentNullException>(response != null); - - this.RequestUri = requestUri; - if (!string.IsNullOrEmpty(response.ContentType)) { - try { - this.ContentType = new ContentType(response.ContentType); - } catch (FormatException) { - Logger.Messaging.ErrorFormat("HTTP response to {0} included an invalid Content-Type header value: {1}", response.ResponseUri.AbsoluteUri, response.ContentType); - } - } - this.ContentEncoding = string.IsNullOrEmpty(response.ContentEncoding) ? DefaultContentEncoding : response.ContentEncoding; - this.FinalUri = response.ResponseUri; - this.Status = response.StatusCode; - this.Headers = response.Headers; - } - - /// <summary> - /// Initializes a new instance of the <see cref="IncomingWebResponse"/> class. - /// </summary> - /// <param name="requestUri">The request URI.</param> - /// <param name="responseUri">The final URI to respond to the request.</param> - /// <param name="headers">The headers.</param> - /// <param name="statusCode">The status code.</param> - /// <param name="contentType">Type of the content.</param> - /// <param name="contentEncoding">The content encoding.</param> - protected IncomingWebResponse(Uri requestUri, Uri responseUri, WebHeaderCollection headers, HttpStatusCode statusCode, string contentType, string contentEncoding) { - Contract.Requires<ArgumentNullException>(requestUri != null); - - this.RequestUri = requestUri; - this.Status = statusCode; - if (!string.IsNullOrEmpty(contentType)) { - try { - this.ContentType = new ContentType(contentType); - } catch (FormatException) { - Logger.Messaging.ErrorFormat("HTTP response to {0} included an invalid Content-Type header value: {1}", responseUri.AbsoluteUri, contentType); - } - } - this.ContentEncoding = string.IsNullOrEmpty(contentEncoding) ? DefaultContentEncoding : contentEncoding; - this.Headers = headers; - this.FinalUri = responseUri; - } - - /// <summary> - /// Gets the type of the content. - /// </summary> - public ContentType ContentType { get; private set; } - - /// <summary> - /// Gets the content encoding. - /// </summary> - public string ContentEncoding { get; private set; } - - /// <summary> - /// Gets the URI of the initial request. - /// </summary> - public Uri RequestUri { get; private set; } - - /// <summary> - /// Gets the URI that finally responded to the request. - /// </summary> - /// <remarks> - /// This can be different from the <see cref="RequestUri"/> in cases of - /// redirection during the request. - /// </remarks> - public Uri FinalUri { get; internal set; } - - /// <summary> - /// Gets the headers that must be included in the response to the user agent. - /// </summary> - /// <remarks> - /// The headers in this collection are not meant to be a comprehensive list - /// of exactly what should be sent, but are meant to augment whatever headers - /// are generally included in a typical response. - /// </remarks> - public WebHeaderCollection Headers { get; internal set; } - - /// <summary> - /// Gets the HTTP status code to use in the HTTP response. - /// </summary> - public HttpStatusCode Status { get; internal set; } - - /// <summary> - /// Gets the body of the HTTP response. - /// </summary> - public abstract Stream ResponseStream { get; } - - /// <summary> - /// Returns a <see cref="T:System.String"/> that represents the current <see cref="T:System.Object"/>. - /// </summary> - /// <returns> - /// A <see cref="T:System.String"/> that represents the current <see cref="T:System.Object"/>. - /// </returns> - public override string ToString() { - StringBuilder sb = new StringBuilder(); - sb.AppendLine(string.Format(CultureInfo.CurrentCulture, "RequestUri = {0}", this.RequestUri)); - sb.AppendLine(string.Format(CultureInfo.CurrentCulture, "ResponseUri = {0}", this.FinalUri)); - sb.AppendLine(string.Format(CultureInfo.CurrentCulture, "StatusCode = {0}", this.Status)); - sb.AppendLine(string.Format(CultureInfo.CurrentCulture, "ContentType = {0}", this.ContentType)); - sb.AppendLine(string.Format(CultureInfo.CurrentCulture, "ContentEncoding = {0}", this.ContentEncoding)); - sb.AppendLine("Headers:"); - foreach (string header in this.Headers) { - sb.AppendLine(string.Format(CultureInfo.CurrentCulture, "\t{0}: {1}", header, this.Headers[header])); - } - - return sb.ToString(); - } - - /// <summary> - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. - /// </summary> - public void Dispose() { - this.Dispose(true); - GC.SuppressFinalize(this); - } - - /// <summary> - /// Creates a text reader for the response stream. - /// </summary> - /// <returns>The text reader, initialized for the proper encoding.</returns> - [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "Costly operation")] - public abstract StreamReader GetResponseReader(); - - /// <summary> - /// Gets an offline snapshot version of this instance. - /// </summary> - /// <param name="maximumBytesToCache">The maximum bytes from the response stream to cache.</param> - /// <returns>A snapshot version of this instance.</returns> - /// <remarks> - /// If this instance is a <see cref="NetworkDirectWebResponse"/> creating a snapshot - /// will automatically close and dispose of the underlying response stream. - /// If this instance is a <see cref="CachedDirectWebResponse"/>, the result will - /// be the self same instance. - /// </remarks> - internal abstract CachedDirectWebResponse GetSnapshot(int maximumBytesToCache); - - /// <summary> - /// Releases unmanaged and - optionally - managed resources - /// </summary> - /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param> - protected virtual void Dispose(bool disposing) { - if (disposing) { - Stream responseStream = this.ResponseStream; - if (responseStream != null) { - responseStream.Dispose(); - } - } - } - } -} diff --git a/src/DotNetOpenAuth/Messaging/IncomingWebResponseContract.cs b/src/DotNetOpenAuth/Messaging/IncomingWebResponseContract.cs deleted file mode 100644 index e9c1941..0000000 --- a/src/DotNetOpenAuth/Messaging/IncomingWebResponseContract.cs +++ /dev/null @@ -1,54 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="IncomingWebResponseContract.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.Messaging { - using System; - using System.Diagnostics.Contracts; - using System.IO; - - /// <summary> - /// Code contract for the <see cref="IncomingWebResponse"/> class. - /// </summary> - [ContractClassFor(typeof(IncomingWebResponse))] - internal abstract class IncomingWebResponseContract : IncomingWebResponse { - /// <summary> - /// Gets the body of the HTTP response. - /// </summary> - /// <value></value> - public override Stream ResponseStream { - get { throw new NotImplementedException(); } - } - - /// <summary> - /// Creates a text reader for the response stream. - /// </summary> - /// <returns> - /// The text reader, initialized for the proper encoding. - /// </returns> - public override StreamReader GetResponseReader() { - Contract.Ensures(Contract.Result<StreamReader>() != null); - throw new NotImplementedException(); - } - - /// <summary> - /// Gets an offline snapshot version of this instance. - /// </summary> - /// <param name="maximumBytesToCache">The maximum bytes from the response stream to cache.</param> - /// <returns>A snapshot version of this instance.</returns> - /// <remarks> - /// If this instance is a <see cref="NetworkDirectWebResponse"/> creating a snapshot - /// will automatically close and dispose of the underlying response stream. - /// If this instance is a <see cref="CachedDirectWebResponse"/>, the result will - /// be the self same instance. - /// </remarks> - internal override CachedDirectWebResponse GetSnapshot(int maximumBytesToCache) { - Contract.Requires<ArgumentOutOfRangeException>(maximumBytesToCache >= 0); - Contract.Requires<InvalidOperationException>(this.RequestUri != null); - Contract.Ensures(Contract.Result<CachedDirectWebResponse>() != null); - throw new NotImplementedException(); - } - } -} diff --git a/src/DotNetOpenAuth/Messaging/KeyedCollectionDelegate.cs b/src/DotNetOpenAuth/Messaging/KeyedCollectionDelegate.cs deleted file mode 100644 index 5555f9c..0000000 --- a/src/DotNetOpenAuth/Messaging/KeyedCollectionDelegate.cs +++ /dev/null @@ -1,45 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="KeyedCollectionDelegate.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.Messaging { - using System; - using System.Collections.ObjectModel; - using System.Diagnostics.Contracts; - - /// <summary> - /// A KeyedCollection whose item -> key transform is provided via a delegate - /// to its constructor, and null items are disallowed. - /// </summary> - /// <typeparam name="TKey">The type of the key.</typeparam> - /// <typeparam name="TItem">The type of the item.</typeparam> - [Serializable] - internal class KeyedCollectionDelegate<TKey, TItem> : KeyedCollection<TKey, TItem> { - /// <summary> - /// The delegate that returns a key for the given item. - /// </summary> - private Func<TItem, TKey> getKeyForItemDelegate; - - /// <summary> - /// Initializes a new instance of the KeyedCollectionDelegate class. - /// </summary> - /// <param name="getKeyForItemDelegate">The delegate that gets the key for a given item.</param> - internal KeyedCollectionDelegate(Func<TItem, TKey> getKeyForItemDelegate) { - Contract.Requires<ArgumentNullException>(getKeyForItemDelegate != null); - - this.getKeyForItemDelegate = getKeyForItemDelegate; - } - - /// <summary> - /// When implemented in a derived class, extracts the key from the specified element. - /// </summary> - /// <param name="item">The element from which to extract the key.</param> - /// <returns>The key for the specified element.</returns> - protected override TKey GetKeyForItem(TItem item) { - ErrorUtilities.VerifyArgumentNotNull(item, "item"); // null items not supported. - return this.getKeyForItemDelegate(item); - } - } -} diff --git a/src/DotNetOpenAuth/Messaging/MessageReceivingEndpoint.cs b/src/DotNetOpenAuth/Messaging/MessageReceivingEndpoint.cs deleted file mode 100644 index e39a047..0000000 --- a/src/DotNetOpenAuth/Messaging/MessageReceivingEndpoint.cs +++ /dev/null @@ -1,54 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="MessageReceivingEndpoint.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.Messaging { - using System; - using System.Diagnostics; - using System.Diagnostics.Contracts; - - /// <summary> - /// An immutable description of a URL that receives messages. - /// </summary> - [DebuggerDisplay("{AllowedMethods} {Location}")] - [Serializable] - public class MessageReceivingEndpoint { - /// <summary> - /// Initializes a new instance of the <see cref="MessageReceivingEndpoint"/> class. - /// </summary> - /// <param name="locationUri">The URL of this endpoint.</param> - /// <param name="method">The HTTP method(s) allowed.</param> - public MessageReceivingEndpoint(string locationUri, HttpDeliveryMethods method) - : this(new Uri(locationUri), method) { - Contract.Requires<ArgumentNullException>(locationUri != null); - Contract.Requires<ArgumentOutOfRangeException>(method != HttpDeliveryMethods.None); - Contract.Requires<ArgumentOutOfRangeException>((method & HttpDeliveryMethods.HttpVerbMask) != 0, MessagingStrings.GetOrPostFlagsRequired); - } - - /// <summary> - /// Initializes a new instance of the <see cref="MessageReceivingEndpoint"/> class. - /// </summary> - /// <param name="location">The URL of this endpoint.</param> - /// <param name="method">The HTTP method(s) allowed.</param> - public MessageReceivingEndpoint(Uri location, HttpDeliveryMethods method) { - Contract.Requires<ArgumentNullException>(location != null); - Contract.Requires<ArgumentOutOfRangeException>(method != HttpDeliveryMethods.None); - Contract.Requires<ArgumentOutOfRangeException>((method & HttpDeliveryMethods.HttpVerbMask) != 0, MessagingStrings.GetOrPostFlagsRequired); - - this.Location = location; - this.AllowedMethods = method; - } - - /// <summary> - /// Gets the URL of this endpoint. - /// </summary> - public Uri Location { get; private set; } - - /// <summary> - /// Gets the HTTP method(s) allowed. - /// </summary> - public HttpDeliveryMethods AllowedMethods { get; private set; } - } -} diff --git a/src/DotNetOpenAuth/Messaging/MessageSerializer.cs b/src/DotNetOpenAuth/Messaging/MessageSerializer.cs deleted file mode 100644 index 77a206c..0000000 --- a/src/DotNetOpenAuth/Messaging/MessageSerializer.cs +++ /dev/null @@ -1,239 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="MessageSerializer.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.Messaging { - using System; - using System.Collections.Generic; - using System.Diagnostics; - using System.Diagnostics.CodeAnalysis; - using System.Diagnostics.Contracts; - using System.Globalization; - using System.Reflection; - using System.Xml; - using DotNetOpenAuth.Messaging.Reflection; - using DotNetOpenAuth.OAuth.ChannelElements; - - /// <summary> - /// Serializes/deserializes OAuth messages for/from transit. - /// </summary> - [ContractVerification(true)] - internal class MessageSerializer { - /// <summary> - /// The specific <see cref="IMessage"/>-derived type - /// that will be serialized and deserialized using this class. - /// </summary> - private readonly Type messageType; - - /// <summary> - /// Initializes a new instance of the MessageSerializer class. - /// </summary> - /// <param name="messageType">The specific <see cref="IMessage"/>-derived type - /// that will be serialized and deserialized using this class.</param> - [ContractVerification(false)] // bugs/limitations in CC static analysis - private MessageSerializer(Type messageType) { - Contract.Requires<ArgumentNullException>(messageType != null); - Contract.Requires<ArgumentException>(typeof(IMessage).IsAssignableFrom(messageType)); - Contract.Ensures(this.messageType != null); - this.messageType = messageType; - } - - /// <summary> - /// Creates or reuses a message serializer for a given message type. - /// </summary> - /// <param name="messageType">The type of message that will be serialized/deserialized.</param> - /// <returns>A message serializer for the given message type.</returns> - [ContractVerification(false)] // bugs/limitations in CC static analysis - internal static MessageSerializer Get(Type messageType) { - Contract.Requires<ArgumentNullException>(messageType != null); - Contract.Requires<ArgumentException>(typeof(IMessage).IsAssignableFrom(messageType)); - - return new MessageSerializer(messageType); - } - - /// <summary> - /// Reads JSON as a flat dictionary into a message. - /// </summary> - /// <param name="messageDictionary">The message dictionary to fill with the JSON-deserialized data.</param> - /// <param name="reader">The JSON reader.</param> - internal static void DeserializeJsonAsFlatDictionary(IDictionary<string, string> messageDictionary, XmlDictionaryReader reader) { - Contract.Requires<ArgumentNullException>(messageDictionary != null); - Contract.Requires<ArgumentNullException>(reader != null); - - reader.Read(); // one extra one to skip the root node. - while (reader.Read()) { - if (reader.NodeType == XmlNodeType.EndElement) { - // This is likely the closing </root> tag. - continue; - } - - string key = reader.Name; - reader.Read(); - string value = reader.ReadContentAsString(); - messageDictionary[key] = value; - } - } - - /// <summary> - /// Reads the data from a message instance and writes a XML/JSON encoding of it. - /// </summary> - /// <param name="messageDictionary">The message to be serialized.</param> - /// <param name="writer">The writer to use for the serialized form.</param> - /// <remarks> - /// Use <see cref="System.Runtime.Serialization.Json.JsonReaderWriterFactory.CreateJsonWriter(System.IO.Stream)"/> - /// to create the <see cref="XmlDictionaryWriter"/> instance capable of emitting JSON. - /// </remarks> - [Pure] - internal static void Serialize(MessageDictionary messageDictionary, XmlDictionaryWriter writer) { - Contract.Requires<ArgumentNullException>(messageDictionary != null); - Contract.Requires<ArgumentNullException>(writer != null); - - writer.WriteStartElement("root"); - writer.WriteAttributeString("type", "object"); - foreach (var pair in messageDictionary) { - bool include = false; - string type = "string"; - MessagePart partDescription; - if (messageDictionary.Description.Mapping.TryGetValue(pair.Key, out partDescription)) { - Contract.Assume(partDescription != null); - if (partDescription.IsRequired || partDescription.IsNondefaultValueSet(messageDictionary.Message)) { - include = true; - if (IsNumeric(partDescription.MemberDeclaredType)) { - type = "number"; - } else if (partDescription.MemberDeclaredType.IsAssignableFrom(typeof(bool))) { - type = "boolean"; - } - } - } else { - // This is extra data. We always write it out. - include = true; - } - - if (include) { - writer.WriteStartElement(pair.Key); - writer.WriteAttributeString("type", type); - writer.WriteString(pair.Value); - writer.WriteEndElement(); - } - } - - writer.WriteEndElement(); - } - - /// <summary> - /// Reads XML/JSON into a message dictionary. - /// </summary> - /// <param name="messageDictionary">The message to deserialize into.</param> - /// <param name="reader">The XML/JSON to read into the message.</param> - /// <exception cref="ProtocolException">Thrown when protocol rules are broken by the incoming message.</exception> - /// <remarks> - /// Use <see cref="System.Runtime.Serialization.Json.JsonReaderWriterFactory.CreateJsonReader(System.IO.Stream, System.Xml.XmlDictionaryReaderQuotas)"/> - /// to create the <see cref="XmlDictionaryReader"/> instance capable of reading JSON. - /// </remarks> - internal static void Deserialize(MessageDictionary messageDictionary, XmlDictionaryReader reader) { - Contract.Requires<ArgumentNullException>(messageDictionary != null); - Contract.Requires<ArgumentNullException>(reader != null); - - DeserializeJsonAsFlatDictionary(messageDictionary, reader); - - // Make sure all the required parts are present and valid. - messageDictionary.Description.EnsureMessagePartsPassBasicValidation(messageDictionary); - messageDictionary.Message.EnsureValidMessage(); - } - - /// <summary> - /// Reads the data from a message instance and returns a series of name=value pairs for the fields that must be included in the message. - /// </summary> - /// <param name="messageDictionary">The message to be serialized.</param> - /// <returns>The dictionary of values to send for the message.</returns> - [Pure] - [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Parallel design with Deserialize method.")] - internal IDictionary<string, string> Serialize(MessageDictionary messageDictionary) { - Contract.Requires<ArgumentNullException>(messageDictionary != null); - Contract.Ensures(Contract.Result<IDictionary<string, string>>() != null); - - // Rather than hand back the whole message dictionary (which - // includes keys with blank values), create a new dictionary - // that only has required keys, and optional keys whose - // values are not empty (or default). - var result = new Dictionary<string, string>(); - foreach (var pair in messageDictionary) { - MessagePart partDescription; - if (messageDictionary.Description.Mapping.TryGetValue(pair.Key, out partDescription)) { - Contract.Assume(partDescription != null); - if (partDescription.IsRequired || partDescription.IsNondefaultValueSet(messageDictionary.Message)) { - result.Add(pair.Key, pair.Value); - } - } else { - // This is extra data. We always write it out. - result.Add(pair.Key, pair.Value); - } - } - - return result; - } - - /// <summary> - /// Reads name=value pairs into a message. - /// </summary> - /// <param name="fields">The name=value pairs that were read in from the transport.</param> - /// <param name="messageDictionary">The message to deserialize into.</param> - /// <exception cref="ProtocolException">Thrown when protocol rules are broken by the incoming message.</exception> - internal void Deserialize(IDictionary<string, string> fields, MessageDictionary messageDictionary) { - Contract.Requires<ArgumentNullException>(fields != null); - Contract.Requires<ArgumentNullException>(messageDictionary != null); - - var messageDescription = messageDictionary.Description; - - // Before we deserialize the message, make sure all the required parts are present. - messageDescription.EnsureMessagePartsPassBasicValidation(fields); - - try { - foreach (var pair in fields) { - messageDictionary[pair.Key] = pair.Value; - } - } catch (ArgumentException ex) { - throw ErrorUtilities.Wrap(ex, MessagingStrings.ErrorDeserializingMessage, this.messageType.Name); - } - - messageDictionary.Message.EnsureValidMessage(); - - var originalPayloadMessage = messageDictionary.Message as IMessageOriginalPayload; - if (originalPayloadMessage != null) { - originalPayloadMessage.OriginalPayload = fields; - } - } - - /// <summary> - /// Determines whether the specified type is numeric. - /// </summary> - /// <param name="type">The type to test.</param> - /// <returns> - /// <c>true</c> if the specified type is numeric; otherwise, <c>false</c>. - /// </returns> - private static bool IsNumeric(Type type) { - return type.IsAssignableFrom(typeof(double)) - || type.IsAssignableFrom(typeof(float)) - || type.IsAssignableFrom(typeof(short)) - || type.IsAssignableFrom(typeof(int)) - || type.IsAssignableFrom(typeof(long)) - || type.IsAssignableFrom(typeof(ushort)) - || type.IsAssignableFrom(typeof(uint)) - || type.IsAssignableFrom(typeof(ulong)); - } - -#if CONTRACTS_FULL - /// <summary> - /// Verifies conditions that should be true for any valid state of this object. - /// </summary> - [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Called by code contracts.")] - [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called by code contracts.")] - [ContractInvariantMethod] - private void ObjectInvariant() { - Contract.Invariant(this.messageType != null); - } -#endif - } -} diff --git a/src/DotNetOpenAuth/Messaging/MessagingStrings.Designer.cs b/src/DotNetOpenAuth/Messaging/MessagingStrings.Designer.cs deleted file mode 100644 index 11bd751..0000000 --- a/src/DotNetOpenAuth/Messaging/MessagingStrings.Designer.cs +++ /dev/null @@ -1,675 +0,0 @@ -//------------------------------------------------------------------------------ -// <auto-generated> -// This code was generated by a tool. -// Runtime Version:4.0.30319.1 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// </auto-generated> -//------------------------------------------------------------------------------ - -namespace DotNetOpenAuth.Messaging { - 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 MessagingStrings { - - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal MessagingStrings() { - } - - /// <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.Messaging.MessagingStrings", typeof(MessagingStrings).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 Argument's {0}.{1} property is required but is empty or null.. - /// </summary> - internal static string ArgumentPropertyMissing { - get { - return ResourceManager.GetString("ArgumentPropertyMissing", resourceCulture); - } - } - - /// <summary> - /// Looks up a localized string similar to Unable to send all message data because some of it requires multi-part POST, but IMessageWithBinaryData.SendAsMultipart was false.. - /// </summary> - internal static string BinaryDataRequiresMultipart { - get { - return ResourceManager.GetString("BinaryDataRequiresMultipart", resourceCulture); - } - } - - /// <summary> - /// Looks up a localized string similar to HttpContext.Current is null. There must be an ASP.NET request in process for this operation to succeed.. - /// </summary> - internal static string CurrentHttpContextRequired { - get { - return ResourceManager.GetString("CurrentHttpContextRequired", resourceCulture); - } - } - - /// <summary> - /// Looks up a localized string similar to DataContractSerializer could not be initialized on message type {0}. Is it missing a [DataContract] attribute?. - /// </summary> - internal static string DataContractMissingFromMessageType { - get { - return ResourceManager.GetString("DataContractMissingFromMessageType", resourceCulture); - } - } - - /// <summary> - /// Looks up a localized string similar to DataContractSerializer could not be initialized on message type {0} because the DataContractAttribute.Namespace property is not set.. - /// </summary> - internal static string DataContractMissingNamespace { - get { - return ResourceManager.GetString("DataContractMissingNamespace", resourceCulture); - } - } - - /// <summary> - /// Looks up a localized string similar to An instance of type {0} was expected, but received unexpected derived type {1}.. - /// </summary> - internal static string DerivedTypeNotExpected { - get { - return ResourceManager.GetString("DerivedTypeNotExpected", resourceCulture); - } - } - - /// <summary> - /// Looks up a localized string similar to The directed message's Recipient property must not be null.. - /// </summary> - internal static string DirectedMessageMissingRecipient { - get { - return ResourceManager.GetString("DirectedMessageMissingRecipient", resourceCulture); - } - } - - /// <summary> - /// Looks up a localized string similar to The given set of options is not supported by this web request handler.. - /// </summary> - internal static string DirectWebRequestOptionsNotSupported { - get { - return ResourceManager.GetString("DirectWebRequestOptionsNotSupported", resourceCulture); - } - } - - /// <summary> - /// Looks up a localized string similar to Unable to instantiate the message part encoder/decoder type {0}.. - /// </summary> - internal static string EncoderInstantiationFailed { - get { - return ResourceManager.GetString("EncoderInstantiationFailed", resourceCulture); - } - } - - /// <summary> - /// Looks up a localized string similar to Error while deserializing message {0}.. - /// </summary> - internal static string ErrorDeserializingMessage { - get { - return ResourceManager.GetString("ErrorDeserializingMessage", resourceCulture); - } - } - - /// <summary> - /// Looks up a localized string similar to Error occurred while sending a direct message or getting the response.. - /// </summary> - internal static string ErrorInRequestReplyMessage { - get { - return ResourceManager.GetString("ErrorInRequestReplyMessage", resourceCulture); - } - } - - /// <summary> - /// Looks up a localized string similar to This exception was not constructed with a root request message that caused it.. - /// </summary> - internal static string ExceptionNotConstructedForTransit { - get { - return ResourceManager.GetString("ExceptionNotConstructedForTransit", resourceCulture); - } - } - - /// <summary> - /// Looks up a localized string similar to This exception must be instantiated with a recipient that will receive the error message, or a direct request message instance that this exception will respond to.. - /// </summary> - internal static string ExceptionUndeliverable { - get { - return ResourceManager.GetString("ExceptionUndeliverable", resourceCulture); - } - } - - /// <summary> - /// Looks up a localized string similar to Expected {0} message but received no recognizable message.. - /// </summary> - internal static string ExpectedMessageNotReceived { - get { - return ResourceManager.GetString("ExpectedMessageNotReceived", resourceCulture); - } - } - - /// <summary> - /// Looks up a localized string similar to The message expired at {0} and it is now {1}.. - /// </summary> - internal static string ExpiredMessage { - get { - return ResourceManager.GetString("ExpiredMessage", resourceCulture); - } - } - - /// <summary> - /// Looks up a localized string similar to Failed to add extra parameter '{0}' with value '{1}'.. - /// </summary> - internal static string ExtraParameterAddFailure { - get { - return ResourceManager.GetString("ExtraParameterAddFailure", resourceCulture); - } - } - - /// <summary> - /// Looks up a localized string similar to At least one of GET or POST flags must be present.. - /// </summary> - internal static string GetOrPostFlagsRequired { - get { - return ResourceManager.GetString("GetOrPostFlagsRequired", resourceCulture); - } - } - - /// <summary> - /// Looks up a localized string similar to This method requires a current HttpContext. Alternatively, use an overload of this method that allows you to pass in information without an HttpContext.. - /// </summary> - internal static string HttpContextRequired { - get { - return ResourceManager.GetString("HttpContextRequired", resourceCulture); - } - } - - /// <summary> - /// Looks up a localized string similar to Messages that indicate indirect transport must implement the {0} interface.. - /// </summary> - internal static string IndirectMessagesMustImplementIDirectedProtocolMessage { - get { - return ResourceManager.GetString("IndirectMessagesMustImplementIDirectedProtocolMessage", resourceCulture); - } - } - - /// <summary> - /// Looks up a localized string similar to Insecure web request for '{0}' aborted due to security requirements demanding HTTPS.. - /// </summary> - internal static string InsecureWebRequestWithSslRequired { - get { - return ResourceManager.GetString("InsecureWebRequestWithSslRequired", resourceCulture); - } - } - - /// <summary> - /// Looks up a localized string similar to The {0} message required protections {{{1}}} but the channel could only apply {{{2}}}.. - /// </summary> - internal static string InsufficientMessageProtection { - get { - return ResourceManager.GetString("InsufficientMessageProtection", resourceCulture); - } - } - - /// <summary> - /// Looks up a localized string similar to The customized binding element ordering is invalid.. - /// </summary> - internal static string InvalidCustomBindingElementOrder { - get { - return ResourceManager.GetString("InvalidCustomBindingElementOrder", resourceCulture); - } - } - - /// <summary> - /// Looks up a localized string similar to Some part(s) of the message have invalid values: {0}. - /// </summary> - internal static string InvalidMessageParts { - get { - return ResourceManager.GetString("InvalidMessageParts", resourceCulture); - } - } - - /// <summary> - /// Looks up a localized string similar to The incoming message had an invalid or missing nonce.. - /// </summary> - internal static string InvalidNonceReceived { - get { - return ResourceManager.GetString("InvalidNonceReceived", resourceCulture); - } - } - - /// <summary> - /// Looks up a localized string similar to An item with the same key has already been added.. - /// </summary> - internal static string KeyAlreadyExists { - get { - return ResourceManager.GetString("KeyAlreadyExists", resourceCulture); - } - } - - /// <summary> - /// Looks up a localized string similar to The {0} message does not support extensions.. - /// </summary> - internal static string MessageNotExtensible { - get { - return ResourceManager.GetString("MessageNotExtensible", resourceCulture); - } - } - - /// <summary> - /// Looks up a localized string similar to The value for {0}.{1} on member {1} was expected to derive from {2} but was {3}.. - /// </summary> - internal static string MessagePartEncoderWrongType { - get { - return ResourceManager.GetString("MessagePartEncoderWrongType", resourceCulture); - } - } - - /// <summary> - /// Looks up a localized string similar to Error while reading message '{0}' parameter '{1}' with value '{2}'.. - /// </summary> - internal static string MessagePartReadFailure { - get { - return ResourceManager.GetString("MessagePartReadFailure", resourceCulture); - } - } - - /// <summary> - /// Looks up a localized string similar to Message parameter '{0}' with value '{1}' failed to base64 decode.. - /// </summary> - internal static string MessagePartValueBase64DecodingFault { - get { - return ResourceManager.GetString("MessagePartValueBase64DecodingFault", resourceCulture); - } - } - - /// <summary> - /// Looks up a localized string similar to Error while preparing message '{0}' parameter '{1}' for sending.. - /// </summary> - internal static string MessagePartWriteFailure { - get { - return ResourceManager.GetString("MessagePartWriteFailure", resourceCulture); - } - } - - /// <summary> - /// Looks up a localized string similar to This message has a timestamp of {0}, which is beyond the allowable clock skew for in the future.. - /// </summary> - internal static string MessageTimestampInFuture { - get { - return ResourceManager.GetString("MessageTimestampInFuture", resourceCulture); - } - } - - /// <summary> - /// Looks up a localized string similar to A non-empty string was expected.. - /// </summary> - internal static string NonEmptyStringExpected { - get { - return ResourceManager.GetString("NonEmptyStringExpected", resourceCulture); - } - } - - /// <summary> - /// Looks up a localized string similar to A message response is already queued for sending in the response stream.. - /// </summary> - internal static string QueuedMessageResponseAlreadyExists { - get { - return ResourceManager.GetString("QueuedMessageResponseAlreadyExists", resourceCulture); - } - } - - /// <summary> - /// Looks up a localized string similar to This message has already been processed. This could indicate a replay attack in progress.. - /// </summary> - internal static string ReplayAttackDetected { - get { - return ResourceManager.GetString("ReplayAttackDetected", resourceCulture); - } - } - - /// <summary> - /// Looks up a localized string similar to This channel does not support replay protection.. - /// </summary> - internal static string ReplayProtectionNotSupported { - get { - return ResourceManager.GetString("ReplayProtectionNotSupported", resourceCulture); - } - } - - /// <summary> - /// Looks up a localized string similar to The following message parts had constant value requirements that were unsatisfied: {0}. - /// </summary> - internal static string RequiredMessagePartConstantIncorrect { - get { - return ResourceManager.GetString("RequiredMessagePartConstantIncorrect", resourceCulture); - } - } - - /// <summary> - /// Looks up a localized string similar to The following required non-empty parameters were empty in the {0} message: {1}. - /// </summary> - internal static string RequiredNonEmptyParameterWasEmpty { - get { - return ResourceManager.GetString("RequiredNonEmptyParameterWasEmpty", resourceCulture); - } - } - - /// <summary> - /// Looks up a localized string similar to The following required parameters were missing from the {0} message: {1}. - /// </summary> - internal static string RequiredParametersMissing { - get { - return ResourceManager.GetString("RequiredParametersMissing", resourceCulture); - } - } - - /// <summary> - /// Looks up a localized string similar to The binding element offering the {0} protection requires other protection that is not provided.. - /// </summary> - internal static string RequiredProtectionMissing { - get { - return ResourceManager.GetString("RequiredProtectionMissing", resourceCulture); - } - } - - /// <summary> - /// Looks up a localized string similar to The list is empty.. - /// </summary> - internal static string SequenceContainsNoElements { - get { - return ResourceManager.GetString("SequenceContainsNoElements", resourceCulture); - } - } - - /// <summary> - /// Looks up a localized string similar to The list contains a null element.. - /// </summary> - internal static string SequenceContainsNullElement { - get { - return ResourceManager.GetString("SequenceContainsNullElement", resourceCulture); - } - } - - /// <summary> - /// Looks up a localized string similar to An HttpContext.Current.Session object is required.. - /// </summary> - internal static string SessionRequired { - get { - return ResourceManager.GetString("SessionRequired", resourceCulture); - } - } - - /// <summary> - /// Looks up a localized string similar to Message signature was incorrect.. - /// </summary> - internal static string SignatureInvalid { - get { - return ResourceManager.GetString("SignatureInvalid", resourceCulture); - } - } - - /// <summary> - /// Looks up a localized string similar to This channel does not support signing messages. To support signing messages, a derived Channel type must override the Sign and IsSignatureValid methods.. - /// </summary> - internal static string SigningNotSupported { - get { - return ResourceManager.GetString("SigningNotSupported", resourceCulture); - } - } - - /// <summary> - /// Looks up a localized string similar to This message factory does not support message type(s): {0}. - /// </summary> - internal static string StandardMessageFactoryUnsupportedMessageType { - get { - return ResourceManager.GetString("StandardMessageFactoryUnsupportedMessageType", resourceCulture); - } - } - - /// <summary> - /// Looks up a localized string similar to The stream must have a known length.. - /// </summary> - internal static string StreamMustHaveKnownLength { - get { - return ResourceManager.GetString("StreamMustHaveKnownLength", resourceCulture); - } - } - - /// <summary> - /// Looks up a localized string similar to The stream's CanRead property returned false.. - /// </summary> - internal static string StreamUnreadable { - get { - return ResourceManager.GetString("StreamUnreadable", resourceCulture); - } - } - - /// <summary> - /// Looks up a localized string similar to The stream's CanWrite property returned false.. - /// </summary> - internal static string StreamUnwritable { - get { - return ResourceManager.GetString("StreamUnwritable", resourceCulture); - } - } - - /// <summary> - /// Looks up a localized string similar to Expected at most 1 binding element to apply the {0} protection, but more than one applied.. - /// </summary> - internal static string TooManyBindingsOfferingSameProtection { - get { - return ResourceManager.GetString("TooManyBindingsOfferingSameProtection", resourceCulture); - } - } - - /// <summary> - /// Looks up a localized string similar to The maximum allowable number of redirects were exceeded while requesting '{0}'.. - /// </summary> - internal static string TooManyRedirects { - get { - return ResourceManager.GetString("TooManyRedirects", resourceCulture); - } - } - - /// <summary> - /// Looks up a localized string similar to The array must not be empty.. - /// </summary> - internal static string UnexpectedEmptyArray { - get { - return ResourceManager.GetString("UnexpectedEmptyArray", resourceCulture); - } - } - - /// <summary> - /// Looks up a localized string similar to The empty string is not allowed.. - /// </summary> - internal static string UnexpectedEmptyString { - get { - return ResourceManager.GetString("UnexpectedEmptyString", resourceCulture); - } - } - - /// <summary> - /// Looks up a localized string similar to Expected direct response to use HTTP status code {0} but was {1} instead.. - /// </summary> - internal static string UnexpectedHttpStatusCode { - get { - return ResourceManager.GetString("UnexpectedHttpStatusCode", resourceCulture); - } - } - - /// <summary> - /// Looks up a localized string similar to Message parameter '{0}' had unexpected value '{1}'.. - /// </summary> - internal static string UnexpectedMessagePartValue { - get { - return ResourceManager.GetString("UnexpectedMessagePartValue", resourceCulture); - } - } - - /// <summary> - /// Looks up a localized string similar to Expected message {0} parameter '{1}' to have value '{2}' but had '{3}' instead.. - /// </summary> - internal static string UnexpectedMessagePartValueForConstant { - get { - return ResourceManager.GetString("UnexpectedMessagePartValueForConstant", resourceCulture); - } - } - - /// <summary> - /// Looks up a localized string similar to Expected message {0} but received {1} instead.. - /// </summary> - internal static string UnexpectedMessageReceived { - get { - return ResourceManager.GetString("UnexpectedMessageReceived", resourceCulture); - } - } - - /// <summary> - /// Looks up a localized string similar to Unexpected message type received.. - /// </summary> - internal static string UnexpectedMessageReceivedOfMany { - get { - return ResourceManager.GetString("UnexpectedMessageReceivedOfMany", resourceCulture); - } - } - - /// <summary> - /// Looks up a localized string similar to A null key was included and is not allowed.. - /// </summary> - internal static string UnexpectedNullKey { - get { - return ResourceManager.GetString("UnexpectedNullKey", resourceCulture); - } - } - - /// <summary> - /// Looks up a localized string similar to A null or empty key was included and is not allowed.. - /// </summary> - internal static string UnexpectedNullOrEmptyKey { - get { - return ResourceManager.GetString("UnexpectedNullOrEmptyKey", resourceCulture); - } - } - - /// <summary> - /// Looks up a localized string similar to A null value was included for key '{0}' and is not allowed.. - /// </summary> - internal static string UnexpectedNullValue { - get { - return ResourceManager.GetString("UnexpectedNullValue", resourceCulture); - } - } - - /// <summary> - /// Looks up a localized string similar to The type {0} or a derived type was expected, but {1} was given.. - /// </summary> - internal static string UnexpectedType { - get { - return ResourceManager.GetString("UnexpectedType", resourceCulture); - } - } - - /// <summary> - /// Looks up a localized string similar to {0} property has unrecognized value {1}.. - /// </summary> - internal static string UnrecognizedEnumValue { - get { - return ResourceManager.GetString("UnrecognizedEnumValue", resourceCulture); - } - } - - /// <summary> - /// Looks up a localized string similar to The URL '{0}' is rated unsafe and cannot be requested this way.. - /// </summary> - internal static string UnsafeWebRequestDetected { - get { - return ResourceManager.GetString("UnsafeWebRequestDetected", resourceCulture); - } - } - - /// <summary> - /// Looks up a localized string similar to This blob is not a recognized encryption format.. - /// </summary> - internal static string UnsupportedEncryptionAlgorithm { - get { - return ResourceManager.GetString("UnsupportedEncryptionAlgorithm", resourceCulture); - } - } - - /// <summary> - /// Looks up a localized string similar to The HTTP verb '{0}' is unrecognized and unsupported.. - /// </summary> - internal static string UnsupportedHttpVerb { - get { - return ResourceManager.GetString("UnsupportedHttpVerb", resourceCulture); - } - } - - /// <summary> - /// Looks up a localized string similar to '{0}' messages cannot be received with HTTP verb '{1}'.. - /// </summary> - internal static string UnsupportedHttpVerbForMessageType { - get { - return ResourceManager.GetString("UnsupportedHttpVerbForMessageType", resourceCulture); - } - } - - /// <summary> - /// Looks up a localized string similar to Redirects on POST requests that are to untrusted servers is not supported.. - /// </summary> - internal static string UntrustedRedirectsOnPOSTNotSupported { - get { - return ResourceManager.GetString("UntrustedRedirectsOnPOSTNotSupported", resourceCulture); - } - } - - /// <summary> - /// Looks up a localized string similar to Web request to '{0}' failed.. - /// </summary> - internal static string WebRequestFailed { - get { - return ResourceManager.GetString("WebRequestFailed", resourceCulture); - } - } - } -} diff --git a/src/DotNetOpenAuth/Messaging/MessagingStrings.resx b/src/DotNetOpenAuth/Messaging/MessagingStrings.resx deleted file mode 100644 index bd10b76..0000000 --- a/src/DotNetOpenAuth/Messaging/MessagingStrings.resx +++ /dev/null @@ -1,324 +0,0 @@ -<?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="ArgumentPropertyMissing" xml:space="preserve"> - <value>Argument's {0}.{1} property is required but is empty or null.</value> - </data> - <data name="CurrentHttpContextRequired" xml:space="preserve"> - <value>HttpContext.Current is null. There must be an ASP.NET request in process for this operation to succeed.</value> - </data> - <data name="DataContractMissingFromMessageType" xml:space="preserve"> - <value>DataContractSerializer could not be initialized on message type {0}. Is it missing a [DataContract] attribute?</value> - </data> - <data name="DataContractMissingNamespace" xml:space="preserve"> - <value>DataContractSerializer could not be initialized on message type {0} because the DataContractAttribute.Namespace property is not set.</value> - </data> - <data name="DerivedTypeNotExpected" xml:space="preserve"> - <value>An instance of type {0} was expected, but received unexpected derived type {1}.</value> - </data> - <data name="DirectedMessageMissingRecipient" xml:space="preserve"> - <value>The directed message's Recipient property must not be null.</value> - </data> - <data name="DirectWebRequestOptionsNotSupported" xml:space="preserve"> - <value>The given set of options is not supported by this web request handler.</value> - </data> - <data name="ErrorDeserializingMessage" xml:space="preserve"> - <value>Error while deserializing message {0}.</value> - </data> - <data name="ErrorInRequestReplyMessage" xml:space="preserve"> - <value>Error occurred while sending a direct message or getting the response.</value> - </data> - <data name="ExceptionNotConstructedForTransit" xml:space="preserve"> - <value>This exception was not constructed with a root request message that caused it.</value> - </data> - <data name="ExpectedMessageNotReceived" xml:space="preserve"> - <value>Expected {0} message but received no recognizable message.</value> - </data> - <data name="ExpiredMessage" xml:space="preserve"> - <value>The message expired at {0} and it is now {1}.</value> - </data> - <data name="GetOrPostFlagsRequired" xml:space="preserve"> - <value>At least one of GET or POST flags must be present.</value> - </data> - <data name="HttpContextRequired" xml:space="preserve"> - <value>This method requires a current HttpContext. Alternatively, use an overload of this method that allows you to pass in information without an HttpContext.</value> - </data> - <data name="IndirectMessagesMustImplementIDirectedProtocolMessage" xml:space="preserve"> - <value>Messages that indicate indirect transport must implement the {0} interface.</value> - </data> - <data name="InsecureWebRequestWithSslRequired" xml:space="preserve"> - <value>Insecure web request for '{0}' aborted due to security requirements demanding HTTPS.</value> - </data> - <data name="InsufficientMessageProtection" xml:space="preserve"> - <value>The {0} message required protections {{{1}}} but the channel could only apply {{{2}}}.</value> - </data> - <data name="InvalidCustomBindingElementOrder" xml:space="preserve"> - <value>The customized binding element ordering is invalid.</value> - </data> - <data name="InvalidMessageParts" xml:space="preserve"> - <value>Some part(s) of the message have invalid values: {0}</value> - </data> - <data name="InvalidNonceReceived" xml:space="preserve"> - <value>The incoming message had an invalid or missing nonce.</value> - </data> - <data name="KeyAlreadyExists" xml:space="preserve"> - <value>An item with the same key has already been added.</value> - </data> - <data name="MessageNotExtensible" xml:space="preserve"> - <value>The {0} message does not support extensions.</value> - </data> - <data name="MessagePartEncoderWrongType" xml:space="preserve"> - <value>The value for {0}.{1} on member {1} was expected to derive from {2} but was {3}.</value> - </data> - <data name="MessagePartReadFailure" xml:space="preserve"> - <value>Error while reading message '{0}' parameter '{1}' with value '{2}'.</value> - </data> - <data name="MessagePartValueBase64DecodingFault" xml:space="preserve"> - <value>Message parameter '{0}' with value '{1}' failed to base64 decode.</value> - </data> - <data name="MessagePartWriteFailure" xml:space="preserve"> - <value>Error while preparing message '{0}' parameter '{1}' for sending.</value> - </data> - <data name="QueuedMessageResponseAlreadyExists" xml:space="preserve"> - <value>A message response is already queued for sending in the response stream.</value> - </data> - <data name="ReplayAttackDetected" xml:space="preserve"> - <value>This message has already been processed. This could indicate a replay attack in progress.</value> - </data> - <data name="ReplayProtectionNotSupported" xml:space="preserve"> - <value>This channel does not support replay protection.</value> - </data> - <data name="RequiredNonEmptyParameterWasEmpty" xml:space="preserve"> - <value>The following required non-empty parameters were empty in the {0} message: {1}</value> - </data> - <data name="RequiredParametersMissing" xml:space="preserve"> - <value>The following required parameters were missing from the {0} message: {1}</value> - </data> - <data name="RequiredProtectionMissing" xml:space="preserve"> - <value>The binding element offering the {0} protection requires other protection that is not provided.</value> - </data> - <data name="SequenceContainsNoElements" xml:space="preserve"> - <value>The list is empty.</value> - </data> - <data name="SequenceContainsNullElement" xml:space="preserve"> - <value>The list contains a null element.</value> - </data> - <data name="SignatureInvalid" xml:space="preserve"> - <value>Message signature was incorrect.</value> - </data> - <data name="SigningNotSupported" xml:space="preserve"> - <value>This channel does not support signing messages. To support signing messages, a derived Channel type must override the Sign and IsSignatureValid methods.</value> - </data> - <data name="StreamUnreadable" xml:space="preserve"> - <value>The stream's CanRead property returned false.</value> - </data> - <data name="StreamUnwritable" xml:space="preserve"> - <value>The stream's CanWrite property returned false.</value> - </data> - <data name="TooManyBindingsOfferingSameProtection" xml:space="preserve"> - <value>Expected at most 1 binding element to apply the {0} protection, but more than one applied.</value> - </data> - <data name="TooManyRedirects" xml:space="preserve"> - <value>The maximum allowable number of redirects were exceeded while requesting '{0}'.</value> - </data> - <data name="UnexpectedEmptyArray" xml:space="preserve"> - <value>The array must not be empty.</value> - </data> - <data name="UnexpectedEmptyString" xml:space="preserve"> - <value>The empty string is not allowed.</value> - </data> - <data name="UnexpectedMessagePartValue" xml:space="preserve"> - <value>Message parameter '{0}' had unexpected value '{1}'.</value> - </data> - <data name="UnexpectedMessagePartValueForConstant" xml:space="preserve"> - <value>Expected message {0} parameter '{1}' to have value '{2}' but had '{3}' instead.</value> - </data> - <data name="UnexpectedMessageReceived" xml:space="preserve"> - <value>Expected message {0} but received {1} instead.</value> - </data> - <data name="UnexpectedMessageReceivedOfMany" xml:space="preserve"> - <value>Unexpected message type received.</value> - </data> - <data name="UnexpectedNullKey" xml:space="preserve"> - <value>A null key was included and is not allowed.</value> - </data> - <data name="UnexpectedNullOrEmptyKey" xml:space="preserve"> - <value>A null or empty key was included and is not allowed.</value> - </data> - <data name="UnexpectedNullValue" xml:space="preserve"> - <value>A null value was included for key '{0}' and is not allowed.</value> - </data> - <data name="UnexpectedType" xml:space="preserve"> - <value>The type {0} or a derived type was expected, but {1} was given.</value> - </data> - <data name="UnrecognizedEnumValue" xml:space="preserve"> - <value>{0} property has unrecognized value {1}.</value> - </data> - <data name="UnsafeWebRequestDetected" xml:space="preserve"> - <value>The URL '{0}' is rated unsafe and cannot be requested this way.</value> - </data> - <data name="UntrustedRedirectsOnPOSTNotSupported" xml:space="preserve"> - <value>Redirects on POST requests that are to untrusted servers is not supported.</value> - </data> - <data name="WebRequestFailed" xml:space="preserve"> - <value>Web request to '{0}' failed.</value> - </data> - <data name="ExceptionUndeliverable" xml:space="preserve"> - <value>This exception must be instantiated with a recipient that will receive the error message, or a direct request message instance that this exception will respond to.</value> - </data> - <data name="UnsupportedHttpVerbForMessageType" xml:space="preserve"> - <value>'{0}' messages cannot be received with HTTP verb '{1}'.</value> - </data> - <data name="UnexpectedHttpStatusCode" xml:space="preserve"> - <value>Expected direct response to use HTTP status code {0} but was {1} instead.</value> - </data> - <data name="UnsupportedHttpVerb" xml:space="preserve"> - <value>The HTTP verb '{0}' is unrecognized and unsupported.</value> - </data> - <data name="NonEmptyStringExpected" xml:space="preserve"> - <value>A non-empty string was expected.</value> - </data> - <data name="StreamMustHaveKnownLength" xml:space="preserve"> - <value>The stream must have a known length.</value> - </data> - <data name="BinaryDataRequiresMultipart" xml:space="preserve"> - <value>Unable to send all message data because some of it requires multi-part POST, but IMessageWithBinaryData.SendAsMultipart was false.</value> - </data> - <data name="SessionRequired" xml:space="preserve"> - <value>An HttpContext.Current.Session object is required.</value> - </data> - <data name="StandardMessageFactoryUnsupportedMessageType" xml:space="preserve"> - <value>This message factory does not support message type(s): {0}</value> - </data> - <data name="RequiredMessagePartConstantIncorrect" xml:space="preserve"> - <value>The following message parts had constant value requirements that were unsatisfied: {0}</value> - </data> - <data name="EncoderInstantiationFailed" xml:space="preserve"> - <value>Unable to instantiate the message part encoder/decoder type {0}.</value> - </data> - <data name="MessageTimestampInFuture" xml:space="preserve"> - <value>This message has a timestamp of {0}, which is beyond the allowable clock skew for in the future.</value> - </data> - <data name="UnsupportedEncryptionAlgorithm" xml:space="preserve"> - <value>This blob is not a recognized encryption format.</value> - </data> - <data name="ExtraParameterAddFailure" xml:space="preserve"> - <value>Failed to add extra parameter '{0}' with value '{1}'.</value> - </data> -</root> diff --git a/src/DotNetOpenAuth/Messaging/MessagingUtilities.cs b/src/DotNetOpenAuth/Messaging/MessagingUtilities.cs deleted file mode 100644 index 8fc691f..0000000 --- a/src/DotNetOpenAuth/Messaging/MessagingUtilities.cs +++ /dev/null @@ -1,1709 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="MessagingUtilities.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.Messaging { - using System; - using System.Collections.Generic; - using System.Collections.Specialized; - using System.Diagnostics.CodeAnalysis; - using System.Diagnostics.Contracts; - using System.Globalization; - using System.IO; - using System.IO.Compression; - using System.Linq; - using System.Net; - using System.Net.Mime; - using System.Security; - using System.Security.Cryptography; - using System.Text; - using System.Web; - using System.Web.Mvc; - using DotNetOpenAuth.Messaging.Bindings; - using DotNetOpenAuth.Messaging.Reflection; - - /// <summary> - /// A grab-bag of utility methods useful for the channel stack of the protocol. - /// </summary> - [SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Justification = "Utility class touches lots of surface area")] - public static class MessagingUtilities { - /// <summary> - /// The cryptographically strong random data generator used for creating secrets. - /// </summary> - /// <remarks>The random number generator is thread-safe.</remarks> - internal static readonly RandomNumberGenerator CryptoRandomDataGenerator = new RNGCryptoServiceProvider(); - - /// <summary> - /// A pseudo-random data generator (NOT cryptographically strong random data) - /// </summary> - internal static readonly Random NonCryptoRandomDataGenerator = new Random(); - - /// <summary> - /// The uppercase alphabet. - /// </summary> - internal const string UppercaseLetters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; - - /// <summary> - /// The lowercase alphabet. - /// </summary> - internal const string LowercaseLetters = "abcdefghijklmnopqrstuvwxyz"; - - /// <summary> - /// The set of base 10 digits. - /// </summary> - internal const string Digits = "0123456789"; - - /// <summary> - /// The set of digits and alphabetic letters (upper and lowercase). - /// </summary> - internal const string AlphaNumeric = UppercaseLetters + LowercaseLetters + Digits; - - /// <summary> - /// All the characters that are allowed for use as a base64 encoding character. - /// </summary> - internal const string Base64Characters = AlphaNumeric + "+" + "/"; - - /// <summary> - /// All the characters that are allowed for use as a base64 encoding character - /// in the "web safe" context. - /// </summary> - internal const string Base64WebSafeCharacters = AlphaNumeric + "-" + "_"; - - /// <summary> - /// The set of digits, and alphabetic letters (upper and lowercase) that are clearly - /// visually distinguishable. - /// </summary> - internal const string AlphaNumericNoLookAlikes = "23456789abcdefghjkmnpqrstwxyzABCDEFGHJKMNPQRSTWXYZ"; - - /// <summary> - /// The length of private symmetric secret handles. - /// </summary> - /// <remarks> - /// This value needn't be high, as we only expect to have a small handful of unexpired secrets at a time, - /// and handle recycling is permissible. - /// </remarks> - private const int SymmetricSecretHandleLength = 4; - - /// <summary> - /// The default lifetime of a private secret. - /// </summary> - private static readonly TimeSpan SymmetricSecretKeyLifespan = Configuration.DotNetOpenAuthSection.Configuration.Messaging.PrivateSecretMaximumAge; - - /// <summary> - /// A character array containing just the = character. - /// </summary> - private static readonly char[] EqualsArray = new char[] { '=' }; - - /// <summary> - /// A character array containing just the , character. - /// </summary> - private static readonly char[] CommaArray = new char[] { ',' }; - - /// <summary> - /// A character array containing just the " character. - /// </summary> - private static readonly char[] QuoteArray = new char[] { '"' }; - - /// <summary> - /// The set of characters that are unreserved in RFC 2396 but are NOT unreserved in RFC 3986. - /// </summary> - private static readonly string[] UriRfc3986CharsToEscape = new[] { "!", "*", "'", "(", ")" }; - - /// <summary> - /// A set of escaping mappings that help secure a string from javscript execution. - /// </summary> - /// <remarks> - /// The characters to escape here are inspired by - /// http://code.google.com/p/doctype/wiki/ArticleXSSInJavaScript - /// </remarks> - private static readonly Dictionary<string, string> javascriptStaticStringEscaping = new Dictionary<string, string> { - { "\\", @"\\" }, // this WAS just above the & substitution but we moved it here to prevent double-escaping - { "\t", @"\t" }, - { "\n", @"\n" }, - { "\r", @"\r" }, - { "\u0085", @"\u0085" }, - { "\u2028", @"\u2028" }, - { "\u2029", @"\u2029" }, - { "'", @"\x27" }, - { "\"", @"\x22" }, - { "&", @"\x26" }, - { "<", @"\x3c" }, - { ">", @"\x3e" }, - { "=", @"\x3d" }, - }; - - /// <summary> - /// Transforms an OutgoingWebResponse to an MVC-friendly ActionResult. - /// </summary> - /// <param name="response">The response to send to the user agent.</param> - /// <returns>The <see cref="ActionResult"/> instance to be returned by the Controller's action method.</returns> - public static ActionResult AsActionResult(this OutgoingWebResponse response) { - Contract.Requires<ArgumentNullException>(response != null); - return new OutgoingWebResponseActionResult(response); - } - - /// <summary> - /// Gets the original request URL, as seen from the browser before any URL rewrites on the server if any. - /// Cookieless session directory (if applicable) is also included. - /// </summary> - /// <returns>The URL in the user agent's Location bar.</returns> - [SuppressMessage("Microsoft.Usage", "CA2234:PassSystemUriObjectsInsteadOfStrings", Justification = "The Uri merging requires use of a string value.")] - [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "Expensive call should not be a property.")] - public static Uri GetRequestUrlFromContext() { - Contract.Requires<InvalidOperationException>(HttpContext.Current != null && HttpContext.Current.Request != null, MessagingStrings.HttpContextRequired); - HttpContext context = HttpContext.Current; - - return HttpRequestInfo.GetPublicFacingUrl(context.Request, context.Request.ServerVariables); - } - - /// <summary> - /// Strips any and all URI query parameters that start with some prefix. - /// </summary> - /// <param name="uri">The URI that may have a query with parameters to remove.</param> - /// <param name="prefix">The prefix for parameters to remove. A period is NOT automatically appended.</param> - /// <returns>Either a new Uri with the parameters removed if there were any to remove, or the same Uri instance if no parameters needed to be removed.</returns> - public static Uri StripQueryArgumentsWithPrefix(this Uri uri, string prefix) { - Contract.Requires<ArgumentNullException>(uri != null); - Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(prefix)); - - NameValueCollection queryArgs = HttpUtility.ParseQueryString(uri.Query); - var matchingKeys = queryArgs.Keys.OfType<string>().Where(key => key.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)).ToList(); - if (matchingKeys.Count > 0) { - UriBuilder builder = new UriBuilder(uri); - foreach (string key in matchingKeys) { - queryArgs.Remove(key); - } - builder.Query = CreateQueryString(queryArgs.ToDictionary()); - return builder.Uri; - } else { - return uri; - } - } - - /// <summary> - /// Sends a multipart HTTP POST request (useful for posting files). - /// </summary> - /// <param name="request">The HTTP request.</param> - /// <param name="requestHandler">The request handler.</param> - /// <param name="parts">The parts to include in the POST entity.</param> - /// <returns>The HTTP response.</returns> - public static IncomingWebResponse PostMultipart(this HttpWebRequest request, IDirectWebRequestHandler requestHandler, IEnumerable<MultipartPostPart> parts) { - Contract.Requires<ArgumentNullException>(request != null); - Contract.Requires<ArgumentNullException>(requestHandler != null); - Contract.Requires<ArgumentNullException>(parts != null); - - PostMultipartNoGetResponse(request, requestHandler, parts); - return requestHandler.GetResponse(request); - } - - /// <summary> - /// Assembles a message comprised of the message on a given exception and all inner exceptions. - /// </summary> - /// <param name="exception">The exception.</param> - /// <returns>The assembled message.</returns> - public static string ToStringDescriptive(this Exception exception) { - // The input being null is probably bad, but since this method is called - // from a catch block, we don't really want to throw a new exception and - // hide the details of this one. - if (exception == null) { - Logger.Messaging.Error("MessagingUtilities.GetAllMessages called with null input."); - } - - StringBuilder message = new StringBuilder(); - while (exception != null) { - message.Append(exception.Message); - exception = exception.InnerException; - if (exception != null) { - message.Append(" "); - } - } - - return message.ToString(); - } - - /// <summary> - /// Flattens the specified sequence of sequences. - /// </summary> - /// <typeparam name="T">The type of element contained in the sequence.</typeparam> - /// <param name="sequence">The sequence of sequences to flatten.</param> - /// <returns>A sequence of the contained items.</returns> - [Obsolete("Use Enumerable.SelectMany instead.")] - public static IEnumerable<T> Flatten<T>(this IEnumerable<IEnumerable<T>> sequence) { - ErrorUtilities.VerifyArgumentNotNull(sequence, "sequence"); - - foreach (IEnumerable<T> subsequence in sequence) { - foreach (T item in subsequence) { - yield return item; - } - } - } - - /// <summary> - /// Cuts off precision beyond a second on a DateTime value. - /// </summary> - /// <param name="value">The value.</param> - /// <returns>A DateTime with a 0 millisecond component.</returns> - public static DateTime CutToSecond(this DateTime value) { - return value - TimeSpan.FromMilliseconds(value.Millisecond); - } - - /// <summary> - /// Adds a name-value pair to the end of a given URL - /// as part of the querystring piece. Prefixes a ? or & before - /// first element as necessary. - /// </summary> - /// <param name="builder">The UriBuilder to add arguments to.</param> - /// <param name="name">The name of the parameter to add.</param> - /// <param name="value">The value of the argument.</param> - /// <remarks> - /// If the parameters to add match names of parameters that already are defined - /// in the query string, the existing ones are <i>not</i> replaced. - /// </remarks> - public static void AppendQueryArgument(this UriBuilder builder, string name, string value) { - AppendQueryArgs(builder, new[] { new KeyValuePair<string, string>(name, value) }); - } - - /// <summary> - /// Adds a set of values to a collection. - /// </summary> - /// <typeparam name="T">The type of value kept in the collection.</typeparam> - /// <param name="collection">The collection to add to.</param> - /// <param name="values">The values to add to the collection.</param> - public static void AddRange<T>(this ICollection<T> collection, IEnumerable<T> values) { - Contract.Requires<ArgumentNullException>(collection != null); - Contract.Requires<ArgumentNullException>(values != null); - - foreach (var value in values) { - collection.Add(value); - } - } - - /// <summary> - /// Tests whether two timespans are within reasonable approximation of each other. - /// </summary> - /// <param name="self">One TimeSpan.</param> - /// <param name="other">The other TimeSpan.</param> - /// <param name="marginOfError">The allowable margin of error.</param> - /// <returns><c>true</c> if the two TimeSpans are within <paramref name="marginOfError"/> of each other.</returns> - public static bool Equals(this TimeSpan self, TimeSpan other, TimeSpan marginOfError) { - return TimeSpan.FromMilliseconds(Math.Abs((self - other).TotalMilliseconds)) < marginOfError; - } - - /// <summary> - /// Clears any existing elements in a collection and fills the collection with a given set of values. - /// </summary> - /// <typeparam name="T">The type of value kept in the collection.</typeparam> - /// <param name="collection">The collection to modify.</param> - /// <param name="values">The new values to fill the collection.</param> - internal static void ResetContents<T>(this ICollection<T> collection, IEnumerable<T> values) { - Contract.Requires<ArgumentNullException>(collection != null); - - collection.Clear(); - if (values != null) { - AddRange(collection, values); - } - } - - /// <summary> - /// Strips any and all URI query parameters that serve as parts of a message. - /// </summary> - /// <param name="uri">The URI that may contain query parameters to remove.</param> - /// <param name="messageDescription">The message description whose parts should be removed from the URL.</param> - /// <returns>A cleaned URL.</returns> - internal static Uri StripMessagePartsFromQueryString(this Uri uri, MessageDescription messageDescription) { - Contract.Requires<ArgumentNullException>(uri != null); - Contract.Requires<ArgumentNullException>(messageDescription != null); - - NameValueCollection queryArgs = HttpUtility.ParseQueryString(uri.Query); - var matchingKeys = queryArgs.Keys.OfType<string>().Where(key => messageDescription.Mapping.ContainsKey(key)).ToList(); - if (matchingKeys.Count > 0) { - var builder = new UriBuilder(uri); - foreach (string key in matchingKeys) { - queryArgs.Remove(key); - } - builder.Query = CreateQueryString(queryArgs.ToDictionary()); - return builder.Uri; - } else { - return uri; - } - } - - /// <summary> - /// Sends a multipart HTTP POST request (useful for posting files) but doesn't call GetResponse on it. - /// </summary> - /// <param name="request">The HTTP request.</param> - /// <param name="requestHandler">The request handler.</param> - /// <param name="parts">The parts to include in the POST entity.</param> - internal static void PostMultipartNoGetResponse(this HttpWebRequest request, IDirectWebRequestHandler requestHandler, IEnumerable<MultipartPostPart> parts) { - Contract.Requires<ArgumentNullException>(request != null); - Contract.Requires<ArgumentNullException>(requestHandler != null); - Contract.Requires<ArgumentNullException>(parts != null); - - Reporting.RecordFeatureUse("MessagingUtilities.PostMultipart"); - parts = parts.CacheGeneratedResults(); - string boundary = Guid.NewGuid().ToString(); - string initialPartLeadingBoundary = string.Format(CultureInfo.InvariantCulture, "--{0}\r\n", boundary); - string partLeadingBoundary = string.Format(CultureInfo.InvariantCulture, "\r\n--{0}\r\n", boundary); - string finalTrailingBoundary = string.Format(CultureInfo.InvariantCulture, "\r\n--{0}--\r\n", boundary); - var contentType = new ContentType("multipart/form-data") { - Boundary = boundary, - CharSet = Channel.PostEntityEncoding.WebName, - }; - - request.Method = "POST"; - request.ContentType = contentType.ToString(); - long contentLength = parts.Sum(p => partLeadingBoundary.Length + p.Length) + finalTrailingBoundary.Length; - if (parts.Any()) { - contentLength -= 2; // the initial part leading boundary has no leading \r\n - } - request.ContentLength = contentLength; - - var requestStream = requestHandler.GetRequestStream(request); - try { - StreamWriter writer = new StreamWriter(requestStream, Channel.PostEntityEncoding); - bool firstPart = true; - foreach (var part in parts) { - writer.Write(firstPart ? initialPartLeadingBoundary : partLeadingBoundary); - firstPart = false; - part.Serialize(writer); - part.Dispose(); - } - - writer.Write(finalTrailingBoundary); - writer.Flush(); - } finally { - // We need to be sure to close the request stream... - // unless it is a MemoryStream, which is a clue that we're in - // a mock stream situation and closing it would preclude reading it later. - if (!(requestStream is MemoryStream)) { - requestStream.Dispose(); - } - } - } - - /// <summary> - /// Assembles the content of the HTTP Authorization or WWW-Authenticate header. - /// </summary> - /// <param name="scheme">The scheme.</param> - /// <param name="fields">The fields to include.</param> - /// <returns>A value prepared for an HTTP header.</returns> - internal static string AssembleAuthorizationHeader(string scheme, IEnumerable<KeyValuePair<string, string>> fields) { - Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(scheme)); - Contract.Requires<ArgumentNullException>(fields != null); - - var authorization = new StringBuilder(); - authorization.Append(scheme); - authorization.Append(" "); - foreach (var pair in fields) { - string key = MessagingUtilities.EscapeUriDataStringRfc3986(pair.Key); - string value = MessagingUtilities.EscapeUriDataStringRfc3986(pair.Value); - authorization.Append(key); - authorization.Append("=\""); - authorization.Append(value); - authorization.Append("\","); - } - authorization.Length--; // remove trailing comma - return authorization.ToString(); - } - - /// <summary> - /// Parses the authorization header. - /// </summary> - /// <param name="scheme">The scheme. Must not be null or empty.</param> - /// <param name="authorizationHeader">The authorization header. May be null or empty.</param> - /// <returns>A sequence of key=value pairs discovered in the header. Never null, but may be empty.</returns> - internal static IEnumerable<KeyValuePair<string, string>> ParseAuthorizationHeader(string scheme, string authorizationHeader) { - Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(scheme)); - Contract.Ensures(Contract.Result<IEnumerable<KeyValuePair<string, string>>>() != null); - - string prefix = scheme + " "; - if (authorizationHeader != null) { - // The authorization header may have multiple sections. Look for the appropriate one. - string[] authorizationSections = new string[] { authorizationHeader }; // what is the right delimiter, if any? - foreach (string authorization in authorizationSections) { - string trimmedAuth = authorization.Trim(); - if (trimmedAuth.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)) { // RFC 2617 says this is case INsensitive - string data = trimmedAuth.Substring(prefix.Length); - return from element in data.Split(CommaArray) - let parts = element.Split(EqualsArray, 2) - let key = Uri.UnescapeDataString(parts[0]) - let value = Uri.UnescapeDataString(parts[1].Trim(QuoteArray)) - select new KeyValuePair<string, string>(key, value); - } - } - } - - return Enumerable.Empty<KeyValuePair<string, string>>(); - } - - /// <summary> - /// Encodes a symmetric key handle and the blob that is encrypted/signed with that key into a single string - /// that can be decoded by <see cref="ExtractKeyHandleAndPayload"/>. - /// </summary> - /// <param name="handle">The cryptographic key handle.</param> - /// <param name="payload">The encrypted/signed blob.</param> - /// <returns>The combined encoded value.</returns> - internal static string CombineKeyHandleAndPayload(string handle, string payload) { - Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(handle)); - Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(payload)); - Contract.Ensures(!String.IsNullOrEmpty(Contract.Result<string>())); - - return handle + "!" + payload; - } - - /// <summary> - /// Extracts the key handle and encrypted blob from a string previously returned from <see cref="CombineKeyHandleAndPayload"/>. - /// </summary> - /// <param name="containingMessage">The containing message.</param> - /// <param name="messagePart">The message part.</param> - /// <param name="keyHandleAndBlob">The value previously returned from <see cref="CombineKeyHandleAndPayload"/>.</param> - /// <param name="handle">The crypto key handle.</param> - /// <param name="dataBlob">The encrypted/signed data.</param> - internal static void ExtractKeyHandleAndPayload(IProtocolMessage containingMessage, string messagePart, string keyHandleAndBlob, out string handle, out string dataBlob) { - Contract.Requires<ArgumentNullException>(containingMessage != null); - Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(messagePart)); - Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(keyHandleAndBlob)); - - int privateHandleIndex = keyHandleAndBlob.IndexOf('!'); - ErrorUtilities.VerifyProtocol(privateHandleIndex > 0, MessagingStrings.UnexpectedMessagePartValue, messagePart, keyHandleAndBlob); - handle = keyHandleAndBlob.Substring(0, privateHandleIndex); - dataBlob = keyHandleAndBlob.Substring(privateHandleIndex + 1); - } - - /// <summary> - /// Gets a buffer of random data (not cryptographically strong). - /// </summary> - /// <param name="length">The length of the sequence to generate.</param> - /// <returns>The generated values, which may contain zeros.</returns> - internal static byte[] GetNonCryptoRandomData(int length) { - byte[] buffer = new byte[length]; - NonCryptoRandomDataGenerator.NextBytes(buffer); - return buffer; - } - - /// <summary> - /// Gets a cryptographically strong random sequence of values. - /// </summary> - /// <param name="length">The length of the sequence to generate.</param> - /// <returns>The generated values, which may contain zeros.</returns> - internal static byte[] GetCryptoRandomData(int length) { - byte[] buffer = new byte[length]; - CryptoRandomDataGenerator.GetBytes(buffer); - return buffer; - } - - /// <summary> - /// Gets a cryptographically strong random sequence of values. - /// </summary> - /// <param name="binaryLength">The length of the byte sequence to generate.</param> - /// <returns>A base64 encoding of the generated random data, - /// whose length in characters will likely be greater than <paramref name="binaryLength"/>.</returns> - internal static string GetCryptoRandomDataAsBase64(int binaryLength) { - byte[] uniq_bytes = GetCryptoRandomData(binaryLength); - string uniq = Convert.ToBase64String(uniq_bytes); - return uniq; - } - - /// <summary> - /// Gets a random string made up of a given set of allowable characters. - /// </summary> - /// <param name="length">The length of the desired random string.</param> - /// <param name="allowableCharacters">The allowable characters.</param> - /// <returns>A random string.</returns> - internal static string GetRandomString(int length, string allowableCharacters) { - Contract.Requires<ArgumentOutOfRangeException>(length >= 0); - Contract.Requires<ArgumentException>(allowableCharacters != null && allowableCharacters.Length >= 2); - - char[] randomString = new char[length]; - for (int i = 0; i < length; i++) { - randomString[i] = allowableCharacters[NonCryptoRandomDataGenerator.Next(allowableCharacters.Length)]; - } - - return new string(randomString); - } - - /// <summary> - /// Computes the hash of a string. - /// </summary> - /// <param name="algorithm">The hash algorithm to use.</param> - /// <param name="value">The value to hash.</param> - /// <param name="encoding">The encoding to use when converting the string to a byte array.</param> - /// <returns>A base64 encoded string.</returns> - internal static string ComputeHash(this HashAlgorithm algorithm, string value, Encoding encoding = null) { - Contract.Requires<ArgumentNullException>(algorithm != null); - Contract.Requires<ArgumentNullException>(value != null); - Contract.Ensures(Contract.Result<string>() != null); - - encoding = encoding ?? Encoding.UTF8; - byte[] bytesToHash = encoding.GetBytes(value); - byte[] hash = algorithm.ComputeHash(bytesToHash); - string base64Hash = Convert.ToBase64String(hash); - return base64Hash; - } - - /// <summary> - /// Computes the hash of a sequence of key=value pairs. - /// </summary> - /// <param name="algorithm">The hash algorithm to use.</param> - /// <param name="data">The data to hash.</param> - /// <param name="encoding">The encoding to use when converting the string to a byte array.</param> - /// <returns>A base64 encoded string.</returns> - internal static string ComputeHash(this HashAlgorithm algorithm, IDictionary<string, string> data, Encoding encoding = null) { - Contract.Requires<ArgumentNullException>(algorithm != null); - Contract.Requires<ArgumentNullException>(data != null); - Contract.Ensures(Contract.Result<string>() != null); - - // Assemble the dictionary to sign, taking care to remove the signature itself - // in order to accurately reproduce the original signature (which of course didn't include - // the signature). - // Also we need to sort the dictionary's keys so that we sign in the same order as we did - // the last time. - var sortedData = new SortedDictionary<string, string>(data, StringComparer.OrdinalIgnoreCase); - return ComputeHash(algorithm, (IEnumerable<KeyValuePair<string, string>>)sortedData, encoding); - } - - /// <summary> - /// Computes the hash of a sequence of key=value pairs. - /// </summary> - /// <param name="algorithm">The hash algorithm to use.</param> - /// <param name="sortedData">The data to hash.</param> - /// <param name="encoding">The encoding to use when converting the string to a byte array.</param> - /// <returns>A base64 encoded string.</returns> - internal static string ComputeHash(this HashAlgorithm algorithm, IEnumerable<KeyValuePair<string, string>> sortedData, Encoding encoding = null) { - Contract.Requires<ArgumentNullException>(algorithm != null); - Contract.Requires<ArgumentNullException>(sortedData != null); - Contract.Ensures(Contract.Result<string>() != null); - - return ComputeHash(algorithm, CreateQueryString(sortedData), encoding); - } - - /// <summary> - /// Encrypts a byte buffer. - /// </summary> - /// <param name="buffer">The buffer to encrypt.</param> - /// <param name="key">The symmetric secret to use to encrypt the buffer. Allowed values are 128, 192, or 256 bytes in length.</param> - /// <returns>The encrypted buffer</returns> - internal static byte[] Encrypt(byte[] buffer, byte[] key) { - using (SymmetricAlgorithm crypto = CreateSymmetricAlgorithm(key)) { - using (var ms = new MemoryStream()) { - var binaryWriter = new BinaryWriter(ms); - binaryWriter.Write((byte)1); // version of encryption algorithm - binaryWriter.Write(crypto.IV); - binaryWriter.Flush(); - - var cryptoStream = new CryptoStream(ms, crypto.CreateEncryptor(), CryptoStreamMode.Write); - cryptoStream.Write(buffer, 0, buffer.Length); - cryptoStream.FlushFinalBlock(); - - return ms.ToArray(); - } - } - } - - /// <summary> - /// Decrypts a byte buffer. - /// </summary> - /// <param name="buffer">The buffer to decrypt.</param> - /// <param name="key">The symmetric secret to use to decrypt the buffer. Allowed values are 128, 192, and 256.</param> - /// <returns>The encrypted buffer</returns> - [SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times", Justification = "This Dispose is safe.")] - internal static byte[] Decrypt(byte[] buffer, byte[] key) { - using (SymmetricAlgorithm crypto = CreateSymmetricAlgorithm(key)) { - using (var ms = new MemoryStream(buffer)) { - var binaryReader = new BinaryReader(ms); - int algorithmVersion = binaryReader.ReadByte(); - ErrorUtilities.VerifyProtocol(algorithmVersion == 1, MessagingStrings.UnsupportedEncryptionAlgorithm); - crypto.IV = binaryReader.ReadBytes(crypto.IV.Length); - - // Allocate space for the decrypted buffer. We don't know how long it will be yet, - // but it will never be larger than the encrypted buffer. - var decryptedBuffer = new byte[buffer.Length]; - int actualDecryptedLength; - - using (var cryptoStream = new CryptoStream(ms, crypto.CreateDecryptor(), CryptoStreamMode.Read)) { - actualDecryptedLength = cryptoStream.Read(decryptedBuffer, 0, decryptedBuffer.Length); - } - - // Create a new buffer with only the decrypted data. - var finalDecryptedBuffer = new byte[actualDecryptedLength]; - Array.Copy(decryptedBuffer, finalDecryptedBuffer, actualDecryptedLength); - return finalDecryptedBuffer; - } - } - } - - /// <summary> - /// Encrypts a string. - /// </summary> - /// <param name="plainText">The text to encrypt.</param> - /// <param name="key">The symmetric secret to use to encrypt the buffer. Allowed values are 128, 192, and 256.</param> - /// <returns>The encrypted buffer</returns> - internal static string Encrypt(string plainText, byte[] key) { - byte[] buffer = Encoding.UTF8.GetBytes(plainText); - byte[] cipher = Encrypt(buffer, key); - return Convert.ToBase64String(cipher); - } - - /// <summary> - /// Decrypts a string previously encrypted with <see cref="Encrypt(string, byte[])"/>. - /// </summary> - /// <param name="cipherText">The text to decrypt.</param> - /// <param name="key">The symmetric secret to use to decrypt the buffer. Allowed values are 128, 192, and 256.</param> - /// <returns>The encrypted buffer</returns> - internal static string Decrypt(string cipherText, byte[] key) { - byte[] cipher = Convert.FromBase64String(cipherText); - byte[] plainText = Decrypt(cipher, key); - return Encoding.UTF8.GetString(plainText); - } - - /// <summary> - /// Performs asymmetric encryption of a given buffer. - /// </summary> - /// <param name="crypto">The asymmetric encryption provider to use for encryption.</param> - /// <param name="buffer">The buffer to encrypt.</param> - /// <returns>The encrypted data.</returns> - internal static byte[] EncryptWithRandomSymmetricKey(this RSACryptoServiceProvider crypto, byte[] buffer) { - Contract.Requires<ArgumentNullException>(crypto != null); - Contract.Requires<ArgumentNullException>(buffer != null); - - using (var symmetricCrypto = new RijndaelManaged()) { - symmetricCrypto.Mode = CipherMode.CBC; - - using (var encryptedStream = new MemoryStream()) { - var encryptedStreamWriter = new BinaryWriter(encryptedStream); - - byte[] prequel = new byte[symmetricCrypto.Key.Length + symmetricCrypto.IV.Length]; - Array.Copy(symmetricCrypto.Key, prequel, symmetricCrypto.Key.Length); - Array.Copy(symmetricCrypto.IV, 0, prequel, symmetricCrypto.Key.Length, symmetricCrypto.IV.Length); - byte[] encryptedPrequel = crypto.Encrypt(prequel, false); - - encryptedStreamWriter.Write(encryptedPrequel.Length); - encryptedStreamWriter.Write(encryptedPrequel); - encryptedStreamWriter.Flush(); - - var cryptoStream = new CryptoStream(encryptedStream, symmetricCrypto.CreateEncryptor(), CryptoStreamMode.Write); - cryptoStream.Write(buffer, 0, buffer.Length); - cryptoStream.FlushFinalBlock(); - - return encryptedStream.ToArray(); - } - } - } - - /// <summary> - /// Performs asymmetric decryption of a given buffer. - /// </summary> - /// <param name="crypto">The asymmetric encryption provider to use for decryption.</param> - /// <param name="buffer">The buffer to decrypt.</param> - /// <returns>The decrypted data.</returns> - [SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times", Justification = "This Dispose is safe.")] - internal static byte[] DecryptWithRandomSymmetricKey(this RSACryptoServiceProvider crypto, byte[] buffer) { - Contract.Requires<ArgumentNullException>(crypto != null); - Contract.Requires<ArgumentNullException>(buffer != null); - - using (var encryptedStream = new MemoryStream(buffer)) { - var encryptedStreamReader = new BinaryReader(encryptedStream); - - byte[] encryptedPrequel = encryptedStreamReader.ReadBytes(encryptedStreamReader.ReadInt32()); - byte[] prequel = crypto.Decrypt(encryptedPrequel, false); - - using (var symmetricCrypto = new RijndaelManaged()) { - symmetricCrypto.Mode = CipherMode.CBC; - - byte[] symmetricKey = new byte[symmetricCrypto.Key.Length]; - byte[] symmetricIV = new byte[symmetricCrypto.IV.Length]; - Array.Copy(prequel, symmetricKey, symmetricKey.Length); - Array.Copy(prequel, symmetricKey.Length, symmetricIV, 0, symmetricIV.Length); - symmetricCrypto.Key = symmetricKey; - symmetricCrypto.IV = symmetricIV; - - // Allocate space for the decrypted buffer. We don't know how long it will be yet, - // but it will never be larger than the encrypted buffer. - var decryptedBuffer = new byte[encryptedStream.Length - encryptedStream.Position]; - int actualDecryptedLength; - - using (var cryptoStream = new CryptoStream(encryptedStream, symmetricCrypto.CreateDecryptor(), CryptoStreamMode.Read)) { - actualDecryptedLength = cryptoStream.Read(decryptedBuffer, 0, decryptedBuffer.Length); - } - - // Create a new buffer with only the decrypted data. - var finalDecryptedBuffer = new byte[actualDecryptedLength]; - Array.Copy(decryptedBuffer, finalDecryptedBuffer, actualDecryptedLength); - return finalDecryptedBuffer; - } - } - } - - /// <summary> - /// Gets a key from a given bucket with the longest remaining life, or creates a new one if necessary. - /// </summary> - /// <param name="cryptoKeyStore">The crypto key store.</param> - /// <param name="bucket">The bucket where the key should be found or stored.</param> - /// <param name="minimumRemainingLife">The minimum remaining life required on the returned key.</param> - /// <param name="keySize">The required size of the key, in bits.</param> - /// <returns> - /// A key-value pair whose key is the secret's handle and whose value is the cryptographic key. - /// </returns> - internal static KeyValuePair<string, CryptoKey> GetCurrentKey(this ICryptoKeyStore cryptoKeyStore, string bucket, TimeSpan minimumRemainingLife, int keySize = 256) { - Contract.Requires<ArgumentNullException>(cryptoKeyStore != null); - Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(bucket)); - Contract.Requires<ArgumentException>(keySize % 8 == 0); - - var cryptoKeyPair = cryptoKeyStore.GetKeys(bucket).FirstOrDefault(pair => pair.Value.Key.Length == keySize / 8); - if (cryptoKeyPair.Value == null || cryptoKeyPair.Value.ExpiresUtc < DateTime.UtcNow + minimumRemainingLife) { - // No key exists with enough remaining life for the required purpose. Create a new key. - ErrorUtilities.VerifyHost(minimumRemainingLife <= SymmetricSecretKeyLifespan, "Unable to create a new symmetric key with the required lifespan of {0} because it is beyond the limit of {1}.", minimumRemainingLife, SymmetricSecretKeyLifespan); - byte[] secret = GetCryptoRandomData(keySize / 8); - DateTime expires = DateTime.UtcNow + SymmetricSecretKeyLifespan; - var cryptoKey = new CryptoKey(secret, expires); - - // Store this key so we can find and use it later. - int failedAttempts = 0; - tryAgain: - try { - string handle = GetRandomString(SymmetricSecretHandleLength, Base64WebSafeCharacters); - cryptoKeyPair = new KeyValuePair<string, CryptoKey>(handle, cryptoKey); - cryptoKeyStore.StoreKey(bucket, handle, cryptoKey); - } catch (CryptoKeyCollisionException) { - ErrorUtilities.VerifyInternal(++failedAttempts < 3, "Unable to derive a unique handle to a private symmetric key."); - goto tryAgain; - } - } - - return cryptoKeyPair; - } - - /// <summary> - /// Compresses a given buffer. - /// </summary> - /// <param name="buffer">The buffer to compress.</param> - /// <returns>The compressed data.</returns> - [SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times", Justification = "This Dispose is safe.")] - internal static byte[] Compress(byte[] buffer) { - Contract.Requires<ArgumentNullException>(buffer != null); - Contract.Ensures(Contract.Result<byte[]>() != null); - - using (var ms = new MemoryStream()) { - using (var compressingStream = new DeflateStream(ms, CompressionMode.Compress, true)) { - compressingStream.Write(buffer, 0, buffer.Length); - } - - return ms.ToArray(); - } - } - - /// <summary> - /// Decompresses a given buffer. - /// </summary> - /// <param name="buffer">The buffer to decompress.</param> - /// <returns>The decompressed data.</returns> - internal static byte[] Decompress(byte[] buffer) { - Contract.Requires<ArgumentNullException>(buffer != null); - Contract.Ensures(Contract.Result<byte[]>() != null); - - using (var compressedDataStream = new MemoryStream(buffer)) { - using (var decompressedDataStream = new MemoryStream()) { - using (var decompressingStream = new DeflateStream(compressedDataStream, CompressionMode.Decompress, true)) { - decompressingStream.CopyTo(decompressedDataStream); - } - - return decompressedDataStream.ToArray(); - } - } - } - - /// <summary> - /// Converts to data buffer to a base64-encoded string, using web safe characters and with the padding removed. - /// </summary> - /// <param name="data">The data buffer.</param> - /// <returns>A web-safe base64-encoded string without padding.</returns> - internal static string ConvertToBase64WebSafeString(byte[] data) { - var builder = new StringBuilder(Convert.ToBase64String(data)); - - // Swap out the URL-unsafe characters, and trim the padding characters. - builder.Replace('+', '-').Replace('/', '_'); - while (builder[builder.Length - 1] == '=') { // should happen at most twice. - builder.Length -= 1; - } - - return builder.ToString(); - } - - /// <summary> - /// Decodes a (web-safe) base64-string back to its binary buffer form. - /// </summary> - /// <param name="base64WebSafe">The base64-encoded string. May be web-safe encoded.</param> - /// <returns>A data buffer.</returns> - internal static byte[] FromBase64WebSafeString(string base64WebSafe) { - Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(base64WebSafe)); - Contract.Ensures(Contract.Result<byte[]>() != null); - - // Restore the padding characters and original URL-unsafe characters. - int missingPaddingCharacters; - switch (base64WebSafe.Length % 4) { - case 3: - missingPaddingCharacters = 1; - break; - case 2: - missingPaddingCharacters = 2; - break; - case 0: - missingPaddingCharacters = 0; - break; - default: - throw ErrorUtilities.ThrowInternal("No more than two padding characters should be present for base64."); - } - var builder = new StringBuilder(base64WebSafe, base64WebSafe.Length + missingPaddingCharacters); - builder.Replace('-', '+').Replace('_', '/'); - builder.Append('=', missingPaddingCharacters); - - return Convert.FromBase64String(builder.ToString()); - } - - /// <summary> - /// Compares to string values for ordinal equality in such a way that its execution time does not depend on how much of the value matches. - /// </summary> - /// <param name="value1">The first value.</param> - /// <param name="value2">The second value.</param> - /// <returns>A value indicating whether the two strings share ordinal equality.</returns> - /// <remarks> - /// In signature equality checks, a difference in execution time based on how many initial characters match MAY - /// be used as an attack to figure out the expected signature. It is therefore important to make a signature - /// equality check's execution time independent of how many characters match the expected value. - /// See http://codahale.com/a-lesson-in-timing-attacks/ for more information. - /// </remarks> - internal static bool EqualsConstantTime(string value1, string value2) { - // If exactly one value is null, they don't match. - if (value1 == null ^ value2 == null) { - return false; - } - - // If both values are null (since if one is at this point then they both are), it's a match. - if (value1 == null) { - return true; - } - - if (value1.Length != value2.Length) { - return false; - } - - // This looks like a pretty crazy way to compare values, but it provides a constant time equality check, - // and is more resistant to compiler optimizations than simply setting a boolean flag and returning the boolean after the loop. - int result = 0; - for (int i = 0; i < value1.Length; i++) { - result |= value1[i] ^ value2[i]; - } - - return result == 0; - } - - /// <summary> - /// Adds a set of HTTP headers to an <see cref="HttpResponse"/> instance, - /// taking care to set some headers to the appropriate properties of - /// <see cref="HttpResponse" /> - /// </summary> - /// <param name="headers">The headers to add.</param> - /// <param name="response">The <see cref="HttpResponse"/> instance to set the appropriate values to.</param> - internal static void ApplyHeadersToResponse(WebHeaderCollection headers, HttpResponse response) { - Contract.Requires<ArgumentNullException>(headers != null); - Contract.Requires<ArgumentNullException>(response != null); - - foreach (string headerName in headers) { - switch (headerName) { - case "Content-Type": - response.ContentType = headers[HttpResponseHeader.ContentType]; - break; - - // Add more special cases here as necessary. - default: - response.AddHeader(headerName, headers[headerName]); - break; - } - } - } - - /// <summary> - /// Adds a set of HTTP headers to an <see cref="HttpResponse"/> instance, - /// taking care to set some headers to the appropriate properties of - /// <see cref="HttpResponse" /> - /// </summary> - /// <param name="headers">The headers to add.</param> - /// <param name="response">The <see cref="HttpListenerResponse"/> instance to set the appropriate values to.</param> - internal static void ApplyHeadersToResponse(WebHeaderCollection headers, HttpListenerResponse response) { - Contract.Requires<ArgumentNullException>(headers != null); - Contract.Requires<ArgumentNullException>(response != null); - - foreach (string headerName in headers) { - switch (headerName) { - case "Content-Type": - response.ContentType = headers[HttpResponseHeader.ContentType]; - break; - - // Add more special cases here as necessary. - default: - response.AddHeader(headerName, headers[headerName]); - break; - } - } - } - -#if !CLR4 - /// <summary> - /// Copies the contents of one stream to another. - /// </summary> - /// <param name="copyFrom">The stream to copy from, at the position where copying should begin.</param> - /// <param name="copyTo">The stream to copy to, at the position where bytes should be written.</param> - /// <returns>The total number of bytes copied.</returns> - /// <remarks> - /// Copying begins at the streams' current positions. - /// The positions are NOT reset after copying is complete. - /// </remarks> - internal static int CopyTo(this Stream copyFrom, Stream copyTo) { - Contract.Requires<ArgumentNullException>(copyFrom != null); - Contract.Requires<ArgumentNullException>(copyTo != null); - Contract.Requires<ArgumentException>(copyFrom.CanRead, MessagingStrings.StreamUnreadable); - Contract.Requires<ArgumentException>(copyTo.CanWrite, MessagingStrings.StreamUnwritable); - return CopyUpTo(copyFrom, copyTo, int.MaxValue); - } -#endif - - /// <summary> - /// Copies the contents of one stream to another. - /// </summary> - /// <param name="copyFrom">The stream to copy from, at the position where copying should begin.</param> - /// <param name="copyTo">The stream to copy to, at the position where bytes should be written.</param> - /// <param name="maximumBytesToCopy">The maximum bytes to copy.</param> - /// <returns>The total number of bytes copied.</returns> - /// <remarks> - /// Copying begins at the streams' current positions. - /// The positions are NOT reset after copying is complete. - /// </remarks> - internal static int CopyUpTo(this Stream copyFrom, Stream copyTo, int maximumBytesToCopy) { - Contract.Requires<ArgumentNullException>(copyFrom != null); - Contract.Requires<ArgumentNullException>(copyTo != null); - Contract.Requires<ArgumentException>(copyFrom.CanRead, MessagingStrings.StreamUnreadable); - Contract.Requires<ArgumentException>(copyTo.CanWrite, MessagingStrings.StreamUnwritable); - - byte[] buffer = new byte[1024]; - int readBytes; - int totalCopiedBytes = 0; - while ((readBytes = copyFrom.Read(buffer, 0, Math.Min(1024, maximumBytesToCopy))) > 0) { - int writeBytes = Math.Min(maximumBytesToCopy, readBytes); - copyTo.Write(buffer, 0, writeBytes); - totalCopiedBytes += writeBytes; - maximumBytesToCopy -= writeBytes; - } - - return totalCopiedBytes; - } - - /// <summary> - /// Creates a snapshot of some stream so it is seekable, and the original can be closed. - /// </summary> - /// <param name="copyFrom">The stream to copy bytes from.</param> - /// <returns>A seekable stream with the same contents as the original.</returns> - internal static Stream CreateSnapshot(this Stream copyFrom) { - Contract.Requires<ArgumentNullException>(copyFrom != null); - Contract.Requires<ArgumentException>(copyFrom.CanRead); - - MemoryStream copyTo = new MemoryStream(copyFrom.CanSeek ? (int)copyFrom.Length : 4 * 1024); - try { - copyFrom.CopyTo(copyTo); - copyTo.Position = 0; - return copyTo; - } catch { - copyTo.Dispose(); - throw; - } - } - - /// <summary> - /// Clones an <see cref="HttpWebRequest"/> in order to send it again. - /// </summary> - /// <param name="request">The request to clone.</param> - /// <returns>The newly created instance.</returns> - internal static HttpWebRequest Clone(this HttpWebRequest request) { - Contract.Requires<ArgumentNullException>(request != null); - Contract.Requires<ArgumentException>(request.RequestUri != null); - return Clone(request, request.RequestUri); - } - - /// <summary> - /// Clones an <see cref="HttpWebRequest"/> in order to send it again. - /// </summary> - /// <param name="request">The request to clone.</param> - /// <param name="newRequestUri">The new recipient of the request.</param> - /// <returns>The newly created instance.</returns> - internal static HttpWebRequest Clone(this HttpWebRequest request, Uri newRequestUri) { - Contract.Requires<ArgumentNullException>(request != null); - Contract.Requires<ArgumentNullException>(newRequestUri != null); - - var newRequest = (HttpWebRequest)WebRequest.Create(newRequestUri); - - // First copy headers. Only set those that are explicitly set on the original request, - // because some properties (like IfModifiedSince) activate special behavior when set, - // even when set to their "original" values. - foreach (string headerName in request.Headers) { - switch (headerName) { - case "Accept": newRequest.Accept = request.Accept; break; - case "Connection": break; // Keep-Alive controls this - case "Content-Length": newRequest.ContentLength = request.ContentLength; break; - case "Content-Type": newRequest.ContentType = request.ContentType; break; - case "Expect": newRequest.Expect = request.Expect; break; - case "Host": break; // implicitly copied as part of the RequestUri - case "If-Modified-Since": newRequest.IfModifiedSince = request.IfModifiedSince; break; - case "Keep-Alive": newRequest.KeepAlive = request.KeepAlive; break; - case "Proxy-Connection": break; // no property equivalent? - case "Referer": newRequest.Referer = request.Referer; break; - case "Transfer-Encoding": newRequest.TransferEncoding = request.TransferEncoding; break; - case "User-Agent": newRequest.UserAgent = request.UserAgent; break; - default: newRequest.Headers[headerName] = request.Headers[headerName]; break; - } - } - - newRequest.AllowAutoRedirect = request.AllowAutoRedirect; - newRequest.AllowWriteStreamBuffering = request.AllowWriteStreamBuffering; - newRequest.AuthenticationLevel = request.AuthenticationLevel; - newRequest.AutomaticDecompression = request.AutomaticDecompression; - newRequest.CachePolicy = request.CachePolicy; - newRequest.ClientCertificates = request.ClientCertificates; - newRequest.ConnectionGroupName = request.ConnectionGroupName; - newRequest.ContinueDelegate = request.ContinueDelegate; - newRequest.CookieContainer = request.CookieContainer; - newRequest.Credentials = request.Credentials; - newRequest.ImpersonationLevel = request.ImpersonationLevel; - newRequest.MaximumAutomaticRedirections = request.MaximumAutomaticRedirections; - newRequest.MaximumResponseHeadersLength = request.MaximumResponseHeadersLength; - newRequest.MediaType = request.MediaType; - newRequest.Method = request.Method; - newRequest.Pipelined = request.Pipelined; - newRequest.PreAuthenticate = request.PreAuthenticate; - newRequest.ProtocolVersion = request.ProtocolVersion; - newRequest.ReadWriteTimeout = request.ReadWriteTimeout; - newRequest.SendChunked = request.SendChunked; - newRequest.Timeout = request.Timeout; - newRequest.UseDefaultCredentials = request.UseDefaultCredentials; - - try { - newRequest.Proxy = request.Proxy; - newRequest.UnsafeAuthenticatedConnectionSharing = request.UnsafeAuthenticatedConnectionSharing; - } catch (SecurityException) { - Logger.Messaging.Warn("Unable to clone some HttpWebRequest properties due to partial trust."); - } - - return newRequest; - } - - /// <summary> - /// Tests whether two arrays are equal in contents and ordering. - /// </summary> - /// <typeparam name="T">The type of elements in the arrays.</typeparam> - /// <param name="first">The first array in the comparison. May not be null.</param> - /// <param name="second">The second array in the comparison. May not be null.</param> - /// <returns>True if the arrays equal; false otherwise.</returns> - internal static bool AreEquivalent<T>(T[] first, T[] second) { - Contract.Requires<ArgumentNullException>(first != null); - Contract.Requires<ArgumentNullException>(second != null); - if (first.Length != second.Length) { - return false; - } - for (int i = 0; i < first.Length; i++) { - if (!first[i].Equals(second[i])) { - return false; - } - } - return true; - } - - /// <summary> - /// Tests whether two arrays are equal in contents and ordering, - /// guaranteeing roughly equivalent execution time regardless of where a signature mismatch may exist. - /// </summary> - /// <param name="first">The first array in the comparison. May not be null.</param> - /// <param name="second">The second array in the comparison. May not be null.</param> - /// <returns>True if the arrays equal; false otherwise.</returns> - /// <remarks> - /// Guaranteeing equal execution time is useful in mitigating against timing attacks on a signature - /// or other secret. - /// </remarks> - internal static bool AreEquivalentConstantTime(byte[] first, byte[] second) { - Contract.Requires<ArgumentNullException>(first != null); - Contract.Requires<ArgumentNullException>(second != null); - if (first.Length != second.Length) { - return false; - } - - int result = 0; - for (int i = 0; i < first.Length; i++) { - result |= first[i] ^ second[i]; - } - return result == 0; - } - - /// <summary> - /// Tests two sequences for same contents and ordering. - /// </summary> - /// <typeparam name="T">The type of elements in the arrays.</typeparam> - /// <param name="sequence1">The first sequence in the comparison. May not be null.</param> - /// <param name="sequence2">The second sequence in the comparison. May not be null.</param> - /// <returns>True if the arrays equal; false otherwise.</returns> - internal static bool AreEquivalent<T>(IEnumerable<T> sequence1, IEnumerable<T> sequence2) { - if (sequence1 == null && sequence2 == null) { - return true; - } - if ((sequence1 == null) ^ (sequence2 == null)) { - return false; - } - - IEnumerator<T> iterator1 = sequence1.GetEnumerator(); - IEnumerator<T> iterator2 = sequence2.GetEnumerator(); - bool movenext1, movenext2; - while (true) { - movenext1 = iterator1.MoveNext(); - movenext2 = iterator2.MoveNext(); - if (!movenext1 || !movenext2) { // if we've reached the end of at least one sequence - break; - } - object obj1 = iterator1.Current; - object obj2 = iterator2.Current; - if (obj1 == null && obj2 == null) { - continue; // both null is ok - } - if (obj1 == null ^ obj2 == null) { - return false; // exactly one null is different - } - if (!obj1.Equals(obj2)) { - return false; // if they're not equal to each other - } - } - - return movenext1 == movenext2; // did they both reach the end together? - } - - /// <summary> - /// Tests two unordered collections for same contents. - /// </summary> - /// <typeparam name="T">The type of elements in the collections.</typeparam> - /// <param name="first">The first collection in the comparison. May not be null.</param> - /// <param name="second">The second collection in the comparison. May not be null.</param> - /// <returns>True if the collections have the same contents; false otherwise.</returns> - internal static bool AreEquivalentUnordered<T>(ICollection<T> first, ICollection<T> second) { - if (first == null && second == null) { - return true; - } - if ((first == null) ^ (second == null)) { - return false; - } - - if (first.Count != second.Count) { - return false; - } - - foreach (T value in first) { - if (!second.Contains(value)) { - return false; - } - } - - return true; - } - - /// <summary> - /// Tests whether two dictionaries are equal in length and contents. - /// </summary> - /// <typeparam name="TKey">The type of keys in the dictionaries.</typeparam> - /// <typeparam name="TValue">The type of values in the dictionaries.</typeparam> - /// <param name="first">The first dictionary in the comparison. May not be null.</param> - /// <param name="second">The second dictionary in the comparison. May not be null.</param> - /// <returns>True if the arrays equal; false otherwise.</returns> - internal static bool AreEquivalent<TKey, TValue>(IDictionary<TKey, TValue> first, IDictionary<TKey, TValue> second) { - Contract.Requires<ArgumentNullException>(first != null); - Contract.Requires<ArgumentNullException>(second != null); - return AreEquivalent(first.ToArray(), second.ToArray()); - } - - /// <summary> - /// Concatenates a list of name-value pairs as key=value&key=value, - /// taking care to properly encode each key and value for URL - /// transmission according to RFC 3986. No ? is prefixed to the string. - /// </summary> - /// <param name="args">The dictionary of key/values to read from.</param> - /// <returns>The formulated querystring style string.</returns> - internal static string CreateQueryString(IEnumerable<KeyValuePair<string, string>> args) { - Contract.Requires<ArgumentNullException>(args != null); - Contract.Ensures(Contract.Result<string>() != null); - - if (args.Count() == 0) { - return string.Empty; - } - StringBuilder sb = new StringBuilder(args.Count() * 10); - - foreach (var p in args) { - ErrorUtilities.VerifyArgument(!string.IsNullOrEmpty(p.Key), MessagingStrings.UnexpectedNullOrEmptyKey); - ErrorUtilities.VerifyArgument(p.Value != null, MessagingStrings.UnexpectedNullValue, p.Key); - sb.Append(EscapeUriDataStringRfc3986(p.Key)); - sb.Append('='); - sb.Append(EscapeUriDataStringRfc3986(p.Value)); - sb.Append('&'); - } - sb.Length--; // remove trailing & - - return sb.ToString(); - } - - /// <summary> - /// Adds a set of name-value pairs to the end of a given URL - /// as part of the querystring piece. Prefixes a ? or & before - /// first element as necessary. - /// </summary> - /// <param name="builder">The UriBuilder to add arguments to.</param> - /// <param name="args"> - /// The arguments to add to the query. - /// If null, <paramref name="builder"/> is not changed. - /// </param> - /// <remarks> - /// If the parameters to add match names of parameters that already are defined - /// in the query string, the existing ones are <i>not</i> replaced. - /// </remarks> - internal static void AppendQueryArgs(this UriBuilder builder, IEnumerable<KeyValuePair<string, string>> args) { - Contract.Requires<ArgumentNullException>(builder != null); - - if (args != null && args.Count() > 0) { - StringBuilder sb = new StringBuilder(50 + (args.Count() * 10)); - if (!string.IsNullOrEmpty(builder.Query)) { - sb.Append(builder.Query.Substring(1)); - sb.Append('&'); - } - sb.Append(CreateQueryString(args)); - - builder.Query = sb.ToString(); - } - } - - /// <summary> - /// Adds a set of name-value pairs to the end of a given URL - /// as part of the fragment piece. Prefixes a # or & before - /// first element as necessary. - /// </summary> - /// <param name="builder">The UriBuilder to add arguments to.</param> - /// <param name="args"> - /// The arguments to add to the query. - /// If null, <paramref name="builder"/> is not changed. - /// </param> - /// <remarks> - /// If the parameters to add match names of parameters that already are defined - /// in the fragment, the existing ones are <i>not</i> replaced. - /// </remarks> - internal static void AppendFragmentArgs(this UriBuilder builder, IEnumerable<KeyValuePair<string, string>> args) { - Contract.Requires<ArgumentNullException>(builder != null); - - if (args != null && args.Count() > 0) { - StringBuilder sb = new StringBuilder(50 + (args.Count() * 10)); - if (!string.IsNullOrEmpty(builder.Fragment)) { - sb.Append(builder.Fragment); - sb.Append('&'); - } - sb.Append(CreateQueryString(args)); - - builder.Fragment = sb.ToString(); - } - } - - /// <summary> - /// Adds parameters to a query string, replacing parameters that - /// match ones that already exist in the query string. - /// </summary> - /// <param name="builder">The UriBuilder to add arguments to.</param> - /// <param name="args"> - /// The arguments to add to the query. - /// If null, <paramref name="builder"/> is not changed. - /// </param> - internal static void AppendAndReplaceQueryArgs(this UriBuilder builder, IEnumerable<KeyValuePair<string, string>> args) { - Contract.Requires<ArgumentNullException>(builder != null); - - if (args != null && args.Count() > 0) { - NameValueCollection aggregatedArgs = HttpUtility.ParseQueryString(builder.Query); - foreach (var pair in args) { - aggregatedArgs[pair.Key] = pair.Value; - } - - builder.Query = CreateQueryString(aggregatedArgs.ToDictionary()); - } - } - - /// <summary> - /// Extracts the recipient from an HttpRequestInfo. - /// </summary> - /// <param name="request">The request to get recipient information from.</param> - /// <returns>The recipient.</returns> - /// <exception cref="ArgumentException">Thrown if the HTTP request is something we can't handle.</exception> - internal static MessageReceivingEndpoint GetRecipient(this HttpRequestInfo request) { - return new MessageReceivingEndpoint(request.UrlBeforeRewriting, GetHttpDeliveryMethod(request.HttpMethod)); - } - - /// <summary> - /// Gets the <see cref="HttpDeliveryMethods"/> enum value for a given HTTP verb. - /// </summary> - /// <param name="httpVerb">The HTTP verb.</param> - /// <returns>A <see cref="HttpDeliveryMethods"/> enum value that is within the <see cref="HttpDeliveryMethods.HttpVerbMask"/>.</returns> - /// <exception cref="ArgumentException">Thrown if the HTTP request is something we can't handle.</exception> - internal static HttpDeliveryMethods GetHttpDeliveryMethod(string httpVerb) { - if (httpVerb == "GET") { - return HttpDeliveryMethods.GetRequest; - } else if (httpVerb == "POST") { - return HttpDeliveryMethods.PostRequest; - } else if (httpVerb == "PUT") { - return HttpDeliveryMethods.PutRequest; - } else if (httpVerb == "DELETE") { - return HttpDeliveryMethods.DeleteRequest; - } else if (httpVerb == "HEAD") { - return HttpDeliveryMethods.HeadRequest; - } else { - throw ErrorUtilities.ThrowArgumentNamed("httpVerb", MessagingStrings.UnsupportedHttpVerb, httpVerb); - } - } - - /// <summary> - /// Gets the HTTP verb to use for a given <see cref="HttpDeliveryMethods"/> enum value. - /// </summary> - /// <param name="httpMethod">The HTTP method.</param> - /// <returns>An HTTP verb, such as GET, POST, PUT, or DELETE.</returns> - internal static string GetHttpVerb(HttpDeliveryMethods httpMethod) { - if ((httpMethod & HttpDeliveryMethods.HttpVerbMask) == HttpDeliveryMethods.GetRequest) { - return "GET"; - } else if ((httpMethod & HttpDeliveryMethods.HttpVerbMask) == HttpDeliveryMethods.PostRequest) { - return "POST"; - } else if ((httpMethod & HttpDeliveryMethods.HttpVerbMask) == HttpDeliveryMethods.PutRequest) { - return "PUT"; - } else if ((httpMethod & HttpDeliveryMethods.HttpVerbMask) == HttpDeliveryMethods.DeleteRequest) { - return "DELETE"; - } else if ((httpMethod & HttpDeliveryMethods.HttpVerbMask) == HttpDeliveryMethods.HeadRequest) { - return "HEAD"; - } else if ((httpMethod & HttpDeliveryMethods.AuthorizationHeaderRequest) != 0) { - return "GET"; // if AuthorizationHeaderRequest is specified without an explicit HTTP verb, assume GET. - } else { - throw ErrorUtilities.ThrowArgumentNamed("httpMethod", MessagingStrings.UnsupportedHttpVerb, httpMethod); - } - } - - /// <summary> - /// Copies some extra parameters into a message. - /// </summary> - /// <param name="messageDictionary">The message to copy the extra data into.</param> - /// <param name="extraParameters">The extra data to copy into the message. May be null to do nothing.</param> - internal static void AddExtraParameters(this MessageDictionary messageDictionary, IDictionary<string, string> extraParameters) { - Contract.Requires<ArgumentNullException>(messageDictionary != null); - - if (extraParameters != null) { - foreach (var pair in extraParameters) { - try { - messageDictionary.Add(pair); - } catch (ArgumentException ex) { - throw ErrorUtilities.Wrap(ex, MessagingStrings.ExtraParameterAddFailure, pair.Key, pair.Value); - } - } - } - } - - /// <summary> - /// Collects a sequence of key=value pairs into a dictionary. - /// </summary> - /// <typeparam name="TKey">The type of the key.</typeparam> - /// <typeparam name="TValue">The type of the value.</typeparam> - /// <param name="sequence">The sequence.</param> - /// <returns>A dictionary.</returns> - internal static Dictionary<TKey, TValue> ToDictionary<TKey, TValue>(this IEnumerable<KeyValuePair<TKey, TValue>> sequence) { - Contract.Requires<ArgumentNullException>(sequence != null); - return sequence.ToDictionary(pair => pair.Key, pair => pair.Value); - } - - /// <summary> - /// Converts a <see cref="NameValueCollection"/> to an IDictionary<string, string>. - /// </summary> - /// <param name="nvc">The NameValueCollection to convert. May be null.</param> - /// <returns>The generated dictionary, or null if <paramref name="nvc"/> is null.</returns> - /// <remarks> - /// If a <c>null</c> key is encountered, its value is ignored since - /// <c>Dictionary<string, string></c> does not allow null keys. - /// </remarks> - internal static Dictionary<string, string> ToDictionary(this NameValueCollection nvc) { - Contract.Ensures((nvc != null && Contract.Result<Dictionary<string, string>>() != null) || (nvc == null && Contract.Result<Dictionary<string, string>>() == null)); - return ToDictionary(nvc, false); - } - - /// <summary> - /// Converts a <see cref="NameValueCollection"/> to an IDictionary<string, string>. - /// </summary> - /// <param name="nvc">The NameValueCollection to convert. May be null.</param> - /// <param name="throwOnNullKey"> - /// A value indicating whether a null key in the <see cref="NameValueCollection"/> should be silently skipped since it is not a valid key in a Dictionary. - /// Use <c>true</c> to throw an exception if a null key is encountered. - /// Use <c>false</c> to silently continue converting the valid keys. - /// </param> - /// <returns>The generated dictionary, or null if <paramref name="nvc"/> is null.</returns> - /// <exception cref="ArgumentException">Thrown if <paramref name="throwOnNullKey"/> is <c>true</c> and a null key is encountered.</exception> - internal static Dictionary<string, string> ToDictionary(this NameValueCollection nvc, bool throwOnNullKey) { - Contract.Ensures((nvc != null && Contract.Result<Dictionary<string, string>>() != null) || (nvc == null && Contract.Result<Dictionary<string, string>>() == null)); - if (nvc == null) { - return null; - } - - var dictionary = new Dictionary<string, string>(); - foreach (string key in nvc) { - // NameValueCollection supports a null key, but Dictionary<K,V> does not. - if (key == null) { - if (throwOnNullKey) { - throw new ArgumentException(MessagingStrings.UnexpectedNullKey); - } else { - // Only emit a warning if there was a non-empty value. - if (!string.IsNullOrEmpty(nvc[key])) { - Logger.OpenId.WarnFormat("Null key with value {0} encountered while translating NameValueCollection to Dictionary.", nvc[key]); - } - } - } else { - dictionary.Add(key, nvc[key]); - } - } - - return dictionary; - } - - /// <summary> - /// Sorts the elements of a sequence in ascending order by using a specified comparer. - /// </summary> - /// <typeparam name="TSource">The type of the elements of source.</typeparam> - /// <typeparam name="TKey">The type of the key returned by keySelector.</typeparam> - /// <param name="source">A sequence of values to order.</param> - /// <param name="keySelector">A function to extract a key from an element.</param> - /// <param name="comparer">A comparison function to compare keys.</param> - /// <returns>An System.Linq.IOrderedEnumerable<TElement> whose elements are sorted according to a key.</returns> - internal static IOrderedEnumerable<TSource> OrderBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, Comparison<TKey> comparer) { - Contract.Requires<ArgumentNullException>(source != null); - Contract.Requires<ArgumentNullException>(comparer != null); - Contract.Requires<ArgumentNullException>(keySelector != null); - Contract.Ensures(Contract.Result<IOrderedEnumerable<TSource>>() != null); - return System.Linq.Enumerable.OrderBy<TSource, TKey>(source, keySelector, new ComparisonHelper<TKey>(comparer)); - } - - /// <summary> - /// Determines whether the specified message is a request (indirect message or direct request). - /// </summary> - /// <param name="message">The message in question.</param> - /// <returns> - /// <c>true</c> if the specified message is a request; otherwise, <c>false</c>. - /// </returns> - /// <remarks> - /// Although an <see cref="IProtocolMessage"/> may implement the <see cref="IDirectedProtocolMessage"/> - /// interface, it may only be doing that for its derived classes. These objects are only requests - /// if their <see cref="IDirectedProtocolMessage.Recipient"/> property is non-null. - /// </remarks> - internal static bool IsRequest(this IDirectedProtocolMessage message) { - Contract.Requires<ArgumentNullException>(message != null); - return message.Recipient != null; - } - - /// <summary> - /// Determines whether the specified message is a direct response. - /// </summary> - /// <param name="message">The message in question.</param> - /// <returns> - /// <c>true</c> if the specified message is a direct response; otherwise, <c>false</c>. - /// </returns> - /// <remarks> - /// Although an <see cref="IProtocolMessage"/> may implement the - /// <see cref="IDirectResponseProtocolMessage"/> interface, it may only be doing - /// that for its derived classes. These objects are only requests if their - /// <see cref="IDirectResponseProtocolMessage.OriginatingRequest"/> property is non-null. - /// </remarks> - internal static bool IsDirectResponse(this IDirectResponseProtocolMessage message) { - Contract.Requires<ArgumentNullException>(message != null); - return message.OriginatingRequest != null; - } - - /// <summary> - /// Writes a buffer, prefixed with its own length. - /// </summary> - /// <param name="writer">The binary writer.</param> - /// <param name="buffer">The buffer.</param> - internal static void WriteBuffer(this BinaryWriter writer, byte[] buffer) { - Contract.Requires<ArgumentNullException>(writer != null); - Contract.Requires<ArgumentNullException>(buffer != null); - writer.Write(buffer.Length); - writer.Write(buffer, 0, buffer.Length); - } - - /// <summary> - /// Reads a buffer that is prefixed with its own length. - /// </summary> - /// <param name="reader">The binary reader positioned at the buffer length.</param> - /// <returns>The read buffer.</returns> - internal static byte[] ReadBuffer(this BinaryReader reader) { - Contract.Requires<ArgumentNullException>(reader != null); - int length = reader.ReadInt32(); - byte[] buffer = new byte[length]; - ErrorUtilities.VerifyProtocol(reader.Read(buffer, 0, length) == length, "Unexpected buffer length."); - return buffer; - } - - /// <summary> - /// Constructs a Javascript expression that will create an object - /// on the user agent when assigned to a variable. - /// </summary> - /// <param name="namesAndValues">The untrusted names and untrusted values to inject into the JSON object.</param> - /// <param name="valuesPreEncoded">if set to <c>true</c> the values will NOT be escaped as if it were a pure string.</param> - /// <returns>The Javascript JSON object as a string.</returns> - internal static string CreateJsonObject(IEnumerable<KeyValuePair<string, string>> namesAndValues, bool valuesPreEncoded) { - StringBuilder builder = new StringBuilder(); - builder.Append("{ "); - - foreach (var pair in namesAndValues) { - builder.Append(MessagingUtilities.GetSafeJavascriptValue(pair.Key)); - builder.Append(": "); - builder.Append(valuesPreEncoded ? pair.Value : MessagingUtilities.GetSafeJavascriptValue(pair.Value)); - builder.Append(","); - } - - if (builder[builder.Length - 1] == ',') { - builder.Length -= 1; - } - builder.Append("}"); - return builder.ToString(); - } - - /// <summary> - /// Prepares what SHOULD be simply a string value for safe injection into Javascript - /// by using appropriate character escaping. - /// </summary> - /// <param name="value">The untrusted string value to be escaped to protected against XSS attacks. May be null.</param> - /// <returns>The escaped string, surrounded by single-quotes.</returns> - internal static string GetSafeJavascriptValue(string value) { - if (value == null) { - return "null"; - } - - // We use a StringBuilder because we have potentially many replacements to do, - // and we don't want to create a new string for every intermediate replacement step. - StringBuilder builder = new StringBuilder(value); - foreach (var pair in javascriptStaticStringEscaping) { - builder.Replace(pair.Key, pair.Value); - } - builder.Insert(0, '\''); - builder.Append('\''); - return builder.ToString(); - } - - /// <summary> - /// Escapes a string according to the URI data string rules given in RFC 3986. - /// </summary> - /// <param name="value">The value to escape.</param> - /// <returns>The escaped value.</returns> - /// <remarks> - /// The <see cref="Uri.EscapeDataString"/> method is <i>supposed</i> to take on - /// RFC 3986 behavior if certain elements are present in a .config file. Even if this - /// actually worked (which in my experiments it <i>doesn't</i>), we can't rely on every - /// host actually having this configuration element present. - /// </remarks> - internal static string EscapeUriDataStringRfc3986(string value) { - Contract.Requires<ArgumentNullException>(value != null); - - // Start with RFC 2396 escaping by calling the .NET method to do the work. - // This MAY sometimes exhibit RFC 3986 behavior (according to the documentation). - // If it does, the escaping we do that follows it will be a no-op since the - // characters we search for to replace can't possibly exist in the string. - StringBuilder escaped = new StringBuilder(Uri.EscapeDataString(value)); - - // Upgrade the escaping to RFC 3986, if necessary. - for (int i = 0; i < UriRfc3986CharsToEscape.Length; i++) { - escaped.Replace(UriRfc3986CharsToEscape[i], Uri.HexEscape(UriRfc3986CharsToEscape[i][0])); - } - - // Return the fully-RFC3986-escaped string. - return escaped.ToString(); - } - - /// <summary> - /// Ensures that UTC times are converted to local times. Unspecified kinds are unchanged. - /// </summary> - /// <param name="value">The date-time to convert.</param> - /// <returns>The date-time in local time.</returns> - internal static DateTime ToLocalTimeSafe(this DateTime value) { - if (value.Kind == DateTimeKind.Unspecified) { - return value; - } - - return value.ToLocalTime(); - } - - /// <summary> - /// Ensures that local times are converted to UTC times. Unspecified kinds are unchanged. - /// </summary> - /// <param name="value">The date-time to convert.</param> - /// <returns>The date-time in UTC time.</returns> - internal static DateTime ToUniversalTimeSafe(this DateTime value) { - if (value.Kind == DateTimeKind.Unspecified) { - return value; - } - - return value.ToUniversalTime(); - } - - /// <summary> - /// Creates a symmetric algorithm for use in encryption/decryption. - /// </summary> - /// <param name="key">The symmetric key to use for encryption/decryption.</param> - /// <returns>A symmetric algorithm.</returns> - private static SymmetricAlgorithm CreateSymmetricAlgorithm(byte[] key) { - SymmetricAlgorithm result = null; - try { - result = new RijndaelManaged(); - result.Mode = CipherMode.CBC; - result.Key = key; - return result; - } catch { - IDisposable disposableResult = result; - if (disposableResult != null) { - disposableResult.Dispose(); - } - - throw; - } - } - - /// <summary> - /// A class to convert a <see cref="Comparison<T>"/> into an <see cref="IComparer<T>"/>. - /// </summary> - /// <typeparam name="T">The type of objects being compared.</typeparam> - private class ComparisonHelper<T> : IComparer<T> { - /// <summary> - /// The comparison method to use. - /// </summary> - private Comparison<T> comparison; - - /// <summary> - /// Initializes a new instance of the ComparisonHelper class. - /// </summary> - /// <param name="comparison">The comparison method to use.</param> - internal ComparisonHelper(Comparison<T> comparison) { - Contract.Requires<ArgumentNullException>(comparison != null); - - this.comparison = comparison; - } - - #region IComparer<T> Members - - /// <summary> - /// Compares two instances of <typeparamref name="T"/>. - /// </summary> - /// <param name="x">The first object to compare.</param> - /// <param name="y">The second object to compare.</param> - /// <returns>Any of -1, 0, or 1 according to standard comparison rules.</returns> - public int Compare(T x, T y) { - return this.comparison(x, y); - } - - #endregion - } - } -} diff --git a/src/DotNetOpenAuth/Messaging/MultipartPostPart.cs b/src/DotNetOpenAuth/Messaging/MultipartPostPart.cs deleted file mode 100644 index eff40dd..0000000 --- a/src/DotNetOpenAuth/Messaging/MultipartPostPart.cs +++ /dev/null @@ -1,223 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="MultipartPostPart.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.Messaging { - using System; - using System.Collections.Generic; - using System.Diagnostics.CodeAnalysis; - using System.Diagnostics.Contracts; - using System.IO; - using System.Net; - using System.Text; - - /// <summary> - /// Represents a single part in a HTTP multipart POST request. - /// </summary> - public class MultipartPostPart : IDisposable { - /// <summary> - /// The "Content-Disposition" string. - /// </summary> - private const string ContentDispositionHeader = "Content-Disposition"; - - /// <summary> - /// The two-character \r\n newline character sequence to use. - /// </summary> - private const string NewLine = "\r\n"; - - /// <summary> - /// Initializes a new instance of the <see cref="MultipartPostPart"/> class. - /// </summary> - /// <param name="contentDisposition">The content disposition of the part.</param> - public MultipartPostPart(string contentDisposition) { - Contract.Requires<ArgumentException>(!string.IsNullOrEmpty(contentDisposition)); - - this.ContentDisposition = contentDisposition; - this.ContentAttributes = new Dictionary<string, string>(); - this.PartHeaders = new WebHeaderCollection(); - } - - /// <summary> - /// Gets or sets the content disposition. - /// </summary> - /// <value>The content disposition.</value> - public string ContentDisposition { get; set; } - - /// <summary> - /// Gets the key=value attributes that appear on the same line as the Content-Disposition. - /// </summary> - /// <value>The content attributes.</value> - public IDictionary<string, string> ContentAttributes { get; private set; } - - /// <summary> - /// Gets the headers that appear on subsequent lines after the Content-Disposition. - /// </summary> - public WebHeaderCollection PartHeaders { get; private set; } - - /// <summary> - /// Gets or sets the content of the part. - /// </summary> - public Stream Content { get; set; } - - /// <summary> - /// Gets the length of this entire part. - /// </summary> - /// <remarks>Useful for calculating the ContentLength HTTP header to send before actually serializing the content.</remarks> - public long Length { - get { - ErrorUtilities.VerifyOperation(this.Content != null && this.Content.Length >= 0, MessagingStrings.StreamMustHaveKnownLength); - - long length = 0; - length += ContentDispositionHeader.Length; - length += ": ".Length; - length += this.ContentDisposition.Length; - foreach (var pair in this.ContentAttributes) { - length += "; ".Length + pair.Key.Length + "=\"".Length + pair.Value.Length + "\"".Length; - } - - length += NewLine.Length; - foreach (string headerName in this.PartHeaders) { - length += headerName.Length; - length += ": ".Length; - length += this.PartHeaders[headerName].Length; - length += NewLine.Length; - } - - length += NewLine.Length; - length += this.Content.Length; - - return length; - } - } - - /// <summary> - /// Creates a part that represents a simple form field. - /// </summary> - /// <param name="name">The name of the form field.</param> - /// <param name="value">The value.</param> - /// <returns>The constructed part.</returns> - public static MultipartPostPart CreateFormPart(string name, string value) { - Contract.Requires<ArgumentException>(!string.IsNullOrEmpty(name)); - Contract.Requires<ArgumentException>(value != null); - - var part = new MultipartPostPart("form-data"); - try { - part.ContentAttributes["name"] = name; - part.Content = new MemoryStream(Encoding.UTF8.GetBytes(value)); - return part; - } catch { - part.Dispose(); - throw; - } - } - - /// <summary> - /// Creates a part that represents a file attachment. - /// </summary> - /// <param name="name">The name of the form field.</param> - /// <param name="filePath">The path to the file to send.</param> - /// <param name="contentType">Type of the content in HTTP Content-Type format.</param> - /// <returns>The constructed part.</returns> - public static MultipartPostPart CreateFormFilePart(string name, string filePath, string contentType) { - Contract.Requires<ArgumentException>(!string.IsNullOrEmpty(name)); - Contract.Requires<ArgumentException>(!string.IsNullOrEmpty(filePath)); - Contract.Requires<ArgumentException>(!string.IsNullOrEmpty(contentType)); - - string fileName = Path.GetFileName(filePath); - var fileStream = File.OpenRead(filePath); - try { - return CreateFormFilePart(name, fileName, contentType, fileStream); - } catch { - fileStream.Dispose(); - throw; - } - } - - /// <summary> - /// Creates a part that represents a file attachment. - /// </summary> - /// <param name="name">The name of the form field.</param> - /// <param name="fileName">Name of the file as the server should see it.</param> - /// <param name="contentType">Type of the content in HTTP Content-Type format.</param> - /// <param name="content">The content of the file.</param> - /// <returns>The constructed part.</returns> - public static MultipartPostPart CreateFormFilePart(string name, string fileName, string contentType, Stream content) { - Contract.Requires<ArgumentException>(!string.IsNullOrEmpty(name)); - Contract.Requires<ArgumentException>(!string.IsNullOrEmpty(fileName)); - Contract.Requires<ArgumentException>(!string.IsNullOrEmpty(contentType)); - Contract.Requires<ArgumentException>(content != null); - - var part = new MultipartPostPart("file"); - try { - part.ContentAttributes["name"] = name; - part.ContentAttributes["filename"] = fileName; - part.PartHeaders[HttpRequestHeader.ContentType] = contentType; - if (!contentType.StartsWith("text/", StringComparison.Ordinal)) { - part.PartHeaders["Content-Transfer-Encoding"] = "binary"; - } - - part.Content = content; - return part; - } catch { - part.Dispose(); - throw; - } - } - - /// <summary> - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. - /// </summary> - public void Dispose() { - this.Dispose(true); - GC.SuppressFinalize(this); - } - - /// <summary> - /// Serializes the part to a stream. - /// </summary> - /// <param name="streamWriter">The stream writer.</param> - internal void Serialize(StreamWriter streamWriter) { - // VERY IMPORTANT: any changes at all made to this must be kept in sync with the - // Length property which calculates exactly how many bytes this method will write. - streamWriter.NewLine = NewLine; - streamWriter.Write("{0}: {1}", ContentDispositionHeader, this.ContentDisposition); - foreach (var pair in this.ContentAttributes) { - streamWriter.Write("; {0}=\"{1}\"", pair.Key, pair.Value); - } - - streamWriter.WriteLine(); - foreach (string headerName in this.PartHeaders) { - streamWriter.WriteLine("{0}: {1}", headerName, this.PartHeaders[headerName]); - } - - streamWriter.WriteLine(); - streamWriter.Flush(); - this.Content.CopyTo(streamWriter.BaseStream); - } - - /// <summary> - /// Releases unmanaged and - optionally - managed resources - /// </summary> - /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param> - protected virtual void Dispose(bool disposing) { - if (disposing) { - this.Content.Dispose(); - } - } - -#if CONTRACTS_FULL - /// <summary> - /// Verifies conditions that should be true for any valid state of this object. - /// </summary> - [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called by code contracts.")] - [ContractInvariantMethod] - private void Invariant() { - Contract.Invariant(!string.IsNullOrEmpty(this.ContentDisposition)); - Contract.Invariant(this.PartHeaders != null); - Contract.Invariant(this.ContentAttributes != null); - } -#endif - } -} diff --git a/src/DotNetOpenAuth/Messaging/NetworkDirectWebResponse.cs b/src/DotNetOpenAuth/Messaging/NetworkDirectWebResponse.cs deleted file mode 100644 index 800d2ba..0000000 --- a/src/DotNetOpenAuth/Messaging/NetworkDirectWebResponse.cs +++ /dev/null @@ -1,116 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="NetworkDirectWebResponse.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.Messaging { - using System; - using System.Diagnostics; - using System.Diagnostics.Contracts; - using System.IO; - using System.Net; - using System.Text; - - /// <summary> - /// A live network HTTP response - /// </summary> - [DebuggerDisplay("{Status} {ContentType.MediaType}")] - [ContractVerification(true)] - internal class NetworkDirectWebResponse : IncomingWebResponse, IDisposable { - /// <summary> - /// The network response object, used to initialize this instance, that still needs - /// to be closed if applicable. - /// </summary> - private HttpWebResponse httpWebResponse; - - /// <summary> - /// The incoming network response stream. - /// </summary> - private Stream responseStream; - - /// <summary> - /// A value indicating whether a stream reader has already been - /// created on this instance. - /// </summary> - private bool streamReadBegun; - - /// <summary> - /// Initializes a new instance of the <see cref="NetworkDirectWebResponse"/> class. - /// </summary> - /// <param name="requestUri">The request URI.</param> - /// <param name="response">The response.</param> - internal NetworkDirectWebResponse(Uri requestUri, HttpWebResponse response) - : base(requestUri, response) { - Contract.Requires<ArgumentNullException>(requestUri != null); - Contract.Requires<ArgumentNullException>(response != null); - this.httpWebResponse = response; - this.responseStream = response.GetResponseStream(); - } - - /// <summary> - /// Gets the body of the HTTP response. - /// </summary> - public override Stream ResponseStream { - get { return this.responseStream; } - } - - /// <summary> - /// Creates a text reader for the response stream. - /// </summary> - /// <returns>The text reader, initialized for the proper encoding.</returns> - public override StreamReader GetResponseReader() { - this.streamReadBegun = true; - if (this.responseStream == null) { - throw new ObjectDisposedException(GetType().Name); - } - - string contentEncoding = this.Headers[HttpResponseHeader.ContentEncoding]; - if (string.IsNullOrEmpty(contentEncoding)) { - return new StreamReader(this.ResponseStream); - } else { - return new StreamReader(this.ResponseStream, Encoding.GetEncoding(contentEncoding)); - } - } - - /// <summary> - /// Gets an offline snapshot version of this instance. - /// </summary> - /// <param name="maximumBytesToCache">The maximum bytes from the response stream to cache.</param> - /// <returns>A snapshot version of this instance.</returns> - /// <remarks> - /// If this instance is a <see cref="NetworkDirectWebResponse"/> creating a snapshot - /// will automatically close and dispose of the underlying response stream. - /// If this instance is a <see cref="CachedDirectWebResponse"/>, the result will - /// be the self same instance. - /// </remarks> - internal override CachedDirectWebResponse GetSnapshot(int maximumBytesToCache) { - ErrorUtilities.VerifyOperation(!this.streamReadBegun, "Network stream reading has already begun."); - ErrorUtilities.VerifyOperation(this.httpWebResponse != null, "httpWebResponse != null"); - - this.streamReadBegun = true; - var result = new CachedDirectWebResponse(this.RequestUri, this.httpWebResponse, maximumBytesToCache); - this.Dispose(); - return result; - } - - /// <summary> - /// Releases unmanaged and - optionally - managed resources - /// </summary> - /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param> - protected override void Dispose(bool disposing) { - if (disposing) { - if (this.responseStream != null) { - this.responseStream.Dispose(); - this.responseStream = null; - } - if (this.httpWebResponse != null) { - this.httpWebResponse.Close(); - this.httpWebResponse = null; - } - } - - base.Dispose(disposing); - } - } -} diff --git a/src/DotNetOpenAuth/Messaging/OutgoingWebResponse.cs b/src/DotNetOpenAuth/Messaging/OutgoingWebResponse.cs deleted file mode 100644 index d9cedf6..0000000 --- a/src/DotNetOpenAuth/Messaging/OutgoingWebResponse.cs +++ /dev/null @@ -1,300 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="OutgoingWebResponse.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.Messaging { - using System; - using System.ComponentModel; - using System.Diagnostics.CodeAnalysis; - using System.Diagnostics.Contracts; - using System.IO; - using System.Net; - using System.Net.Mime; - using System.Text; - using System.Threading; - using System.Web; - - /// <summary> - /// A protocol message (request or response) that passes from this - /// to a remote party via the user agent using a redirect or form - /// POST submission, OR a direct message response. - /// </summary> - /// <remarks> - /// <para>An instance of this type describes the HTTP response that must be sent - /// in response to the current HTTP request.</para> - /// <para>It is important that this response make up the entire HTTP response. - /// A hosting ASPX page should not be allowed to render its normal HTML output - /// after this response is sent. The normal rendered output of an ASPX page - /// can be canceled by calling <see cref="HttpResponse.End"/> after this message - /// is sent on the response stream.</para> - /// </remarks> - public class OutgoingWebResponse { - /// <summary> - /// The encoder to use for serializing the response body. - /// </summary> - private static Encoding bodyStringEncoder = new UTF8Encoding(false); - - /// <summary> - /// Initializes a new instance of the <see cref="OutgoingWebResponse"/> class. - /// </summary> - internal OutgoingWebResponse() { - this.Status = HttpStatusCode.OK; - this.Headers = new WebHeaderCollection(); - } - - /// <summary> - /// Initializes a new instance of the <see cref="OutgoingWebResponse"/> class - /// based on the contents of an <see cref="HttpWebResponse"/>. - /// </summary> - /// <param name="response">The <see cref="HttpWebResponse"/> to clone.</param> - /// <param name="maximumBytesToRead">The maximum bytes to read from the response stream.</param> - protected internal OutgoingWebResponse(HttpWebResponse response, int maximumBytesToRead) { - Contract.Requires<ArgumentNullException>(response != null); - - this.Status = response.StatusCode; - this.Headers = response.Headers; - this.ResponseStream = new MemoryStream(response.ContentLength < 0 ? 4 * 1024 : (int)response.ContentLength); - using (Stream responseStream = response.GetResponseStream()) { - // BUGBUG: strictly speaking, is the response were exactly the limit, we'd report it as truncated here. - this.IsResponseTruncated = responseStream.CopyUpTo(this.ResponseStream, maximumBytesToRead) == maximumBytesToRead; - this.ResponseStream.Seek(0, SeekOrigin.Begin); - } - } - - /// <summary> - /// Gets the headers that must be included in the response to the user agent. - /// </summary> - /// <remarks> - /// The headers in this collection are not meant to be a comprehensive list - /// of exactly what should be sent, but are meant to augment whatever headers - /// are generally included in a typical response. - /// </remarks> - public WebHeaderCollection Headers { get; internal set; } - - /// <summary> - /// Gets the body of the HTTP response. - /// </summary> - public Stream ResponseStream { get; internal set; } - - /// <summary> - /// Gets a value indicating whether the response stream is incomplete due - /// to a length limitation imposed by the HttpWebRequest or calling method. - /// </summary> - public bool IsResponseTruncated { get; internal set; } - - /// <summary> - /// Gets or sets the body of the response as a string. - /// </summary> - public string Body { - get { return this.ResponseStream != null ? this.GetResponseReader().ReadToEnd() : null; } - set { this.SetResponse(value, null); } - } - - /// <summary> - /// Gets the HTTP status code to use in the HTTP response. - /// </summary> - public HttpStatusCode Status { get; internal set; } - - /// <summary> - /// Gets or sets a reference to the actual protocol message that - /// is being sent via the user agent. - /// </summary> - internal IProtocolMessage OriginalMessage { get; set; } - - /// <summary> - /// Creates a text reader for the response stream. - /// </summary> - /// <returns>The text reader, initialized for the proper encoding.</returns> - [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "Costly operation")] - public StreamReader GetResponseReader() { - this.ResponseStream.Seek(0, SeekOrigin.Begin); - string contentEncoding = this.Headers[HttpResponseHeader.ContentEncoding]; - if (string.IsNullOrEmpty(contentEncoding)) { - return new StreamReader(this.ResponseStream); - } else { - return new StreamReader(this.ResponseStream, Encoding.GetEncoding(contentEncoding)); - } - } - - /// <summary> - /// Automatically sends the appropriate response to the user agent - /// and ends execution on the current page or handler. - /// </summary> - /// <exception cref="ThreadAbortException">Typically thrown by ASP.NET in order to prevent additional data from the page being sent to the client and corrupting the response.</exception> - /// <remarks> - /// Requires a current HttpContext. - /// </remarks> - [EditorBrowsable(EditorBrowsableState.Never)] - public virtual void Send() { - Contract.Requires<InvalidOperationException>(HttpContext.Current != null && HttpContext.Current.Request != null, MessagingStrings.HttpContextRequired); - - this.Send(HttpContext.Current); - } - - /// <summary> - /// Automatically sends the appropriate response to the user agent - /// and ends execution on the current page or handler. - /// </summary> - /// <param name="context">The context of the HTTP request whose response should be set. - /// Typically this is <see cref="HttpContext.Current"/>.</param> - /// <exception cref="ThreadAbortException">Typically thrown by ASP.NET in order to prevent additional data from the page being sent to the client and corrupting the response.</exception> - [EditorBrowsable(EditorBrowsableState.Never)] - public virtual void Send(HttpContext context) { - this.Respond(context, true); - } - - /// <summary> - /// Automatically sends the appropriate response to the user agent - /// and signals ASP.NET to short-circuit the page execution pipeline - /// now that the response has been completed. - /// Not safe to call from ASP.NET web forms. - /// </summary> - /// <remarks> - /// Requires a current HttpContext. - /// This call is not safe to make from an ASP.NET web form (.aspx file or code-behind) because - /// ASP.NET will render HTML after the protocol message has been sent, which will corrupt the response. - /// Use the <see cref="Send"/> method instead for web forms. - /// </remarks> - public virtual void Respond() { - Contract.Requires<InvalidOperationException>(HttpContext.Current != null && HttpContext.Current.Request != null, MessagingStrings.HttpContextRequired); - - this.Respond(HttpContext.Current); - } - - /// <summary> - /// Automatically sends the appropriate response to the user agent - /// and signals ASP.NET to short-circuit the page execution pipeline - /// now that the response has been completed. - /// Not safe to call from ASP.NET web forms. - /// </summary> - /// <param name="context">The context of the HTTP request whose response should be set. - /// Typically this is <see cref="HttpContext.Current"/>.</param> - /// <remarks> - /// This call is not safe to make from an ASP.NET web form (.aspx file or code-behind) because - /// ASP.NET will render HTML after the protocol message has been sent, which will corrupt the response. - /// Use the <see cref="Send"/> method instead for web forms. - /// </remarks> - public virtual void Respond(HttpContext context) { - Contract.Requires<ArgumentNullException>(context != null); - - this.Respond(context, false); - } - - /// <summary> - /// Automatically sends the appropriate response to the user agent. - /// </summary> - /// <param name="response">The response to set to this message.</param> - public virtual void Send(HttpListenerResponse response) { - Contract.Requires<ArgumentNullException>(response != null); - - response.StatusCode = (int)this.Status; - MessagingUtilities.ApplyHeadersToResponse(this.Headers, response); - if (this.ResponseStream != null) { - response.ContentLength64 = this.ResponseStream.Length; - this.ResponseStream.CopyTo(response.OutputStream); - } - - response.OutputStream.Close(); - } - - /// <summary> - /// Gets the URI that, when requested with an HTTP GET request, - /// would transmit the message that normally would be transmitted via a user agent redirect. - /// </summary> - /// <param name="channel">The channel to use for encoding.</param> - /// <returns> - /// The URL that would transmit the original message. This URL may exceed the normal 2K limit, - /// and should therefore be broken up manually and POSTed as form fields when it exceeds this length. - /// </returns> - /// <remarks> - /// This is useful for desktop applications that will spawn a user agent to transmit the message - /// rather than cause a redirect. - /// </remarks> - internal Uri GetDirectUriRequest(Channel channel) { - Contract.Requires<ArgumentNullException>(channel != null); - - var message = this.OriginalMessage as IDirectedProtocolMessage; - if (message == null) { - throw new InvalidOperationException(); // this only makes sense for directed messages (indirect responses) - } - - var fields = channel.MessageDescriptions.GetAccessor(message).Serialize(); - UriBuilder builder = new UriBuilder(message.Recipient); - MessagingUtilities.AppendQueryArgs(builder, fields); - return builder.Uri; - } - - /// <summary> - /// Sets the response to some string, encoded as UTF-8. - /// </summary> - /// <param name="body">The string to set the response to.</param> - /// <param name="contentType">Type of the content. May be null.</param> - internal void SetResponse(string body, ContentType contentType) { - if (body == null) { - this.ResponseStream = null; - return; - } - - if (contentType == null) { - contentType = new ContentType("text/html"); - contentType.CharSet = bodyStringEncoder.WebName; - } else if (contentType.CharSet != bodyStringEncoder.WebName) { - // clone the original so we're not tampering with our inputs if it came as a parameter. - contentType = new ContentType(contentType.ToString()); - contentType.CharSet = bodyStringEncoder.WebName; - } - - this.Headers[HttpResponseHeader.ContentType] = contentType.ToString(); - this.ResponseStream = new MemoryStream(); - StreamWriter writer = new StreamWriter(this.ResponseStream, bodyStringEncoder); - writer.Write(body); - writer.Flush(); - this.ResponseStream.Seek(0, SeekOrigin.Begin); - } - - /// <summary> - /// Automatically sends the appropriate response to the user agent - /// and signals ASP.NET to short-circuit the page execution pipeline - /// now that the response has been completed. - /// </summary> - /// <param name="context">The context of the HTTP request whose response should be set. - /// Typically this is <see cref="HttpContext.Current"/>.</param> - /// <param name="endRequest">If set to <c>false</c>, this method calls - /// <see cref="HttpApplication.CompleteRequest"/> rather than <see cref="HttpResponse.End"/> - /// to avoid a <see cref="ThreadAbortException"/>.</param> - protected internal virtual void Respond(HttpContext context, bool endRequest) { - Contract.Requires<ArgumentNullException>(context != null); - - context.Response.Clear(); - context.Response.StatusCode = (int)this.Status; - MessagingUtilities.ApplyHeadersToResponse(this.Headers, context.Response); - if (this.ResponseStream != null) { - try { - this.ResponseStream.CopyTo(context.Response.OutputStream); - } catch (HttpException ex) { - if (ex.ErrorCode == -2147467259 && context.Response.Output != null) { - // Test scenarios can generate this, since the stream is being spoofed: - // System.Web.HttpException: OutputStream is not available when a custom TextWriter is used. - context.Response.Output.Write(this.Body); - } else { - throw; - } - } - } - - if (endRequest) { - // This approach throws an exception in order that - // no more code is executed in the calling page. - // Microsoft no longer recommends this approach. - context.Response.End(); - } else if (context.ApplicationInstance != null) { - // This approach doesn't throw an exception, but - // still tells ASP.NET to short-circuit most of the - // request handling pipeline to speed things up. - context.ApplicationInstance.CompleteRequest(); - } - } - } -} diff --git a/src/DotNetOpenAuth/Messaging/OutgoingWebResponseActionResult.cs b/src/DotNetOpenAuth/Messaging/OutgoingWebResponseActionResult.cs deleted file mode 100644 index f2b31e9..0000000 --- a/src/DotNetOpenAuth/Messaging/OutgoingWebResponseActionResult.cs +++ /dev/null @@ -1,40 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="OutgoingWebResponseActionResult.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.Messaging { - using System; - using System.Diagnostics.Contracts; - using System.Web.Mvc; - using DotNetOpenAuth.Messaging; - - /// <summary> - /// An ASP.NET MVC structure to represent the response to send - /// to the user agent when the controller has finished its work. - /// </summary> - internal class OutgoingWebResponseActionResult : ActionResult { - /// <summary> - /// The outgoing web response to send when the ActionResult is executed. - /// </summary> - private readonly OutgoingWebResponse response; - - /// <summary> - /// Initializes a new instance of the <see cref="OutgoingWebResponseActionResult"/> class. - /// </summary> - /// <param name="response">The response.</param> - internal OutgoingWebResponseActionResult(OutgoingWebResponse response) { - Contract.Requires<ArgumentNullException>(response != null); - this.response = response; - } - - /// <summary> - /// Enables processing of the result of an action method by a custom type that inherits from <see cref="T:System.Web.Mvc.ActionResult"/>. - /// </summary> - /// <param name="context">The context in which to set the response.</param> - public override void ExecuteResult(ControllerContext context) { - this.response.Respond(); - } - } -} diff --git a/src/DotNetOpenAuth/Messaging/ProtocolException.cs b/src/DotNetOpenAuth/Messaging/ProtocolException.cs deleted file mode 100644 index fb9ec6d..0000000 --- a/src/DotNetOpenAuth/Messaging/ProtocolException.cs +++ /dev/null @@ -1,93 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="ProtocolException.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.Messaging { - using System; - using System.Collections.Generic; - using System.Diagnostics.Contracts; - using System.Security; - using System.Security.Permissions; - - /// <summary> - /// An exception to represent errors in the local or remote implementation of the protocol. - /// </summary> - [Serializable] - public class ProtocolException : Exception { - /// <summary> - /// Initializes a new instance of the <see cref="ProtocolException"/> class. - /// </summary> - public ProtocolException() { - } - - /// <summary> - /// Initializes a new instance of the <see cref="ProtocolException"/> class. - /// </summary> - /// <param name="message">A message describing the specific error the occurred or was detected.</param> - public ProtocolException(string message) : base(message) { - } - - /// <summary> - /// Initializes a new instance of the <see cref="ProtocolException"/> class. - /// </summary> - /// <param name="message">A message describing the specific error the occurred or was detected.</param> - /// <param name="inner">The inner exception to include.</param> - public ProtocolException(string message, Exception inner) : base(message, inner) { - } - - /// <summary> - /// Initializes a new instance of the <see cref="ProtocolException"/> class - /// such that it can be sent as a protocol message response to a remote caller. - /// </summary> - /// <param name="message">The human-readable exception message.</param> - /// <param name="faultedMessage">The message that was the cause of the exception. Must not be null.</param> - protected internal ProtocolException(string message, IProtocolMessage faultedMessage) - : base(message) { - Contract.Requires<ArgumentNullException>(faultedMessage != null); - this.FaultedMessage = faultedMessage; - } - - /// <summary> - /// Initializes a new instance of the <see cref="ProtocolException"/> class. - /// </summary> - /// <param name="info">The <see cref="System.Runtime.Serialization.SerializationInfo"/> - /// that holds the serialized object data about the exception being thrown.</param> - /// <param name="context">The System.Runtime.Serialization.StreamingContext - /// that contains contextual information about the source or destination.</param> - protected ProtocolException( - System.Runtime.Serialization.SerializationInfo info, - System.Runtime.Serialization.StreamingContext context) - : base(info, context) { - throw new NotImplementedException(); - } - - /// <summary> - /// Gets the message that caused the exception. - /// </summary> - internal IProtocolMessage FaultedMessage { get; private set; } - - /// <summary> - /// When overridden in a derived class, sets the <see cref="T:System.Runtime.Serialization.SerializationInfo"/> with information about the exception. - /// </summary> - /// <param name="info">The <see cref="T:System.Runtime.Serialization.SerializationInfo"/> that holds the serialized object data about the exception being thrown.</param> - /// <param name="context">The <see cref="T:System.Runtime.Serialization.StreamingContext"/> that contains contextual information about the source or destination.</param> - /// <exception cref="T:System.ArgumentNullException"> - /// The <paramref name="info"/> parameter is a null reference (Nothing in Visual Basic). - /// </exception> - /// <PermissionSet> - /// <IPermission class="System.Security.Permissions.FileIOPermission, mscorlib, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Read="*AllFiles*" PathDiscovery="*AllFiles*"/> - /// <IPermission class="System.Security.Permissions.SecurityPermission, mscorlib, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Flags="SerializationFormatter"/> - /// </PermissionSet> -#if CLR4 - [SecurityCritical] -#else - [SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.SerializationFormatter)] -#endif - public override void GetObjectData(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) { - base.GetObjectData(info, context); - throw new NotImplementedException(); - } - } -} diff --git a/src/DotNetOpenAuth/Messaging/Reflection/IMessagePartEncoder.cs b/src/DotNetOpenAuth/Messaging/Reflection/IMessagePartEncoder.cs deleted file mode 100644 index 03534f2..0000000 --- a/src/DotNetOpenAuth/Messaging/Reflection/IMessagePartEncoder.cs +++ /dev/null @@ -1,78 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="IMessagePartEncoder.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.Messaging.Reflection { - using System; - using System.Collections.Generic; - using System.Diagnostics.Contracts; - using System.Linq; - using System.Text; - - /// <summary> - /// An interface describing how various objects can be serialized and deserialized between their object and string forms. - /// </summary> - /// <remarks> - /// Implementations of this interface must include a default constructor and must be thread-safe. - /// </remarks> - [ContractClass(typeof(IMessagePartEncoderContract))] - public interface IMessagePartEncoder { - /// <summary> - /// Encodes the specified value. - /// </summary> - /// <param name="value">The value. Guaranteed to never be null.</param> - /// <returns>The <paramref name="value"/> in string form, ready for message transport.</returns> - string Encode(object value); - - /// <summary> - /// Decodes the specified value. - /// </summary> - /// <param name="value">The string value carried by the transport. Guaranteed to never be null, although it may be empty.</param> - /// <returns>The deserialized form of the given string.</returns> - /// <exception cref="FormatException">Thrown when the string value given cannot be decoded into the required object type.</exception> - object Decode(string value); - } - - /// <summary> - /// Code contract for the <see cref="IMessagePartEncoder"/> type. - /// </summary> - [ContractClassFor(typeof(IMessagePartEncoder))] - internal abstract class IMessagePartEncoderContract : IMessagePartEncoder { - /// <summary> - /// Initializes a new instance of the <see cref="IMessagePartEncoderContract"/> class. - /// </summary> - protected IMessagePartEncoderContract() { - } - - #region IMessagePartEncoder Members - - /// <summary> - /// Encodes the specified value. - /// </summary> - /// <param name="value">The value. Guaranteed to never be null.</param> - /// <returns> - /// The <paramref name="value"/> in string form, ready for message transport. - /// </returns> - string IMessagePartEncoder.Encode(object value) { - Contract.Requires<ArgumentNullException>(value != null); - throw new NotImplementedException(); - } - - /// <summary> - /// Decodes the specified value. - /// </summary> - /// <param name="value">The string value carried by the transport. Guaranteed to never be null, although it may be empty.</param> - /// <returns> - /// The deserialized form of the given string. - /// </returns> - /// <exception cref="FormatException">Thrown when the string value given cannot be decoded into the required object type.</exception> - object IMessagePartEncoder.Decode(string value) { - Contract.Requires<ArgumentNullException>(value != null); - throw new NotImplementedException(); - } - - #endregion - } -} diff --git a/src/DotNetOpenAuth/Messaging/Reflection/MessageDescription.cs b/src/DotNetOpenAuth/Messaging/Reflection/MessageDescription.cs deleted file mode 100644 index 7dbab80..0000000 --- a/src/DotNetOpenAuth/Messaging/Reflection/MessageDescription.cs +++ /dev/null @@ -1,285 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="MessageDescription.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.Messaging.Reflection { - using System; - using System.Collections.Generic; - using System.Diagnostics; - using System.Diagnostics.Contracts; - using System.Globalization; - using System.Linq; - using System.Reflection; - - /// <summary> - /// A mapping between serialized key names and <see cref="MessagePart"/> instances describing - /// those key/values pairs. - /// </summary> - internal class MessageDescription { - /// <summary> - /// A mapping between the serialized key names and their - /// describing <see cref="MessagePart"/> instances. - /// </summary> - private Dictionary<string, MessagePart> mapping; - - /// <summary> - /// Initializes a new instance of the <see cref="MessageDescription"/> class. - /// </summary> - /// <param name="messageType">Type of the message.</param> - /// <param name="messageVersion">The message version.</param> - internal MessageDescription(Type messageType, Version messageVersion) { - Contract.Requires<ArgumentNullException>(messageType != null); - Contract.Requires<ArgumentException>(typeof(IMessage).IsAssignableFrom(messageType)); - Contract.Requires<ArgumentNullException>(messageVersion != null); - - this.MessageType = messageType; - this.MessageVersion = messageVersion; - this.ReflectMessageType(); - } - - /// <summary> - /// Gets the mapping between the serialized key names and their describing - /// <see cref="MessagePart"/> instances. - /// </summary> - internal IDictionary<string, MessagePart> Mapping { - get { return this.mapping; } - } - - /// <summary> - /// Gets the message version this instance was generated from. - /// </summary> - internal Version MessageVersion { get; private set; } - - /// <summary> - /// Gets the type of message this instance was generated from. - /// </summary> - /// <value>The type of the described message.</value> - internal Type MessageType { get; private set; } - - /// <summary> - /// Gets the constructors available on the message type. - /// </summary> - internal ConstructorInfo[] Constructors { get; private set; } - - /// <summary> - /// Returns a <see cref="System.String"/> that represents this instance. - /// </summary> - /// <returns> - /// A <see cref="System.String"/> that represents this instance. - /// </returns> - public override string ToString() { - return this.MessageType.Name + " (" + this.MessageVersion + ")"; - } - - /// <summary> - /// Gets a dictionary that provides read/write access to a message. - /// </summary> - /// <param name="message">The message the dictionary should provide access to.</param> - /// <returns>The dictionary accessor to the message</returns> - [Pure] - internal MessageDictionary GetDictionary(IMessage message) { - Contract.Requires<ArgumentNullException>(message != null); - Contract.Ensures(Contract.Result<MessageDictionary>() != null); - return this.GetDictionary(message, false); - } - - /// <summary> - /// Gets a dictionary that provides read/write access to a message. - /// </summary> - /// <param name="message">The message the dictionary should provide access to.</param> - /// <param name="getOriginalValues">A value indicating whether this message dictionary will retrieve original values instead of normalized ones.</param> - /// <returns>The dictionary accessor to the message</returns> - [Pure] - internal MessageDictionary GetDictionary(IMessage message, bool getOriginalValues) { - Contract.Requires<ArgumentNullException>(message != null); - Contract.Ensures(Contract.Result<MessageDictionary>() != null); - return new MessageDictionary(message, this, getOriginalValues); - } - - /// <summary> - /// Ensures the message parts pass basic validation. - /// </summary> - /// <param name="parts">The key/value pairs of the serialized message.</param> - internal void EnsureMessagePartsPassBasicValidation(IDictionary<string, string> parts) { - try { - this.CheckRequiredMessagePartsArePresent(parts.Keys, true); - this.CheckRequiredProtocolMessagePartsAreNotEmpty(parts, true); - this.CheckMessagePartsConstantValues(parts, true); - } catch (ProtocolException) { - Logger.Messaging.ErrorFormat( - "Error while performing basic validation of {0} with these message parts:{1}{2}", - this.MessageType.Name, - Environment.NewLine, - parts.ToStringDeferred()); - throw; - } - } - - /// <summary> - /// Tests whether all the required message parts pass basic validation for the given data. - /// </summary> - /// <param name="parts">The key/value pairs of the serialized message.</param> - /// <returns>A value indicating whether the provided data fits the message's basic requirements.</returns> - internal bool CheckMessagePartsPassBasicValidation(IDictionary<string, string> parts) { - Contract.Requires<ArgumentNullException>(parts != null); - - return this.CheckRequiredMessagePartsArePresent(parts.Keys, false) && - this.CheckRequiredProtocolMessagePartsAreNotEmpty(parts, false) && - this.CheckMessagePartsConstantValues(parts, false); - } - - /// <summary> - /// Verifies that a given set of keys include all the required parameters - /// for this message type or throws an exception. - /// </summary> - /// <param name="keys">The names of all parameters included in a message.</param> - /// <param name="throwOnFailure">if set to <c>true</c> an exception is thrown on failure with details.</param> - /// <returns>A value indicating whether the provided data fits the message's basic requirements.</returns> - /// <exception cref="ProtocolException"> - /// Thrown when required parts of a message are not in <paramref name="keys"/> - /// if <paramref name="throwOnFailure"/> is <c>true</c>. - /// </exception> - private bool CheckRequiredMessagePartsArePresent(IEnumerable<string> keys, bool throwOnFailure) { - Contract.Requires<ArgumentNullException>(keys != null); - - var missingKeys = (from part in this.Mapping.Values - where part.IsRequired && !keys.Contains(part.Name) - select part.Name).ToArray(); - if (missingKeys.Length > 0) { - if (throwOnFailure) { - ErrorUtilities.ThrowProtocol( - MessagingStrings.RequiredParametersMissing, - this.MessageType.FullName, - string.Join(", ", missingKeys)); - } else { - Logger.Messaging.DebugFormat( - MessagingStrings.RequiredParametersMissing, - this.MessageType.FullName, - missingKeys.ToStringDeferred()); - return false; - } - } - - return true; - } - - /// <summary> - /// Ensures the protocol message parts that must not be empty are in fact not empty. - /// </summary> - /// <param name="partValues">A dictionary of key/value pairs that make up the serialized message.</param> - /// <param name="throwOnFailure">if set to <c>true</c> an exception is thrown on failure with details.</param> - /// <returns>A value indicating whether the provided data fits the message's basic requirements.</returns> - /// <exception cref="ProtocolException"> - /// Thrown when required parts of a message are not in <paramref name="partValues"/> - /// if <paramref name="throwOnFailure"/> is <c>true</c>. - /// </exception> - private bool CheckRequiredProtocolMessagePartsAreNotEmpty(IDictionary<string, string> partValues, bool throwOnFailure) { - Contract.Requires<ArgumentNullException>(partValues != null); - - string value; - var emptyValuedKeys = (from part in this.Mapping.Values - where !part.AllowEmpty && partValues.TryGetValue(part.Name, out value) && value != null && value.Length == 0 - select part.Name).ToArray(); - if (emptyValuedKeys.Length > 0) { - if (throwOnFailure) { - ErrorUtilities.ThrowProtocol( - MessagingStrings.RequiredNonEmptyParameterWasEmpty, - this.MessageType.FullName, - string.Join(", ", emptyValuedKeys)); - } else { - Logger.Messaging.DebugFormat( - MessagingStrings.RequiredNonEmptyParameterWasEmpty, - this.MessageType.FullName, - emptyValuedKeys.ToStringDeferred()); - return false; - } - } - - return true; - } - - /// <summary> - /// Checks that a bunch of message part values meet the constant value requirements of this message description. - /// </summary> - /// <param name="partValues">The part values.</param> - /// <param name="throwOnFailure">if set to <c>true</c>, this method will throw on failure.</param> - /// <returns>A value indicating whether all the requirements are met.</returns> - private bool CheckMessagePartsConstantValues(IDictionary<string, string> partValues, bool throwOnFailure) - { - Contract.Requires<ArgumentNullException>(partValues != null); - - var badConstantValues = (from part in this.Mapping.Values - where part.IsConstantValueAvailableStatically - where partValues.ContainsKey(part.Name) - where !string.Equals(partValues[part.Name], part.StaticConstantValue, StringComparison.Ordinal) - select part.Name).ToArray(); - if (badConstantValues.Length > 0) { - if (throwOnFailure) { - ErrorUtilities.ThrowProtocol( - MessagingStrings.RequiredMessagePartConstantIncorrect, - this.MessageType.FullName, - string.Join(", ", badConstantValues)); - } else { - Logger.Messaging.DebugFormat( - MessagingStrings.RequiredMessagePartConstantIncorrect, - this.MessageType.FullName, - badConstantValues.ToStringDeferred()); - return false; - } - } - - return true; - } - - /// <summary> - /// Reflects over some <see cref="IMessage"/>-implementing type - /// and prepares to serialize/deserialize instances of that type. - /// </summary> - private void ReflectMessageType() { - this.mapping = new Dictionary<string, MessagePart>(); - - Type currentType = this.MessageType; - do { - foreach (MemberInfo member in currentType.GetMembers(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.DeclaredOnly)) { - if (member is PropertyInfo || member is FieldInfo) { - MessagePartAttribute partAttribute = - (from a in member.GetCustomAttributes(typeof(MessagePartAttribute), true).OfType<MessagePartAttribute>() - orderby a.MinVersionValue descending - where a.MinVersionValue <= this.MessageVersion - where a.MaxVersionValue >= this.MessageVersion - select a).FirstOrDefault(); - if (partAttribute != null) { - MessagePart part = new MessagePart(member, partAttribute); - if (this.mapping.ContainsKey(part.Name)) { - Logger.Messaging.WarnFormat( - "Message type {0} has more than one message part named {1}. Inherited members will be hidden.", - this.MessageType.Name, - part.Name); - } else { - this.mapping.Add(part.Name, part); - } - } - } - } - currentType = currentType.BaseType; - } while (currentType != null); - - BindingFlags flags = BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public; - this.Constructors = this.MessageType.GetConstructors(flags); - } - -#if CONTRACTS_FULL - /// <summary> - /// Describes traits of this class that are always true. - /// </summary> - [ContractInvariantMethod] - private void Invariant() { - Contract.Invariant(this.MessageType != null); - Contract.Invariant(this.MessageVersion != null); - Contract.Invariant(this.Constructors != null); - } -#endif - } -} diff --git a/src/DotNetOpenAuth/Messaging/Reflection/MessageDescriptionCollection.cs b/src/DotNetOpenAuth/Messaging/Reflection/MessageDescriptionCollection.cs deleted file mode 100644 index e332fc4..0000000 --- a/src/DotNetOpenAuth/Messaging/Reflection/MessageDescriptionCollection.cs +++ /dev/null @@ -1,218 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="MessageDescriptionCollection.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.Messaging.Reflection { - using System; - using System.Collections.Generic; - using System.Diagnostics.CodeAnalysis; - using System.Diagnostics.Contracts; - - /// <summary> - /// A cache of <see cref="MessageDescription"/> instances. - /// </summary> - [ContractVerification(true)] - internal class MessageDescriptionCollection : IEnumerable<MessageDescription> { - /// <summary> - /// A dictionary of reflected message types and the generated reflection information. - /// </summary> - private readonly Dictionary<MessageTypeAndVersion, MessageDescription> reflectedMessageTypes = new Dictionary<MessageTypeAndVersion, MessageDescription>(); - - /// <summary> - /// Initializes a new instance of the <see cref="MessageDescriptionCollection"/> class. - /// </summary> - internal MessageDescriptionCollection() { - } - - #region IEnumerable<MessageDescription> Members - - /// <summary> - /// Returns an enumerator that iterates through a collection. - /// </summary> - /// <returns> - /// An <see cref="T:System.Collections.IEnumerator"/> object that can be used to iterate through the collection. - /// </returns> - public IEnumerator<MessageDescription> GetEnumerator() { - return this.reflectedMessageTypes.Values.GetEnumerator(); - } - - #endregion - - #region IEnumerable Members - - /// <summary> - /// Returns an enumerator that iterates through a collection. - /// </summary> - /// <returns> - /// An <see cref="T:System.Collections.IEnumerator"/> object that can be used to iterate through the collection. - /// </returns> - System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { - return this.reflectedMessageTypes.Values.GetEnumerator(); - } - - #endregion - - /// <summary> - /// Gets a <see cref="MessageDescription"/> instance prepared for the - /// given message type. - /// </summary> - /// <param name="messageType">A type that implements <see cref="IMessage"/>.</param> - /// <param name="messageVersion">The protocol version of the message.</param> - /// <returns>A <see cref="MessageDescription"/> instance.</returns> - [SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", MessageId = "System.Diagnostics.Contracts.__ContractsRuntime.Assume(System.Boolean,System.String,System.String)", Justification = "No localization required.")] - [Pure] - internal MessageDescription Get(Type messageType, Version messageVersion) { - Contract.Requires<ArgumentNullException>(messageType != null); - Contract.Requires<ArgumentException>(typeof(IMessage).IsAssignableFrom(messageType)); - Contract.Requires<ArgumentNullException>(messageVersion != null); - Contract.Ensures(Contract.Result<MessageDescription>() != null); - - MessageTypeAndVersion key = new MessageTypeAndVersion(messageType, messageVersion); - - MessageDescription result; - if (!this.reflectedMessageTypes.TryGetValue(key, out result)) { - lock (this.reflectedMessageTypes) { - if (!this.reflectedMessageTypes.TryGetValue(key, out result)) { - this.reflectedMessageTypes[key] = result = new MessageDescription(messageType, messageVersion); - } - } - } - - Contract.Assume(result != null, "We should never assign null values to this dictionary."); - return result; - } - - /// <summary> - /// Gets a <see cref="MessageDescription"/> instance prepared for the - /// given message type. - /// </summary> - /// <param name="message">The message for which a <see cref="MessageDescription"/> should be obtained.</param> - /// <returns> - /// A <see cref="MessageDescription"/> instance. - /// </returns> - [Pure] - internal MessageDescription Get(IMessage message) { - Contract.Requires<ArgumentNullException>(message != null); - Contract.Ensures(Contract.Result<MessageDescription>() != null); - return this.Get(message.GetType(), message.Version); - } - - /// <summary> - /// Gets the dictionary that provides read/write access to a message. - /// </summary> - /// <param name="message">The message.</param> - /// <returns>The dictionary.</returns> - [Pure] - internal MessageDictionary GetAccessor(IMessage message) { - Contract.Requires<ArgumentNullException>(message != null); - return this.GetAccessor(message, false); - } - - /// <summary> - /// Gets the dictionary that provides read/write access to a message. - /// </summary> - /// <param name="message">The message.</param> - /// <param name="getOriginalValues">A value indicating whether this message dictionary will retrieve original values instead of normalized ones.</param> - /// <returns>The dictionary.</returns> - [Pure] - internal MessageDictionary GetAccessor(IMessage message, bool getOriginalValues) { - Contract.Requires<ArgumentNullException>(message != null); - return this.Get(message).GetDictionary(message, getOriginalValues); - } - - /// <summary> - /// A struct used as the key to bundle message type and version. - /// </summary> - [ContractVerification(true)] - private struct MessageTypeAndVersion { - /// <summary> - /// Backing store for the <see cref="Type"/> property. - /// </summary> - private readonly Type type; - - /// <summary> - /// Backing store for the <see cref="Version"/> property. - /// </summary> - private readonly Version version; - - /// <summary> - /// Initializes a new instance of the <see cref="MessageTypeAndVersion"/> struct. - /// </summary> - /// <param name="messageType">Type of the message.</param> - /// <param name="messageVersion">The message version.</param> - internal MessageTypeAndVersion(Type messageType, Version messageVersion) { - Contract.Requires<ArgumentNullException>(messageType != null); - Contract.Requires<ArgumentNullException>(messageVersion != null); - - this.type = messageType; - this.version = messageVersion; - } - - /// <summary> - /// Gets the message type. - /// </summary> - [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Exposes basic identity on the type.")] - internal Type Type { - get { return this.type; } - } - - /// <summary> - /// Gets the message version. - /// </summary> - [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Exposes basic identity on the type.")] - internal Version Version { - get { return this.version; } - } - - /// <summary> - /// Implements the operator ==. - /// </summary> - /// <param name="first">The first object to compare.</param> - /// <param name="second">The second object to compare.</param> - /// <returns>The result of the operator.</returns> - public static bool operator ==(MessageTypeAndVersion first, MessageTypeAndVersion second) { - // structs cannot be null, so this is safe - return first.Equals(second); - } - - /// <summary> - /// Implements the operator !=. - /// </summary> - /// <param name="first">The first object to compare.</param> - /// <param name="second">The second object to compare.</param> - /// <returns>The result of the operator.</returns> - public static bool operator !=(MessageTypeAndVersion first, MessageTypeAndVersion second) { - // structs cannot be null, so this is safe - return !first.Equals(second); - } - - /// <summary> - /// Indicates whether this instance and a specified object are equal. - /// </summary> - /// <param name="obj">Another object to compare to.</param> - /// <returns> - /// true if <paramref name="obj"/> and this instance are the same type and represent the same value; otherwise, false. - /// </returns> - public override bool Equals(object obj) { - if (obj is MessageTypeAndVersion) { - MessageTypeAndVersion other = (MessageTypeAndVersion)obj; - return this.type == other.type && this.version == other.version; - } else { - return false; - } - } - - /// <summary> - /// Returns the hash code for this instance. - /// </summary> - /// <returns> - /// A 32-bit signed integer that is the hash code for this instance. - /// </returns> - public override int GetHashCode() { - return this.type.GetHashCode(); - } - } - } -} diff --git a/src/DotNetOpenAuth/Messaging/Reflection/MessageDictionary.cs b/src/DotNetOpenAuth/Messaging/Reflection/MessageDictionary.cs deleted file mode 100644 index 2b60a9c..0000000 --- a/src/DotNetOpenAuth/Messaging/Reflection/MessageDictionary.cs +++ /dev/null @@ -1,409 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="MessageDictionary.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.Messaging.Reflection { - using System; - using System.Collections; - using System.Collections.Generic; - using System.Diagnostics; - using System.Diagnostics.CodeAnalysis; - using System.Diagnostics.Contracts; - - /// <summary> - /// Wraps an <see cref="IMessage"/> instance in a dictionary that - /// provides access to both well-defined message properties and "extra" - /// name/value pairs that have no properties associated with them. - /// </summary> - [ContractVerification(false)] - internal class MessageDictionary : IDictionary<string, string> { - /// <summary> - /// The <see cref="IMessage"/> instance manipulated by this dictionary. - /// </summary> - private readonly IMessage message; - - /// <summary> - /// The <see cref="MessageDescription"/> instance that describes the message type. - /// </summary> - private readonly MessageDescription description; - - /// <summary> - /// Whether original string values should be retrieved instead of normalized ones. - /// </summary> - private readonly bool getOriginalValues; - - /// <summary> - /// Initializes a new instance of the <see cref="MessageDictionary"/> class. - /// </summary> - /// <param name="message">The message instance whose values will be manipulated by this dictionary.</param> - /// <param name="description">The message description.</param> - /// <param name="getOriginalValues">A value indicating whether this message dictionary will retrieve original values instead of normalized ones.</param> - [Pure] - internal MessageDictionary(IMessage message, MessageDescription description, bool getOriginalValues) { - Contract.Requires<ArgumentNullException>(message != null); - Contract.Requires<ArgumentNullException>(description != null); - - this.message = message; - this.description = description; - this.getOriginalValues = getOriginalValues; - } - - /// <summary> - /// Gets the message this dictionary provides access to. - /// </summary> - public IMessage Message { - get { - Contract.Ensures(Contract.Result<IMessage>() != null); - return this.message; - } - } - - /// <summary> - /// Gets the description of the type of message this dictionary provides access to. - /// </summary> - public MessageDescription Description { - get { - Contract.Ensures(Contract.Result<MessageDescription>() != null); - return this.description; - } - } - - #region ICollection<KeyValuePair<string,string>> Properties - - /// <summary> - /// Gets the number of explicitly set values in the message. - /// </summary> - public int Count { - get { return this.Keys.Count; } - } - - /// <summary> - /// Gets a value indicating whether this message is read only. - /// </summary> - bool ICollection<KeyValuePair<string, string>>.IsReadOnly { - get { return false; } - } - - #endregion - - #region IDictionary<string,string> Properties - - /// <summary> - /// Gets all the keys that have values associated with them. - /// </summary> - public ICollection<string> Keys { - get { - List<string> keys = new List<string>(this.message.ExtraData.Count + this.description.Mapping.Count); - keys.AddRange(this.DeclaredKeys); - keys.AddRange(this.AdditionalKeys); - return keys.AsReadOnly(); - } - } - - /// <summary> - /// Gets the set of official message part names that have non-null values associated with them. - /// </summary> - public ICollection<string> DeclaredKeys { - get { - List<string> keys = new List<string>(this.description.Mapping.Count); - foreach (var pair in this.description.Mapping) { - // Don't include keys with null values, but default values for structs is ok - if (pair.Value.GetValue(this.message, this.getOriginalValues) != null) { - keys.Add(pair.Key); - } - } - - return keys.AsReadOnly(); - } - } - - /// <summary> - /// Gets the keys that are in the message but not declared as official OAuth properties. - /// </summary> - public ICollection<string> AdditionalKeys { - get { return this.message.ExtraData.Keys; } - } - - /// <summary> - /// Gets all the values. - /// </summary> - public ICollection<string> Values { - get { - List<string> values = new List<string>(this.message.ExtraData.Count + this.description.Mapping.Count); - foreach (MessagePart part in this.description.Mapping.Values) { - if (part.GetValue(this.message, this.getOriginalValues) != null) { - values.Add(part.GetValue(this.message, this.getOriginalValues)); - } - } - - foreach (string value in this.message.ExtraData.Values) { - Debug.Assert(value != null, "Null values should never be allowed in the extra data dictionary."); - values.Add(value); - } - - return values.AsReadOnly(); - } - } - - #endregion - - /// <summary> - /// Gets the serializer for the message this dictionary provides access to. - /// </summary> - private MessageSerializer Serializer { - get { return MessageSerializer.Get(this.Message.GetType()); } - } - - #region IDictionary<string,string> Indexers - - /// <summary> - /// Gets or sets a value for some named value. - /// </summary> - /// <param name="key">The serialized form of a name for the value to read or write.</param> - /// <returns>The named value.</returns> - /// <remarks> - /// If the key matches a declared property or field on the message type, - /// that type member is set. Otherwise the key/value is stored in a - /// dictionary for extra (weakly typed) strings. - /// </remarks> - /// <exception cref="ArgumentException">Thrown when setting a value that is not allowed for a given <paramref name="key"/>.</exception> - public string this[string key] { - get { - MessagePart part; - if (this.description.Mapping.TryGetValue(key, out part)) { - // Never throw KeyNotFoundException for declared properties. - return part.GetValue(this.message, this.getOriginalValues); - } else { - return this.message.ExtraData[key]; - } - } - - set { - MessagePart part; - if (this.description.Mapping.TryGetValue(key, out part)) { - part.SetValue(this.message, value); - } else { - if (value == null) { - this.message.ExtraData.Remove(key); - } else { - this.message.ExtraData[key] = value; - } - } - } - } - - #endregion - - #region IDictionary<string,string> Methods - - /// <summary> - /// Adds a named value to the message. - /// </summary> - /// <param name="key">The serialized form of the name whose value is being set.</param> - /// <param name="value">The serialized form of the value.</param> - /// <exception cref="ArgumentException"> - /// Thrown if <paramref name="key"/> already has a set value in this message. - /// </exception> - /// <exception cref="ArgumentNullException"> - /// Thrown if <paramref name="value"/> is null. - /// </exception> - public void Add(string key, string value) { - ErrorUtilities.VerifyArgumentNotNull(value, "value"); - - MessagePart part; - if (this.description.Mapping.TryGetValue(key, out part)) { - if (part.IsNondefaultValueSet(this.message)) { - throw new ArgumentException(MessagingStrings.KeyAlreadyExists); - } - part.SetValue(this.message, value); - } else { - this.message.ExtraData.Add(key, value); - } - } - - /// <summary> - /// Checks whether some named parameter has a value set in the message. - /// </summary> - /// <param name="key">The serialized form of the message part's name.</param> - /// <returns>True if the parameter by the given name has a set value. False otherwise.</returns> - public bool ContainsKey(string key) { - return this.message.ExtraData.ContainsKey(key) || - (this.description.Mapping.ContainsKey(key) && this.description.Mapping[key].GetValue(this.message, this.getOriginalValues) != null); - } - - /// <summary> - /// Removes a name and value from the message given its name. - /// </summary> - /// <param name="key">The serialized form of the name to remove.</param> - /// <returns>True if a message part by the given name was found and removed. False otherwise.</returns> - public bool Remove(string key) { - if (this.message.ExtraData.Remove(key)) { - return true; - } else { - MessagePart part; - if (this.description.Mapping.TryGetValue(key, out part)) { - if (part.GetValue(this.message, this.getOriginalValues) != null) { - part.SetValue(this.message, null); - return true; - } - } - return false; - } - } - - /// <summary> - /// Gets some named value if the key has a value. - /// </summary> - /// <param name="key">The name (in serialized form) of the value being sought.</param> - /// <param name="value">The variable where the value will be set.</param> - /// <returns>True if the key was found and <paramref name="value"/> was set. False otherwise.</returns> - public bool TryGetValue(string key, out string value) { - MessagePart part; - if (this.description.Mapping.TryGetValue(key, out part)) { - value = part.GetValue(this.message, this.getOriginalValues); - return value != null; - } - return this.message.ExtraData.TryGetValue(key, out value); - } - - #endregion - - #region ICollection<KeyValuePair<string,string>> Methods - - /// <summary> - /// Sets a named value in the message. - /// </summary> - /// <param name="item">The name-value pair to add. The name is the serialized form of the key.</param> - [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Code Contracts ccrewrite does this.")] - public void Add(KeyValuePair<string, string> item) { - this.Add(item.Key, item.Value); - } - - /// <summary> - /// Removes all values in the message. - /// </summary> - public void ClearValues() { - foreach (string key in this.Keys) { - this.Remove(key); - } - } - - /// <summary> - /// Removes all items from the <see cref="T:System.Collections.Generic.ICollection`1"/>. - /// </summary> - /// <exception cref="T:System.NotSupportedException"> - /// The <see cref="T:System.Collections.Generic.ICollection`1"/> is read-only. - /// </exception> - /// <remarks> - /// This method cannot be implemented because keys are not guaranteed to be removed - /// since some are inherent to the type of message that this dictionary provides - /// access to. - /// </remarks> - public void Clear() { - throw new NotSupportedException(); - } - - /// <summary> - /// Checks whether a named value has been set on the message. - /// </summary> - /// <param name="item">The name/value pair.</param> - /// <returns>True if the key exists and has the given value. False otherwise.</returns> - public bool Contains(KeyValuePair<string, string> item) { - MessagePart part; - if (this.description.Mapping.TryGetValue(item.Key, out part)) { - return string.Equals(part.GetValue(this.message, this.getOriginalValues), item.Value, StringComparison.Ordinal); - } else { - return this.message.ExtraData.Contains(item); - } - } - - /// <summary> - /// Copies all the serializable data from the message to a key/value array. - /// </summary> - /// <param name="array">The array to copy to.</param> - /// <param name="arrayIndex">The index in the <paramref name="array"/> to begin copying to.</param> - void ICollection<KeyValuePair<string, string>>.CopyTo(KeyValuePair<string, string>[] array, int arrayIndex) { - foreach (var pair in (IDictionary<string, string>)this) { - array[arrayIndex++] = pair; - } - } - - /// <summary> - /// Removes a named value from the message if it exists. - /// </summary> - /// <param name="item">The serialized form of the name and value to remove.</param> - /// <returns>True if the name/value was found and removed. False otherwise.</returns> - public bool Remove(KeyValuePair<string, string> item) { - // We use contains because that checks that the value is equal as well. - if (((ICollection<KeyValuePair<string, string>>)this).Contains(item)) { - ((IDictionary<string, string>)this).Remove(item.Key); - return true; - } - return false; - } - - #endregion - - #region IEnumerable<KeyValuePair<string,string>> Members - - /// <summary> - /// Gets an enumerator that generates KeyValuePair<string, string> instances - /// for all the key/value pairs that are set in the message. - /// </summary> - /// <returns>The enumerator that can generate the name/value pairs.</returns> - public IEnumerator<KeyValuePair<string, string>> GetEnumerator() { - foreach (string key in this.Keys) { - yield return new KeyValuePair<string, string>(key, this[key]); - } - } - - #endregion - - #region IEnumerable Members - - /// <summary> - /// Gets an enumerator that generates KeyValuePair<string, string> instances - /// for all the key/value pairs that are set in the message. - /// </summary> - /// <returns>The enumerator that can generate the name/value pairs.</returns> - IEnumerator System.Collections.IEnumerable.GetEnumerator() { - return ((IEnumerable<KeyValuePair<string, string>>)this).GetEnumerator(); - } - - #endregion - - /// <summary> - /// Saves the data in a message to a standard dictionary. - /// </summary> - /// <returns>The generated dictionary.</returns> - [Pure] - public IDictionary<string, string> Serialize() { - Contract.Ensures(Contract.Result<IDictionary<string, string>>() != null); - return this.Serializer.Serialize(this); - } - - /// <summary> - /// Loads data from a dictionary into the message. - /// </summary> - /// <param name="fields">The data to load into the message.</param> - public void Deserialize(IDictionary<string, string> fields) { - Contract.Requires<ArgumentNullException>(fields != null); - this.Serializer.Deserialize(fields, this); - } - -#if CONTRACTS_FULL - /// <summary> - /// Verifies conditions that should be true for any valid state of this object. - /// </summary> - [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Called by code contracts.")] - [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called by code contracts.")] - [ContractInvariantMethod] - private void ObjectInvariant() { - Contract.Invariant(this.Message != null); - Contract.Invariant(this.Description != null); - } -#endif - } -} diff --git a/src/DotNetOpenAuth/Messaging/Reflection/MessagePart.cs b/src/DotNetOpenAuth/Messaging/Reflection/MessagePart.cs deleted file mode 100644 index e5cbff8..0000000 --- a/src/DotNetOpenAuth/Messaging/Reflection/MessagePart.cs +++ /dev/null @@ -1,439 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="MessagePart.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.Messaging.Reflection { - using System; - using System.Collections.Generic; - using System.Diagnostics; - using System.Diagnostics.CodeAnalysis; - using System.Diagnostics.Contracts; - using System.Globalization; - using System.Linq; - using System.Net.Security; - using System.Reflection; - using System.Xml; - using DotNetOpenAuth.Configuration; - using DotNetOpenAuth.OpenId; - - /// <summary> - /// Describes an individual member of a message and assists in its serialization. - /// </summary> - [ContractVerification(true)] - [DebuggerDisplay("MessagePart {Name}")] - internal class MessagePart { - /// <summary> - /// A map of converters that help serialize custom objects to string values and back again. - /// </summary> - private static readonly Dictionary<Type, ValueMapping> converters = new Dictionary<Type, ValueMapping>(); - - /// <summary> - /// A map of instantiated custom encoders used to encode/decode message parts. - /// </summary> - private static readonly Dictionary<Type, IMessagePartEncoder> encoders = new Dictionary<Type, IMessagePartEncoder>(); - - /// <summary> - /// The string-object conversion routines to use for this individual message part. - /// </summary> - private ValueMapping converter; - - /// <summary> - /// The property that this message part is associated with, if aplicable. - /// </summary> - private PropertyInfo property; - - /// <summary> - /// The field that this message part is associated with, if aplicable. - /// </summary> - private FieldInfo field; - - /// <summary> - /// The type of the message part. (Not the type of the message itself). - /// </summary> - private Type memberDeclaredType; - - /// <summary> - /// The default (uninitialized) value of the member inherent in its type. - /// </summary> - private object defaultMemberValue; - - /// <summary> - /// Initializes static members of the <see cref="MessagePart"/> class. - /// </summary> - [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Justification = "This simplifies the rest of the code.")] - [SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase", Justification = "By design.")] - [SuppressMessage("Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline", Justification = "Much more efficient initialization when we can call methods.")] - static MessagePart() { - Func<string, Uri> safeUri = str => { - Contract.Assume(str != null); - return new Uri(str); - }; - Func<string, bool> safeBool = str => { - Contract.Assume(str != null); - return bool.Parse(str); - }; - Func<string, Identifier> safeIdentifier = str => { - Contract.Assume(str != null); - ErrorUtilities.VerifyFormat(str.Length > 0, MessagingStrings.NonEmptyStringExpected); - return Identifier.Parse(str, true); - }; - Func<byte[], string> safeFromByteArray = bytes => { - Contract.Assume(bytes != null); - return Convert.ToBase64String(bytes); - }; - Func<string, byte[]> safeToByteArray = str => { - Contract.Assume(str != null); - return Convert.FromBase64String(str); - }; - Func<string, Realm> safeRealm = str => { - Contract.Assume(str != null); - return new Realm(str); - }; - Map<Uri>(uri => uri.AbsoluteUri, uri => uri.OriginalString, safeUri); - Map<DateTime>(dt => XmlConvert.ToString(dt, XmlDateTimeSerializationMode.Utc), null, str => XmlConvert.ToDateTime(str, XmlDateTimeSerializationMode.Utc)); - Map<TimeSpan>(ts => ts.ToString(), null, str => TimeSpan.Parse(str)); - Map<byte[]>(safeFromByteArray, null, safeToByteArray); - Map<Realm>(realm => realm.ToString(), realm => realm.OriginalString, safeRealm); - Map<Identifier>(id => id.SerializedString, id => id.OriginalString, safeIdentifier); - Map<bool>(value => value.ToString().ToLowerInvariant(), null, safeBool); - Map<CultureInfo>(c => c.Name, null, str => new CultureInfo(str)); - Map<CultureInfo[]>(cs => string.Join(",", cs.Select(c => c.Name).ToArray()), null, str => str.Split(',').Select(s => new CultureInfo(s)).ToArray()); - Map<Type>(t => t.FullName, null, str => Type.GetType(str)); - } - - /// <summary> - /// Initializes a new instance of the <see cref="MessagePart"/> class. - /// </summary> - /// <param name="member"> - /// A property or field of an <see cref="IMessage"/> implementing type - /// that has a <see cref="MessagePartAttribute"/> attached to it. - /// </param> - /// <param name="attribute"> - /// The attribute discovered on <paramref name="member"/> that describes the - /// serialization requirements of the message part. - /// </param> - [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Justification = "Unavoidable"), SuppressMessage("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily", Justification = "Code contracts requires it.")] - internal MessagePart(MemberInfo member, MessagePartAttribute attribute) { - Contract.Requires<ArgumentNullException>(member != null); - Contract.Requires<ArgumentException>(member is FieldInfo || member is PropertyInfo); - Contract.Requires<ArgumentNullException>(attribute != null); - - this.field = member as FieldInfo; - this.property = member as PropertyInfo; - this.Name = attribute.Name ?? member.Name; - this.RequiredProtection = attribute.RequiredProtection; - this.IsRequired = attribute.IsRequired; - this.AllowEmpty = attribute.AllowEmpty; - this.memberDeclaredType = (this.field != null) ? this.field.FieldType : this.property.PropertyType; - this.defaultMemberValue = DeriveDefaultValue(this.memberDeclaredType); - - Contract.Assume(this.memberDeclaredType != null); // CC missing PropertyInfo.PropertyType ensures result != null - if (attribute.Encoder == null) { - if (!converters.TryGetValue(this.memberDeclaredType, out this.converter)) { - if (this.memberDeclaredType.IsGenericType && - this.memberDeclaredType.GetGenericTypeDefinition() == typeof(Nullable<>)) { - // It's a nullable type. Try again to look up an appropriate converter for the underlying type. - Type underlyingType = Nullable.GetUnderlyingType(this.memberDeclaredType); - ValueMapping underlyingMapping; - if (converters.TryGetValue(underlyingType, out underlyingMapping)) { - this.converter = new ValueMapping( - underlyingMapping.ValueToString, - null, - str => str != null ? underlyingMapping.StringToValue(str) : null); - } else { - this.converter = new ValueMapping( - obj => obj != null ? obj.ToString() : null, - null, - str => str != null ? Convert.ChangeType(str, underlyingType, CultureInfo.InvariantCulture) : null); - } - } else { - this.converter = new ValueMapping( - obj => obj != null ? obj.ToString() : null, - null, - str => str != null ? Convert.ChangeType(str, this.memberDeclaredType, CultureInfo.InvariantCulture) : null); - } - } - } else { - this.converter = new ValueMapping(GetEncoder(attribute.Encoder)); - } - - // readonly and const fields are considered legal, and "constants" for message transport. - FieldAttributes constAttributes = FieldAttributes.Static | FieldAttributes.Literal | FieldAttributes.HasDefault; - if (this.field != null && ( - (this.field.Attributes & FieldAttributes.InitOnly) == FieldAttributes.InitOnly || - (this.field.Attributes & constAttributes) == constAttributes)) { - this.IsConstantValue = true; - this.IsConstantValueAvailableStatically = this.field.IsStatic; - } else if (this.property != null && !this.property.CanWrite) { - this.IsConstantValue = true; - } - - // Validate a sane combination of settings - this.ValidateSettings(); - } - - /// <summary> - /// Gets or sets the name to use when serializing or deserializing this parameter in a message. - /// </summary> - internal string Name { get; set; } - - /// <summary> - /// Gets or sets whether this message part must be signed. - /// </summary> - internal ProtectionLevel RequiredProtection { get; set; } - - /// <summary> - /// Gets or sets a value indicating whether this message part is required for the - /// containing message to be valid. - /// </summary> - internal bool IsRequired { get; set; } - - /// <summary> - /// Gets or sets a value indicating whether the string value is allowed to be empty in the serialized message. - /// </summary> - internal bool AllowEmpty { get; set; } - - /// <summary> - /// Gets or sets a value indicating whether the field or property must remain its default value. - /// </summary> - internal bool IsConstantValue { get; set; } - - /// <summary> - /// Gets or sets a value indicating whether this part is defined as a constant field and can be read without a message instance. - /// </summary> - internal bool IsConstantValueAvailableStatically { get; set; } - - /// <summary> - /// Gets the static constant value for this message part without a message instance. - /// </summary> - internal string StaticConstantValue { - get { - Contract.Requires<InvalidOperationException>(this.IsConstantValueAvailableStatically); - return this.ToString(this.field.GetValue(null), false); - } - } - - /// <summary> - /// Gets the type of the declared member. - /// </summary> - internal Type MemberDeclaredType { - get { return this.memberDeclaredType; } - } - - /// <summary> - /// Sets the member of a given message to some given value. - /// Used in deserialization. - /// </summary> - /// <param name="message">The message instance containing the member whose value should be set.</param> - /// <param name="value">The string representation of the value to set.</param> - internal void SetValue(IMessage message, string value) { - Contract.Requires<ArgumentNullException>(message != null); - - try { - if (this.IsConstantValue) { - string constantValue = this.GetValue(message); - var caseSensitivity = DotNetOpenAuthSection.Configuration.Messaging.Strict ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase; - if (!string.Equals(constantValue, value, caseSensitivity)) { - throw new ArgumentException(string.Format( - CultureInfo.CurrentCulture, - MessagingStrings.UnexpectedMessagePartValueForConstant, - message.GetType().Name, - this.Name, - constantValue, - value)); - } - } else { - this.SetValueAsObject(message, this.ToValue(value)); - } - } catch (Exception ex) { - throw ErrorUtilities.Wrap(ex, MessagingStrings.MessagePartReadFailure, message.GetType(), this.Name, value); - } - } - - /// <summary> - /// Gets the normalized form of a value of a member of a given message. - /// Used in serialization. - /// </summary> - /// <param name="message">The message instance to read the value from.</param> - /// <returns>The string representation of the member's value.</returns> - internal string GetValue(IMessage message) { - try { - object value = this.GetValueAsObject(message); - return this.ToString(value, false); - } catch (FormatException ex) { - throw ErrorUtilities.Wrap(ex, MessagingStrings.MessagePartWriteFailure, message.GetType(), this.Name); - } - } - - /// <summary> - /// Gets the value of a member of a given message. - /// Used in serialization. - /// </summary> - /// <param name="message">The message instance to read the value from.</param> - /// <param name="originalValue">A value indicating whether the original value should be retrieved (as opposed to a normalized form of it).</param> - /// <returns>The string representation of the member's value.</returns> - internal string GetValue(IMessage message, bool originalValue) { - try { - object value = this.GetValueAsObject(message); - return this.ToString(value, originalValue); - } catch (FormatException ex) { - throw ErrorUtilities.Wrap(ex, MessagingStrings.MessagePartWriteFailure, message.GetType(), this.Name); - } - } - - /// <summary> - /// Gets whether the value has been set to something other than its CLR type default value. - /// </summary> - /// <param name="message">The message instance to check the value on.</param> - /// <returns>True if the value is not the CLR default value.</returns> - internal bool IsNondefaultValueSet(IMessage message) { - if (this.memberDeclaredType.IsValueType) { - return !this.GetValueAsObject(message).Equals(this.defaultMemberValue); - } else { - return this.defaultMemberValue != this.GetValueAsObject(message); - } - } - - /// <summary> - /// Figures out the CLR default value for a given type. - /// </summary> - /// <param name="type">The type whose default value is being sought.</param> - /// <returns>Either null, or some default value like 0 or 0.0.</returns> - private static object DeriveDefaultValue(Type type) { - if (type.IsValueType) { - return Activator.CreateInstance(type); - } else { - return null; - } - } - - /// <summary> - /// Adds a pair of type conversion functions to the static conversion map. - /// </summary> - /// <typeparam name="T">The custom type to convert to and from strings.</typeparam> - /// <param name="toString">The function to convert the custom type to a string.</param> - /// <param name="toOriginalString">The mapping function that converts some custom value to its original (non-normalized) string. May be null if the same as the <paramref name="toString"/> function.</param> - /// <param name="toValue">The function to convert a string to the custom type.</param> - [SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", MessageId = "System.Diagnostics.Contracts.__ContractsRuntime.Requires<System.ArgumentNullException>(System.Boolean,System.String,System.String)", Justification = "Code contracts"), SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "toString", Justification = "Code contracts"), SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "toValue", Justification = "Code contracts")] - private static void Map<T>(Func<T, string> toString, Func<T, string> toOriginalString, Func<string, T> toValue) { - Contract.Requires<ArgumentNullException>(toString != null); - Contract.Requires<ArgumentNullException>(toValue != null); - - if (toOriginalString == null) { - toOriginalString = toString; - } - - Func<object, string> safeToString = obj => obj != null ? toString((T)obj) : null; - Func<object, string> safeToOriginalString = obj => obj != null ? toOriginalString((T)obj) : null; - Func<string, object> safeToT = str => str != null ? toValue(str) : default(T); - converters.Add(typeof(T), new ValueMapping(safeToString, safeToOriginalString, safeToT)); - } - - /// <summary> - /// Checks whether a type is a nullable value type (i.e. int?) - /// </summary> - /// <param name="type">The type in question.</param> - /// <returns>True if this is a nullable value type.</returns> - private static bool IsNonNullableValueType(Type type) { - if (!type.IsValueType) { - return false; - } - - if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>)) { - return false; - } - - return true; - } - - /// <summary> - /// Retrieves a previously instantiated encoder of a given type, or creates a new one and stores it for later retrieval as well. - /// </summary> - /// <param name="messagePartEncoder">The message part encoder type.</param> - /// <returns>An instance of the desired encoder.</returns> - private static IMessagePartEncoder GetEncoder(Type messagePartEncoder) { - Contract.Requires<ArgumentNullException>(messagePartEncoder != null); - Contract.Ensures(Contract.Result<IMessagePartEncoder>() != null); - - IMessagePartEncoder encoder; - if (!encoders.TryGetValue(messagePartEncoder, out encoder)) { - try { - encoder = encoders[messagePartEncoder] = (IMessagePartEncoder)Activator.CreateInstance(messagePartEncoder); - } catch (MissingMethodException ex) { - throw ErrorUtilities.Wrap(ex, MessagingStrings.EncoderInstantiationFailed, messagePartEncoder.FullName); - } - } - - return encoder; - } - - /// <summary> - /// Gets the value of the message part, without converting it to/from a string. - /// </summary> - /// <param name="message">The message instance to read from.</param> - /// <returns>The value of the member.</returns> - private object GetValueAsObject(IMessage message) { - if (this.property != null) { - return this.property.GetValue(message, null); - } else { - return this.field.GetValue(message); - } - } - - /// <summary> - /// Sets the value of a message part directly with a given value. - /// </summary> - /// <param name="message">The message instance to read from.</param> - /// <param name="value">The value to set on the this part.</param> - private void SetValueAsObject(IMessage message, object value) { - if (this.property != null) { - this.property.SetValue(message, value, null); - } else { - this.field.SetValue(message, value); - } - } - - /// <summary> - /// Converts a string representation of the member's value to the appropriate type. - /// </summary> - /// <param name="value">The string representation of the member's value.</param> - /// <returns> - /// An instance of the appropriate type for setting the member. - /// </returns> - private object ToValue(string value) { - return this.converter.StringToValue(value); - } - - /// <summary> - /// Converts the member's value to its string representation. - /// </summary> - /// <param name="value">The value of the member.</param> - /// <param name="originalString">A value indicating whether a string matching the originally decoded string should be returned (as opposed to a normalized string).</param> - /// <returns> - /// The string representation of the member's value. - /// </returns> - private string ToString(object value, bool originalString) { - return originalString ? this.converter.ValueToOriginalString(value) : this.converter.ValueToString(value); - } - - /// <summary> - /// Validates that the message part and its attribute have agreeable settings. - /// </summary> - /// <exception cref="ArgumentException"> - /// Thrown when a non-nullable value type is set as optional. - /// </exception> - private void ValidateSettings() { - if (!this.IsRequired && IsNonNullableValueType(this.memberDeclaredType)) { - MemberInfo member = (MemberInfo)this.field ?? this.property; - throw new ArgumentException( - string.Format( - CultureInfo.CurrentCulture, - "Invalid combination: {0} on message type {1} is a non-nullable value type but is marked as optional.", - member.Name, - member.DeclaringType)); - } - } - } -} diff --git a/src/DotNetOpenAuth/Messaging/Reflection/ValueMapping.cs b/src/DotNetOpenAuth/Messaging/Reflection/ValueMapping.cs deleted file mode 100644 index b0b8b47..0000000 --- a/src/DotNetOpenAuth/Messaging/Reflection/ValueMapping.cs +++ /dev/null @@ -1,67 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="ValueMapping.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.Messaging.Reflection { - using System; - using System.Diagnostics.Contracts; - - /// <summary> - /// A pair of conversion functions to map some type to a string and back again. - /// </summary> - [ContractVerification(true)] - internal struct ValueMapping { - /// <summary> - /// The mapping function that converts some custom type to a string. - /// </summary> - internal readonly Func<object, string> ValueToString; - - /// <summary> - /// The mapping function that converts some custom type to the original string - /// (possibly non-normalized) that represents it. - /// </summary> - internal readonly Func<object, string> ValueToOriginalString; - - /// <summary> - /// The mapping function that converts a string to some custom type. - /// </summary> - internal readonly Func<string, object> StringToValue; - - /// <summary> - /// Initializes a new instance of the <see cref="ValueMapping"/> struct. - /// </summary> - /// <param name="toString">The mapping function that converts some custom value to a string.</param> - /// <param name="toOriginalString">The mapping function that converts some custom value to its original (non-normalized) string. May be null if the same as the <paramref name="toString"/> function.</param> - /// <param name="toValue">The mapping function that converts a string to some custom value.</param> - internal ValueMapping(Func<object, string> toString, Func<object, string> toOriginalString, Func<string, object> toValue) { - Contract.Requires<ArgumentNullException>(toString != null); - Contract.Requires<ArgumentNullException>(toValue != null); - - this.ValueToString = toString; - this.ValueToOriginalString = toOriginalString ?? toString; - this.StringToValue = toValue; - } - - /// <summary> - /// Initializes a new instance of the <see cref="ValueMapping"/> struct. - /// </summary> - /// <param name="encoder">The encoder.</param> - internal ValueMapping(IMessagePartEncoder encoder) { - Contract.Requires<ArgumentNullException>(encoder != null); - var nullEncoder = encoder as IMessagePartNullEncoder; - string nullString = nullEncoder != null ? nullEncoder.EncodedNullValue : null; - - var originalStringEncoder = encoder as IMessagePartOriginalEncoder; - Func<object, string> originalStringEncode = encoder.Encode; - if (originalStringEncoder != null) { - originalStringEncode = originalStringEncoder.EncodeAsOriginalString; - } - - this.ValueToString = obj => (obj != null) ? encoder.Encode(obj) : nullString; - this.StringToValue = str => (str != null) ? encoder.Decode(str) : null; - this.ValueToOriginalString = obj => (obj != null) ? originalStringEncode(obj) : nullString; - } - } -} diff --git a/src/DotNetOpenAuth/Messaging/StandardMessageFactory.cs b/src/DotNetOpenAuth/Messaging/StandardMessageFactory.cs deleted file mode 100644 index b28ce74..0000000 --- a/src/DotNetOpenAuth/Messaging/StandardMessageFactory.cs +++ /dev/null @@ -1,298 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="StandardMessageFactory.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.Messaging { - using System; - using System.Collections.Generic; - using System.Diagnostics.Contracts; - using System.Linq; - using System.Reflection; - using System.Text; - using DotNetOpenAuth.Messaging.Reflection; - - /// <summary> - /// A message factory that automatically selects the message type based on the incoming data. - /// </summary> - internal class StandardMessageFactory : IMessageFactory { - /// <summary> - /// The request message types and their constructors to use for instantiating the messages. - /// </summary> - private readonly Dictionary<MessageDescription, ConstructorInfo> requestMessageTypes = new Dictionary<MessageDescription, ConstructorInfo>(); - - /// <summary> - /// The response message types and their constructors to use for instantiating the messages. - /// </summary> - /// <value> - /// The value is a dictionary, whose key is the type of the constructor's lone parameter. - /// </value> - private readonly Dictionary<MessageDescription, Dictionary<Type, ConstructorInfo>> responseMessageTypes = new Dictionary<MessageDescription, Dictionary<Type, ConstructorInfo>>(); - - /// <summary> - /// Initializes a new instance of the <see cref="StandardMessageFactory"/> class. - /// </summary> - internal StandardMessageFactory() { - } - - /// <summary> - /// Adds message types to the set that this factory can create. - /// </summary> - /// <param name="messageTypes">The message types that this factory may instantiate.</param> - public virtual void AddMessageTypes(IEnumerable<MessageDescription> messageTypes) { - Contract.Requires<ArgumentNullException>(messageTypes != null); - Contract.Requires<ArgumentException>(messageTypes.All(msg => msg != null)); - - var unsupportedMessageTypes = new List<MessageDescription>(0); - foreach (MessageDescription messageDescription in messageTypes) { - bool supportedMessageType = false; - - // First see whether this message fits the recognized pattern for request messages. - if (typeof(IDirectedProtocolMessage).IsAssignableFrom(messageDescription.MessageType)) { - foreach (ConstructorInfo ctor in messageDescription.Constructors) { - ParameterInfo[] parameters = ctor.GetParameters(); - if (parameters.Length == 2 && parameters[0].ParameterType == typeof(Uri) && parameters[1].ParameterType == typeof(Version)) { - supportedMessageType = true; - this.requestMessageTypes.Add(messageDescription, ctor); - break; - } - } - } - - // Also see if this message fits the recognized pattern for response messages. - if (typeof(IDirectResponseProtocolMessage).IsAssignableFrom(messageDescription.MessageType)) { - var responseCtors = new Dictionary<Type, ConstructorInfo>(messageDescription.Constructors.Length); - foreach (ConstructorInfo ctor in messageDescription.Constructors) { - ParameterInfo[] parameters = ctor.GetParameters(); - if (parameters.Length == 1 && typeof(IDirectedProtocolMessage).IsAssignableFrom(parameters[0].ParameterType)) { - responseCtors.Add(parameters[0].ParameterType, ctor); - } - } - - if (responseCtors.Count > 0) { - supportedMessageType = true; - this.responseMessageTypes.Add(messageDescription, responseCtors); - } - } - - if (!supportedMessageType) { - unsupportedMessageTypes.Add(messageDescription); - } - } - - ErrorUtilities.VerifySupported( - !unsupportedMessageTypes.Any(), - MessagingStrings.StandardMessageFactoryUnsupportedMessageType, - unsupportedMessageTypes.ToStringDeferred()); - } - - #region IMessageFactory Members - - /// <summary> - /// Analyzes an incoming request message payload to discover what kind of - /// message is embedded in it and returns the type, or null if no match is found. - /// </summary> - /// <param name="recipient">The intended or actual recipient of the request message.</param> - /// <param name="fields">The name/value pairs that make up the message payload.</param> - /// <returns> - /// A newly instantiated <see cref="IProtocolMessage"/>-derived object that this message can - /// deserialize to. Null if the request isn't recognized as a valid protocol message. - /// </returns> - public virtual IDirectedProtocolMessage GetNewRequestMessage(MessageReceivingEndpoint recipient, IDictionary<string, string> fields) { - MessageDescription matchingType = this.GetMessageDescription(recipient, fields); - if (matchingType != null) { - return this.InstantiateAsRequest(matchingType, recipient); - } else { - return null; - } - } - - /// <summary> - /// Analyzes an incoming request message payload to discover what kind of - /// message is embedded in it and returns the type, or null if no match is found. - /// </summary> - /// <param name="request">The message that was sent as a request that resulted in the response.</param> - /// <param name="fields">The name/value pairs that make up the message payload.</param> - /// <returns> - /// A newly instantiated <see cref="IProtocolMessage"/>-derived object that this message can - /// deserialize to. Null if the request isn't recognized as a valid protocol message. - /// </returns> - public virtual IDirectResponseProtocolMessage GetNewResponseMessage(IDirectedProtocolMessage request, IDictionary<string, string> fields) { - MessageDescription matchingType = this.GetMessageDescription(request, fields); - if (matchingType != null) { - return this.InstantiateAsResponse(matchingType, request); - } else { - return null; - } - } - - #endregion - - /// <summary> - /// Gets the message type that best fits the given incoming request data. - /// </summary> - /// <param name="recipient">The recipient of the incoming data. Typically not used, but included just in case.</param> - /// <param name="fields">The data of the incoming message.</param> - /// <returns> - /// The message type that matches the incoming data; or <c>null</c> if no match. - /// </returns> - /// <exception cref="ProtocolException">May be thrown if the incoming data is ambiguous.</exception> - protected virtual MessageDescription GetMessageDescription(MessageReceivingEndpoint recipient, IDictionary<string, string> fields) { - Contract.Requires<ArgumentNullException>(recipient != null); - Contract.Requires<ArgumentNullException>(fields != null); - - var matches = this.requestMessageTypes.Keys - .Where(message => message.CheckMessagePartsPassBasicValidation(fields)) - .OrderByDescending(message => CountInCommon(message.Mapping.Keys, fields.Keys)) - .ThenByDescending(message => message.Mapping.Count) - .CacheGeneratedResults(); - var match = matches.FirstOrDefault(); - if (match != null) { - if (Logger.Messaging.IsWarnEnabled && matches.Count() > 1) { - Logger.Messaging.WarnFormat( - "Multiple message types seemed to fit the incoming data: {0}", - matches.ToStringDeferred()); - } - - return match; - } else { - // No message type matches the incoming data. - return null; - } - } - - /// <summary> - /// Gets the message type that best fits the given incoming direct response data. - /// </summary> - /// <param name="request">The request message that prompted the response data.</param> - /// <param name="fields">The data of the incoming message.</param> - /// <returns> - /// The message type that matches the incoming data; or <c>null</c> if no match. - /// </returns> - /// <exception cref="ProtocolException">May be thrown if the incoming data is ambiguous.</exception> - protected virtual MessageDescription GetMessageDescription(IDirectedProtocolMessage request, IDictionary<string, string> fields) { - Contract.Requires<ArgumentNullException>(request != null); - Contract.Requires<ArgumentNullException>(fields != null); - - var matches = (from responseMessageType in this.responseMessageTypes - let message = responseMessageType.Key - where message.CheckMessagePartsPassBasicValidation(fields) - let ctors = this.FindMatchingResponseConstructors(message, request.GetType()) - where ctors.Any() - orderby GetDerivationDistance(ctors.First().GetParameters()[0].ParameterType, request.GetType()), - CountInCommon(message.Mapping.Keys, fields.Keys) descending, - message.Mapping.Count descending - select message).CacheGeneratedResults(); - var match = matches.FirstOrDefault(); - if (match != null) { - if (Logger.Messaging.IsWarnEnabled && matches.Count() > 1) { - Logger.Messaging.WarnFormat( - "Multiple message types seemed to fit the incoming data: {0}", - matches.ToStringDeferred()); - } - - return match; - } else { - // No message type matches the incoming data. - return null; - } - } - - /// <summary> - /// Instantiates the given request message type. - /// </summary> - /// <param name="messageDescription">The message description.</param> - /// <param name="recipient">The recipient.</param> - /// <returns>The instantiated message. Never null.</returns> - protected virtual IDirectedProtocolMessage InstantiateAsRequest(MessageDescription messageDescription, MessageReceivingEndpoint recipient) { - Contract.Requires<ArgumentNullException>(messageDescription != null); - Contract.Requires<ArgumentNullException>(recipient != null); - Contract.Ensures(Contract.Result<IDirectedProtocolMessage>() != null); - - ConstructorInfo ctor = this.requestMessageTypes[messageDescription]; - return (IDirectedProtocolMessage)ctor.Invoke(new object[] { recipient.Location, messageDescription.MessageVersion }); - } - - /// <summary> - /// Instantiates the given request message type. - /// </summary> - /// <param name="messageDescription">The message description.</param> - /// <param name="request">The request that resulted in this response.</param> - /// <returns>The instantiated message. Never null.</returns> - protected virtual IDirectResponseProtocolMessage InstantiateAsResponse(MessageDescription messageDescription, IDirectedProtocolMessage request) { - Contract.Requires<ArgumentNullException>(messageDescription != null); - Contract.Requires<ArgumentNullException>(request != null); - Contract.Ensures(Contract.Result<IDirectResponseProtocolMessage>() != null); - - Type requestType = request.GetType(); - var ctors = this.FindMatchingResponseConstructors(messageDescription, requestType); - ConstructorInfo ctor = null; - try { - ctor = ctors.Single(); - } catch (InvalidOperationException) { - if (ctors.Any()) { - ErrorUtilities.ThrowInternal("More than one matching constructor for request type " + requestType.Name + " and response type " + messageDescription.MessageType.Name); - } else { - ErrorUtilities.ThrowInternal("Unexpected request message type " + requestType.FullName + " for response type " + messageDescription.MessageType.Name); - } - } - return (IDirectResponseProtocolMessage)ctor.Invoke(new object[] { request }); - } - - /// <summary> - /// Gets the hierarchical distance between a type and a type it derives from or implements. - /// </summary> - /// <param name="assignableType">The base type or interface.</param> - /// <param name="derivedType">The concrete class that implements the <paramref name="assignableType"/>.</param> - /// <returns>The distance between the two types. 0 if the types are equivalent, 1 if the type immediately derives from or implements the base type, or progressively higher integers.</returns> - private static int GetDerivationDistance(Type assignableType, Type derivedType) { - Contract.Requires<ArgumentNullException>(assignableType != null); - Contract.Requires<ArgumentNullException>(derivedType != null); - Contract.Requires<ArgumentException>(assignableType.IsAssignableFrom(derivedType)); - - // If this is the two types are equivalent... - if (derivedType.IsAssignableFrom(assignableType)) - { - return 0; - } - - int steps; - derivedType = derivedType.BaseType; - for (steps = 1; assignableType.IsAssignableFrom(derivedType); steps++) - { - derivedType = derivedType.BaseType; - } - - return steps; - } - - /// <summary> - /// Counts how many strings are in the intersection of two collections. - /// </summary> - /// <param name="collection1">The first collection.</param> - /// <param name="collection2">The second collection.</param> - /// <param name="comparison">The string comparison method to use.</param> - /// <returns>A non-negative integer no greater than the count of elements in the smallest collection.</returns> - private static int CountInCommon(ICollection<string> collection1, ICollection<string> collection2, StringComparison comparison = StringComparison.Ordinal) { - Contract.Requires<ArgumentNullException>(collection1 != null); - Contract.Requires<ArgumentNullException>(collection2 != null); - Contract.Ensures(Contract.Result<int>() >= 0 && Contract.Result<int>() <= Math.Min(collection1.Count, collection2.Count)); - - return collection1.Count(value1 => collection2.Any(value2 => string.Equals(value1, value2, comparison))); - } - - /// <summary> - /// Finds constructors for response messages that take a given request message type. - /// </summary> - /// <param name="messageDescription">The message description.</param> - /// <param name="requestType">Type of the request message.</param> - /// <returns>A sequence of matching constructors.</returns> - private IEnumerable<ConstructorInfo> FindMatchingResponseConstructors(MessageDescription messageDescription, Type requestType) { - Contract.Requires<ArgumentNullException>(messageDescription != null); - Contract.Requires<ArgumentNullException>(requestType != null); - - return this.responseMessageTypes[messageDescription].Where(pair => pair.Key.IsAssignableFrom(requestType)).Select(pair => pair.Value); - } - } -} diff --git a/src/DotNetOpenAuth/Messaging/StandardMessageFactoryChannel.cs b/src/DotNetOpenAuth/Messaging/StandardMessageFactoryChannel.cs deleted file mode 100644 index 1fc3608..0000000 --- a/src/DotNetOpenAuth/Messaging/StandardMessageFactoryChannel.cs +++ /dev/null @@ -1,111 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="StandardMessageFactoryChannel.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.Messaging { - using System; - using System.Collections.Generic; - using System.Diagnostics.Contracts; - using System.Linq; - using System.Text; - using Reflection; - - /// <summary> - /// A channel that uses the standard message factory. - /// </summary> - public abstract class StandardMessageFactoryChannel : Channel { - /// <summary> - /// The message types receivable by this channel. - /// </summary> - private readonly ICollection<Type> messageTypes; - - /// <summary> - /// The protocol versions supported by this channel. - /// </summary> - private readonly ICollection<Version> versions; - - /// <summary> - /// Initializes a new instance of the <see cref="StandardMessageFactoryChannel"/> class. - /// </summary> - /// <param name="messageTypes">The message types that might be encountered.</param> - /// <param name="versions">All the possible message versions that might be encountered.</param> - /// <param name="bindingElements">The binding elements to apply to the channel.</param> - protected StandardMessageFactoryChannel(ICollection<Type> messageTypes, ICollection<Version> versions, params IChannelBindingElement[] bindingElements) - : base(new StandardMessageFactory(), bindingElements) { - Contract.Requires<ArgumentNullException>(messageTypes != null); - Contract.Requires<ArgumentNullException>(versions != null); - - this.messageTypes = messageTypes; - this.versions = versions; - this.StandardMessageFactory.AddMessageTypes(GetMessageDescriptions(this.messageTypes, this.versions, this.MessageDescriptions)); - } - - /// <summary> - /// Gets or sets a tool that can figure out what kind of message is being received - /// so it can be deserialized. - /// </summary> - internal StandardMessageFactory StandardMessageFactory { - get { return (Messaging.StandardMessageFactory)this.MessageFactory; } - set { this.MessageFactory = value; } - } - - /// <summary> - /// Gets or sets the message descriptions. - /// </summary> - internal sealed override MessageDescriptionCollection MessageDescriptions { - get { - return base.MessageDescriptions; - } - - set { - base.MessageDescriptions = value; - - // We must reinitialize the message factory so it can use the new message descriptions. - var factory = new StandardMessageFactory(); - factory.AddMessageTypes(GetMessageDescriptions(this.messageTypes, this.versions, value)); - this.MessageFactory = factory; - } - } - - /// <summary> - /// Gets or sets a tool that can figure out what kind of message is being received - /// so it can be deserialized. - /// </summary> - protected sealed override IMessageFactory MessageFactory { - get { - return (StandardMessageFactory)base.MessageFactory; - } - - set { - StandardMessageFactory newValue = (StandardMessageFactory)value; - base.MessageFactory = newValue; - } - } - - /// <summary> - /// Generates all the message descriptions for a given set of message types and versions. - /// </summary> - /// <param name="messageTypes">The message types.</param> - /// <param name="versions">The message versions.</param> - /// <param name="descriptionsCache">The cache to use when obtaining the message descriptions.</param> - /// <returns>The generated/retrieved message descriptions.</returns> - private static IEnumerable<MessageDescription> GetMessageDescriptions(ICollection<Type> messageTypes, ICollection<Version> versions, MessageDescriptionCollection descriptionsCache) - { - Contract.Requires<ArgumentNullException>(messageTypes != null); - Contract.Requires<ArgumentNullException>(descriptionsCache != null); - Contract.Ensures(Contract.Result<IEnumerable<MessageDescription>>() != null); - - // Get all the MessageDescription objects through the standard cache, - // so that perhaps it will be a quick lookup, or at least it will be - // stored there for a quick lookup later. - var messageDescriptions = new List<MessageDescription>(messageTypes.Count * versions.Count); - messageDescriptions.AddRange(from version in versions - from messageType in messageTypes - select descriptionsCache.Get(messageType, version)); - - return messageDescriptions; - } - } -} diff --git a/src/DotNetOpenAuth/Messaging/StandardWebRequestHandler.cs b/src/DotNetOpenAuth/Messaging/StandardWebRequestHandler.cs deleted file mode 100644 index a5725cd..0000000 --- a/src/DotNetOpenAuth/Messaging/StandardWebRequestHandler.cs +++ /dev/null @@ -1,249 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="StandardWebRequestHandler.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.Messaging { - using System; - using System.Diagnostics; - using System.Diagnostics.Contracts; - using System.IO; - using System.Net; - using System.Net.Sockets; - using System.Reflection; - using DotNetOpenAuth.Messaging; - - /// <summary> - /// The default handler for transmitting <see cref="HttpWebRequest"/> instances - /// and returning the responses. - /// </summary> - public class StandardWebRequestHandler : IDirectWebRequestHandler { - /// <summary> - /// The set of options this web request handler supports. - /// </summary> - private const DirectWebRequestOptions SupportedOptions = DirectWebRequestOptions.AcceptAllHttpResponses; - - /// <summary> - /// The value to use for the User-Agent HTTP header. - /// </summary> - private static string userAgentValue = Assembly.GetExecutingAssembly().GetName().Name + "/" + Assembly.GetExecutingAssembly().GetName().Version; - - #region IWebRequestHandler Members - - /// <summary> - /// Determines whether this instance can support the specified options. - /// </summary> - /// <param name="options">The set of options that might be given in a subsequent web request.</param> - /// <returns> - /// <c>true</c> if this instance can support the specified options; otherwise, <c>false</c>. - /// </returns> - [Pure] - public bool CanSupport(DirectWebRequestOptions options) { - return (options & ~SupportedOptions) == 0; - } - - /// <summary> - /// Prepares an <see cref="HttpWebRequest"/> that contains an POST entity for sending the entity. - /// </summary> - /// <param name="request">The <see cref="HttpWebRequest"/> that should contain the entity.</param> - /// <returns> - /// The writer the caller should write out the entity data to. - /// </returns> - /// <exception cref="ProtocolException">Thrown for any network error.</exception> - /// <remarks> - /// <para>The caller should have set the <see cref="HttpWebRequest.ContentLength"/> - /// and any other appropriate properties <i>before</i> calling this method.</para> - /// <para>Implementations should catch <see cref="WebException"/> and wrap it in a - /// <see cref="ProtocolException"/> to abstract away the transport and provide - /// a single exception type for hosts to catch.</para> - /// </remarks> - public Stream GetRequestStream(HttpWebRequest request) { - return this.GetRequestStream(request, DirectWebRequestOptions.None); - } - - /// <summary> - /// Prepares an <see cref="HttpWebRequest"/> that contains an POST entity for sending the entity. - /// </summary> - /// <param name="request">The <see cref="HttpWebRequest"/> that should contain the entity.</param> - /// <param name="options">The options to apply to this web request.</param> - /// <returns> - /// The writer the caller should write out the entity data to. - /// </returns> - /// <exception cref="ProtocolException">Thrown for any network error.</exception> - /// <remarks> - /// <para>The caller should have set the <see cref="HttpWebRequest.ContentLength"/> - /// and any other appropriate properties <i>before</i> calling this method.</para> - /// <para>Implementations should catch <see cref="WebException"/> and wrap it in a - /// <see cref="ProtocolException"/> to abstract away the transport and provide - /// a single exception type for hosts to catch.</para> - /// </remarks> - public Stream GetRequestStream(HttpWebRequest request, DirectWebRequestOptions options) { - return GetRequestStreamCore(request); - } - - /// <summary> - /// Processes an <see cref="HttpWebRequest"/> and converts the - /// <see cref="HttpWebResponse"/> to a <see cref="IncomingWebResponse"/> instance. - /// </summary> - /// <param name="request">The <see cref="HttpWebRequest"/> to handle.</param> - /// <returns> - /// An instance of <see cref="IncomingWebResponse"/> describing the response. - /// </returns> - /// <exception cref="ProtocolException">Thrown for any network error.</exception> - /// <remarks> - /// <para>Implementations should catch <see cref="WebException"/> and wrap it in a - /// <see cref="ProtocolException"/> to abstract away the transport and provide - /// a single exception type for hosts to catch. The <see cref="WebException.Response"/> - /// value, if set, should be Closed before throwing.</para> - /// </remarks> - public IncomingWebResponse GetResponse(HttpWebRequest request) { - return this.GetResponse(request, DirectWebRequestOptions.None); - } - - /// <summary> - /// Processes an <see cref="HttpWebRequest"/> and converts the - /// <see cref="HttpWebResponse"/> to a <see cref="IncomingWebResponse"/> instance. - /// </summary> - /// <param name="request">The <see cref="HttpWebRequest"/> to handle.</param> - /// <param name="options">The options to apply to this web request.</param> - /// <returns> - /// An instance of <see cref="IncomingWebResponse"/> describing the response. - /// </returns> - /// <exception cref="ProtocolException">Thrown for any network error.</exception> - /// <remarks> - /// <para>Implementations should catch <see cref="WebException"/> and wrap it in a - /// <see cref="ProtocolException"/> to abstract away the transport and provide - /// a single exception type for hosts to catch. The <see cref="WebException.Response"/> - /// value, if set, should be Closed before throwing.</para> - /// </remarks> - public IncomingWebResponse GetResponse(HttpWebRequest request, DirectWebRequestOptions options) { - // This request MAY have already been prepared by GetRequestStream, but - // we have no guarantee, so do it just to be safe. - PrepareRequest(request, false); - - try { - Logger.Http.DebugFormat("HTTP {0} {1}", request.Method, request.RequestUri); - HttpWebResponse response = (HttpWebResponse)request.GetResponse(); - return new NetworkDirectWebResponse(request.RequestUri, response); - } catch (WebException ex) { - HttpWebResponse response = (HttpWebResponse)ex.Response; - if (response != null && response.StatusCode == HttpStatusCode.ExpectationFailed && - request.ServicePoint.Expect100Continue) { - // Some OpenID servers doesn't understand the Expect header and send 417 error back. - // If this server just failed from that, alter the ServicePoint for this server - // so that we don't send that header again next time (whenever that is). - // "Expect: 100-Continue" HTTP header. (see Google Code Issue 72) - // We don't want to blindly set all ServicePoints to not use the Expect header - // as that would be a security hole allowing any visitor to a web site change - // the web site's global behavior when calling that host. - Logger.Http.InfoFormat("HTTP POST to {0} resulted in 417 Expectation Failed. Changing ServicePoint to not use Expect: Continue next time.", request.RequestUri); - request.ServicePoint.Expect100Continue = false; // TODO: investigate that CAS may throw here - - // An alternative to ServicePoint if we don't have permission to set that, - // but we'd have to set it BEFORE each request. - ////request.Expect = ""; - } - - if ((options & DirectWebRequestOptions.AcceptAllHttpResponses) != 0 && response != null && - response.StatusCode != HttpStatusCode.ExpectationFailed) { - Logger.Http.InfoFormat("The HTTP error code {0} {1} is being accepted because the {2} flag is set.", (int)response.StatusCode, response.StatusCode, DirectWebRequestOptions.AcceptAllHttpResponses); - return new NetworkDirectWebResponse(request.RequestUri, response); - } - - if (Logger.Http.IsErrorEnabled) { - if (response != null) { - using (var reader = new StreamReader(ex.Response.GetResponseStream())) { - Logger.Http.ErrorFormat("WebException from {0}: {1}{2}", ex.Response.ResponseUri, Environment.NewLine, reader.ReadToEnd()); - } - } else { - Logger.Http.ErrorFormat("WebException {1} from {0}, no response available.", request.RequestUri, ex.Status); - } - } - - // Be sure to close the response stream to conserve resources and avoid - // filling up all our incoming pipes and denying future requests. - // If in the future, some callers actually want to read this response - // we'll need to figure out how to reliably call Close on exception - // responses at all callers. - if (response != null) { - response.Close(); - } - - throw ErrorUtilities.Wrap(ex, MessagingStrings.ErrorInRequestReplyMessage); - } - } - - #endregion - - /// <summary> - /// Determines whether an exception was thrown because of the remote HTTP server returning HTTP 417 Expectation Failed. - /// </summary> - /// <param name="ex">The caught exception.</param> - /// <returns> - /// <c>true</c> if the failure was originally caused by a 417 Exceptation Failed error; otherwise, <c>false</c>. - /// </returns> - internal static bool IsExceptionFrom417ExpectationFailed(Exception ex) { - while (ex != null) { - WebException webEx = ex as WebException; - if (webEx != null) { - HttpWebResponse response = webEx.Response as HttpWebResponse; - if (response != null) { - if (response.StatusCode == HttpStatusCode.ExpectationFailed) { - return true; - } - } - } - - ex = ex.InnerException; - } - - return false; - } - - /// <summary> - /// Initiates a POST request and prepares for sending data. - /// </summary> - /// <param name="request">The HTTP request with information about the remote party to contact.</param> - /// <returns> - /// The stream where the POST entity can be written. - /// </returns> - private static Stream GetRequestStreamCore(HttpWebRequest request) { - PrepareRequest(request, true); - - try { - return request.GetRequestStream(); - } catch (SocketException ex) { - throw ErrorUtilities.Wrap(ex, MessagingStrings.WebRequestFailed, request.RequestUri); - } catch (WebException ex) { - throw ErrorUtilities.Wrap(ex, MessagingStrings.WebRequestFailed, request.RequestUri); - } - } - - /// <summary> - /// Prepares an HTTP request. - /// </summary> - /// <param name="request">The request.</param> - /// <param name="preparingPost"><c>true</c> if this is a POST request whose headers have not yet been sent out; <c>false</c> otherwise.</param> - private static void PrepareRequest(HttpWebRequest request, bool preparingPost) { - Contract.Requires<ArgumentNullException>(request != null); - - // Be careful to not try to change the HTTP headers that have already gone out. - if (preparingPost || request.Method == "GET") { - // Set/override a few properties of the request to apply our policies for requests. - if (Debugger.IsAttached) { - // Since a debugger is attached, requests may be MUCH slower, - // so give ourselves huge timeouts. - request.ReadWriteTimeout = (int)TimeSpan.FromHours(1).TotalMilliseconds; - request.Timeout = (int)TimeSpan.FromHours(1).TotalMilliseconds; - } - - // Some sites, such as Technorati, return 403 Forbidden on identity - // pages unless a User-Agent header is included. - if (string.IsNullOrEmpty(request.UserAgent)) { - request.UserAgent = userAgentValue; - } - } - } - } -} diff --git a/src/DotNetOpenAuth/Messaging/UntrustedWebRequestHandler.cs b/src/DotNetOpenAuth/Messaging/UntrustedWebRequestHandler.cs deleted file mode 100644 index 838b7e8..0000000 --- a/src/DotNetOpenAuth/Messaging/UntrustedWebRequestHandler.cs +++ /dev/null @@ -1,476 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="UntrustedWebRequestHandler.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.Messaging { - using System; - using System.Collections.Generic; - using System.Diagnostics; - using System.Diagnostics.CodeAnalysis; - using System.Diagnostics.Contracts; - using System.Globalization; - using System.IO; - using System.Net; - using System.Net.Cache; - using System.Text.RegularExpressions; - using DotNetOpenAuth.Configuration; - using DotNetOpenAuth.Messaging; - - /// <summary> - /// A paranoid HTTP get/post request engine. It helps to protect against attacks from remote - /// server leaving dangling connections, sending too much data, causing requests against - /// internal servers, etc. - /// </summary> - /// <remarks> - /// Protections include: - /// * Conservative maximum time to receive the complete response. - /// * Only HTTP and HTTPS schemes are permitted. - /// * Internal IP address ranges are not permitted: 127.*.*.*, 1::* - /// * Internal host names are not permitted (periods must be found in the host name) - /// If a particular host would be permitted but is in the blacklist, it is not allowed. - /// If a particular host would not be permitted but is in the whitelist, it is allowed. - /// </remarks> - public class UntrustedWebRequestHandler : IDirectWebRequestHandler { - /// <summary> - /// The set of URI schemes allowed in untrusted web requests. - /// </summary> - private ICollection<string> allowableSchemes = new List<string> { "http", "https" }; - - /// <summary> - /// The collection of blacklisted hosts. - /// </summary> - private ICollection<string> blacklistHosts = new List<string>(Configuration.BlacklistHosts.KeysAsStrings); - - /// <summary> - /// The collection of regular expressions used to identify additional blacklisted hosts. - /// </summary> - private ICollection<Regex> blacklistHostsRegex = new List<Regex>(Configuration.BlacklistHostsRegex.KeysAsRegexs); - - /// <summary> - /// The collection of whitelisted hosts. - /// </summary> - private ICollection<string> whitelistHosts = new List<string>(Configuration.WhitelistHosts.KeysAsStrings); - - /// <summary> - /// The collection of regular expressions used to identify additional whitelisted hosts. - /// </summary> - private ICollection<Regex> whitelistHostsRegex = new List<Regex>(Configuration.WhitelistHostsRegex.KeysAsRegexs); - - /// <summary> - /// The maximum redirections to follow in the course of a single request. - /// </summary> - [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private int maximumRedirections = Configuration.MaximumRedirections; - - /// <summary> - /// The maximum number of bytes to read from the response of an untrusted server. - /// </summary> - [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private int maximumBytesToRead = Configuration.MaximumBytesToRead; - - /// <summary> - /// The handler that will actually send the HTTP request and collect - /// the response once the untrusted server gates have been satisfied. - /// </summary> - private IDirectWebRequestHandler chainedWebRequestHandler; - - /// <summary> - /// Initializes a new instance of the <see cref="UntrustedWebRequestHandler"/> class. - /// </summary> - public UntrustedWebRequestHandler() - : this(new StandardWebRequestHandler()) { - } - - /// <summary> - /// Initializes a new instance of the <see cref="UntrustedWebRequestHandler"/> class. - /// </summary> - /// <param name="chainedWebRequestHandler">The chained web request handler.</param> - public UntrustedWebRequestHandler(IDirectWebRequestHandler chainedWebRequestHandler) { - Contract.Requires<ArgumentNullException>(chainedWebRequestHandler != null); - - this.chainedWebRequestHandler = chainedWebRequestHandler; - if (Debugger.IsAttached) { - // Since a debugger is attached, requests may be MUCH slower, - // so give ourselves huge timeouts. - this.ReadWriteTimeout = TimeSpan.FromHours(1); - this.Timeout = TimeSpan.FromHours(1); - } else { - this.ReadWriteTimeout = Configuration.ReadWriteTimeout; - this.Timeout = Configuration.Timeout; - } - } - - /// <summary> - /// Gets or sets the default maximum bytes to read in any given HTTP request. - /// </summary> - /// <value>Default is 1MB. Cannot be less than 2KB.</value> - public int MaximumBytesToRead { - get { - return this.maximumBytesToRead; - } - - set { - Contract.Requires<ArgumentOutOfRangeException>(value >= 2048); - this.maximumBytesToRead = value; - } - } - - /// <summary> - /// Gets or sets the total number of redirections to allow on any one request. - /// Default is 10. - /// </summary> - public int MaximumRedirections { - get { - return this.maximumRedirections; - } - - set { - Contract.Requires<ArgumentOutOfRangeException>(value >= 0); - this.maximumRedirections = value; - } - } - - /// <summary> - /// Gets or sets the time allowed to wait for single read or write operation to complete. - /// Default is 500 milliseconds. - /// </summary> - public TimeSpan ReadWriteTimeout { get; set; } - - /// <summary> - /// Gets or sets the time allowed for an entire HTTP request. - /// Default is 5 seconds. - /// </summary> - public TimeSpan Timeout { get; set; } - - /// <summary> - /// Gets a collection of host name literals that should be allowed even if they don't - /// pass standard security checks. - /// </summary> - [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Whitelist", Justification = "Spelling as intended.")] - public ICollection<string> WhitelistHosts { get { return this.whitelistHosts; } } - - /// <summary> - /// Gets a collection of host name regular expressions that indicate hosts that should - /// be allowed even though they don't pass standard security checks. - /// </summary> - [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Whitelist", Justification = "Spelling as intended.")] - public ICollection<Regex> WhitelistHostsRegex { get { return this.whitelistHostsRegex; } } - - /// <summary> - /// Gets a collection of host name literals that should be rejected even if they - /// pass standard security checks. - /// </summary> - public ICollection<string> BlacklistHosts { get { return this.blacklistHosts; } } - - /// <summary> - /// Gets a collection of host name regular expressions that indicate hosts that should - /// be rejected even if they pass standard security checks. - /// </summary> - public ICollection<Regex> BlacklistHostsRegex { get { return this.blacklistHostsRegex; } } - - /// <summary> - /// Gets the configuration for this class that is specified in the host's .config file. - /// </summary> - private static UntrustedWebRequestElement Configuration { - get { return DotNetOpenAuthSection.Configuration.Messaging.UntrustedWebRequest; } - } - - #region IDirectWebRequestHandler Members - - /// <summary> - /// Determines whether this instance can support the specified options. - /// </summary> - /// <param name="options">The set of options that might be given in a subsequent web request.</param> - /// <returns> - /// <c>true</c> if this instance can support the specified options; otherwise, <c>false</c>. - /// </returns> - [Pure] - public bool CanSupport(DirectWebRequestOptions options) { - // We support whatever our chained handler supports, plus RequireSsl. - return this.chainedWebRequestHandler.CanSupport(options & ~DirectWebRequestOptions.RequireSsl); - } - - /// <summary> - /// Prepares an <see cref="HttpWebRequest"/> that contains an POST entity for sending the entity. - /// </summary> - /// <param name="request">The <see cref="HttpWebRequest"/> that should contain the entity.</param> - /// <param name="options">The options to apply to this web request.</param> - /// <returns> - /// The writer the caller should write out the entity data to. - /// </returns> - /// <exception cref="ProtocolException">Thrown for any network error.</exception> - /// <remarks> - /// <para>The caller should have set the <see cref="HttpWebRequest.ContentLength"/> - /// and any other appropriate properties <i>before</i> calling this method.</para> - /// <para>Implementations should catch <see cref="WebException"/> and wrap it in a - /// <see cref="ProtocolException"/> to abstract away the transport and provide - /// a single exception type for hosts to catch.</para> - /// </remarks> - public Stream GetRequestStream(HttpWebRequest request, DirectWebRequestOptions options) { - this.EnsureAllowableRequestUri(request.RequestUri, (options & DirectWebRequestOptions.RequireSsl) != 0); - - this.PrepareRequest(request, true); - - // Submit the request and get the request stream back. - return this.chainedWebRequestHandler.GetRequestStream(request, options & ~DirectWebRequestOptions.RequireSsl); - } - - /// <summary> - /// Processes an <see cref="HttpWebRequest"/> and converts the - /// <see cref="HttpWebResponse"/> to a <see cref="IncomingWebResponse"/> instance. - /// </summary> - /// <param name="request">The <see cref="HttpWebRequest"/> to handle.</param> - /// <param name="options">The options to apply to this web request.</param> - /// <returns> - /// An instance of <see cref="CachedDirectWebResponse"/> describing the response. - /// </returns> - /// <exception cref="ProtocolException">Thrown for any network error.</exception> - /// <remarks> - /// <para>Implementations should catch <see cref="WebException"/> and wrap it in a - /// <see cref="ProtocolException"/> to abstract away the transport and provide - /// a single exception type for hosts to catch. The <see cref="WebException.Response"/> - /// value, if set, should be Closed before throwing.</para> - /// </remarks> - [SuppressMessage("Microsoft.Usage", "CA2234:PassSystemUriObjectsInsteadOfStrings", Justification = "Uri(Uri, string) accepts second arguments that Uri(Uri, new Uri(string)) does not that we must support.")] - public IncomingWebResponse GetResponse(HttpWebRequest request, DirectWebRequestOptions options) { - // This request MAY have already been prepared by GetRequestStream, but - // we have no guarantee, so do it just to be safe. - this.PrepareRequest(request, false); - - // Since we may require SSL for every redirect, we handle each redirect manually - // in order to detect and fail if any redirect sends us to an HTTP url. - // We COULD allow automatic redirect in the cases where HTTPS is not required, - // but our mock request infrastructure can't do redirects on its own either. - Uri originalRequestUri = request.RequestUri; - int i; - for (i = 0; i < this.MaximumRedirections; i++) { - this.EnsureAllowableRequestUri(request.RequestUri, (options & DirectWebRequestOptions.RequireSsl) != 0); - CachedDirectWebResponse response = this.chainedWebRequestHandler.GetResponse(request, options & ~DirectWebRequestOptions.RequireSsl).GetSnapshot(this.MaximumBytesToRead); - if (response.Status == HttpStatusCode.MovedPermanently || - response.Status == HttpStatusCode.Redirect || - response.Status == HttpStatusCode.RedirectMethod || - response.Status == HttpStatusCode.RedirectKeepVerb) { - // We have no copy of the post entity stream to repeat on our manually - // cloned HttpWebRequest, so we have to bail. - ErrorUtilities.VerifyProtocol(request.Method != "POST", MessagingStrings.UntrustedRedirectsOnPOSTNotSupported); - Uri redirectUri = new Uri(response.FinalUri, response.Headers[HttpResponseHeader.Location]); - request = request.Clone(redirectUri); - } else { - if (response.FinalUri != request.RequestUri) { - // Since we don't automatically follow redirects, there's only one scenario where this - // can happen: when the server sends a (non-redirecting) Content-Location header in the response. - // It's imperative that we do not trust that header though, so coerce the FinalUri to be - // what we just requested. - Logger.Http.WarnFormat("The response from {0} included an HTTP header indicating it's the same as {1}, but it's not a redirect so we won't trust that.", request.RequestUri, response.FinalUri); - response.FinalUri = request.RequestUri; - } - - return response; - } - } - - throw ErrorUtilities.ThrowProtocol(MessagingStrings.TooManyRedirects, originalRequestUri); - } - - /// <summary> - /// Prepares an <see cref="HttpWebRequest"/> that contains an POST entity for sending the entity. - /// </summary> - /// <param name="request">The <see cref="HttpWebRequest"/> that should contain the entity.</param> - /// <returns> - /// The writer the caller should write out the entity data to. - /// </returns> - Stream IDirectWebRequestHandler.GetRequestStream(HttpWebRequest request) { - return this.GetRequestStream(request, DirectWebRequestOptions.None); - } - - /// <summary> - /// Processes an <see cref="HttpWebRequest"/> and converts the - /// <see cref="HttpWebResponse"/> to a <see cref="IncomingWebResponse"/> instance. - /// </summary> - /// <param name="request">The <see cref="HttpWebRequest"/> to handle.</param> - /// <returns> - /// An instance of <see cref="IncomingWebResponse"/> describing the response. - /// </returns> - /// <exception cref="ProtocolException">Thrown for any network error.</exception> - /// <remarks> - /// <para>Implementations should catch <see cref="WebException"/> and wrap it in a - /// <see cref="ProtocolException"/> to abstract away the transport and provide - /// a single exception type for hosts to catch. The <see cref="WebException.Response"/> - /// value, if set, should be Closed before throwing.</para> - /// </remarks> - IncomingWebResponse IDirectWebRequestHandler.GetResponse(HttpWebRequest request) { - return this.GetResponse(request, DirectWebRequestOptions.None); - } - - #endregion - - /// <summary> - /// Determines whether an IP address is the IPv6 equivalent of "localhost/127.0.0.1". - /// </summary> - /// <param name="ip">The ip address to check.</param> - /// <returns> - /// <c>true</c> if this is a loopback IP address; <c>false</c> otherwise. - /// </returns> - private static bool IsIPv6Loopback(IPAddress ip) { - Contract.Requires<ArgumentNullException>(ip != null); - byte[] addressBytes = ip.GetAddressBytes(); - for (int i = 0; i < addressBytes.Length - 1; i++) { - if (addressBytes[i] != 0) { - return false; - } - } - if (addressBytes[addressBytes.Length - 1] != 1) { - return false; - } - return true; - } - - /// <summary> - /// Determines whether the given host name is in a host list or host name regex list. - /// </summary> - /// <param name="host">The host name.</param> - /// <param name="stringList">The list of host names.</param> - /// <param name="regexList">The list of regex patterns of host names.</param> - /// <returns> - /// <c>true</c> if the specified host falls within at least one of the given lists; otherwise, <c>false</c>. - /// </returns> - private static bool IsHostInList(string host, ICollection<string> stringList, ICollection<Regex> regexList) { - Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(host)); - Contract.Requires<ArgumentNullException>(stringList != null); - Contract.Requires<ArgumentNullException>(regexList != null); - foreach (string testHost in stringList) { - if (string.Equals(host, testHost, StringComparison.OrdinalIgnoreCase)) { - return true; - } - } - foreach (Regex regex in regexList) { - if (regex.IsMatch(host)) { - return true; - } - } - return false; - } - - /// <summary> - /// Determines whether a given host is whitelisted. - /// </summary> - /// <param name="host">The host name to test.</param> - /// <returns> - /// <c>true</c> if the host is whitelisted; otherwise, <c>false</c>. - /// </returns> - private bool IsHostWhitelisted(string host) { - return IsHostInList(host, this.WhitelistHosts, this.WhitelistHostsRegex); - } - - /// <summary> - /// Determines whether a given host is blacklisted. - /// </summary> - /// <param name="host">The host name to test.</param> - /// <returns> - /// <c>true</c> if the host is blacklisted; otherwise, <c>false</c>. - /// </returns> - private bool IsHostBlacklisted(string host) { - return IsHostInList(host, this.BlacklistHosts, this.BlacklistHostsRegex); - } - - /// <summary> - /// Verify that the request qualifies under our security policies - /// </summary> - /// <param name="requestUri">The request URI.</param> - /// <param name="requireSsl">If set to <c>true</c>, only web requests that can be made entirely over SSL will succeed.</param> - /// <exception cref="ProtocolException">Thrown when the URI is disallowed for security reasons.</exception> - private void EnsureAllowableRequestUri(Uri requestUri, bool requireSsl) { - ErrorUtilities.VerifyProtocol(this.IsUriAllowable(requestUri), MessagingStrings.UnsafeWebRequestDetected, requestUri); - ErrorUtilities.VerifyProtocol(!requireSsl || String.Equals(requestUri.Scheme, Uri.UriSchemeHttps, StringComparison.OrdinalIgnoreCase), MessagingStrings.InsecureWebRequestWithSslRequired, requestUri); - } - - /// <summary> - /// Determines whether a URI is allowed based on scheme and host name. - /// No requireSSL check is done here - /// </summary> - /// <param name="uri">The URI to test for whether it should be allowed.</param> - /// <returns> - /// <c>true</c> if [is URI allowable] [the specified URI]; otherwise, <c>false</c>. - /// </returns> - private bool IsUriAllowable(Uri uri) { - Contract.Requires<ArgumentNullException>(uri != null); - if (!this.allowableSchemes.Contains(uri.Scheme)) { - Logger.Http.WarnFormat("Rejecting URL {0} because it uses a disallowed scheme.", uri); - return false; - } - - // Allow for whitelist or blacklist to override our detection. - Func<string, bool> failsUnlessWhitelisted = (string reason) => { - if (IsHostWhitelisted(uri.DnsSafeHost)) { - return true; - } - Logger.Http.WarnFormat("Rejecting URL {0} because {1}.", uri, reason); - return false; - }; - - // Try to interpret the hostname as an IP address so we can test for internal - // IP address ranges. Note that IP addresses can appear in many forms - // (e.g. http://127.0.0.1, http://2130706433, http://0x0100007f, http://::1 - // So we convert them to a canonical IPAddress instance, and test for all - // non-routable IP ranges: 10.*.*.*, 127.*.*.*, ::1 - // Note that Uri.IsLoopback is very unreliable, not catching many of these variants. - IPAddress hostIPAddress; - if (IPAddress.TryParse(uri.DnsSafeHost, out hostIPAddress)) { - byte[] addressBytes = hostIPAddress.GetAddressBytes(); - - // The host is actually an IP address. - switch (hostIPAddress.AddressFamily) { - case System.Net.Sockets.AddressFamily.InterNetwork: - if (addressBytes[0] == 127 || addressBytes[0] == 10) { - return failsUnlessWhitelisted("it is a loopback address."); - } - break; - case System.Net.Sockets.AddressFamily.InterNetworkV6: - if (IsIPv6Loopback(hostIPAddress)) { - return failsUnlessWhitelisted("it is a loopback address."); - } - break; - default: - return failsUnlessWhitelisted("it does not use an IPv4 or IPv6 address."); - } - } else { - // The host is given by name. We require names to contain periods to - // help make sure it's not an internal address. - if (!uri.Host.Contains(".")) { - return failsUnlessWhitelisted("it does not contain a period in the host name."); - } - } - if (this.IsHostBlacklisted(uri.DnsSafeHost)) { - Logger.Http.WarnFormat("Rejected URL {0} because it is blacklisted.", uri); - return false; - } - return true; - } - - /// <summary> - /// Prepares the request by setting timeout and redirect policies. - /// </summary> - /// <param name="request">The request to prepare.</param> - /// <param name="preparingPost"><c>true</c> if this is a POST request whose headers have not yet been sent out; <c>false</c> otherwise.</param> - private void PrepareRequest(HttpWebRequest request, bool preparingPost) { - Contract.Requires<ArgumentNullException>(request != null); - - // Be careful to not try to change the HTTP headers that have already gone out. - if (preparingPost || request.Method == "GET") { - // Set/override a few properties of the request to apply our policies for untrusted requests. - request.ReadWriteTimeout = (int)this.ReadWriteTimeout.TotalMilliseconds; - request.Timeout = (int)this.Timeout.TotalMilliseconds; - request.KeepAlive = false; - } - - // If SSL is required throughout, we cannot allow auto redirects because - // it may include a pass through an unprotected HTTP request. - // We have to follow redirects manually. - // It also allows us to ignore HttpWebResponse.FinalUri since that can be affected by - // the Content-Location header and open security holes. - request.AllowAutoRedirect = false; - } - } -} diff --git a/src/DotNetOpenAuth/Messaging/UriStyleMessageFormatter.cs b/src/DotNetOpenAuth/Messaging/UriStyleMessageFormatter.cs deleted file mode 100644 index 9bffc3d..0000000 --- a/src/DotNetOpenAuth/Messaging/UriStyleMessageFormatter.cs +++ /dev/null @@ -1,77 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="UriStyleMessageFormatter.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.Messaging { - using System; - using System.Collections.Generic; - using System.Diagnostics.Contracts; - using System.Linq; - using System.Security.Cryptography; - using System.Text; - using System.Web; - using DotNetOpenAuth.Messaging; - using DotNetOpenAuth.Messaging.Bindings; - using DotNetOpenAuth.Messaging.Reflection; - - /// <summary> - /// A serializer for <see cref="DataBag"/>-derived types - /// </summary> - /// <typeparam name="T">The DataBag-derived type that is to be serialized/deserialized.</typeparam> - internal class UriStyleMessageFormatter<T> : DataBagFormatterBase<T> where T : DataBag, new() { - /// <summary> - /// Initializes a new instance of the <see cref="UriStyleMessageFormatter<T>"/> class. - /// </summary> - /// <param name="signingKey">The crypto service provider with the asymmetric key to use for signing or verifying the token.</param> - /// <param name="encryptingKey">The crypto service provider with the asymmetric key to use for encrypting or decrypting the token.</param> - /// <param name="compressed">A value indicating whether the data in this instance will be GZip'd.</param> - /// <param name="maximumAge">The maximum age of a token that can be decoded; useful only when <paramref name="decodeOnceOnly"/> is <c>true</c>.</param> - /// <param name="decodeOnceOnly">The nonce store to use to ensure that this instance is only decoded once.</param> - protected internal UriStyleMessageFormatter(RSACryptoServiceProvider signingKey = null, RSACryptoServiceProvider encryptingKey = null, bool compressed = false, TimeSpan? maximumAge = null, INonceStore decodeOnceOnly = null) - : base(signingKey, encryptingKey, compressed, maximumAge, decodeOnceOnly) { - } - - /// <summary> - /// Initializes a new instance of the <see cref="UriStyleMessageFormatter<T>"/> class. - /// </summary> - /// <param name="cryptoKeyStore">The crypto key store used when signing or encrypting.</param> - /// <param name="bucket">The bucket in which symmetric keys are stored for signing/encrypting data.</param> - /// <param name="signed">A value indicating whether the data in this instance will be protected against tampering.</param> - /// <param name="encrypted">A value indicating whether the data in this instance will be protected against eavesdropping.</param> - /// <param name="compressed">A value indicating whether the data in this instance will be GZip'd.</param> - /// <param name="minimumAge">The minimum age.</param> - /// <param name="maximumAge">The maximum age of a token that can be decoded; useful only when <paramref name="decodeOnceOnly"/> is <c>true</c>.</param> - /// <param name="decodeOnceOnly">The nonce store to use to ensure that this instance is only decoded once.</param> - protected internal UriStyleMessageFormatter(ICryptoKeyStore cryptoKeyStore = null, string bucket = null, bool signed = false, bool encrypted = false, bool compressed = false, TimeSpan? minimumAge = null, TimeSpan? maximumAge = null, INonceStore decodeOnceOnly = null) - : base(cryptoKeyStore, bucket, signed, encrypted, compressed, minimumAge, maximumAge, decodeOnceOnly) { - Contract.Requires<ArgumentException>((cryptoKeyStore != null && !String.IsNullOrEmpty(bucket)) || (!signed && !encrypted)); - } - - /// <summary> - /// Serializes the <see cref="DataBag"/> instance to a buffer. - /// </summary> - /// <param name="message">The message.</param> - /// <returns>The buffer containing the serialized data.</returns> - protected override byte[] SerializeCore(T message) { - var fields = MessageSerializer.Get(message.GetType()).Serialize(MessageDescriptions.GetAccessor(message)); - string value = MessagingUtilities.CreateQueryString(fields); - return Encoding.UTF8.GetBytes(value); - } - - /// <summary> - /// Deserializes the <see cref="DataBag"/> instance from a buffer. - /// </summary> - /// <param name="message">The message instance to initialize with data from the buffer.</param> - /// <param name="data">The data buffer.</param> - protected override void DeserializeCore(T message, byte[] data) { - string value = Encoding.UTF8.GetString(data); - - // Deserialize into message newly created instance. - var serializer = MessageSerializer.Get(message.GetType()); - var fields = MessageDescriptions.GetAccessor(message); - serializer.Deserialize(HttpUtility.ParseQueryString(value).ToDictionary(), fields); - } - } -} diff --git a/src/DotNetOpenAuth/Mvc/OpenIdHelper.cs b/src/DotNetOpenAuth/Mvc/OpenIdHelper.cs deleted file mode 100644 index adde6b6..0000000 --- a/src/DotNetOpenAuth/Mvc/OpenIdHelper.cs +++ /dev/null @@ -1,431 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="OpenIdHelper.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.Mvc { - using System; - using System.Collections.Generic; - using System.Diagnostics.CodeAnalysis; - using System.Diagnostics.Contracts; - using System.Globalization; - using System.IO; - using System.Linq; - using System.Text; - using System.Web; - using System.Web.Mvc; - using System.Web.Routing; - using System.Web.UI; - using DotNetOpenAuth.Configuration; - using DotNetOpenAuth.Messaging; - using DotNetOpenAuth.OpenId; - using DotNetOpenAuth.OpenId.RelyingParty; - - /// <summary> - /// Methods that generate HTML or Javascript for hosting AJAX OpenID "controls" on - /// ASP.NET MVC web sites. - /// </summary> - public static class OpenIdHelper { - /// <summary> - /// Emits a series of stylesheet import tags to support the AJAX OpenID Selector. - /// </summary> - /// <param name="html">The <see cref="HtmlHelper"/> on the view.</param> - /// <returns>HTML that should be sent directly to the browser.</returns> - public static string OpenIdSelectorStyles(this HtmlHelper html) { - Contract.Requires<ArgumentNullException>(html != null); - Contract.Ensures(Contract.Result<string>() != null); - - using (var result = new StringWriter(CultureInfo.CurrentCulture)) { - result.WriteStylesheetLink(OpenId.RelyingParty.OpenIdSelector.EmbeddedStylesheetResourceName); - result.WriteStylesheetLink(OpenId.RelyingParty.OpenIdAjaxTextBox.EmbeddedStylesheetResourceName); - return result.ToString(); - } - } - - /// <summary> - /// Emits a series of script import tags and some inline script to support the AJAX OpenID Selector. - /// </summary> - /// <param name="html">The <see cref="HtmlHelper"/> on the view.</param> - /// <returns>HTML that should be sent directly to the browser.</returns> - public static string OpenIdSelectorScripts(this HtmlHelper html) { - return OpenIdSelectorScripts(html, null, null); - } - - /// <summary> - /// Emits a series of script import tags and some inline script to support the AJAX OpenID Selector. - /// </summary> - /// <param name="html">The <see cref="HtmlHelper"/> on the view.</param> - /// <param name="selectorOptions">An optional instance of an <see cref="OpenIdSelector"/> control, whose properties have been customized to express how this MVC control should be rendered.</param> - /// <param name="additionalOptions">An optional set of additional script customizations.</param> - /// <returns> - /// HTML that should be sent directly to the browser. - /// </returns> - [SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "False positive")] - public static string OpenIdSelectorScripts(this HtmlHelper html, OpenIdSelector selectorOptions, OpenIdAjaxOptions additionalOptions) { - Contract.Requires<ArgumentNullException>(html != null); - Contract.Ensures(Contract.Result<string>() != null); - - bool selectorOptionsOwned = false; - if (selectorOptions == null) { - selectorOptionsOwned = true; - selectorOptions = new OpenId.RelyingParty.OpenIdSelector(); - } - try { - if (additionalOptions == null) { - additionalOptions = new OpenIdAjaxOptions(); - } - - using (StringWriter result = new StringWriter(CultureInfo.CurrentCulture)) { - if (additionalOptions.ShowDiagnosticIFrame || additionalOptions.ShowDiagnosticTrace) { - string scriptFormat = @"window.openid_visible_iframe = {0}; // causes the hidden iframe to show up -window.openid_trace = {1}; // causes lots of messages"; - result.WriteScriptBlock(string.Format( - CultureInfo.InvariantCulture, - scriptFormat, - additionalOptions.ShowDiagnosticIFrame ? "true" : "false", - additionalOptions.ShowDiagnosticTrace ? "true" : "false")); - } - var scriptResources = new[] { - OpenIdRelyingPartyControlBase.EmbeddedJavascriptResource, - OpenIdRelyingPartyAjaxControlBase.EmbeddedAjaxJavascriptResource, - OpenId.RelyingParty.OpenIdAjaxTextBox.EmbeddedScriptResourceName, - }; - result.WriteScriptTags(scriptResources); - - if (selectorOptions.DownloadYahooUILibrary) { - result.WriteScriptTagsUrls(new[] { "https://ajax.googleapis.com/ajax/libs/yui/2.8.0r4/build/yuiloader/yuiloader-min.js" }); - } - - using (var blockBuilder = new StringWriter(CultureInfo.CurrentCulture)) { - if (selectorOptions.DownloadYahooUILibrary) { - blockBuilder.WriteLine(@" try { - if (YAHOO) { - var loader = new YAHOO.util.YUILoader({ - require: ['button', 'menu'], - loadOptional: false, - combine: true - }); - - loader.insert(); - } - } catch (e) { }"); - } - - blockBuilder.WriteLine("window.aspnetapppath = '{0}';", VirtualPathUtility.AppendTrailingSlash(HttpContext.Current.Request.ApplicationPath)); - - // Positive assertions can last no longer than this library is willing to consider them valid, - // and when they come with OP private associations they last no longer than the OP is willing - // to consider them valid. We assume the OP will hold them valid for at least five minutes. - double assertionLifetimeInMilliseconds = Math.Min(TimeSpan.FromMinutes(5).TotalMilliseconds, Math.Min(DotNetOpenAuthSection.Configuration.OpenId.MaxAuthenticationTime.TotalMilliseconds, DotNetOpenAuthSection.Configuration.Messaging.MaximumMessageLifetime.TotalMilliseconds)); - blockBuilder.WriteLine( - "{0} = {1};", - OpenIdRelyingPartyAjaxControlBase.MaxPositiveAssertionLifetimeJsName, - assertionLifetimeInMilliseconds.ToString(CultureInfo.InvariantCulture)); - - if (additionalOptions.PreloadedDiscoveryResults != null) { - blockBuilder.WriteLine(additionalOptions.PreloadedDiscoveryResults); - } - - string discoverUrl = VirtualPathUtility.AppendTrailingSlash(HttpContext.Current.Request.ApplicationPath) + html.RouteCollection["OpenIdDiscover"].GetVirtualPath(html.ViewContext.RequestContext, new RouteValueDictionary(new { identifier = "xxx" })).VirtualPath; - string blockFormat = @" {0} = function (argument, resultFunction, errorCallback) {{ - jQuery.ajax({{ - async: true, - dataType: 'text', - error: function (request, status, error) {{ errorCallback(status, argument); }}, - success: function (result) {{ resultFunction(result, argument); }}, - url: '{1}'.replace('xxx', encodeURIComponent(argument)) - }}); - }};"; - blockBuilder.WriteLine(blockFormat, OpenIdRelyingPartyAjaxControlBase.CallbackJSFunctionAsync, discoverUrl); - - blockFormat = @" window.postLoginAssertion = function (positiveAssertion) {{ - $('#{0}')[0].setAttribute('value', positiveAssertion); - if ($('#{1}')[0] && !$('#{1}')[0].value) {{ // popups have no ReturnUrl predefined, but full page LogOn does. - $('#{1}')[0].setAttribute('value', window.parent.location.href); - }} - document.forms[{2}].submit(); - }};"; - blockBuilder.WriteLine( - blockFormat, - additionalOptions.AssertionHiddenFieldId, - additionalOptions.ReturnUrlHiddenFieldId, - additionalOptions.FormKey); - - blockFormat = @" $(function () {{ - var box = document.getElementsByName('openid_identifier')[0]; - initAjaxOpenId(box, {0}, {1}, {2}, {3}, {4}, {5}, - null, // js function to invoke on receiving a positive assertion - {6}, {7}, {8}, {9}, {10}, {11}, {12}, {13}, {14}, {15}, {16}, {17}, - false, // auto postback - null); // PostBackEventReference (unused in MVC) - }});"; - blockBuilder.WriteLine( - blockFormat, - MessagingUtilities.GetSafeJavascriptValue(Util.GetWebResourceUrl(typeof(OpenIdRelyingPartyControlBase), OpenIdTextBox.EmbeddedLogoResourceName)), - MessagingUtilities.GetSafeJavascriptValue(Util.GetWebResourceUrl(typeof(OpenIdRelyingPartyControlBase), OpenId.RelyingParty.OpenIdAjaxTextBox.EmbeddedSpinnerResourceName)), - MessagingUtilities.GetSafeJavascriptValue(Util.GetWebResourceUrl(typeof(OpenIdRelyingPartyControlBase), OpenId.RelyingParty.OpenIdAjaxTextBox.EmbeddedLoginSuccessResourceName)), - MessagingUtilities.GetSafeJavascriptValue(Util.GetWebResourceUrl(typeof(OpenIdRelyingPartyControlBase), OpenId.RelyingParty.OpenIdAjaxTextBox.EmbeddedLoginFailureResourceName)), - selectorOptions.Throttle, - selectorOptions.Timeout.TotalMilliseconds, - MessagingUtilities.GetSafeJavascriptValue(selectorOptions.TextBox.LogOnText), - MessagingUtilities.GetSafeJavascriptValue(selectorOptions.TextBox.LogOnToolTip), - selectorOptions.TextBox.ShowLogOnPostBackButton ? "true" : "false", - MessagingUtilities.GetSafeJavascriptValue(selectorOptions.TextBox.LogOnPostBackToolTip), - MessagingUtilities.GetSafeJavascriptValue(selectorOptions.TextBox.RetryText), - MessagingUtilities.GetSafeJavascriptValue(selectorOptions.TextBox.RetryToolTip), - MessagingUtilities.GetSafeJavascriptValue(selectorOptions.TextBox.BusyToolTip), - MessagingUtilities.GetSafeJavascriptValue(selectorOptions.TextBox.IdentifierRequiredMessage), - MessagingUtilities.GetSafeJavascriptValue(selectorOptions.TextBox.LogOnInProgressMessage), - MessagingUtilities.GetSafeJavascriptValue(selectorOptions.TextBox.AuthenticationSucceededToolTip), - MessagingUtilities.GetSafeJavascriptValue(selectorOptions.TextBox.AuthenticatedAsToolTip), - MessagingUtilities.GetSafeJavascriptValue(selectorOptions.TextBox.AuthenticationFailedToolTip)); - - result.WriteScriptBlock(blockBuilder.ToString()); - result.WriteScriptTags(OpenId.RelyingParty.OpenIdSelector.EmbeddedScriptResourceName); - - Reporting.RecordFeatureUse("MVC " + typeof(OpenIdSelector).Name); - return result.ToString(); - } - } - } catch { - if (selectorOptionsOwned) { - selectorOptions.Dispose(); - } - - throw; - } - } - - /// <summary> - /// Emits the HTML to render an OpenID Provider button as a part of the overall OpenID Selector UI. - /// </summary> - /// <param name="html">The <see cref="HtmlHelper"/> on the view.</param> - /// <param name="providerIdentifier">The OP Identifier.</param> - /// <param name="imageUrl">The URL of the image to display on the button.</param> - /// <returns> - /// HTML that should be sent directly to the browser. - /// </returns> - public static string OpenIdSelectorOPButton(this HtmlHelper html, Identifier providerIdentifier, string imageUrl) { - Contract.Requires<ArgumentNullException>(html != null); - Contract.Requires<ArgumentNullException>(providerIdentifier != null); - Contract.Requires<ArgumentException>(!string.IsNullOrEmpty(imageUrl)); - Contract.Ensures(Contract.Result<string>() != null); - - return OpenIdSelectorButton(html, providerIdentifier, "OPButton", imageUrl); - } - - /// <summary> - /// Emits the HTML to render a generic OpenID button as a part of the overall OpenID Selector UI, - /// allowing the user to enter their own OpenID. - /// </summary> - /// <param name="html">The <see cref="HtmlHelper"/> on the view.</param> - /// <param name="imageUrl">The URL of the image to display on the button.</param> - /// <returns> - /// HTML that should be sent directly to the browser. - /// </returns> - public static string OpenIdSelectorOpenIdButton(this HtmlHelper html, string imageUrl) { - Contract.Requires<ArgumentNullException>(html != null); - Contract.Requires<ArgumentException>(!string.IsNullOrEmpty(imageUrl)); - Contract.Ensures(Contract.Result<string>() != null); - - return OpenIdSelectorButton(html, "OpenIDButton", "OpenIDButton", imageUrl); - } - - /// <summary> - /// Emits the HTML to render the entire OpenID Selector UI. - /// </summary> - /// <param name="html">The <see cref="HtmlHelper"/> on the view.</param> - /// <param name="buttons">The buttons to include on the selector.</param> - /// <returns> - /// HTML that should be sent directly to the browser. - /// </returns> - [SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times", Justification = "Not a problem for this type.")] - public static string OpenIdSelector(this HtmlHelper html, params SelectorButton[] buttons) { - Contract.Requires<ArgumentNullException>(html != null); - Contract.Requires<ArgumentNullException>(buttons != null); - Contract.Ensures(Contract.Result<string>() != null); - - using (var writer = new StringWriter(CultureInfo.CurrentCulture)) { - using (var h = new HtmlTextWriter(writer)) { - h.AddAttribute(HtmlTextWriterAttribute.Class, "OpenIdProviders"); - h.RenderBeginTag(HtmlTextWriterTag.Ul); - - foreach (SelectorButton button in buttons) { - var op = button as SelectorProviderButton; - if (op != null) { - h.Write(OpenIdSelectorOPButton(html, op.OPIdentifier, op.Image)); - continue; - } - - var openid = button as SelectorOpenIdButton; - if (openid != null) { - h.Write(OpenIdSelectorOpenIdButton(html, openid.Image)); - continue; - } - - ErrorUtilities.VerifySupported(false, "The {0} button is not yet supported for MVC.", button.GetType().Name); - } - - h.RenderEndTag(); // ul - - if (buttons.OfType<SelectorOpenIdButton>().Any()) { - h.Write(OpenIdAjaxTextBox(html)); - } - } - - return writer.ToString(); - } - } - - /// <summary> - /// Emits the HTML to render the <see cref="OpenIdAjaxTextBox"/> control as a part of the overall - /// OpenID Selector UI. - /// </summary> - /// <param name="html">The <see cref="HtmlHelper"/> on the view.</param> - /// <returns> - /// HTML that should be sent directly to the browser. - /// </returns> - [SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "html", Justification = "Breaking change, and it's an extension method so it's useful.")] - public static string OpenIdAjaxTextBox(this HtmlHelper html) { - return @"<div style='display: none' id='OpenIDForm'> - <span class='OpenIdAjaxTextBox' style='display: inline-block; position: relative; font-size: 16px'> - <input name='openid_identifier' id='openid_identifier' size='40' style='padding-left: 18px; border-style: solid; border-width: 1px; border-color: lightgray' /> - </span> - </div>"; - } - - /// <summary> - /// Emits the HTML to render a button as a part of the overall OpenID Selector UI. - /// </summary> - /// <param name="html">The <see cref="HtmlHelper"/> on the view.</param> - /// <param name="id">The value to assign to the HTML id attribute.</param> - /// <param name="cssClass">The value to assign to the HTML class attribute.</param> - /// <param name="imageUrl">The URL of the image to draw on the button.</param> - /// <returns> - /// HTML that should be sent directly to the browser. - /// </returns> - [SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times", Justification = "Not a problem for this type.")] - private static string OpenIdSelectorButton(this HtmlHelper html, string id, string cssClass, string imageUrl) { - Contract.Requires<ArgumentNullException>(html != null); - Contract.Requires<ArgumentNullException>(id != null); - Contract.Requires<ArgumentException>(!string.IsNullOrEmpty(imageUrl)); - Contract.Ensures(Contract.Result<string>() != null); - - using (var writer = new StringWriter(CultureInfo.CurrentCulture)) { - using (var h = new HtmlTextWriter(writer)) { - h.AddAttribute(HtmlTextWriterAttribute.Id, id); - if (!string.IsNullOrEmpty(cssClass)) { - h.AddAttribute(HtmlTextWriterAttribute.Class, cssClass); - } - h.RenderBeginTag(HtmlTextWriterTag.Li); - - h.AddAttribute(HtmlTextWriterAttribute.Href, "#"); - h.RenderBeginTag(HtmlTextWriterTag.A); - - h.RenderBeginTag(HtmlTextWriterTag.Div); - h.RenderBeginTag(HtmlTextWriterTag.Div); - - h.AddAttribute(HtmlTextWriterAttribute.Src, imageUrl); - h.RenderBeginTag(HtmlTextWriterTag.Img); - h.RenderEndTag(); - - h.AddAttribute(HtmlTextWriterAttribute.Src, Util.GetWebResourceUrl(typeof(OpenIdSelector), OpenId.RelyingParty.OpenIdAjaxTextBox.EmbeddedLoginSuccessResourceName)); - h.AddAttribute(HtmlTextWriterAttribute.Class, "loginSuccess"); - h.AddAttribute(HtmlTextWriterAttribute.Title, "Authenticated as {0}"); - h.RenderBeginTag(HtmlTextWriterTag.Img); - h.RenderEndTag(); - - h.RenderEndTag(); // div - - h.AddAttribute(HtmlTextWriterAttribute.Class, "ui-widget-overlay"); - h.RenderBeginTag(HtmlTextWriterTag.Div); - h.RenderEndTag(); // div - - h.RenderEndTag(); // div - h.RenderEndTag(); // a - h.RenderEndTag(); // li - } - - return writer.ToString(); - } - } - - /// <summary> - /// Emits <script> tags that import a given set of scripts given their URLs. - /// </summary> - /// <param name="writer">The writer to emit the tags to.</param> - /// <param name="scriptUrls">The locations of the scripts to import.</param> - private static void WriteScriptTagsUrls(this TextWriter writer, IEnumerable<string> scriptUrls) { - Contract.Requires<ArgumentNullException>(writer != null); - Contract.Requires<ArgumentNullException>(scriptUrls != null); - - foreach (string script in scriptUrls) { - writer.WriteLine("<script type='text/javascript' src='{0}'></script>", script); - } - } - - /// <summary> - /// Writes out script tags that import a script from resources embedded in this assembly. - /// </summary> - /// <param name="writer">The writer to emit the tags to.</param> - /// <param name="resourceName">Name of the resource.</param> - private static void WriteScriptTags(this TextWriter writer, string resourceName) { - Contract.Requires<ArgumentNullException>(writer != null); - Contract.Requires<ArgumentException>(!string.IsNullOrEmpty(resourceName)); - - WriteScriptTags(writer, new[] { resourceName }); - } - - /// <summary> - /// Writes out script tags that import scripts from resources embedded in this assembly. - /// </summary> - /// <param name="writer">The writer to emit the tags to.</param> - /// <param name="resourceNames">The resource names.</param> - private static void WriteScriptTags(this TextWriter writer, IEnumerable<string> resourceNames) { - Contract.Requires<ArgumentNullException>(writer != null); - Contract.Requires<ArgumentNullException>(resourceNames != null); - - writer.WriteScriptTagsUrls(resourceNames.Select(r => Util.GetWebResourceUrl(typeof(OpenIdRelyingPartyControlBase), r))); - } - - /// <summary> - /// Writes a given script block, surrounding it with <script> and CDATA tags. - /// </summary> - /// <param name="writer">The writer to emit the tags to.</param> - /// <param name="script">The script to inline on the page.</param> - private static void WriteScriptBlock(this TextWriter writer, string script) { - Contract.Requires<ArgumentNullException>(writer != null); - Contract.Requires<ArgumentException>(!string.IsNullOrEmpty(script)); - - writer.WriteLine("<script type='text/javascript' language='javascript'><!--"); - writer.WriteLine("//<![CDATA["); - writer.WriteLine(script); - writer.WriteLine("//]]>--></script>"); - } - - /// <summary> - /// Writes a given CSS link. - /// </summary> - /// <param name="writer">The writer to emit the tags to.</param> - /// <param name="resourceName">Name of the resource containing the CSS content.</param> - private static void WriteStylesheetLink(this TextWriter writer, string resourceName) { - Contract.Requires<ArgumentNullException>(writer != null); - Contract.Requires<ArgumentException>(!string.IsNullOrEmpty(resourceName)); - - WriteStylesheetLinkUrl(writer, Util.GetWebResourceUrl(typeof(OpenIdRelyingPartyAjaxControlBase), resourceName)); - } - - /// <summary> - /// Writes a given CSS link. - /// </summary> - /// <param name="writer">The writer to emit the tags to.</param> - /// <param name="stylesheet">The stylesheet to link in.</param> - private static void WriteStylesheetLinkUrl(this TextWriter writer, string stylesheet) { - Contract.Requires<ArgumentNullException>(writer != null); - Contract.Requires<ArgumentException>(!string.IsNullOrEmpty(stylesheet)); - - writer.WriteLine("<link rel='stylesheet' type='text/css' href='{0}' />", stylesheet); - } - } -} diff --git a/src/DotNetOpenAuth/OAuth/ChannelElements/IServiceProviderTokenManager.cs b/src/DotNetOpenAuth/OAuth/ChannelElements/IServiceProviderTokenManager.cs deleted file mode 100644 index 7df67ce..0000000 --- a/src/DotNetOpenAuth/OAuth/ChannelElements/IServiceProviderTokenManager.cs +++ /dev/null @@ -1,251 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="IServiceProviderTokenManager.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OAuth.ChannelElements { - using System; - using System.Collections.Generic; - using System.Diagnostics.Contracts; - using System.Linq; - using System.Text; - - /// <summary> - /// A token manager for use by a web site in its role as a - /// service provider. - /// </summary> - [ContractClass(typeof(IServiceProviderTokenManagerContract))] - public interface IServiceProviderTokenManager : ITokenManager { - /// <summary> - /// Gets the Consumer description for a given a Consumer Key. - /// </summary> - /// <param name="consumerKey">The Consumer Key.</param> - /// <returns>A description of the consumer. Never null.</returns> - /// <exception cref="KeyNotFoundException">Thrown if the consumer key cannot be found.</exception> - IConsumerDescription GetConsumer(string consumerKey); - - /// <summary> - /// Checks whether a given request token has already been authorized - /// by some user for use by the Consumer that requested it. - /// </summary> - /// <param name="requestToken">The Consumer's request token.</param> - /// <returns> - /// True if the request token has already been fully authorized by the user - /// who owns the relevant protected resources. False if the token has not yet - /// been authorized, has expired or does not exist. - /// </returns> - bool IsRequestTokenAuthorized(string requestToken); - - /// <summary> - /// Gets details on the named request token. - /// </summary> - /// <param name="token">The request token.</param> - /// <returns>A description of the token. Never null.</returns> - /// <exception cref="KeyNotFoundException">Thrown if the token cannot be found.</exception> - /// <remarks> - /// It is acceptable for implementations to find the token, see that it has expired, - /// delete it from the database and then throw <see cref="KeyNotFoundException"/>, - /// or alternatively it can return the expired token anyway and the OAuth channel will - /// log and throw the appropriate error. - /// </remarks> - IServiceProviderRequestToken GetRequestToken(string token); - - /// <summary> - /// Gets details on the named access token. - /// </summary> - /// <param name="token">The access token.</param> - /// <returns>A description of the token. Never null.</returns> - /// <exception cref="KeyNotFoundException">Thrown if the token cannot be found.</exception> - /// <remarks> - /// It is acceptable for implementations to find the token, see that it has expired, - /// delete it from the database and then throw <see cref="KeyNotFoundException"/>, - /// or alternatively it can return the expired token anyway and the OAuth channel will - /// log and throw the appropriate error. - /// </remarks> - IServiceProviderAccessToken GetAccessToken(string token); - - /// <summary> - /// Persists any changes made to the token. - /// </summary> - /// <param name="token">The token whose properties have been changed.</param> - /// <remarks> - /// This library will invoke this method after making a set - /// of changes to the token as part of a web request to give the host - /// the opportunity to persist those changes to a database. - /// Depending on the object persistence framework the host site uses, - /// this method MAY not need to do anything (if changes made to the token - /// will automatically be saved without any extra handling). - /// </remarks> - void UpdateToken(IServiceProviderRequestToken token); - } - - /// <summary> - /// Code contract class for the <see cref="IServiceProviderTokenManager"/> interface. - /// </summary> - [ContractClassFor(typeof(IServiceProviderTokenManager))] - internal abstract class IServiceProviderTokenManagerContract : IServiceProviderTokenManager { - /// <summary> - /// Prevents a default instance of the <see cref="IServiceProviderTokenManagerContract"/> class from being created. - /// </summary> - private IServiceProviderTokenManagerContract() { - } - - #region IServiceProviderTokenManager Members - - /// <summary> - /// Gets the Consumer description for a given a Consumer Key. - /// </summary> - /// <param name="consumerKey">The Consumer Key.</param> - /// <returns> - /// A description of the consumer. Never null. - /// </returns> - /// <exception cref="KeyNotFoundException">Thrown if the consumer key cannot be found.</exception> - IConsumerDescription IServiceProviderTokenManager.GetConsumer(string consumerKey) { - Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(consumerKey)); - Contract.Ensures(Contract.Result<IConsumerDescription>() != null); - throw new NotImplementedException(); - } - - /// <summary> - /// Checks whether a given request token has already been authorized - /// by some user for use by the Consumer that requested it. - /// </summary> - /// <param name="requestToken">The Consumer's request token.</param> - /// <returns> - /// True if the request token has already been fully authorized by the user - /// who owns the relevant protected resources. False if the token has not yet - /// been authorized, has expired or does not exist. - /// </returns> - bool IServiceProviderTokenManager.IsRequestTokenAuthorized(string requestToken) { - Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(requestToken)); - throw new NotImplementedException(); - } - - /// <summary> - /// Gets details on the named request token. - /// </summary> - /// <param name="token">The request token.</param> - /// <returns>A description of the token. Never null.</returns> - /// <exception cref="KeyNotFoundException">Thrown if the token cannot be found.</exception> - /// <remarks> - /// It is acceptable for implementations to find the token, see that it has expired, - /// delete it from the database and then throw <see cref="KeyNotFoundException"/>, - /// or alternatively it can return the expired token anyway and the OAuth channel will - /// log and throw the appropriate error. - /// </remarks> - IServiceProviderRequestToken IServiceProviderTokenManager.GetRequestToken(string token) { - Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(token)); - Contract.Ensures(Contract.Result<IServiceProviderRequestToken>() != null); - throw new NotImplementedException(); - } - - /// <summary> - /// Gets details on the named access token. - /// </summary> - /// <param name="token">The access token.</param> - /// <returns>A description of the token. Never null.</returns> - /// <exception cref="KeyNotFoundException">Thrown if the token cannot be found.</exception> - /// <remarks> - /// It is acceptable for implementations to find the token, see that it has expired, - /// delete it from the database and then throw <see cref="KeyNotFoundException"/>, - /// or alternatively it can return the expired token anyway and the OAuth channel will - /// log and throw the appropriate error. - /// </remarks> - IServiceProviderAccessToken IServiceProviderTokenManager.GetAccessToken(string token) { - Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(token)); - Contract.Ensures(Contract.Result<IServiceProviderAccessToken>() != null); - throw new NotImplementedException(); - } - - /// <summary> - /// Persists any changes made to the token. - /// </summary> - /// <param name="token">The token whose properties have been changed.</param> - /// <remarks> - /// This library will invoke this method after making a set - /// of changes to the token as part of a web request to give the host - /// the opportunity to persist those changes to a database. - /// Depending on the object persistence framework the host site uses, - /// this method MAY not need to do anything (if changes made to the token - /// will automatically be saved without any extra handling). - /// </remarks> - void IServiceProviderTokenManager.UpdateToken(IServiceProviderRequestToken token) { - Contract.Requires<ArgumentNullException>(token != null); - throw new NotImplementedException(); - } - - #endregion - - #region ITokenManager Members - - /// <summary> - /// Gets the Token Secret given a request or access token. - /// </summary> - /// <param name="token">The request or access token.</param> - /// <returns> - /// The secret associated with the given token. - /// </returns> - /// <exception cref="ArgumentException">Thrown if the secret cannot be found for the given token.</exception> - string ITokenManager.GetTokenSecret(string token) { - throw new NotImplementedException(); - } - - /// <summary> - /// Stores a newly generated unauthorized request token, secret, and optional - /// application-specific parameters for later recall. - /// </summary> - /// <param name="request">The request message that resulted in the generation of a new unauthorized request token.</param> - /// <param name="response">The response message that includes the unauthorized request token.</param> - /// <exception cref="ArgumentException">Thrown if the consumer key is not registered, or a required parameter was not found in the parameters collection.</exception> - /// <remarks> - /// Request tokens stored by this method SHOULD NOT associate any user account with this token. - /// It usually opens up security holes in your application to do so. Instead, you associate a user - /// account with access tokens (not request tokens) in the <see cref="ITokenManager.ExpireRequestTokenAndStoreNewAccessToken"/> - /// method. - /// </remarks> - void ITokenManager.StoreNewRequestToken(DotNetOpenAuth.OAuth.Messages.UnauthorizedTokenRequest request, DotNetOpenAuth.OAuth.Messages.ITokenSecretContainingMessage response) { - throw new NotImplementedException(); - } - - /// <summary> - /// Deletes a request token and its associated secret and stores a new access token and secret. - /// </summary> - /// <param name="consumerKey">The Consumer that is exchanging its request token for an access token.</param> - /// <param name="requestToken">The Consumer's request token that should be deleted/expired.</param> - /// <param name="accessToken">The new access token that is being issued to the Consumer.</param> - /// <param name="accessTokenSecret">The secret associated with the newly issued access token.</param> - /// <remarks> - /// <para> - /// Any scope of granted privileges associated with the request token from the - /// original call to <see cref="ITokenManager.StoreNewRequestToken"/> should be carried over - /// to the new Access Token. - /// </para> - /// <para> - /// To associate a user account with the new access token, - /// <see cref="System.Web.HttpContext.User">HttpContext.Current.User</see> may be - /// useful in an ASP.NET web application within the implementation of this method. - /// Alternatively you may store the access token here without associating with a user account, - /// and wait until <see cref="WebConsumer.ProcessUserAuthorization()"/> or - /// <see cref="DesktopConsumer.ProcessUserAuthorization(string, string)"/> return the access - /// token to associate the access token with a user account at that point. - /// </para> - /// </remarks> - void ITokenManager.ExpireRequestTokenAndStoreNewAccessToken(string consumerKey, string requestToken, string accessToken, string accessTokenSecret) { - throw new NotImplementedException(); - } - - /// <summary> - /// Classifies a token as a request token or an access token. - /// </summary> - /// <param name="token">The token to classify.</param> - /// <returns> - /// Request or Access token, or invalid if the token is not recognized. - /// </returns> - TokenType ITokenManager.GetTokenType(string token) { - throw new NotImplementedException(); - } - - #endregion - } -} diff --git a/src/DotNetOpenAuth/OAuth/ChannelElements/ITokenManager.cs b/src/DotNetOpenAuth/OAuth/ChannelElements/ITokenManager.cs deleted file mode 100644 index 459cd28..0000000 --- a/src/DotNetOpenAuth/OAuth/ChannelElements/ITokenManager.cs +++ /dev/null @@ -1,169 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="ITokenManager.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OAuth.ChannelElements { - using System; - using System.Collections.Generic; - using System.Diagnostics.Contracts; - using System.Linq; - using System.Text; - using DotNetOpenAuth.OAuth.Messages; - - /// <summary> - /// An interface OAuth hosts must implement for persistent storage - /// and recall of tokens and secrets for an individual OAuth consumer - /// or service provider. - /// </summary> - [ContractClass(typeof(ITokenManagerContract))] - public interface ITokenManager { - /// <summary> - /// Gets the Token Secret given a request or access token. - /// </summary> - /// <param name="token">The request or access token.</param> - /// <returns>The secret associated with the given token.</returns> - /// <exception cref="ArgumentException">Thrown if the secret cannot be found for the given token.</exception> - string GetTokenSecret(string token); - - /// <summary> - /// Stores a newly generated unauthorized request token, secret, and optional - /// application-specific parameters for later recall. - /// </summary> - /// <param name="request">The request message that resulted in the generation of a new unauthorized request token.</param> - /// <param name="response">The response message that includes the unauthorized request token.</param> - /// <exception cref="ArgumentException">Thrown if the consumer key is not registered, or a required parameter was not found in the parameters collection.</exception> - /// <remarks> - /// Request tokens stored by this method SHOULD NOT associate any user account with this token. - /// It usually opens up security holes in your application to do so. Instead, you associate a user - /// account with access tokens (not request tokens) in the <see cref="ExpireRequestTokenAndStoreNewAccessToken"/> - /// method. - /// </remarks> - void StoreNewRequestToken(UnauthorizedTokenRequest request, ITokenSecretContainingMessage response); - - /// <summary> - /// Deletes a request token and its associated secret and stores a new access token and secret. - /// </summary> - /// <param name="consumerKey">The Consumer that is exchanging its request token for an access token.</param> - /// <param name="requestToken">The Consumer's request token that should be deleted/expired.</param> - /// <param name="accessToken">The new access token that is being issued to the Consumer.</param> - /// <param name="accessTokenSecret">The secret associated with the newly issued access token.</param> - /// <remarks> - /// <para> - /// Any scope of granted privileges associated with the request token from the - /// original call to <see cref="StoreNewRequestToken"/> should be carried over - /// to the new Access Token. - /// </para> - /// <para> - /// To associate a user account with the new access token, - /// <see cref="System.Web.HttpContext.User">HttpContext.Current.User</see> may be - /// useful in an ASP.NET web application within the implementation of this method. - /// Alternatively you may store the access token here without associating with a user account, - /// and wait until <see cref="WebConsumer.ProcessUserAuthorization()"/> or - /// <see cref="DesktopConsumer.ProcessUserAuthorization(string, string)"/> return the access - /// token to associate the access token with a user account at that point. - /// </para> - /// </remarks> - void ExpireRequestTokenAndStoreNewAccessToken(string consumerKey, string requestToken, string accessToken, string accessTokenSecret); - - /// <summary> - /// Classifies a token as a request token or an access token. - /// </summary> - /// <param name="token">The token to classify.</param> - /// <returns>Request or Access token, or invalid if the token is not recognized.</returns> - TokenType GetTokenType(string token); - } - - /// <summary> - /// The code contract class for the <see cref="ITokenManager"/> interface. - /// </summary> - [ContractClassFor(typeof(ITokenManager))] - internal abstract class ITokenManagerContract : ITokenManager { - /// <summary> - /// Prevents a default instance of the <see cref="ITokenManagerContract"/> class from being created. - /// </summary> - private ITokenManagerContract() { - } - - #region ITokenManager Members - - /// <summary> - /// Gets the Token Secret given a request or access token. - /// </summary> - /// <param name="token">The request or access token.</param> - /// <returns> - /// The secret associated with the given token. - /// </returns> - /// <exception cref="ArgumentException">Thrown if the secret cannot be found for the given token.</exception> - string ITokenManager.GetTokenSecret(string token) { - Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(token)); - Contract.Ensures(Contract.Result<string>() != null); - throw new NotImplementedException(); - } - - /// <summary> - /// Stores a newly generated unauthorized request token, secret, and optional - /// application-specific parameters for later recall. - /// </summary> - /// <param name="request">The request message that resulted in the generation of a new unauthorized request token.</param> - /// <param name="response">The response message that includes the unauthorized request token.</param> - /// <exception cref="ArgumentException">Thrown if the consumer key is not registered, or a required parameter was not found in the parameters collection.</exception> - /// <remarks> - /// Request tokens stored by this method SHOULD NOT associate any user account with this token. - /// It usually opens up security holes in your application to do so. Instead, you associate a user - /// account with access tokens (not request tokens) in the <see cref="ITokenManager.ExpireRequestTokenAndStoreNewAccessToken"/> - /// method. - /// </remarks> - void ITokenManager.StoreNewRequestToken(UnauthorizedTokenRequest request, ITokenSecretContainingMessage response) { - Contract.Requires<ArgumentNullException>(request != null); - Contract.Requires<ArgumentNullException>(response != null); - throw new NotImplementedException(); - } - - /// <summary> - /// Deletes a request token and its associated secret and stores a new access token and secret. - /// </summary> - /// <param name="consumerKey">The Consumer that is exchanging its request token for an access token.</param> - /// <param name="requestToken">The Consumer's request token that should be deleted/expired.</param> - /// <param name="accessToken">The new access token that is being issued to the Consumer.</param> - /// <param name="accessTokenSecret">The secret associated with the newly issued access token.</param> - /// <remarks> - /// <para> - /// Any scope of granted privileges associated with the request token from the - /// original call to <see cref="ITokenManager.StoreNewRequestToken"/> should be carried over - /// to the new Access Token. - /// </para> - /// <para> - /// To associate a user account with the new access token, - /// <see cref="System.Web.HttpContext.User">HttpContext.Current.User</see> may be - /// useful in an ASP.NET web application within the implementation of this method. - /// Alternatively you may store the access token here without associating with a user account, - /// and wait until <see cref="WebConsumer.ProcessUserAuthorization()"/> or - /// <see cref="DesktopConsumer.ProcessUserAuthorization(string, string)"/> return the access - /// token to associate the access token with a user account at that point. - /// </para> - /// </remarks> - void ITokenManager.ExpireRequestTokenAndStoreNewAccessToken(string consumerKey, string requestToken, string accessToken, string accessTokenSecret) { - Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(consumerKey)); - Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(requestToken)); - Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(accessToken)); - Contract.Requires<ArgumentNullException>(accessTokenSecret != null); - throw new NotImplementedException(); - } - - /// <summary> - /// Classifies a token as a request token or an access token. - /// </summary> - /// <param name="token">The token to classify.</param> - /// <returns> - /// Request or Access token, or invalid if the token is not recognized. - /// </returns> - TokenType ITokenManager.GetTokenType(string token) { - Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(token)); - throw new NotImplementedException(); - } - - #endregion - } -} diff --git a/src/DotNetOpenAuth/OAuth/ChannelElements/OAuthChannel.cs b/src/DotNetOpenAuth/OAuth/ChannelElements/OAuthChannel.cs deleted file mode 100644 index 76b5149..0000000 --- a/src/DotNetOpenAuth/OAuth/ChannelElements/OAuthChannel.cs +++ /dev/null @@ -1,410 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="OAuthChannel.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OAuth.ChannelElements { - using System; - using System.Collections.Generic; - using System.Diagnostics; - using System.Diagnostics.CodeAnalysis; - using System.Diagnostics.Contracts; - using System.Globalization; - using System.IO; - using System.Linq; - using System.Net; - using System.Net.Mime; - using System.Text; - using System.Web; - using DotNetOpenAuth.Messaging; - using DotNetOpenAuth.Messaging.Bindings; - using DotNetOpenAuth.Messaging.Reflection; - using DotNetOpenAuth.OAuth.Messages; - - /// <summary> - /// An OAuth-specific implementation of the <see cref="Channel"/> class. - /// </summary> - internal class OAuthChannel : Channel { - /// <summary> - /// Initializes a new instance of the <see cref="OAuthChannel"/> class. - /// </summary> - /// <param name="signingBindingElement">The binding element to use for signing.</param> - /// <param name="store">The web application store to use for nonces.</param> - /// <param name="tokenManager">The token manager instance to use.</param> - /// <param name="securitySettings">The security settings.</param> - [SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", MessageId = "System.Diagnostics.Contracts.__ContractsRuntime.Requires<System.ArgumentNullException>(System.Boolean,System.String,System.String)", Justification = "Code contracts"), SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "securitySettings", Justification = "Code contracts")] - internal OAuthChannel(ITamperProtectionChannelBindingElement signingBindingElement, INonceStore store, IConsumerTokenManager tokenManager, ConsumerSecuritySettings securitySettings) - : this( - signingBindingElement, - store, - tokenManager, - securitySettings, - new OAuthConsumerMessageFactory()) { - Contract.Requires<ArgumentNullException>(tokenManager != null); - Contract.Requires<ArgumentNullException>(securitySettings != null); - Contract.Requires<ArgumentNullException>(signingBindingElement != null); - Contract.Requires<ArgumentException>(signingBindingElement.SignatureCallback == null, OAuthStrings.SigningElementAlreadyAssociatedWithChannel); - } - - /// <summary> - /// Initializes a new instance of the <see cref="OAuthChannel"/> class. - /// </summary> - /// <param name="signingBindingElement">The binding element to use for signing.</param> - /// <param name="store">The web application store to use for nonces.</param> - /// <param name="tokenManager">The token manager instance to use.</param> - /// <param name="securitySettings">The security settings.</param> - [SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", MessageId = "System.Diagnostics.Contracts.__ContractsRuntime.Requires<System.ArgumentNullException>(System.Boolean,System.String,System.String)", Justification = "Code contracts"), SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "securitySettings", Justification = "Code contracts")] - internal OAuthChannel(ITamperProtectionChannelBindingElement signingBindingElement, INonceStore store, IServiceProviderTokenManager tokenManager, ServiceProviderSecuritySettings securitySettings) - : this( - signingBindingElement, - store, - tokenManager, - securitySettings, - new OAuthServiceProviderMessageFactory(tokenManager)) { - Contract.Requires<ArgumentNullException>(tokenManager != null); - Contract.Requires<ArgumentNullException>(securitySettings != null); - Contract.Requires<ArgumentNullException>(signingBindingElement != null); - Contract.Requires<ArgumentException>(signingBindingElement.SignatureCallback == null, OAuthStrings.SigningElementAlreadyAssociatedWithChannel); - } - - /// <summary> - /// Initializes a new instance of the <see cref="OAuthChannel"/> class. - /// </summary> - /// <param name="signingBindingElement">The binding element to use for signing.</param> - /// <param name="store">The web application store to use for nonces.</param> - /// <param name="tokenManager">The ITokenManager instance to use.</param> - /// <param name="securitySettings">The security settings.</param> - /// <param name="messageTypeProvider">An injected message type provider instance. - /// Except for mock testing, this should always be one of - /// <see cref="OAuthConsumerMessageFactory"/> or <see cref="OAuthServiceProviderMessageFactory"/>.</param> - [SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", MessageId = "System.Diagnostics.Contracts.__ContractsRuntime.Requires<System.ArgumentNullException>(System.Boolean,System.String,System.String)", Justification = "Code contracts"), SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "securitySettings", Justification = "Code contracts")] - internal OAuthChannel(ITamperProtectionChannelBindingElement signingBindingElement, INonceStore store, ITokenManager tokenManager, SecuritySettings securitySettings, IMessageFactory messageTypeProvider) - : base(messageTypeProvider, InitializeBindingElements(signingBindingElement, store, tokenManager, securitySettings)) { - Contract.Requires<ArgumentNullException>(tokenManager != null); - Contract.Requires<ArgumentNullException>(securitySettings != null); - Contract.Requires<ArgumentNullException>(signingBindingElement != null); - Contract.Requires<ArgumentException>(signingBindingElement.SignatureCallback == null, OAuthStrings.SigningElementAlreadyAssociatedWithChannel); - - this.TokenManager = tokenManager; - signingBindingElement.SignatureCallback = this.SignatureCallback; - } - - /// <summary> - /// Gets or sets the Consumer web application path. - /// </summary> - internal Uri Realm { get; set; } - - /// <summary> - /// Gets the token manager being used. - /// </summary> - protected internal ITokenManager TokenManager { get; private set; } - - /// <summary> - /// Uri-escapes the names and values in a dictionary per OAuth 1.0 section 5.1. - /// </summary> - /// <param name="message">The message with data to encode.</param> - /// <returns>A dictionary of name-value pairs with their strings encoded.</returns> - internal static IDictionary<string, string> GetUriEscapedParameters(IEnumerable<KeyValuePair<string, string>> message) { - var encodedDictionary = new Dictionary<string, string>(); - UriEscapeParameters(message, encodedDictionary); - return encodedDictionary; - } - - /// <summary> - /// Initializes a web request for sending by attaching a message to it. - /// Use this method to prepare a protected resource request that you do NOT - /// expect an OAuth message response to. - /// </summary> - /// <param name="request">The message to attach.</param> - /// <returns>The initialized web request.</returns> - internal HttpWebRequest InitializeRequest(IDirectedProtocolMessage request) { - Contract.Requires<ArgumentNullException>(request != null); - - ProcessOutgoingMessage(request); - return this.CreateHttpRequest(request); - } - - /// <summary> - /// Searches an incoming HTTP request for data that could be used to assemble - /// a protocol request message. - /// </summary> - /// <param name="request">The HTTP request to search.</param> - /// <returns>The deserialized message, if one is found. Null otherwise.</returns> - protected override IDirectedProtocolMessage ReadFromRequestCore(HttpRequestInfo request) { - // First search the Authorization header. - string authorization = request.Headers[HttpRequestHeader.Authorization]; - var fields = MessagingUtilities.ParseAuthorizationHeader(Protocol.AuthorizationHeaderScheme, authorization).ToDictionary(); - fields.Remove("realm"); // ignore the realm parameter, since we don't use it, and it must be omitted from signature base string. - - // Scrape the entity - if (!string.IsNullOrEmpty(request.Headers[HttpRequestHeader.ContentType])) { - var contentType = new ContentType(request.Headers[HttpRequestHeader.ContentType]); - if (string.Equals(contentType.MediaType, HttpFormUrlEncoded, StringComparison.Ordinal)) { - foreach (string key in request.Form) { - if (key != null) { - fields.Add(key, request.Form[key]); - } else { - Logger.OAuth.WarnFormat("Ignoring query string parameter '{0}' since it isn't a standard name=value parameter.", request.Form[key]); - } - } - } - } - - // Scrape the query string - foreach (string key in request.QueryStringBeforeRewriting) { - if (key != null) { - fields.Add(key, request.QueryStringBeforeRewriting[key]); - } else { - Logger.OAuth.WarnFormat("Ignoring query string parameter '{0}' since it isn't a standard name=value parameter.", request.QueryStringBeforeRewriting[key]); - } - } - - MessageReceivingEndpoint recipient; - try { - recipient = request.GetRecipient(); - } catch (ArgumentException ex) { - Logger.OAuth.WarnFormat("Unrecognized HTTP request: " + ex.ToString()); - return null; - } - - // Deserialize the message using all the data we've collected. - var message = (IDirectedProtocolMessage)this.Receive(fields, recipient); - - // Add receiving HTTP transport information required for signature generation. - var signedMessage = message as ITamperResistantOAuthMessage; - if (signedMessage != null) { - signedMessage.Recipient = request.UrlBeforeRewriting; - signedMessage.HttpMethod = request.HttpMethod; - } - - return message; - } - - /// <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> - protected override IDictionary<string, string> ReadFromResponseCore(IncomingWebResponse response) { - string body = response.GetResponseReader().ReadToEnd(); - return HttpUtility.ParseQueryString(body).ToDictionary(); - } - - /// <summary> - /// Prepares an HTTP request that carries a given message. - /// </summary> - /// <param name="request">The message to send.</param> - /// <returns> - /// The <see cref="HttpRequest"/> prepared to send the request. - /// </returns> - protected override HttpWebRequest CreateHttpRequest(IDirectedProtocolMessage request) { - HttpWebRequest httpRequest; - - HttpDeliveryMethods transmissionMethod = request.HttpMethods; - if ((transmissionMethod & HttpDeliveryMethods.AuthorizationHeaderRequest) != 0) { - httpRequest = this.InitializeRequestAsAuthHeader(request); - } else if ((transmissionMethod & HttpDeliveryMethods.PostRequest) != 0) { - var requestMessageWithBinaryData = request as IMessageWithBinaryData; - ErrorUtilities.VerifyProtocol(requestMessageWithBinaryData == null || !requestMessageWithBinaryData.SendAsMultipart, OAuthStrings.MultipartPostMustBeUsedWithAuthHeader); - httpRequest = this.InitializeRequestAsPost(request); - } else if ((transmissionMethod & HttpDeliveryMethods.GetRequest) != 0) { - httpRequest = InitializeRequestAsGet(request); - } else if ((transmissionMethod & HttpDeliveryMethods.HeadRequest) != 0) { - httpRequest = InitializeRequestAsHead(request); - } else if ((transmissionMethod & HttpDeliveryMethods.PutRequest) != 0) { - httpRequest = this.InitializeRequestAsPut(request); - } else if ((transmissionMethod & HttpDeliveryMethods.DeleteRequest) != 0) { - httpRequest = InitializeRequestAsDelete(request); - } else { - throw new NotSupportedException(); - } - return httpRequest; - } - - /// <summary> - /// Queues a message for sending in the response stream where the fields - /// are sent in the response stream in querystring style. - /// </summary> - /// <param name="response">The message to send as a response.</param> - /// <returns>The pending user agent redirect based message to be sent as an HttpResponse.</returns> - /// <remarks> - /// This method implements spec V1.0 section 5.3. - /// </remarks> - protected override OutgoingWebResponse PrepareDirectResponse(IProtocolMessage response) { - var messageAccessor = this.MessageDescriptions.GetAccessor(response); - var fields = messageAccessor.Serialize(); - string responseBody = MessagingUtilities.CreateQueryString(fields); - - OutgoingWebResponse encodedResponse = new OutgoingWebResponse { - Body = responseBody, - OriginalMessage = response, - Status = HttpStatusCode.OK, - Headers = new System.Net.WebHeaderCollection(), - }; - - IHttpDirectResponse httpMessage = response as IHttpDirectResponse; - if (httpMessage != null) { - encodedResponse.Status = httpMessage.HttpStatusCode; - } - - return encodedResponse; - } - - /// <summary> - /// Initializes the binding elements for the OAuth channel. - /// </summary> - /// <param name="signingBindingElement">The signing binding element.</param> - /// <param name="store">The nonce store.</param> - /// <param name="tokenManager">The token manager.</param> - /// <param name="securitySettings">The security settings.</param> - /// <returns> - /// An array of binding elements used to initialize the channel. - /// </returns> - private static IChannelBindingElement[] InitializeBindingElements(ITamperProtectionChannelBindingElement signingBindingElement, INonceStore store, ITokenManager tokenManager, SecuritySettings securitySettings) { - Contract.Requires(securitySettings != null); - - var bindingElements = new List<IChannelBindingElement> { - new OAuthHttpMethodBindingElement(), - signingBindingElement, - new StandardExpirationBindingElement(), - new StandardReplayProtectionBindingElement(store), - }; - - var spTokenManager = tokenManager as IServiceProviderTokenManager; - var serviceProviderSecuritySettings = securitySettings as ServiceProviderSecuritySettings; - if (spTokenManager != null && serviceProviderSecuritySettings != null) { - bindingElements.Insert(0, new TokenHandlingBindingElement(spTokenManager, serviceProviderSecuritySettings)); - } - - return bindingElements.ToArray(); - } - - /// <summary> - /// Uri-escapes the names and values in a dictionary per OAuth 1.0 section 5.1. - /// </summary> - /// <param name="source">The dictionary with names and values to encode.</param> - /// <param name="destination">The dictionary to add the encoded pairs to.</param> - private static void UriEscapeParameters(IEnumerable<KeyValuePair<string, string>> source, IDictionary<string, string> destination) { - Contract.Requires<ArgumentNullException>(source != null); - Contract.Requires<ArgumentNullException>(destination != null); - - foreach (var pair in source) { - var key = MessagingUtilities.EscapeUriDataStringRfc3986(pair.Key); - var value = MessagingUtilities.EscapeUriDataStringRfc3986(pair.Value); - destination.Add(key, value); - } - } - - /// <summary> - /// Gets the HTTP method to use for a message. - /// </summary> - /// <param name="message">The message.</param> - /// <returns>"POST", "GET" or some other similar http verb.</returns> - private static string GetHttpMethod(IDirectedProtocolMessage message) { - Contract.Requires<ArgumentNullException>(message != null); - - var signedMessage = message as ITamperResistantOAuthMessage; - if (signedMessage != null) { - return signedMessage.HttpMethod; - } else { - return MessagingUtilities.GetHttpVerb(message.HttpMethods); - } - } - - /// <summary> - /// Prepares to send a request to the Service Provider via the Authorization header. - /// </summary> - /// <param name="requestMessage">The message to be transmitted to the ServiceProvider.</param> - /// <returns>The web request ready to send.</returns> - /// <remarks> - /// <para>If the message has non-empty ExtraData in it, the request stream is sent to - /// the server automatically. If it is empty, the request stream must be sent by the caller.</para> - /// <para>This method implements OAuth 1.0 section 5.2, item #1 (described in section 5.4).</para> - /// </remarks> - private HttpWebRequest InitializeRequestAsAuthHeader(IDirectedProtocolMessage requestMessage) { - var dictionary = this.MessageDescriptions.GetAccessor(requestMessage); - - // copy so as to not modify original - var fields = new Dictionary<string, string>(); - foreach (string key in dictionary.DeclaredKeys) { - fields.Add(key, dictionary[key]); - } - if (this.Realm != null) { - fields.Add("realm", this.Realm.AbsoluteUri); - } - - HttpWebRequest httpRequest; - UriBuilder recipientBuilder = new UriBuilder(requestMessage.Recipient); - bool hasEntity = HttpMethodHasEntity(GetHttpMethod(requestMessage)); - - if (!hasEntity) { - MessagingUtilities.AppendQueryArgs(recipientBuilder, requestMessage.ExtraData); - } - httpRequest = (HttpWebRequest)WebRequest.Create(recipientBuilder.Uri); - httpRequest.Method = GetHttpMethod(requestMessage); - - httpRequest.Headers.Add(HttpRequestHeader.Authorization, MessagingUtilities.AssembleAuthorizationHeader(Protocol.AuthorizationHeaderScheme, fields)); - - if (hasEntity) { - // WARNING: We only set up the request stream for the caller if there is - // extra data. If there isn't any extra data, the caller must do this themselves. - var requestMessageWithBinaryData = requestMessage as IMessageWithBinaryData; - if (requestMessageWithBinaryData != null && requestMessageWithBinaryData.SendAsMultipart) { - // Include the binary data in the multipart entity, and any standard text extra message data. - // The standard declared message parts are included in the authorization header. - var multiPartFields = new List<MultipartPostPart>(requestMessageWithBinaryData.BinaryData); - multiPartFields.AddRange(requestMessage.ExtraData.Select(field => MultipartPostPart.CreateFormPart(field.Key, field.Value))); - this.SendParametersInEntityAsMultipart(httpRequest, multiPartFields); - } else { - ErrorUtilities.VerifyProtocol(requestMessageWithBinaryData == null || requestMessageWithBinaryData.BinaryData.Count == 0, MessagingStrings.BinaryDataRequiresMultipart); - if (requestMessage.ExtraData.Count > 0) { - this.SendParametersInEntity(httpRequest, requestMessage.ExtraData); - } else { - // We'll assume the content length is zero since the caller may not have - // anything. They're responsible to change it when the add the payload if they have one. - httpRequest.ContentLength = 0; - } - } - } - - return httpRequest; - } - - /// <summary> - /// Fills out the secrets in a message so that signing/verification can be performed. - /// </summary> - /// <param name="message">The message about to be signed or whose signature is about to be verified.</param> - private void SignatureCallback(ITamperResistantProtocolMessage message) { - var oauthMessage = message as ITamperResistantOAuthMessage; - try { - Logger.Channel.Debug("Applying secrets to message to prepare for signing or signature verification."); - oauthMessage.ConsumerSecret = this.GetConsumerSecret(oauthMessage.ConsumerKey); - - var tokenMessage = message as ITokenContainingMessage; - if (tokenMessage != null) { - oauthMessage.TokenSecret = this.TokenManager.GetTokenSecret(tokenMessage.Token); - } - } catch (KeyNotFoundException ex) { - throw new ProtocolException(OAuthStrings.ConsumerOrTokenSecretNotFound, ex); - } - } - - /// <summary> - /// Gets the consumer secret for a given consumer key. - /// </summary> - /// <param name="consumerKey">The consumer key.</param> - /// <returns>The consumer secret.</returns> - private string GetConsumerSecret(string consumerKey) { - var consumerTokenManager = this.TokenManager as IConsumerTokenManager; - if (consumerTokenManager != null) { - ErrorUtilities.VerifyInternal(consumerKey == consumerTokenManager.ConsumerKey, "The token manager consumer key and the consumer key set earlier do not match!"); - return consumerTokenManager.ConsumerSecret; - } else { - return ((IServiceProviderTokenManager)this.TokenManager).GetConsumer(consumerKey).Secret; - } - } - } -} diff --git a/src/DotNetOpenAuth/OAuth/ChannelElements/OAuthIdentity.cs b/src/DotNetOpenAuth/OAuth/ChannelElements/OAuthIdentity.cs deleted file mode 100644 index 65bde20..0000000 --- a/src/DotNetOpenAuth/OAuth/ChannelElements/OAuthIdentity.cs +++ /dev/null @@ -1,64 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="OAuthIdentity.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OAuth.ChannelElements { - using System; - using System.Diagnostics.CodeAnalysis; - using System.Diagnostics.Contracts; - using System.Runtime.InteropServices; - using System.Security.Principal; - using DotNetOpenAuth.Messaging; - - /// <summary> - /// Represents an OAuth consumer that is impersonating a known user on the system. - /// </summary> - [SuppressMessage("Microsoft.Interoperability", "CA1409:ComVisibleTypesShouldBeCreatable", Justification = "Not cocreatable.")] - [Serializable] - [ComVisible(true)] - public class OAuthIdentity : IIdentity { - /// <summary> - /// Initializes a new instance of the <see cref="OAuthIdentity"/> class. - /// </summary> - /// <param name="username">The username.</param> - internal OAuthIdentity(string username) { - Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(username)); - this.Name = username; - } - - #region IIdentity Members - - /// <summary> - /// Gets the type of authentication used. - /// </summary> - /// <value>The constant "OAuth"</value> - /// <returns> - /// The type of authentication used to identify the user. - /// </returns> - public string AuthenticationType { - get { return "OAuth"; } - } - - /// <summary> - /// Gets a value indicating whether the user has been authenticated. - /// </summary> - /// <value>The value <c>true</c></value> - /// <returns>true if the user was authenticated; otherwise, false. - /// </returns> - public bool IsAuthenticated { - get { return true; } - } - - /// <summary> - /// Gets the name of the user who authorized the OAuth token the consumer is using for authorization. - /// </summary> - /// <returns> - /// The name of the user on whose behalf the code is running. - /// </returns> - public string Name { get; private set; } - - #endregion - } -} diff --git a/src/DotNetOpenAuth/OAuth/ChannelElements/OAuthPrincipal.cs b/src/DotNetOpenAuth/OAuth/ChannelElements/OAuthPrincipal.cs deleted file mode 100644 index 6ef2b6e..0000000 --- a/src/DotNetOpenAuth/OAuth/ChannelElements/OAuthPrincipal.cs +++ /dev/null @@ -1,109 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="OAuthPrincipal.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OAuth.ChannelElements { - using System; - using System.Collections.Generic; - using System.Collections.ObjectModel; - using System.Diagnostics.CodeAnalysis; - using System.Diagnostics.Contracts; - using System.Linq; - using System.Runtime.InteropServices; - using System.Security.Principal; - - /// <summary> - /// Represents an OAuth consumer that is impersonating a known user on the system. - /// </summary> - [SuppressMessage("Microsoft.Interoperability", "CA1409:ComVisibleTypesShouldBeCreatable", Justification = "Not cocreatable.")] - [Serializable] - [ComVisible(true)] - public class OAuthPrincipal : IPrincipal { - /// <summary> - /// The roles this user belongs to. - /// </summary> - private ICollection<string> roles; - - /// <summary> - /// Initializes a new instance of the <see cref="OAuthPrincipal"/> class. - /// </summary> - /// <param name="userName">The username.</param> - /// <param name="roles">The roles this user belongs to.</param> - public OAuthPrincipal(string userName, string[] roles) - : this(new OAuthIdentity(userName), roles) { - } - - /// <summary> - /// Initializes a new instance of the <see cref="OAuthPrincipal"/> class. - /// </summary> - /// <param name="token">The access token.</param> - internal OAuthPrincipal(IServiceProviderAccessToken token) - : this(token.Username, token.Roles) { - Contract.Requires<ArgumentNullException>(token != null); - - this.AccessToken = token.Token; - } - - /// <summary> - /// Initializes a new instance of the <see cref="OAuthPrincipal"/> class. - /// </summary> - /// <param name="identity">The identity.</param> - /// <param name="roles">The roles this user belongs to.</param> - internal OAuthPrincipal(OAuthIdentity identity, string[] roles) { - this.Identity = identity; - this.roles = roles; - } - - /// <summary> - /// Gets the access token used to create this principal. - /// </summary> - /// <value>A non-empty string.</value> - public string AccessToken { get; private set; } - - /// <summary> - /// Gets the roles that this principal has as a ReadOnlyCollection. - /// </summary> - public ReadOnlyCollection<string> Roles - { - get { return new ReadOnlyCollection<string>(this.roles.ToList()); } - } - - #region IPrincipal Members - - /// <summary> - /// Gets the identity of the current principal. - /// </summary> - /// <value></value> - /// <returns> - /// The <see cref="T:System.Security.Principal.IIdentity"/> object associated with the current principal. - /// </returns> - public IIdentity Identity { get; private set; } - - /// <summary> - /// Determines whether the current principal belongs to the specified role. - /// </summary> - /// <param name="role">The name of the role for which to check membership.</param> - /// <returns> - /// true if the current principal is a member of the specified role; otherwise, false. - /// </returns> - /// <remarks> - /// The role membership check uses <see cref="StringComparer.OrdinalIgnoreCase"/>. - /// </remarks> - public bool IsInRole(string role) { - return this.roles.Contains(role, StringComparer.OrdinalIgnoreCase); - } - - #endregion - - /// <summary> - /// Creates a new instance of GenericPrincipal based on this OAuthPrincipal. - /// </summary> - /// <returns>A new instance of GenericPrincipal with a GenericIdentity, having the same username and roles as this OAuthPrincipal and OAuthIdentity</returns> - public GenericPrincipal CreateGenericPrincipal() - { - return new GenericPrincipal(new GenericIdentity(this.Identity.Name), this.roles.ToArray()); - } - } -} diff --git a/src/DotNetOpenAuth/OAuth/ChannelElements/OAuthServiceProviderMessageFactory.cs b/src/DotNetOpenAuth/OAuth/ChannelElements/OAuthServiceProviderMessageFactory.cs deleted file mode 100644 index 5b3c918..0000000 --- a/src/DotNetOpenAuth/OAuth/ChannelElements/OAuthServiceProviderMessageFactory.cs +++ /dev/null @@ -1,126 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="OAuthServiceProviderMessageFactory.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OAuth.ChannelElements { - using System; - using System.Collections.Generic; - using System.Diagnostics.Contracts; - using DotNetOpenAuth.Messaging; - using DotNetOpenAuth.OAuth.Messages; - - /// <summary> - /// An OAuth-protocol specific implementation of the <see cref="IMessageFactory"/> - /// interface. - /// </summary> - public class OAuthServiceProviderMessageFactory : IMessageFactory { - /// <summary> - /// The token manager to use for discerning between request and access tokens. - /// </summary> - private IServiceProviderTokenManager tokenManager; - - /// <summary> - /// Initializes a new instance of the <see cref="OAuthServiceProviderMessageFactory"/> class. - /// </summary> - /// <param name="tokenManager">The token manager instance to use.</param> - public OAuthServiceProviderMessageFactory(IServiceProviderTokenManager tokenManager) { - Contract.Requires<ArgumentNullException>(tokenManager != null); - - this.tokenManager = tokenManager; - } - - #region IMessageFactory Members - - /// <summary> - /// Analyzes an incoming request message payload to discover what kind of - /// message is embedded in it and returns the type, or null if no match is found. - /// </summary> - /// <param name="recipient">The intended or actual recipient of the request message.</param> - /// <param name="fields">The name/value pairs that make up the message payload.</param> - /// <returns> - /// A newly instantiated <see cref="IProtocolMessage"/>-derived object that this message can - /// deserialize to. Null if the request isn't recognized as a valid protocol message. - /// </returns> - /// <remarks> - /// The request messages are: - /// UnauthorizedTokenRequest - /// AuthorizedTokenRequest - /// UserAuthorizationRequest - /// AccessProtectedResourceRequest - /// </remarks> - public virtual IDirectedProtocolMessage GetNewRequestMessage(MessageReceivingEndpoint recipient, IDictionary<string, string> fields) { - MessageBase message = null; - Protocol protocol = Protocol.V10; // default to assuming the less-secure 1.0 instead of 1.0a until we prove otherwise. - string token; - fields.TryGetValue("oauth_token", out token); - - try { - if (fields.ContainsKey("oauth_consumer_key") && !fields.ContainsKey("oauth_token")) { - protocol = fields.ContainsKey("oauth_callback") ? Protocol.V10a : Protocol.V10; - message = new UnauthorizedTokenRequest(recipient, protocol.Version); - } else if (fields.ContainsKey("oauth_consumer_key") && fields.ContainsKey("oauth_token")) { - // Discern between RequestAccessToken and AccessProtectedResources, - // which have all the same parameters, by figuring out what type of token - // is in the token parameter. - bool tokenTypeIsAccessToken = this.tokenManager.GetTokenType(token) == TokenType.AccessToken; - - if (tokenTypeIsAccessToken) { - message = (MessageBase)new AccessProtectedResourceRequest(recipient, protocol.Version); - } else { - // Discern between 1.0 and 1.0a requests by checking on the consumer version we stored - // when the consumer first requested an unauthorized token. - protocol = Protocol.Lookup(this.tokenManager.GetRequestToken(token).ConsumerVersion); - message = new AuthorizedTokenRequest(recipient, protocol.Version); - } - } else { - // fail over to the message with no required fields at all. - if (token != null) { - protocol = Protocol.Lookup(this.tokenManager.GetRequestToken(token).ConsumerVersion); - } - - // If a callback parameter is included, that suggests either the consumer - // is following OAuth 1.0 instead of 1.0a, or that a hijacker is trying - // to attack. Either way, if the consumer started out as a 1.0a, keep it - // that way, and we'll just ignore the oauth_callback included in this message - // by virtue of the UserAuthorizationRequest message not including it in its - // 1.0a payload. - message = new UserAuthorizationRequest(recipient, protocol.Version); - } - - if (message != null) { - message.SetAsIncoming(); - } - - return message; - } catch (KeyNotFoundException ex) { - throw ErrorUtilities.Wrap(ex, OAuthStrings.TokenNotFound); - } - } - - /// <summary> - /// Analyzes an incoming request message payload to discover what kind of - /// message is embedded in it and returns the type, or null if no match is found. - /// </summary> - /// <param name="request"> - /// The message that was sent as a request that resulted in the response. - /// Null on a Consumer site that is receiving an indirect message from the Service Provider. - /// </param> - /// <param name="fields">The name/value pairs that make up the message payload.</param> - /// <returns> - /// The <see cref="IProtocolMessage"/>-derived concrete class that this message can - /// deserialize to. Null if the request isn't recognized as a valid protocol message. - /// </returns> - /// <remarks> - /// The response messages are: - /// None. - /// </remarks> - public virtual IDirectResponseProtocolMessage GetNewResponseMessage(IDirectedProtocolMessage request, IDictionary<string, string> fields) { - Logger.OAuth.Error("Service Providers are not expected to ever receive responses."); - return null; - } - - #endregion - } -} diff --git a/src/DotNetOpenAuth/OAuth/ChannelElements/RsaSha1SigningBindingElement.cs b/src/DotNetOpenAuth/OAuth/ChannelElements/RsaSha1SigningBindingElement.cs deleted file mode 100644 index f7b8370..0000000 --- a/src/DotNetOpenAuth/OAuth/ChannelElements/RsaSha1SigningBindingElement.cs +++ /dev/null @@ -1,115 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="RsaSha1SigningBindingElement.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OAuth.ChannelElements { - using System; - using System.Diagnostics.Contracts; - using System.Security.Cryptography; - using System.Security.Cryptography.X509Certificates; - using System.Text; - using DotNetOpenAuth.Messaging; - - /// <summary> - /// A binding element that signs outgoing messages and verifies the signature on incoming messages. - /// </summary> - public class RsaSha1SigningBindingElement : SigningBindingElementBase { - /// <summary> - /// The name of the hash algorithm to use. - /// </summary> - private const string HashAlgorithmName = "RSA-SHA1"; - - /// <summary> - /// The token manager for the service provider. - /// </summary> - private IServiceProviderTokenManager tokenManager; - - /// <summary> - /// Initializes a new instance of the <see cref="RsaSha1SigningBindingElement"/> class - /// for use by Consumers. - /// </summary> - /// <param name="signingCertificate">The certificate used to sign outgoing messages.</param> - public RsaSha1SigningBindingElement(X509Certificate2 signingCertificate) - : base(HashAlgorithmName) { - Contract.Requires<ArgumentNullException>(signingCertificate != null); - - this.SigningCertificate = signingCertificate; - } - - /// <summary> - /// Initializes a new instance of the <see cref="RsaSha1SigningBindingElement"/> class - /// for use by Service Providers. - /// </summary> - /// <param name="tokenManager">The token manager.</param> - public RsaSha1SigningBindingElement(IServiceProviderTokenManager tokenManager) - : base(HashAlgorithmName) { - Contract.Requires<ArgumentNullException>(tokenManager != null); - - this.tokenManager = tokenManager; - } - - /// <summary> - /// Gets or sets the certificate used to sign outgoing messages. Used only by Consumers. - /// </summary> - public X509Certificate2 SigningCertificate { get; set; } - - /// <summary> - /// Calculates a signature for a given message. - /// </summary> - /// <param name="message">The message to sign.</param> - /// <returns>The signature for the message.</returns> - /// <remarks> - /// This method signs the message per OAuth 1.0 section 9.3. - /// </remarks> - protected override string GetSignature(ITamperResistantOAuthMessage message) { - ErrorUtilities.VerifyOperation(this.SigningCertificate != null, OAuthStrings.X509CertificateNotProvidedForSigning); - - string signatureBaseString = ConstructSignatureBaseString(message, this.Channel.MessageDescriptions.GetAccessor(message)); - byte[] data = Encoding.ASCII.GetBytes(signatureBaseString); - var provider = (RSACryptoServiceProvider)this.SigningCertificate.PrivateKey; - byte[] binarySignature = provider.SignData(data, "SHA1"); - string base64Signature = Convert.ToBase64String(binarySignature); - return base64Signature; - } - - /// <summary> - /// Determines whether the signature on some message is valid. - /// </summary> - /// <param name="message">The message to check the signature on.</param> - /// <returns> - /// <c>true</c> if the signature on the message is valid; otherwise, <c>false</c>. - /// </returns> - protected override bool IsSignatureValid(ITamperResistantOAuthMessage message) { - ErrorUtilities.VerifyInternal(this.tokenManager != null, "No token manager available for fetching Consumer public certificates."); - - string signatureBaseString = ConstructSignatureBaseString(message, this.Channel.MessageDescriptions.GetAccessor(message)); - byte[] data = Encoding.ASCII.GetBytes(signatureBaseString); - - byte[] carriedSignature = Convert.FromBase64String(message.Signature); - - X509Certificate2 cert = this.tokenManager.GetConsumer(message.ConsumerKey).Certificate; - if (cert == null) { - Logger.Signatures.WarnFormat("Incoming message from consumer '{0}' could not be matched with an appropriate X.509 certificate for signature verification.", message.ConsumerKey); - return false; - } - - var provider = (RSACryptoServiceProvider)cert.PublicKey.Key; - bool valid = provider.VerifyData(data, "SHA1", carriedSignature); - return valid; - } - - /// <summary> - /// Clones this instance. - /// </summary> - /// <returns>A new instance of the binding element.</returns> - protected override ITamperProtectionChannelBindingElement Clone() { - if (this.tokenManager != null) { - return new RsaSha1SigningBindingElement(this.tokenManager); - } else { - return new RsaSha1SigningBindingElement(this.SigningCertificate); - } - } - } -} diff --git a/src/DotNetOpenAuth/OAuth/ChannelElements/SigningBindingElementBase.cs b/src/DotNetOpenAuth/OAuth/ChannelElements/SigningBindingElementBase.cs deleted file mode 100644 index 31b5149..0000000 --- a/src/DotNetOpenAuth/OAuth/ChannelElements/SigningBindingElementBase.cs +++ /dev/null @@ -1,329 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="SigningBindingElementBase.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OAuth.ChannelElements { - using System; - using System.Collections.Generic; - using System.Collections.Specialized; - using System.Diagnostics.CodeAnalysis; - using System.Diagnostics.Contracts; - using System.Globalization; - using System.Linq; - using System.Text; - using System.Web; - using DotNetOpenAuth.Messaging; - using DotNetOpenAuth.Messaging.Bindings; - using DotNetOpenAuth.Messaging.Reflection; - - /// <summary> - /// A binding element that signs outgoing messages and verifies the signature on incoming messages. - /// </summary> - [ContractClass(typeof(SigningBindingElementBaseContract))] - public abstract class SigningBindingElementBase : ITamperProtectionChannelBindingElement { - /// <summary> - /// The signature method this binding element uses. - /// </summary> - private string signatureMethod; - - /// <summary> - /// Initializes a new instance of the <see cref="SigningBindingElementBase"/> class. - /// </summary> - /// <param name="signatureMethod">The OAuth signature method that the binding element uses.</param> - internal SigningBindingElementBase(string signatureMethod) { - this.signatureMethod = signatureMethod; - } - - #region IChannelBindingElement Properties - - /// <summary> - /// Gets the message protection provided by this binding element. - /// </summary> - public MessageProtections Protection { - get { return MessageProtections.TamperProtection; } - } - - /// <summary> - /// Gets or sets the channel that this binding element belongs to. - /// </summary> - public Channel Channel { get; set; } - - #endregion - - #region ITamperProtectionChannelBindingElement members - - /// <summary> - /// Gets or sets the delegate that will initialize the non-serialized properties necessary on a signed - /// message so that its signature can be correctly calculated for verification. - /// </summary> - public Action<ITamperResistantOAuthMessage> SignatureCallback { get; set; } - - /// <summary> - /// Creates a new object that is a copy of the current instance. - /// </summary> - /// <returns> - /// A new object that is a copy of this instance. - /// </returns> - ITamperProtectionChannelBindingElement ITamperProtectionChannelBindingElement.Clone() { - ITamperProtectionChannelBindingElement clone = this.Clone(); - clone.SignatureCallback = this.SignatureCallback; - return clone; - } - - #endregion - - #region IChannelBindingElement Methods - - /// <summary> - /// Signs the outgoing message. - /// </summary> - /// <param name="message">The message to sign.</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> - public MessageProtections? ProcessOutgoingMessage(IProtocolMessage message) { - var signedMessage = message as ITamperResistantOAuthMessage; - if (signedMessage != null && this.IsMessageApplicable(signedMessage)) { - if (this.SignatureCallback != null) { - this.SignatureCallback(signedMessage); - } else { - Logger.Bindings.Warn("Signing required, but callback delegate was not provided to provide additional data for signing."); - } - - signedMessage.SignatureMethod = this.signatureMethod; - Logger.Bindings.DebugFormat("Signing {0} message using {1}.", message.GetType().Name, this.signatureMethod); - signedMessage.Signature = this.GetSignature(signedMessage); - return MessageProtections.TamperProtection; - } - - return null; - } - - /// <summary> - /// Verifies the signature on an incoming message. - /// </summary> - /// <param name="message">The message whose signature should be verified.</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="InvalidSignatureException">Thrown if the signature is invalid.</exception> - public MessageProtections? ProcessIncomingMessage(IProtocolMessage message) { - var signedMessage = message as ITamperResistantOAuthMessage; - if (signedMessage != null && this.IsMessageApplicable(signedMessage)) { - Logger.Bindings.DebugFormat("Verifying incoming {0} message signature of: {1}", message.GetType().Name, signedMessage.Signature); - - if (!string.Equals(signedMessage.SignatureMethod, this.signatureMethod, StringComparison.Ordinal)) { - Logger.Bindings.WarnFormat("Expected signature method '{0}' but received message with a signature method of '{1}'.", this.signatureMethod, signedMessage.SignatureMethod); - return MessageProtections.None; - } - - if (this.SignatureCallback != null) { - this.SignatureCallback(signedMessage); - } else { - Logger.Bindings.Warn("Signature verification required, but callback delegate was not provided to provide additional data for signature verification."); - } - - if (!this.IsSignatureValid(signedMessage)) { - Logger.Bindings.Error("Signature verification failed."); - throw new InvalidSignatureException(message); - } - - return MessageProtections.TamperProtection; - } - - return null; - } - - #endregion - - /// <summary> - /// Constructs the OAuth Signature Base String and returns the result. - /// </summary> - /// <param name="message">The message.</param> - /// <param name="messageDictionary">The message to derive the signature base string from.</param> - /// <returns>The signature base string.</returns> - /// <remarks> - /// This method implements OAuth 1.0 section 9.1. - /// </remarks> - [SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Justification = "Unavoidable")] - internal static string ConstructSignatureBaseString(ITamperResistantOAuthMessage message, MessageDictionary messageDictionary) { - Contract.Requires<ArgumentNullException>(message != null); - Contract.Requires<ArgumentException>(!string.IsNullOrEmpty(message.HttpMethod)); - Contract.Requires<ArgumentNullException>(messageDictionary != null); - Contract.Requires<ArgumentException>(messageDictionary.Message == message); - - List<string> signatureBaseStringElements = new List<string>(3); - - signatureBaseStringElements.Add(message.HttpMethod.ToUpperInvariant()); - - // For multipart POST messages, only include the message parts that are NOT - // in the POST entity (those parts that may appear in an OAuth authorization header). - var encodedDictionary = new Dictionary<string, string>(); - IEnumerable<KeyValuePair<string, string>> partsToInclude = Enumerable.Empty<KeyValuePair<string, string>>(); - var binaryMessage = message as IMessageWithBinaryData; - if (binaryMessage != null && binaryMessage.SendAsMultipart) { - HttpDeliveryMethods authHeaderInUseFlags = HttpDeliveryMethods.PostRequest | HttpDeliveryMethods.AuthorizationHeaderRequest; - ErrorUtilities.VerifyProtocol((binaryMessage.HttpMethods & authHeaderInUseFlags) == authHeaderInUseFlags, OAuthStrings.MultipartPostMustBeUsedWithAuthHeader); - - // Include the declared keys in the signature as those will be signable. - // Cache in local variable to avoid recalculating DeclaredKeys in the delegate. - ICollection<string> declaredKeys = messageDictionary.DeclaredKeys; - partsToInclude = messageDictionary.Where(pair => declaredKeys.Contains(pair.Key)); - } else { - partsToInclude = messageDictionary; - } - - // If this message was deserialized, include only those explicitly included message parts (excludes defaulted values) - // in the signature. - var originalPayloadMessage = (IMessageOriginalPayload)message; - if (originalPayloadMessage.OriginalPayload != null) { - partsToInclude = partsToInclude.Where(pair => originalPayloadMessage.OriginalPayload.ContainsKey(pair.Key)); - } - - foreach (var pair in OAuthChannel.GetUriEscapedParameters(partsToInclude)) { - encodedDictionary[pair.Key] = pair.Value; - } - - // An incoming message will already have included the query and form parameters - // in the message dictionary, but an outgoing message COULD have SOME parameters - // in the query that are not in the message dictionary because they were included - // in the receiving endpoint (the original URL). - // In an outgoing message, the POST entity can only contain parameters if they were - // in the message dictionary, so no need to pull out any parameters from there. - if (message.Recipient.Query != null) { - NameValueCollection nvc = HttpUtility.ParseQueryString(message.Recipient.Query); - foreach (string key in nvc) { - string escapedKey = MessagingUtilities.EscapeUriDataStringRfc3986(key); - string escapedValue = MessagingUtilities.EscapeUriDataStringRfc3986(nvc[key]); - string existingValue; - if (!encodedDictionary.TryGetValue(escapedKey, out existingValue)) { - encodedDictionary.Add(escapedKey, escapedValue); - } else { - ErrorUtilities.VerifyInternal(escapedValue == existingValue, "Somehow we have conflicting values for the '{0}' parameter.", escapedKey); - } - } - } - encodedDictionary.Remove("oauth_signature"); - - UriBuilder endpoint = new UriBuilder(message.Recipient); - endpoint.Query = null; - endpoint.Fragment = null; - signatureBaseStringElements.Add(endpoint.Uri.AbsoluteUri); - - var sortedKeyValueList = new List<KeyValuePair<string, string>>(encodedDictionary); - sortedKeyValueList.Sort(SignatureBaseStringParameterComparer); - StringBuilder paramBuilder = new StringBuilder(); - foreach (var pair in sortedKeyValueList) { - if (paramBuilder.Length > 0) { - paramBuilder.Append("&"); - } - - paramBuilder.Append(pair.Key); - paramBuilder.Append('='); - paramBuilder.Append(pair.Value); - } - - signatureBaseStringElements.Add(paramBuilder.ToString()); - - StringBuilder signatureBaseString = new StringBuilder(); - foreach (string element in signatureBaseStringElements) { - if (signatureBaseString.Length > 0) { - signatureBaseString.Append("&"); - } - - signatureBaseString.Append(MessagingUtilities.EscapeUriDataStringRfc3986(element)); - } - - Logger.Bindings.DebugFormat("Constructed signature base string: {0}", signatureBaseString); - return signatureBaseString.ToString(); - } - - /// <summary> - /// Calculates a signature for a given message. - /// </summary> - /// <param name="message">The message to sign.</param> - /// <returns>The signature for the message.</returns> - /// <remarks> - /// This method signs the message per OAuth 1.0 section 9.2. - /// </remarks> - internal string GetSignatureTestHook(ITamperResistantOAuthMessage message) { - return this.GetSignature(message); - } - - /// <summary> - /// Gets the "ConsumerSecret&TokenSecret" string, allowing either property to be empty or null. - /// </summary> - /// <param name="message">The message to extract the secrets from.</param> - /// <returns>The concatenated string.</returns> - protected static string GetConsumerAndTokenSecretString(ITamperResistantOAuthMessage message) { - StringBuilder builder = new StringBuilder(); - if (!string.IsNullOrEmpty(message.ConsumerSecret)) { - builder.Append(MessagingUtilities.EscapeUriDataStringRfc3986(message.ConsumerSecret)); - } - builder.Append("&"); - if (!string.IsNullOrEmpty(message.TokenSecret)) { - builder.Append(MessagingUtilities.EscapeUriDataStringRfc3986(message.TokenSecret)); - } - return builder.ToString(); - } - - /// <summary> - /// Determines whether the signature on some message is valid. - /// </summary> - /// <param name="message">The message to check the signature on.</param> - /// <returns> - /// <c>true</c> if the signature on the message is valid; otherwise, <c>false</c>. - /// </returns> - protected virtual bool IsSignatureValid(ITamperResistantOAuthMessage message) { - Contract.Requires<ArgumentNullException>(message != null); - - string signature = this.GetSignature(message); - return MessagingUtilities.EqualsConstantTime(message.Signature, signature); - } - - /// <summary> - /// Clones this instance. - /// </summary> - /// <returns>A new instance of the binding element.</returns> - /// <remarks> - /// Implementations of this method need not clone the SignatureVerificationCallback member, as the - /// <see cref="SigningBindingElementBase"/> class does this. - /// </remarks> - protected abstract ITamperProtectionChannelBindingElement Clone(); - - /// <summary> - /// Calculates a signature for a given message. - /// </summary> - /// <param name="message">The message to sign.</param> - /// <returns>The signature for the message.</returns> - protected abstract string GetSignature(ITamperResistantOAuthMessage message); - - /// <summary> - /// Checks whether this binding element applies to this message. - /// </summary> - /// <param name="message">The message that needs to be signed.</param> - /// <returns>True if this binding element can be used to sign the message. False otherwise.</returns> - protected virtual bool IsMessageApplicable(ITamperResistantOAuthMessage message) { - return string.IsNullOrEmpty(message.SignatureMethod) || message.SignatureMethod == this.signatureMethod; - } - - /// <summary> - /// Sorts parameters according to OAuth signature base string rules. - /// </summary> - /// <param name="left">The first parameter to compare.</param> - /// <param name="right">The second parameter to compare.</param> - /// <returns>Negative, zero or positive.</returns> - private static int SignatureBaseStringParameterComparer(KeyValuePair<string, string> left, KeyValuePair<string, string> right) { - int result = string.CompareOrdinal(left.Key, right.Key); - if (result != 0) { - return result; - } - - return string.CompareOrdinal(left.Value, right.Value); - } - } -} diff --git a/src/DotNetOpenAuth/OAuth/ChannelElements/SigningBindingElementBaseContract.cs b/src/DotNetOpenAuth/OAuth/ChannelElements/SigningBindingElementBaseContract.cs deleted file mode 100644 index 4ff52fd..0000000 --- a/src/DotNetOpenAuth/OAuth/ChannelElements/SigningBindingElementBaseContract.cs +++ /dev/null @@ -1,47 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="SigningBindingElementBaseContract.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OAuth.ChannelElements { - using System; - using System.Diagnostics.Contracts; - using DotNetOpenAuth.Messaging; - - /// <summary> - /// Code Contract for the <see cref="SigningBindingElementBase"/> class. - /// </summary> - [ContractClassFor(typeof(SigningBindingElementBase))] - internal abstract class SigningBindingElementBaseContract : SigningBindingElementBase { - /// <summary> - /// Prevents a default instance of the SigningBindingElementBaseContract class from being created. - /// </summary> - private SigningBindingElementBaseContract() - : base(string.Empty) { - } - - /// <summary> - /// Clones this instance. - /// </summary> - /// <returns>A new instance of the binding element.</returns> - /// <remarks> - /// Implementations of this method need not clone the SignatureVerificationCallback member, as the - /// <see cref="SigningBindingElementBase"/> class does this. - /// </remarks> - protected override ITamperProtectionChannelBindingElement Clone() { - throw new NotImplementedException(); - } - - /// <summary> - /// Calculates a signature for a given message. - /// </summary> - /// <param name="message">The message to sign.</param> - /// <returns>The signature for the message.</returns> - protected override string GetSignature(ITamperResistantOAuthMessage message) { - Contract.Requires<ArgumentNullException>(message != null); - Contract.Requires<InvalidOperationException>(this.Channel != null); - throw new NotImplementedException(); - } - } -} diff --git a/src/DotNetOpenAuth/OAuth/ChannelElements/SigningBindingElementChain.cs b/src/DotNetOpenAuth/OAuth/ChannelElements/SigningBindingElementChain.cs deleted file mode 100644 index 67c5205..0000000 --- a/src/DotNetOpenAuth/OAuth/ChannelElements/SigningBindingElementChain.cs +++ /dev/null @@ -1,143 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="SigningBindingElementChain.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OAuth.ChannelElements { - using System; - using System.Collections.Generic; - using System.Diagnostics.Contracts; - using System.Linq; - using System.Text; - using DotNetOpenAuth.Messaging; - - /// <summary> - /// A tamper protection applying binding element that can use any of several given - /// binding elements to apply the protection. - /// </summary> - internal class SigningBindingElementChain : ITamperProtectionChannelBindingElement { - /// <summary> - /// The various signing binding elements that may be applicable to a message in preferred use order. - /// </summary> - private readonly ITamperProtectionChannelBindingElement[] signers; - - /// <summary> - /// Initializes a new instance of the <see cref="SigningBindingElementChain"/> class. - /// </summary> - /// <param name="signers"> - /// The signing binding elements that may be used for some outgoing message, - /// in preferred use order. - /// </param> - internal SigningBindingElementChain(ITamperProtectionChannelBindingElement[] signers) { - Contract.Requires<ArgumentNullException>(signers != null); - Contract.Requires<ArgumentException>(signers.Length > 0); - Contract.Requires<ArgumentException>(!signers.Contains(null), MessagingStrings.SequenceContainsNullElement); - Contract.Requires<ArgumentException>(signers.Select(s => s.Protection).Distinct().Count() == 1, OAuthStrings.SigningElementsMustShareSameProtection); - - this.signers = signers; - } - - #region ITamperProtectionChannelBindingElement Properties - - /// <summary> - /// Gets or sets the delegate that will initialize the non-serialized properties necessary on a signed - /// message so that its signature can be correctly calculated for verification. - /// May be null for Consumers (who never have to verify signatures). - /// </summary> - public Action<ITamperResistantOAuthMessage> SignatureCallback { - get { - return this.signers[0].SignatureCallback; - } - - set { - foreach (ITamperProtectionChannelBindingElement signer in this.signers) { - signer.SignatureCallback = value; - } - } - } - - #endregion - - #region IChannelBindingElement Members - - /// <summary> - /// Gets the protection offered (if any) by this binding element. - /// </summary> - public MessageProtections Protection { - get { return this.signers[0].Protection; } - } - - /// <summary> - /// Gets or sets the channel that this binding element belongs to. - /// </summary> - public Channel Channel { - get { - return this.signers[0].Channel; - } - - set { - foreach (var signer in this.signers) { - signer.Channel = value; - } - } - } - - /// <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> - public MessageProtections? ProcessOutgoingMessage(IProtocolMessage message) { - foreach (IChannelBindingElement signer in this.signers) { - ErrorUtilities.VerifyInternal(signer.Channel != null, "A binding element's Channel property is unexpectedly null."); - MessageProtections? result = signer.ProcessOutgoingMessage(message); - if (result.HasValue) { - return result; - } - } - - 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> - public MessageProtections? ProcessIncomingMessage(IProtocolMessage message) { - foreach (IChannelBindingElement signer in this.signers) { - ErrorUtilities.VerifyInternal(signer.Channel != null, "A binding element's Channel property is unexpectedly null."); - MessageProtections? result = signer.ProcessIncomingMessage(message); - if (result.HasValue) { - return result; - } - } - - return null; - } - - #endregion - - #region ITamperProtectionChannelBindingElement Methods - - /// <summary> - /// Creates a new object that is a copy of the current instance. - /// </summary> - /// <returns> - /// A new object that is a copy of this instance. - /// </returns> - ITamperProtectionChannelBindingElement ITamperProtectionChannelBindingElement.Clone() { - return new SigningBindingElementChain(this.signers.Select(el => (ITamperProtectionChannelBindingElement)el.Clone()).ToArray()); - } - - #endregion - } -} diff --git a/src/DotNetOpenAuth/OAuth/ChannelElements/TokenHandlingBindingElement.cs b/src/DotNetOpenAuth/OAuth/ChannelElements/TokenHandlingBindingElement.cs deleted file mode 100644 index f53aa1b..0000000 --- a/src/DotNetOpenAuth/OAuth/ChannelElements/TokenHandlingBindingElement.cs +++ /dev/null @@ -1,201 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="TokenHandlingBindingElement.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OAuth.ChannelElements { - using System; - using System.Collections.Generic; - using System.Diagnostics.CodeAnalysis; - using System.Diagnostics.Contracts; - using System.Linq; - using System.Text; - using DotNetOpenAuth.Configuration; - using DotNetOpenAuth.Messaging; - using DotNetOpenAuth.OAuth.Messages; - - /// <summary> - /// A binding element for Service Providers to manage the - /// callbacks and verification codes on applicable messages. - /// </summary> - internal class TokenHandlingBindingElement : IChannelBindingElement { - /// <summary> - /// The token manager offered by the service provider. - /// </summary> - private IServiceProviderTokenManager tokenManager; - - /// <summary> - /// The security settings for this service provider. - /// </summary> - private ServiceProviderSecuritySettings securitySettings; - - /// <summary> - /// Initializes a new instance of the <see cref="TokenHandlingBindingElement"/> class. - /// </summary> - /// <param name="tokenManager">The token manager.</param> - /// <param name="securitySettings">The security settings.</param> - [SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", MessageId = "System.Diagnostics.Contracts.__ContractsRuntime.Requires<System.ArgumentNullException>(System.Boolean,System.String,System.String)", Justification = "Code contract"), SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "securitySettings", Justification = "Code contracts")] - internal TokenHandlingBindingElement(IServiceProviderTokenManager tokenManager, ServiceProviderSecuritySettings securitySettings) { - Contract.Requires<ArgumentNullException>(tokenManager != null); - Contract.Requires<ArgumentNullException>(securitySettings != null); - - this.tokenManager = tokenManager; - this.securitySettings = securitySettings; - } - - #region IChannelBindingElement Members - - /// <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 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 MessageProtections? ProcessOutgoingMessage(IProtocolMessage message) { - var userAuthResponse = message as UserAuthorizationResponse; - if (userAuthResponse != null && userAuthResponse.Version >= Protocol.V10a.Version) { - var requestToken = this.tokenManager.GetRequestToken(userAuthResponse.RequestToken); - requestToken.VerificationCode = userAuthResponse.VerificationCode; - this.tokenManager.UpdateToken(requestToken); - return MessageProtections.None; - } - - // Hook to store the token and secret on its way down to the Consumer. - var grantRequestTokenResponse = message as UnauthorizedTokenResponse; - if (grantRequestTokenResponse != null) { - this.tokenManager.StoreNewRequestToken(grantRequestTokenResponse.RequestMessage, grantRequestTokenResponse); - - // The host may have already set these properties, but just to make sure... - var requestToken = this.tokenManager.GetRequestToken(grantRequestTokenResponse.RequestToken); - requestToken.ConsumerVersion = grantRequestTokenResponse.Version; - if (grantRequestTokenResponse.RequestMessage.Callback != null) { - requestToken.Callback = grantRequestTokenResponse.RequestMessage.Callback; - } - this.tokenManager.UpdateToken(requestToken); - - return MessageProtections.None; - } - - 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 MessageProtections? ProcessIncomingMessage(IProtocolMessage message) { - var authorizedTokenRequest = message as AuthorizedTokenRequest; - if (authorizedTokenRequest != null) { - if (authorizedTokenRequest.Version >= Protocol.V10a.Version) { - string expectedVerifier = this.tokenManager.GetRequestToken(authorizedTokenRequest.RequestToken).VerificationCode; - ErrorUtilities.VerifyProtocol(string.Equals(authorizedTokenRequest.VerificationCode, expectedVerifier, StringComparison.Ordinal), OAuthStrings.IncorrectVerifier); - return MessageProtections.None; - } - - this.VerifyThrowTokenTimeToLive(authorizedTokenRequest); - } - - var userAuthorizationRequest = message as UserAuthorizationRequest; - if (userAuthorizationRequest != null) { - this.VerifyThrowTokenTimeToLive(userAuthorizationRequest); - } - - var accessResourceRequest = message as AccessProtectedResourceRequest; - if (accessResourceRequest != null) { - this.VerifyThrowTokenNotExpired(accessResourceRequest); - } - - return null; - } - - #endregion - - /// <summary> - /// Ensures that access tokens have not yet expired. - /// </summary> - /// <param name="message">The incoming message carrying the access token.</param> - private void VerifyThrowTokenNotExpired(AccessProtectedResourceRequest message) { - Contract.Requires<ArgumentNullException>(message != null); - - try { - IServiceProviderAccessToken token = this.tokenManager.GetAccessToken(message.AccessToken); - if (token.ExpirationDate.HasValue && DateTime.Now >= token.ExpirationDate.Value.ToLocalTimeSafe()) { - Logger.OAuth.ErrorFormat( - "OAuth access token {0} rejected because it expired at {1}, and it is now {2}.", - token.Token, - token.ExpirationDate.Value, - DateTime.Now); - ErrorUtilities.ThrowProtocol(OAuthStrings.TokenNotFound); - } - } catch (KeyNotFoundException ex) { - throw ErrorUtilities.Wrap(ex, OAuthStrings.TokenNotFound); - } - } - - /// <summary> - /// Ensures that short-lived request tokens included in incoming messages have not expired. - /// </summary> - /// <param name="message">The incoming message.</param> - /// <exception cref="ProtocolException">Thrown when the token in the message has expired.</exception> - private void VerifyThrowTokenTimeToLive(ITokenContainingMessage message) { - ErrorUtilities.VerifyInternal(!(message is AccessProtectedResourceRequest), "We shouldn't be verifying TTL on access tokens."); - if (message == null || string.IsNullOrEmpty(message.Token)) { - return; - } - - try { - IServiceProviderRequestToken token = this.tokenManager.GetRequestToken(message.Token); - TimeSpan ttl = this.securitySettings.MaximumRequestTokenTimeToLive; - if (DateTime.Now >= token.CreatedOn.ToLocalTimeSafe() + ttl) { - Logger.OAuth.ErrorFormat( - "OAuth request token {0} rejected because it was originally issued at {1}, expired at {2}, and it is now {3}.", - token.Token, - token.CreatedOn, - token.CreatedOn + ttl, - DateTime.Now); - ErrorUtilities.ThrowProtocol(OAuthStrings.TokenNotFound); - } - } catch (KeyNotFoundException ex) { - throw ErrorUtilities.Wrap(ex, OAuthStrings.TokenNotFound); - } - } - } -} diff --git a/src/DotNetOpenAuth/OAuth/ConsumerBase.cs b/src/DotNetOpenAuth/OAuth/ConsumerBase.cs deleted file mode 100644 index d9fa889..0000000 --- a/src/DotNetOpenAuth/OAuth/ConsumerBase.cs +++ /dev/null @@ -1,302 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="ConsumerBase.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OAuth { - using System; - using System.Collections.Generic; - using System.Diagnostics.CodeAnalysis; - using System.Diagnostics.Contracts; - using System.Linq; - using System.Net; - using DotNetOpenAuth.Configuration; - using DotNetOpenAuth.Messaging; - using DotNetOpenAuth.Messaging.Bindings; - using DotNetOpenAuth.OAuth.ChannelElements; - using DotNetOpenAuth.OAuth.Messages; - - /// <summary> - /// Base class for <see cref="WebConsumer"/> and <see cref="DesktopConsumer"/> types. - /// </summary> - public class ConsumerBase : IDisposable { - /// <summary> - /// Initializes a new instance of the <see cref="ConsumerBase"/> class. - /// </summary> - /// <param name="serviceDescription">The endpoints and behavior of the Service Provider.</param> - /// <param name="tokenManager">The host's method of storing and recalling tokens and secrets.</param> - protected ConsumerBase(ServiceProviderDescription serviceDescription, IConsumerTokenManager tokenManager) { - Contract.Requires<ArgumentNullException>(serviceDescription != null); - Contract.Requires<ArgumentNullException>(tokenManager != null); - - ITamperProtectionChannelBindingElement signingElement = serviceDescription.CreateTamperProtectionElement(); - INonceStore store = new NonceMemoryStore(StandardExpirationBindingElement.MaximumMessageAge); - this.SecuritySettings = DotNetOpenAuthSection.Configuration.OAuth.Consumer.SecuritySettings.CreateSecuritySettings(); - this.OAuthChannel = new OAuthChannel(signingElement, store, tokenManager, this.SecuritySettings); - this.ServiceProvider = serviceDescription; - - Reporting.RecordFeatureAndDependencyUse(this, serviceDescription, tokenManager, null); - } - - /// <summary> - /// Gets the Consumer Key used to communicate with the Service Provider. - /// </summary> - public string ConsumerKey { - get { return this.TokenManager.ConsumerKey; } - } - - /// <summary> - /// Gets the Service Provider that will be accessed. - /// </summary> - public ServiceProviderDescription ServiceProvider { get; private set; } - - /// <summary> - /// Gets the persistence store for tokens and secrets. - /// </summary> - public IConsumerTokenManager TokenManager { - get { return (IConsumerTokenManager)this.OAuthChannel.TokenManager; } - } - - /// <summary> - /// Gets the channel to use for sending/receiving messages. - /// </summary> - public Channel Channel { - get { return this.OAuthChannel; } - } - - /// <summary> - /// Gets the security settings for this consumer. - /// </summary> - internal ConsumerSecuritySettings SecuritySettings { get; private set; } - - /// <summary> - /// Gets or sets the channel to use for sending/receiving messages. - /// </summary> - internal OAuthChannel OAuthChannel { get; set; } - - /// <summary> - /// Obtains an access token for a new account at the Service Provider via 2-legged OAuth. - /// </summary> - /// <param name="requestParameters">Any applicable parameters to include in the query string of the token request.</param> - /// <returns>The access token.</returns> - /// <remarks> - /// The token secret is stored in the <see cref="TokenManager"/>. - /// </remarks> - public string RequestNewClientAccount(IDictionary<string, string> requestParameters = null) { - // Obtain an unauthorized request token. Assume the OAuth version given in the service description. - var token = new UnauthorizedTokenRequest(this.ServiceProvider.RequestTokenEndpoint, this.ServiceProvider.Version) { - ConsumerKey = this.ConsumerKey, - }; - var tokenAccessor = this.Channel.MessageDescriptions.GetAccessor(token); - tokenAccessor.AddExtraParameters(requestParameters); - var requestTokenResponse = this.Channel.Request<UnauthorizedTokenResponse>(token); - this.TokenManager.StoreNewRequestToken(token, requestTokenResponse); - - var requestAccess = new AuthorizedTokenRequest(this.ServiceProvider.AccessTokenEndpoint, this.ServiceProvider.Version) { - RequestToken = requestTokenResponse.RequestToken, - ConsumerKey = this.ConsumerKey, - }; - var grantAccess = this.Channel.Request<AuthorizedTokenResponse>(requestAccess); - this.TokenManager.ExpireRequestTokenAndStoreNewAccessToken(this.ConsumerKey, requestTokenResponse.RequestToken, grantAccess.AccessToken, grantAccess.TokenSecret); - return grantAccess.AccessToken; - } - - /// <summary> - /// Creates a web request prepared with OAuth authorization - /// that may be further tailored by adding parameters by the caller. - /// </summary> - /// <param name="endpoint">The URL and method on the Service Provider to send the request to.</param> - /// <param name="accessToken">The access token that permits access to the protected resource.</param> - /// <returns>The initialized WebRequest object.</returns> - public HttpWebRequest PrepareAuthorizedRequest(MessageReceivingEndpoint endpoint, string accessToken) { - Contract.Requires<ArgumentNullException>(endpoint != null); - Contract.Requires<ArgumentNullException>(!String.IsNullOrEmpty(accessToken)); - - return this.PrepareAuthorizedRequest(endpoint, accessToken, EmptyDictionary<string, string>.Instance); - } - - /// <summary> - /// Creates a web request prepared with OAuth authorization - /// that may be further tailored by adding parameters by the caller. - /// </summary> - /// <param name="endpoint">The URL and method on the Service Provider to send the request to.</param> - /// <param name="accessToken">The access token that permits access to the protected resource.</param> - /// <param name="extraData">Extra parameters to include in the message. Must not be null, but may be empty.</param> - /// <returns>The initialized WebRequest object.</returns> - public HttpWebRequest PrepareAuthorizedRequest(MessageReceivingEndpoint endpoint, string accessToken, IDictionary<string, string> extraData) { - Contract.Requires<ArgumentNullException>(endpoint != null); - Contract.Requires<ArgumentNullException>(!String.IsNullOrEmpty(accessToken)); - Contract.Requires<ArgumentNullException>(extraData != null); - - IDirectedProtocolMessage message = this.CreateAuthorizingMessage(endpoint, accessToken); - foreach (var pair in extraData) { - message.ExtraData.Add(pair); - } - - HttpWebRequest wr = this.OAuthChannel.InitializeRequest(message); - return wr; - } - - /// <summary> - /// Prepares an authorized request that carries an HTTP multi-part POST, allowing for binary data. - /// </summary> - /// <param name="endpoint">The URL and method on the Service Provider to send the request to.</param> - /// <param name="accessToken">The access token that permits access to the protected resource.</param> - /// <param name="binaryData">Extra parameters to include in the message. Must not be null, but may be empty.</param> - /// <returns>The initialized WebRequest object.</returns> - public HttpWebRequest PrepareAuthorizedRequest(MessageReceivingEndpoint endpoint, string accessToken, IEnumerable<MultipartPostPart> binaryData) { - Contract.Requires<ArgumentNullException>(endpoint != null); - Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(accessToken)); - Contract.Requires<ArgumentNullException>(binaryData != null); - - AccessProtectedResourceRequest message = this.CreateAuthorizingMessage(endpoint, accessToken); - foreach (MultipartPostPart part in binaryData) { - message.BinaryData.Add(part); - } - - HttpWebRequest wr = this.OAuthChannel.InitializeRequest(message); - return wr; - } - - /// <summary> - /// Prepares an HTTP request that has OAuth authorization already attached to it. - /// </summary> - /// <param name="message">The OAuth authorization message to attach to the HTTP request.</param> - /// <returns> - /// The HttpWebRequest that can be used to send the HTTP request to the remote service provider. - /// </returns> - /// <remarks> - /// If <see cref="IDirectedProtocolMessage.HttpMethods"/> property on the - /// <paramref name="message"/> has the - /// <see cref="HttpDeliveryMethods.AuthorizationHeaderRequest"/> flag set and - /// <see cref="ITamperResistantOAuthMessage.HttpMethod"/> is set to an HTTP method - /// that includes an entity body, the request stream is automatically sent - /// if and only if the <see cref="IMessage.ExtraData"/> dictionary is non-empty. - /// </remarks> - [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "Type of parameter forces the method to apply only to specific scenario.")] - public HttpWebRequest PrepareAuthorizedRequest(AccessProtectedResourceRequest message) { - Contract.Requires<ArgumentNullException>(message != null); - return this.OAuthChannel.InitializeRequest(message); - } - - /// <summary> - /// Creates a web request prepared with OAuth authorization - /// that may be further tailored by adding parameters by the caller. - /// </summary> - /// <param name="endpoint">The URL and method on the Service Provider to send the request to.</param> - /// <param name="accessToken">The access token that permits access to the protected resource.</param> - /// <returns>The initialized WebRequest object.</returns> - /// <exception cref="WebException">Thrown if the request fails for any reason after it is sent to the Service Provider.</exception> - public IncomingWebResponse PrepareAuthorizedRequestAndSend(MessageReceivingEndpoint endpoint, string accessToken) { - IDirectedProtocolMessage message = this.CreateAuthorizingMessage(endpoint, accessToken); - HttpWebRequest wr = this.OAuthChannel.InitializeRequest(message); - return this.Channel.WebRequestHandler.GetResponse(wr); - } - - #region IDisposable Members - - /// <summary> - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. - /// </summary> - public void Dispose() { - this.Dispose(true); - GC.SuppressFinalize(this); - } - - #endregion - - /// <summary> - /// Creates a web request prepared with OAuth authorization - /// that may be further tailored by adding parameters by the caller. - /// </summary> - /// <param name="endpoint">The URL and method on the Service Provider to send the request to.</param> - /// <param name="accessToken">The access token that permits access to the protected resource.</param> - /// <returns>The initialized WebRequest object.</returns> - protected internal AccessProtectedResourceRequest CreateAuthorizingMessage(MessageReceivingEndpoint endpoint, string accessToken) { - Contract.Requires<ArgumentNullException>(endpoint != null); - Contract.Requires<ArgumentNullException>(!String.IsNullOrEmpty(accessToken)); - - AccessProtectedResourceRequest message = new AccessProtectedResourceRequest(endpoint, this.ServiceProvider.Version) { - AccessToken = accessToken, - ConsumerKey = this.ConsumerKey, - }; - - return message; - } - - /// <summary> - /// Prepares an OAuth message that begins an authorization request that will - /// redirect the user to the Service Provider to provide that authorization. - /// </summary> - /// <param name="callback"> - /// An optional Consumer URL that the Service Provider should redirect the - /// User Agent to upon successful authorization. - /// </param> - /// <param name="requestParameters">Extra parameters to add to the request token message. Optional.</param> - /// <param name="redirectParameters">Extra parameters to add to the redirect to Service Provider message. Optional.</param> - /// <param name="requestToken">The request token that must be exchanged for an access token after the user has provided authorization.</param> - /// <returns>The pending user agent redirect based message to be sent as an HttpResponse.</returns> - [SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "3#", Justification = "Two results")] - protected internal UserAuthorizationRequest PrepareRequestUserAuthorization(Uri callback, IDictionary<string, string> requestParameters, IDictionary<string, string> redirectParameters, out string requestToken) { - // Obtain an unauthorized request token. Assume the OAuth version given in the service description. - var token = new UnauthorizedTokenRequest(this.ServiceProvider.RequestTokenEndpoint, this.ServiceProvider.Version) { - ConsumerKey = this.ConsumerKey, - Callback = callback, - }; - var tokenAccessor = this.Channel.MessageDescriptions.GetAccessor(token); - tokenAccessor.AddExtraParameters(requestParameters); - var requestTokenResponse = this.Channel.Request<UnauthorizedTokenResponse>(token); - this.TokenManager.StoreNewRequestToken(token, requestTokenResponse); - - // Fine-tune our understanding of the SP's supported OAuth version if it's wrong. - if (this.ServiceProvider.Version != requestTokenResponse.Version) { - Logger.OAuth.WarnFormat("Expected OAuth service provider at endpoint {0} to use OAuth {1} but {2} was detected. Adjusting service description to new version.", this.ServiceProvider.RequestTokenEndpoint.Location, this.ServiceProvider.Version, requestTokenResponse.Version); - this.ServiceProvider.ProtocolVersion = Protocol.Lookup(requestTokenResponse.Version).ProtocolVersion; - } - - // Request user authorization. The OAuth version will automatically include - // or drop the callback that we're setting here. - ITokenContainingMessage assignedRequestToken = requestTokenResponse; - var requestAuthorization = new UserAuthorizationRequest(this.ServiceProvider.UserAuthorizationEndpoint, assignedRequestToken.Token, requestTokenResponse.Version) { - Callback = callback, - }; - var requestAuthorizationAccessor = this.Channel.MessageDescriptions.GetAccessor(requestAuthorization); - requestAuthorizationAccessor.AddExtraParameters(redirectParameters); - requestToken = requestAuthorization.RequestToken; - return requestAuthorization; - } - - /// <summary> - /// Exchanges a given request token for access token. - /// </summary> - /// <param name="requestToken">The request token that the user has authorized.</param> - /// <param name="verifier">The verifier code.</param> - /// <returns> - /// The access token assigned by the Service Provider. - /// </returns> - protected AuthorizedTokenResponse ProcessUserAuthorization(string requestToken, string verifier) { - Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(requestToken)); - Contract.Ensures(Contract.Result<AuthorizedTokenResponse>() != null); - - var requestAccess = new AuthorizedTokenRequest(this.ServiceProvider.AccessTokenEndpoint, this.ServiceProvider.Version) { - RequestToken = requestToken, - VerificationCode = verifier, - ConsumerKey = this.ConsumerKey, - }; - var grantAccess = this.Channel.Request<AuthorizedTokenResponse>(requestAccess); - this.TokenManager.ExpireRequestTokenAndStoreNewAccessToken(this.ConsumerKey, requestToken, grantAccess.AccessToken, grantAccess.TokenSecret); - return grantAccess; - } - - /// <summary> - /// Releases unmanaged and - optionally - managed resources - /// </summary> - /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param> - protected virtual void Dispose(bool disposing) { - if (disposing) { - this.Channel.Dispose(); - } - } - } -} diff --git a/src/DotNetOpenAuth/OAuth/Messages/MessageBase.cs b/src/DotNetOpenAuth/OAuth/Messages/MessageBase.cs deleted file mode 100644 index 1a0ba23..0000000 --- a/src/DotNetOpenAuth/OAuth/Messages/MessageBase.cs +++ /dev/null @@ -1,281 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="MessageBase.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OAuth.Messages { - using System; - using System.Collections.Generic; - using System.Diagnostics.Contracts; - using System.Globalization; - using System.Text; - using DotNetOpenAuth.Messaging; - using DotNetOpenAuth.Messaging.Reflection; - using DotNetOpenAuth.OAuth.ChannelElements; - - /// <summary> - /// A base class for all OAuth messages. - /// </summary> - [Serializable] - public abstract class MessageBase : IDirectedProtocolMessage, IDirectResponseProtocolMessage { - /// <summary> - /// A store for extra name/value data pairs that are attached to this message. - /// </summary> - private Dictionary<string, string> extraData = new Dictionary<string, string>(); - - /// <summary> - /// Gets a value indicating whether signing this message is required. - /// </summary> - private MessageProtections protectionRequired; - - /// <summary> - /// Gets a value indicating whether this is a direct or indirect message. - /// </summary> - private MessageTransport transport; - - /// <summary> - /// The URI to the remote endpoint to send this message to. - /// </summary> - private MessageReceivingEndpoint recipient; - - /// <summary> - /// Backing store for the <see cref="OriginatingRequest"/> properties. - /// </summary> - private IDirectedProtocolMessage originatingRequest; - - /// <summary> - /// Backing store for the <see cref="Incoming"/> properties. - /// </summary> - private bool incoming; - -#if DEBUG - /// <summary> - /// Initializes static members of the <see cref="MessageBase"/> class. - /// </summary> - static MessageBase() { - LowSecurityMode = true; - } -#endif - - /// <summary> - /// Initializes a new instance of the <see cref="MessageBase"/> class for direct response messages. - /// </summary> - /// <param name="protectionRequired">The level of protection the message requires.</param> - /// <param name="originatingRequest">The request that asked for this direct response.</param> - /// <param name="version">The OAuth version.</param> - protected MessageBase(MessageProtections protectionRequired, IDirectedProtocolMessage originatingRequest, Version version) { - Contract.Requires<ArgumentNullException>(originatingRequest != null); - Contract.Requires<ArgumentNullException>(version != null); - - this.protectionRequired = protectionRequired; - this.transport = MessageTransport.Direct; - this.originatingRequest = originatingRequest; - this.Version = version; - } - - /// <summary> - /// Initializes a new instance of the <see cref="MessageBase"/> class for direct requests or indirect messages. - /// </summary> - /// <param name="protectionRequired">The level of protection the message requires.</param> - /// <param name="transport">A value indicating whether this message requires a direct or indirect transport.</param> - /// <param name="recipient">The URI that a directed message will be delivered to.</param> - /// <param name="version">The OAuth version.</param> - protected MessageBase(MessageProtections protectionRequired, MessageTransport transport, MessageReceivingEndpoint recipient, Version version) { - Contract.Requires<ArgumentNullException>(recipient != null); - Contract.Requires<ArgumentNullException>(version != null); - - this.protectionRequired = protectionRequired; - this.transport = transport; - this.recipient = recipient; - this.Version = version; - } - - #region IProtocolMessage Properties - - /// <summary> - /// Gets the version of the protocol this message is prepared to implement. - /// </summary> - Version IMessage.Version { - get { return this.Version; } - } - - /// <summary> - /// Gets the level of protection this message requires. - /// </summary> - MessageProtections IProtocolMessage.RequiredProtection { - get { return this.RequiredProtection; } - } - - /// <summary> - /// Gets a value indicating whether this is a direct or indirect message. - /// </summary> - MessageTransport IProtocolMessage.Transport { - get { return this.Transport; } - } - - /// <summary> - /// Gets the dictionary of additional name/value fields tacked on to this message. - /// </summary> - IDictionary<string, string> IMessage.ExtraData { - get { return this.ExtraData; } - } - - #endregion - - #region IDirectedProtocolMessage Members - - /// <summary> - /// Gets the URI to the Service Provider endpoint to send this message to. - /// </summary> - Uri IDirectedProtocolMessage.Recipient { - get { return this.recipient != null ? this.recipient.Location : null; } - } - - /// <summary> - /// Gets the preferred method of transport for the message. - /// </summary> - HttpDeliveryMethods IDirectedProtocolMessage.HttpMethods { - get { return this.HttpMethods; } - } - - #endregion - - #region IDirectResponseProtocolMessage Members - - /// <summary> - /// Gets the originating request message that caused this response to be formed. - /// </summary> - IDirectedProtocolMessage IDirectResponseProtocolMessage.OriginatingRequest { - get { return this.originatingRequest; } - } - - #endregion - - /// <summary> - /// Gets or sets a value indicating whether security sensitive strings are - /// emitted from the ToString() method. - /// </summary> - internal static bool LowSecurityMode { get; set; } - - /// <summary> - /// Gets a value indicating whether this message was deserialized as an incoming message. - /// </summary> - protected internal bool Incoming { - get { return this.incoming; } - } - - /// <summary> - /// Gets the version of the protocol this message is prepared to implement. - /// </summary> - protected internal Version Version { get; private set; } - - /// <summary> - /// Gets the level of protection this message requires. - /// </summary> - protected MessageProtections RequiredProtection { - get { return this.protectionRequired; } - } - - /// <summary> - /// Gets a value indicating whether this is a direct or indirect message. - /// </summary> - protected MessageTransport Transport { - get { return this.transport; } - } - - /// <summary> - /// Gets the dictionary of additional name/value fields tacked on to this message. - /// </summary> - protected IDictionary<string, string> ExtraData { - get { return this.extraData; } - } - - /// <summary> - /// Gets the preferred method of transport for the message. - /// </summary> - protected HttpDeliveryMethods HttpMethods { - get { return this.recipient != null ? this.recipient.AllowedMethods : HttpDeliveryMethods.None; } - } - - /// <summary> - /// Gets or sets the URI to the Service Provider endpoint to send this message to. - /// </summary> - protected Uri Recipient { - get { - return this.recipient != null ? this.recipient.Location : null; - } - - set { - if (this.recipient != null) { - this.recipient = new MessageReceivingEndpoint(value, this.recipient.AllowedMethods); - } else if (value != null) { - throw new InvalidOperationException(); - } - } - } - - /// <summary> - /// Gets the originating request message that caused this response to be formed. - /// </summary> - protected IDirectedProtocolMessage OriginatingRequest { - get { return this.originatingRequest; } - } - - #region IProtocolMessage Methods - - /// <summary> - /// Checks the message state for conformity to the protocol specification - /// and throws an exception if the message is invalid. - /// </summary> - void IMessage.EnsureValidMessage() { - this.EnsureValidMessage(); - } - - #endregion - - /// <summary> - /// Returns a human-friendly string describing the message and all serializable properties. - /// </summary> - /// <param name="channel">The channel that will carry this message.</param> - /// <returns> - /// The string representation of this object. - /// </returns> - internal virtual string ToString(Channel channel) { - Contract.Requires<ArgumentNullException>(channel != null); - - StringBuilder builder = new StringBuilder(); - builder.AppendFormat(CultureInfo.InvariantCulture, "{0} message", GetType().Name); - if (this.recipient != null) { - builder.AppendFormat(CultureInfo.InvariantCulture, " as {0} to {1}", this.recipient.AllowedMethods, this.recipient.Location); - } - builder.AppendLine(); - MessageDictionary dictionary = channel.MessageDescriptions.GetAccessor(this); - foreach (var pair in dictionary) { - string value = pair.Value; - if (pair.Key == "oauth_signature" && !LowSecurityMode) { - value = "xxxxxxxxxxxxx (not shown)"; - } - builder.Append('\t'); - builder.Append(pair.Key); - builder.Append(": "); - builder.AppendLine(value); - } - - return builder.ToString(); - } - - /// <summary> - /// Sets a flag indicating that this message is received (as opposed to sent). - /// </summary> - internal void SetAsIncoming() { - this.incoming = true; - } - - /// <summary> - /// Checks the message state for conformity to the protocol specification - /// and throws an exception if the message is invalid. - /// </summary> - protected virtual void EnsureValidMessage() { } - } -} diff --git a/src/DotNetOpenAuth/OAuth/Messages/UnauthorizedTokenResponse.cs b/src/DotNetOpenAuth/OAuth/Messages/UnauthorizedTokenResponse.cs deleted file mode 100644 index 0be9f63..0000000 --- a/src/DotNetOpenAuth/OAuth/Messages/UnauthorizedTokenResponse.cs +++ /dev/null @@ -1,100 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="UnauthorizedTokenResponse.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OAuth.Messages { - using System; - using System.Collections.Generic; - using System.Diagnostics.CodeAnalysis; - using System.Diagnostics.Contracts; - using DotNetOpenAuth.Messaging; - - /// <summary> - /// A direct message sent from Service Provider to Consumer in response to - /// a Consumer's <see cref="UnauthorizedTokenRequest"/> request. - /// </summary> - public class UnauthorizedTokenResponse : MessageBase, ITokenSecretContainingMessage { - /// <summary> - /// Initializes a new instance of the <see cref="UnauthorizedTokenResponse"/> class. - /// </summary> - /// <param name="requestMessage">The unauthorized request token message that this message is being generated in response to.</param> - /// <param name="requestToken">The request token.</param> - /// <param name="tokenSecret">The token secret.</param> - /// <remarks> - /// This constructor is used by the Service Provider to send the message. - /// </remarks> - protected internal UnauthorizedTokenResponse(UnauthorizedTokenRequest requestMessage, string requestToken, string tokenSecret) - : this(requestMessage, requestMessage.Version) { - Contract.Requires<ArgumentNullException>(requestToken != null); - Contract.Requires<ArgumentNullException>(tokenSecret != null); - - this.RequestToken = requestToken; - this.TokenSecret = tokenSecret; - } - - /// <summary> - /// Initializes a new instance of the <see cref="UnauthorizedTokenResponse"/> class. - /// </summary> - /// <param name="originatingRequest">The originating request.</param> - /// <param name="version">The OAuth version.</param> - /// <remarks>This constructor is used by the consumer to deserialize the message.</remarks> - protected internal UnauthorizedTokenResponse(UnauthorizedTokenRequest originatingRequest, Version version) - : base(MessageProtections.None, originatingRequest, version) { - } - - /// <summary> - /// Gets or sets the Request or Access Token. - /// </summary> - [SuppressMessage("Microsoft.Design", "CA1033:InterfaceMethodsShouldBeCallableByChildTypes", Justification = "This property IS accessible by a different name.")] - string ITokenContainingMessage.Token { - get { return this.RequestToken; } - set { this.RequestToken = value; } - } - - /// <summary> - /// Gets or sets the Request or Access Token secret. - /// </summary> - string ITokenSecretContainingMessage.TokenSecret { - get { return this.TokenSecret; } - set { this.TokenSecret = value; } - } - - /// <summary> - /// Gets the extra, non-OAuth parameters that will be included in the message. - /// </summary> - public new IDictionary<string, string> ExtraData { - get { return base.ExtraData; } - } - - /// <summary> - /// Gets or sets the Request Token. - /// </summary> - [MessagePart("oauth_token", IsRequired = true)] - internal string RequestToken { get; set; } - - /// <summary> - /// Gets the original request for an unauthorized token. - /// </summary> - internal UnauthorizedTokenRequest RequestMessage { - get { return (UnauthorizedTokenRequest)this.OriginatingRequest; } - } - - /// <summary> - /// Gets or sets the Token Secret. - /// </summary> - [MessagePart("oauth_token_secret", IsRequired = true)] - protected internal string TokenSecret { get; set; } - - /// <summary> - /// Gets a value indicating whether the Service Provider recognized the callback parameter in the request. - /// </summary> - [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Message serialization invoked.")] - [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Message parts must be instance members.")] - [MessagePart("oauth_callback_confirmed", IsRequired = true, MinVersion = Protocol.V10aVersion)] - private bool CallbackConfirmed { - get { return true; } - } - } -} diff --git a/src/DotNetOpenAuth/OAuth/Protocol.cs b/src/DotNetOpenAuth/OAuth/Protocol.cs deleted file mode 100644 index 4418b5e..0000000 --- a/src/DotNetOpenAuth/OAuth/Protocol.cs +++ /dev/null @@ -1,148 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="Protocol.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OAuth { - using System; - using System.Collections.Generic; - using System.Diagnostics; - using System.Diagnostics.CodeAnalysis; - using System.Diagnostics.Contracts; - using System.Linq; - using System.Text; - using DotNetOpenAuth.Messaging; - - /// <summary> - /// An enumeration of the OAuth protocol versions supported by this library. - /// </summary> - public enum ProtocolVersion { - /// <summary> - /// OAuth 1.0 specification - /// </summary> - V10, - - /// <summary> - /// OAuth 1.0a specification - /// </summary> - [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "a", Justification = "By design.")] - V10a, - } - - /// <summary> - /// Constants used in the OAuth protocol. - /// </summary> - /// <remarks> - /// OAuth Protocol Parameter names and values are case sensitive. Each OAuth Protocol Parameters MUST NOT appear more than once per request, and are REQUIRED unless otherwise noted, - /// per OAuth 1.0 section 5. - /// </remarks> - [DebuggerDisplay("OAuth {Version}")] - internal class Protocol { - /// <summary> - /// The namespace to use for V1.0 of the protocol. - /// </summary> - internal const string DataContractNamespaceV10 = "http://oauth.net/core/1.0/"; - - /// <summary> - /// The prefix used for all key names in the protocol. - /// </summary> - internal const string ParameterPrefix = "oauth_"; - - /// <summary> - /// The string representation of a <see cref="Version"/> instance to be used to represent OAuth 1.0a. - /// </summary> - internal const string V10aVersion = "1.0.1"; - - /// <summary> - /// The scheme to use in Authorization header message requests. - /// </summary> - internal const string AuthorizationHeaderScheme = "OAuth"; - - /// <summary> - /// Gets the <see cref="Protocol"/> instance with values initialized for V1.0 of the protocol. - /// </summary> - internal static readonly Protocol V10 = new Protocol { - dataContractNamespace = DataContractNamespaceV10, - Version = new Version(1, 0), - ProtocolVersion = ProtocolVersion.V10, - }; - - /// <summary> - /// Gets the <see cref="Protocol"/> instance with values initialized for V1.0a of the protocol. - /// </summary> - internal static readonly Protocol V10a = new Protocol { - dataContractNamespace = DataContractNamespaceV10, - Version = new Version(V10aVersion), - ProtocolVersion = ProtocolVersion.V10a, - }; - - /// <summary> - /// A list of all supported OAuth versions, in order starting from newest version. - /// </summary> - internal static readonly List<Protocol> AllVersions = new List<Protocol>() { V10a, V10 }; - - /// <summary> - /// The default (or most recent) supported version of the OAuth protocol. - /// </summary> - internal static readonly Protocol Default = AllVersions[0]; - - /// <summary> - /// The namespace to use for this version of the protocol. - /// </summary> - private string dataContractNamespace; - - /// <summary> - /// Initializes a new instance of the <see cref="Protocol"/> class. - /// </summary> - internal Protocol() { - this.PublishedVersion = "1.0"; - } - - /// <summary> - /// Gets the OAuth version this instance represents. - /// </summary> - internal Version Version { get; private set; } - - /// <summary> - /// Gets the version to declare on the wire. - /// </summary> - internal string PublishedVersion { get; private set; } - - /// <summary> - /// Gets the <see cref="ProtocolVersion"/> enum value for the <see cref="Protocol"/> instance. - /// </summary> - internal ProtocolVersion ProtocolVersion { get; private set; } - - /// <summary> - /// Gets the namespace to use for this version of the protocol. - /// </summary> - internal string DataContractNamespace { - get { return this.dataContractNamespace; } - } - - /// <summary> - /// Gets the OAuth Protocol instance to use for the given version. - /// </summary> - /// <param name="version">The OAuth version to get.</param> - /// <returns>A matching <see cref="Protocol"/> instance.</returns> - public static Protocol Lookup(ProtocolVersion version) { - switch (version) { - case ProtocolVersion.V10: return Protocol.V10; - case ProtocolVersion.V10a: return Protocol.V10a; - default: throw new ArgumentOutOfRangeException("version"); - } - } - - /// <summary> - /// Gets the OAuth Protocol instance to use for the given version. - /// </summary> - /// <param name="version">The OAuth version to get.</param> - /// <returns>A matching <see cref="Protocol"/> instance.</returns> - internal static Protocol Lookup(Version version) { - Contract.Requires<ArgumentNullException>(version != null); - Contract.Requires<ArgumentOutOfRangeException>(AllVersions.Any(p => p.Version == version)); - return AllVersions.First(p => p.Version == version); - } - } -} diff --git a/src/DotNetOpenAuth/OAuth/ServiceProvider.cs b/src/DotNetOpenAuth/OAuth/ServiceProvider.cs deleted file mode 100644 index ec9206e..0000000 --- a/src/DotNetOpenAuth/OAuth/ServiceProvider.cs +++ /dev/null @@ -1,576 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="ServiceProvider.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OAuth { - using System; - using System.Collections.Generic; - using System.ComponentModel; - using System.Diagnostics.CodeAnalysis; - using System.Diagnostics.Contracts; - using System.Globalization; - using System.Security.Principal; - using System.ServiceModel.Channels; - using System.Web; - using DotNetOpenAuth.Configuration; - using DotNetOpenAuth.Messaging; - using DotNetOpenAuth.Messaging.Bindings; - using DotNetOpenAuth.OAuth.ChannelElements; - using DotNetOpenAuth.OAuth.Messages; - using DotNetOpenAuth.OpenId; - using DotNetOpenAuth.OpenId.Extensions.OAuth; - using DotNetOpenAuth.OpenId.Messages; - using DotNetOpenAuth.OpenId.Provider; - - /// <summary> - /// A web application that allows access via OAuth. - /// </summary> - /// <remarks> - /// <para>The Service Provider’s documentation should include:</para> - /// <list> - /// <item>The URLs (Request URLs) the Consumer will use when making OAuth requests, and the HTTP methods (i.e. GET, POST, etc.) used in the Request Token URL and Access Token URL.</item> - /// <item>Signature methods supported by the Service Provider.</item> - /// <item>Any additional request parameters that the Service Provider requires in order to obtain a Token. Service Provider specific parameters MUST NOT begin with oauth_.</item> - /// </list> - /// </remarks> - public class ServiceProvider : IDisposable { - /// <summary> - /// The name of the key to use in the HttpApplication cache to store the - /// instance of <see cref="NonceMemoryStore"/> to use. - /// </summary> - private const string ApplicationStoreKey = "DotNetOpenAuth.OAuth.ServiceProvider.HttpApplicationStore"; - - /// <summary> - /// The length of the verifier code (in raw bytes before base64 encoding) to generate. - /// </summary> - private const int VerifierCodeLength = 5; - - /// <summary> - /// The field behind the <see cref="OAuthChannel"/> property. - /// </summary> - private OAuthChannel channel; - - /// <summary> - /// Initializes a new instance of the <see cref="ServiceProvider"/> class. - /// </summary> - /// <param name="serviceDescription">The endpoints and behavior on the Service Provider.</param> - /// <param name="tokenManager">The host's method of storing and recalling tokens and secrets.</param> - public ServiceProvider(ServiceProviderDescription serviceDescription, IServiceProviderTokenManager tokenManager) - : this(serviceDescription, tokenManager, new OAuthServiceProviderMessageFactory(tokenManager)) { - } - - /// <summary> - /// Initializes a new instance of the <see cref="ServiceProvider"/> class. - /// </summary> - /// <param name="serviceDescription">The endpoints and behavior on the Service Provider.</param> - /// <param name="tokenManager">The host's method of storing and recalling tokens and secrets.</param> - /// <param name="messageTypeProvider">An object that can figure out what type of message is being received for deserialization.</param> - public ServiceProvider(ServiceProviderDescription serviceDescription, IServiceProviderTokenManager tokenManager, OAuthServiceProviderMessageFactory messageTypeProvider) - : this(serviceDescription, tokenManager, DotNetOpenAuthSection.Configuration.OAuth.ServiceProvider.ApplicationStore.CreateInstance(HttpApplicationStore), messageTypeProvider) { - Contract.Requires<ArgumentNullException>(serviceDescription != null); - Contract.Requires<ArgumentNullException>(tokenManager != null); - Contract.Requires<ArgumentNullException>(messageTypeProvider != null); - } - - /// <summary> - /// Initializes a new instance of the <see cref="ServiceProvider"/> class. - /// </summary> - /// <param name="serviceDescription">The endpoints and behavior on the Service Provider.</param> - /// <param name="tokenManager">The host's method of storing and recalling tokens and secrets.</param> - /// <param name="nonceStore">The nonce store.</param> - public ServiceProvider(ServiceProviderDescription serviceDescription, IServiceProviderTokenManager tokenManager, INonceStore nonceStore) - : this(serviceDescription, tokenManager, nonceStore, new OAuthServiceProviderMessageFactory(tokenManager)) { - } - - /// <summary> - /// Initializes a new instance of the <see cref="ServiceProvider"/> class. - /// </summary> - /// <param name="serviceDescription">The endpoints and behavior on the Service Provider.</param> - /// <param name="tokenManager">The host's method of storing and recalling tokens and secrets.</param> - /// <param name="nonceStore">The nonce store.</param> - /// <param name="messageTypeProvider">An object that can figure out what type of message is being received for deserialization.</param> - public ServiceProvider(ServiceProviderDescription serviceDescription, IServiceProviderTokenManager tokenManager, INonceStore nonceStore, OAuthServiceProviderMessageFactory messageTypeProvider) { - Contract.Requires<ArgumentNullException>(serviceDescription != null); - Contract.Requires<ArgumentNullException>(tokenManager != null); - Contract.Requires<ArgumentNullException>(nonceStore != null); - Contract.Requires<ArgumentNullException>(messageTypeProvider != null); - - var signingElement = serviceDescription.CreateTamperProtectionElement(); - this.ServiceDescription = serviceDescription; - this.SecuritySettings = DotNetOpenAuthSection.Configuration.OAuth.ServiceProvider.SecuritySettings.CreateSecuritySettings(); - this.OAuthChannel = new OAuthChannel(signingElement, nonceStore, tokenManager, this.SecuritySettings, messageTypeProvider); - this.TokenGenerator = new StandardTokenGenerator(); - - Reporting.RecordFeatureAndDependencyUse(this, serviceDescription, tokenManager, nonceStore); - } - - /// <summary> - /// Gets the standard state storage mechanism that uses ASP.NET's - /// HttpApplication state dictionary to store associations and nonces. - /// </summary> - [EditorBrowsable(EditorBrowsableState.Advanced)] - public static INonceStore HttpApplicationStore { - get { - Contract.Ensures(Contract.Result<INonceStore>() != null); - - HttpContext context = HttpContext.Current; - ErrorUtilities.VerifyOperation(context != null, Strings.StoreRequiredWhenNoHttpContextAvailable, typeof(INonceStore).Name); - var store = (INonceStore)context.Application[ApplicationStoreKey]; - if (store == null) { - context.Application.Lock(); - try { - if ((store = (INonceStore)context.Application[ApplicationStoreKey]) == null) { - context.Application[ApplicationStoreKey] = store = new NonceMemoryStore(StandardExpirationBindingElement.MaximumMessageAge); - } - } finally { - context.Application.UnLock(); - } - } - - return store; - } - } - - /// <summary> - /// Gets the description of this Service Provider. - /// </summary> - public ServiceProviderDescription ServiceDescription { get; private set; } - - /// <summary> - /// Gets or sets the generator responsible for generating new tokens and secrets. - /// </summary> - public ITokenGenerator TokenGenerator { get; set; } - - /// <summary> - /// Gets the persistence store for tokens and secrets. - /// </summary> - public IServiceProviderTokenManager TokenManager { - get { return (IServiceProviderTokenManager)this.OAuthChannel.TokenManager; } - } - - /// <summary> - /// Gets the channel to use for sending/receiving messages. - /// </summary> - public Channel Channel { - get { return this.OAuthChannel; } - } - - /// <summary> - /// Gets the security settings for this service provider. - /// </summary> - public ServiceProviderSecuritySettings SecuritySettings { get; private set; } - - /// <summary> - /// Gets or sets the channel to use for sending/receiving messages. - /// </summary> - internal OAuthChannel OAuthChannel { - get { - return this.channel; - } - - set { - Contract.Requires<ArgumentNullException>(value != null); - this.channel = value; - } - } - - /// <summary> - /// Creates a cryptographically strong random verification code. - /// </summary> - /// <param name="format">The desired format of the verification code.</param> - /// <param name="length">The length of the code. - /// When <paramref name="format"/> is <see cref="VerificationCodeFormat.IncludedInCallback"/>, - /// this is the length of the original byte array before base64 encoding rather than the actual - /// length of the final string.</param> - /// <returns>The verification code.</returns> - public static string CreateVerificationCode(VerificationCodeFormat format, int length) { - Contract.Requires<ArgumentOutOfRangeException>(length >= 0); - - switch (format) { - case VerificationCodeFormat.IncludedInCallback: - return MessagingUtilities.GetCryptoRandomDataAsBase64(length); - case VerificationCodeFormat.AlphaNumericNoLookAlikes: - return MessagingUtilities.GetRandomString(length, MessagingUtilities.AlphaNumericNoLookAlikes); - case VerificationCodeFormat.AlphaUpper: - return MessagingUtilities.GetRandomString(length, MessagingUtilities.UppercaseLetters); - case VerificationCodeFormat.AlphaLower: - return MessagingUtilities.GetRandomString(length, MessagingUtilities.LowercaseLetters); - case VerificationCodeFormat.Numeric: - return MessagingUtilities.GetRandomString(length, MessagingUtilities.Digits); - default: - throw new ArgumentOutOfRangeException("format"); - } - } - - /// <summary> - /// Reads any incoming OAuth message. - /// </summary> - /// <returns>The deserialized message.</returns> - /// <remarks> - /// Requires HttpContext.Current. - /// </remarks> - public IDirectedProtocolMessage ReadRequest() { - return this.Channel.ReadFromRequest(); - } - - /// <summary> - /// Reads any incoming OAuth message. - /// </summary> - /// <param name="request">The HTTP request to read the message from.</param> - /// <returns>The deserialized message.</returns> - public IDirectedProtocolMessage ReadRequest(HttpRequestInfo request) { - return this.Channel.ReadFromRequest(request); - } - - /// <summary> - /// Gets the incoming request for an unauthorized token, if any. - /// </summary> - /// <returns>The incoming request, or null if no OAuth message was attached.</returns> - /// <exception cref="ProtocolException">Thrown if an unexpected OAuth message is attached to the incoming request.</exception> - /// <remarks> - /// Requires HttpContext.Current. - /// </remarks> - public UnauthorizedTokenRequest ReadTokenRequest() { - return this.ReadTokenRequest(this.Channel.GetRequestFromContext()); - } - - /// <summary> - /// Reads a request for an unauthorized token from the incoming HTTP request. - /// </summary> - /// <param name="request">The HTTP request to read from.</param> - /// <returns>The incoming request, or null if no OAuth message was attached.</returns> - /// <exception cref="ProtocolException">Thrown if an unexpected OAuth message is attached to the incoming request.</exception> - public UnauthorizedTokenRequest ReadTokenRequest(HttpRequestInfo request) { - UnauthorizedTokenRequest message; - if (this.Channel.TryReadFromRequest(request, out message)) { - ErrorUtilities.VerifyProtocol(message.Version >= Protocol.Lookup(this.SecuritySettings.MinimumRequiredOAuthVersion).Version, OAuthStrings.MinimumConsumerVersionRequirementNotMet, this.SecuritySettings.MinimumRequiredOAuthVersion, message.Version); - } - return message; - } - - /// <summary> - /// Prepares a message containing an unauthorized token for the Consumer to use in a - /// user agent redirect for subsequent authorization. - /// </summary> - /// <param name="request">The token request message the Consumer sent that the Service Provider is now responding to.</param> - /// <returns>The response message to send using the <see cref="Channel"/>, after optionally adding extra data to it.</returns> - public UnauthorizedTokenResponse PrepareUnauthorizedTokenMessage(UnauthorizedTokenRequest request) { - Contract.Requires<ArgumentNullException>(request != null); - - string token = this.TokenGenerator.GenerateRequestToken(request.ConsumerKey); - string secret = this.TokenGenerator.GenerateSecret(); - UnauthorizedTokenResponse response = new UnauthorizedTokenResponse(request, token, secret); - - return response; - } - - /// <summary> - /// Gets the incoming request for the Service Provider to authorize a Consumer's - /// access to some protected resources. - /// </summary> - /// <returns>The incoming request, or null if no OAuth message was attached.</returns> - /// <exception cref="ProtocolException">Thrown if an unexpected OAuth message is attached to the incoming request.</exception> - /// <remarks> - /// Requires HttpContext.Current. - /// </remarks> - public UserAuthorizationRequest ReadAuthorizationRequest() { - return this.ReadAuthorizationRequest(this.Channel.GetRequestFromContext()); - } - - /// <summary> - /// Reads in a Consumer's request for the Service Provider to obtain permission from - /// the user to authorize the Consumer's access of some protected resource(s). - /// </summary> - /// <param name="request">The HTTP request to read from.</param> - /// <returns>The incoming request, or null if no OAuth message was attached.</returns> - /// <exception cref="ProtocolException">Thrown if an unexpected OAuth message is attached to the incoming request.</exception> - public UserAuthorizationRequest ReadAuthorizationRequest(HttpRequestInfo request) { - UserAuthorizationRequest message; - this.Channel.TryReadFromRequest(request, out message); - return message; - } - - /// <summary> - /// Gets the OAuth authorization request included with an OpenID authentication - /// request, if there is one. - /// </summary> - /// <param name="openIdRequest">The OpenID authentication request.</param> - /// <returns> - /// The scope of access the relying party is requesting, or null if no OAuth request - /// is present. - /// </returns> - /// <remarks> - /// <para>Call this method rather than simply extracting the OAuth extension - /// out from the authentication request directly to ensure that the additional - /// security measures that are required are taken.</para> - /// </remarks> - public AuthorizationRequest ReadAuthorizationRequest(IHostProcessedRequest openIdRequest) { - Contract.Requires<ArgumentNullException>(openIdRequest != null); - Contract.Requires<InvalidOperationException>(this.TokenManager is ICombinedOpenIdProviderTokenManager); - var openidTokenManager = this.TokenManager as ICombinedOpenIdProviderTokenManager; - ErrorUtilities.VerifyOperation(openidTokenManager != null, OAuthStrings.OpenIdOAuthExtensionRequiresSpecialTokenManagerInterface, typeof(IOpenIdOAuthTokenManager).FullName); - - var authzRequest = openIdRequest.GetExtension<AuthorizationRequest>(); - if (authzRequest == null) { - return null; - } - - // OpenID+OAuth spec section 9: - // The Combined Provider SHOULD verify that the consumer key passed in the - // request is authorized to be used for the realm passed in the request. - string expectedConsumerKey = openidTokenManager.GetConsumerKey(openIdRequest.Realm); - ErrorUtilities.VerifyProtocol( - string.Equals(expectedConsumerKey, authzRequest.Consumer, StringComparison.Ordinal), - OAuthStrings.OpenIdOAuthRealmConsumerKeyDoNotMatch); - - return authzRequest; - } - - /// <summary> - /// Attaches the authorization response to an OpenID authentication response. - /// </summary> - /// <param name="openIdAuthenticationRequest">The OpenID authentication request.</param> - /// <param name="consumerKey">The consumer key. Must be <c>null</c> if and only if <paramref name="scope"/> is null.</param> - /// <param name="scope">The approved access scope. Use <c>null</c> to indicate no access was granted. The empty string will be interpreted as some default level of access is granted.</param> - [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "We want to take IAuthenticationRequest because that's the only supported use case.")] - [Obsolete("Call the overload that doesn't take a consumerKey instead.")] - public void AttachAuthorizationResponse(IHostProcessedRequest openIdAuthenticationRequest, string consumerKey, string scope) { - Contract.Requires<ArgumentNullException>(openIdAuthenticationRequest != null); - Contract.Requires<ArgumentException>((consumerKey == null) == (scope == null)); - Contract.Requires<InvalidOperationException>(this.TokenManager is ICombinedOpenIdProviderTokenManager); - var openidTokenManager = (ICombinedOpenIdProviderTokenManager)this.TokenManager; - ErrorUtilities.VerifyArgument(consumerKey == null || consumerKey == openidTokenManager.GetConsumerKey(openIdAuthenticationRequest.Realm), OAuthStrings.OpenIdOAuthRealmConsumerKeyDoNotMatch); - - this.AttachAuthorizationResponse(openIdAuthenticationRequest, scope); - } - - /// <summary> - /// Attaches the authorization response to an OpenID authentication response. - /// </summary> - /// <param name="openIdAuthenticationRequest">The OpenID authentication request.</param> - /// <param name="scope">The approved access scope. Use <c>null</c> to indicate no access was granted. The empty string will be interpreted as some default level of access is granted.</param> - [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "We want to take IAuthenticationRequest because that's the only supported use case.")] - public void AttachAuthorizationResponse(IHostProcessedRequest openIdAuthenticationRequest, string scope) { - Contract.Requires<ArgumentNullException>(openIdAuthenticationRequest != null); - Contract.Requires<InvalidOperationException>(this.TokenManager is ICombinedOpenIdProviderTokenManager); - - var openidTokenManager = this.TokenManager as ICombinedOpenIdProviderTokenManager; - IOpenIdMessageExtension response; - if (scope != null) { - // Generate an authorized request token to return to the relying party. - string consumerKey = openidTokenManager.GetConsumerKey(openIdAuthenticationRequest.Realm); - var approvedResponse = new AuthorizationApprovedResponse { - RequestToken = this.TokenGenerator.GenerateRequestToken(consumerKey), - Scope = scope, - }; - openidTokenManager.StoreOpenIdAuthorizedRequestToken(consumerKey, approvedResponse); - response = approvedResponse; - } else { - response = new AuthorizationDeclinedResponse(); - } - - openIdAuthenticationRequest.AddResponseExtension(response); - } - - /// <summary> - /// Prepares the message to send back to the consumer following proper authorization of - /// a token by an interactive user at the Service Provider's web site. - /// </summary> - /// <param name="request">The Consumer's original authorization request.</param> - /// <returns> - /// The message to send to the Consumer using <see cref="Channel"/> if one is necessary. - /// Null if the Consumer did not request a callback as part of the authorization request. - /// </returns> - [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Consistent user experience with instance.")] - public UserAuthorizationResponse PrepareAuthorizationResponse(UserAuthorizationRequest request) { - Contract.Requires<ArgumentNullException>(request != null); - - // It is very important for us to ignore the oauth_callback argument in the - // UserAuthorizationRequest if the Consumer is a 1.0a consumer or else we - // open up a security exploit. - IServiceProviderRequestToken token = this.TokenManager.GetRequestToken(request.RequestToken); - Uri callback; - if (request.Version >= Protocol.V10a.Version) { - // In OAuth 1.0a, we'll prefer the token-specific callback to the pre-registered one. - if (token.Callback != null) { - callback = token.Callback; - } else { - IConsumerDescription consumer = this.TokenManager.GetConsumer(token.ConsumerKey); - callback = consumer.Callback; - } - } else { - // In OAuth 1.0, we'll prefer the pre-registered callback over the token-specific one - // since 1.0 has a security weakness for user-modified callback URIs. - IConsumerDescription consumer = this.TokenManager.GetConsumer(token.ConsumerKey); - callback = consumer.Callback ?? request.Callback; - } - - return callback != null ? this.PrepareAuthorizationResponse(request, callback) : null; - } - - /// <summary> - /// Prepares the message to send back to the consumer following proper authorization of - /// a token by an interactive user at the Service Provider's web site. - /// </summary> - /// <param name="request">The Consumer's original authorization request.</param> - /// <param name="callback">The callback URI the consumer has previously registered - /// with this service provider or that came in the <see cref="UnauthorizedTokenRequest"/>.</param> - /// <returns> - /// The message to send to the Consumer using <see cref="Channel"/>. - /// </returns> - [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Consistent user experience with instance.")] - public UserAuthorizationResponse PrepareAuthorizationResponse(UserAuthorizationRequest request, Uri callback) { - Contract.Requires<ArgumentNullException>(request != null); - Contract.Requires<ArgumentNullException>(callback != null); - - var authorization = new UserAuthorizationResponse(callback, request.Version) { - RequestToken = request.RequestToken, - }; - - if (authorization.Version >= Protocol.V10a.Version) { - authorization.VerificationCode = CreateVerificationCode(VerificationCodeFormat.IncludedInCallback, VerifierCodeLength); - } - - return authorization; - } - - /// <summary> - /// Gets the incoming request to exchange an authorized token for an access token. - /// </summary> - /// <returns>The incoming request, or null if no OAuth message was attached.</returns> - /// <exception cref="ProtocolException">Thrown if an unexpected OAuth message is attached to the incoming request.</exception> - /// <remarks> - /// Requires HttpContext.Current. - /// </remarks> - public AuthorizedTokenRequest ReadAccessTokenRequest() { - return this.ReadAccessTokenRequest(this.Channel.GetRequestFromContext()); - } - - /// <summary> - /// Reads in a Consumer's request to exchange an authorized request token for an access token. - /// </summary> - /// <param name="request">The HTTP request to read from.</param> - /// <returns>The incoming request, or null if no OAuth message was attached.</returns> - /// <exception cref="ProtocolException">Thrown if an unexpected OAuth message is attached to the incoming request.</exception> - public AuthorizedTokenRequest ReadAccessTokenRequest(HttpRequestInfo request) { - AuthorizedTokenRequest message; - this.Channel.TryReadFromRequest(request, out message); - return message; - } - - /// <summary> - /// Prepares and sends an access token to a Consumer, and invalidates the request token. - /// </summary> - /// <param name="request">The Consumer's message requesting an access token.</param> - /// <returns>The HTTP response to actually send to the Consumer.</returns> - public AuthorizedTokenResponse PrepareAccessTokenMessage(AuthorizedTokenRequest request) { - Contract.Requires<ArgumentNullException>(request != null); - - ErrorUtilities.VerifyProtocol(this.TokenManager.IsRequestTokenAuthorized(request.RequestToken), OAuthStrings.AccessTokenNotAuthorized, request.RequestToken); - - string accessToken = this.TokenGenerator.GenerateAccessToken(request.ConsumerKey); - string tokenSecret = this.TokenGenerator.GenerateSecret(); - this.TokenManager.ExpireRequestTokenAndStoreNewAccessToken(request.ConsumerKey, request.RequestToken, accessToken, tokenSecret); - var grantAccess = new AuthorizedTokenResponse(request) { - AccessToken = accessToken, - TokenSecret = tokenSecret, - }; - - return grantAccess; - } - - /// <summary> - /// Gets the authorization (access token) for accessing some protected resource. - /// </summary> - /// <returns>The authorization message sent by the Consumer, or null if no authorization message is attached.</returns> - /// <remarks> - /// This method verifies that the access token and token secret are valid. - /// It falls on the caller to verify that the access token is actually authorized - /// to access the resources being requested. - /// </remarks> - /// <exception cref="ProtocolException">Thrown if an unexpected message is attached to the request.</exception> - public AccessProtectedResourceRequest ReadProtectedResourceAuthorization() { - return this.ReadProtectedResourceAuthorization(this.Channel.GetRequestFromContext()); - } - - /// <summary> - /// Gets the authorization (access token) for accessing some protected resource. - /// </summary> - /// <param name="request">HTTP details from an incoming WCF message.</param> - /// <param name="requestUri">The URI of the WCF service endpoint.</param> - /// <returns>The authorization message sent by the Consumer, or null if no authorization message is attached.</returns> - /// <remarks> - /// This method verifies that the access token and token secret are valid. - /// It falls on the caller to verify that the access token is actually authorized - /// to access the resources being requested. - /// </remarks> - /// <exception cref="ProtocolException">Thrown if an unexpected message is attached to the request.</exception> - public AccessProtectedResourceRequest ReadProtectedResourceAuthorization(HttpRequestMessageProperty request, Uri requestUri) { - return this.ReadProtectedResourceAuthorization(new HttpRequestInfo(request, requestUri)); - } - - /// <summary> - /// Gets the authorization (access token) for accessing some protected resource. - /// </summary> - /// <param name="request">The incoming HTTP request.</param> - /// <returns>The authorization message sent by the Consumer, or null if no authorization message is attached.</returns> - /// <remarks> - /// This method verifies that the access token and token secret are valid. - /// It falls on the caller to verify that the access token is actually authorized - /// to access the resources being requested. - /// </remarks> - /// <exception cref="ProtocolException">Thrown if an unexpected message is attached to the request.</exception> - public AccessProtectedResourceRequest ReadProtectedResourceAuthorization(HttpRequestInfo request) { - Contract.Requires<ArgumentNullException>(request != null); - - AccessProtectedResourceRequest accessMessage; - if (this.Channel.TryReadFromRequest<AccessProtectedResourceRequest>(request, out accessMessage)) { - if (this.TokenManager.GetTokenType(accessMessage.AccessToken) != TokenType.AccessToken) { - throw new ProtocolException( - string.Format( - CultureInfo.CurrentCulture, - OAuthStrings.BadAccessTokenInProtectedResourceRequest, - accessMessage.AccessToken)); - } - } - - return accessMessage; - } - - /// <summary> - /// Creates a security principal that may be used. - /// </summary> - /// <param name="request">The request.</param> - /// <returns>The <see cref="IPrincipal"/> instance that can be used for access control of resources.</returns> - public OAuthPrincipal CreatePrincipal(AccessProtectedResourceRequest request) { - Contract.Requires<ArgumentNullException>(request != null); - - IServiceProviderAccessToken accessToken = this.TokenManager.GetAccessToken(request.AccessToken); - return new OAuthPrincipal(accessToken); - } - - #region IDisposable Members - - /// <summary> - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. - /// </summary> - public void Dispose() { - this.Dispose(true); - GC.SuppressFinalize(this); - } - - /// <summary> - /// Releases unmanaged and - optionally - managed resources - /// </summary> - /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param> - protected virtual void Dispose(bool disposing) { - if (disposing) { - this.Channel.Dispose(); - } - } - - #endregion - } -} diff --git a/src/DotNetOpenAuth/OAuth/WebConsumer.cs b/src/DotNetOpenAuth/OAuth/WebConsumer.cs deleted file mode 100644 index de37b80..0000000 --- a/src/DotNetOpenAuth/OAuth/WebConsumer.cs +++ /dev/null @@ -1,155 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="WebConsumer.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OAuth { - using System; - using System.Collections.Generic; - using System.Diagnostics.Contracts; - using System.Web; - using DotNetOpenAuth.Messaging; - using DotNetOpenAuth.OAuth.ChannelElements; - using DotNetOpenAuth.OAuth.Messages; - using DotNetOpenAuth.OpenId.Extensions.OAuth; - using DotNetOpenAuth.OpenId.RelyingParty; - - /// <summary> - /// A website or application that uses OAuth to access the Service Provider on behalf of the User. - /// </summary> - /// <remarks> - /// The methods on this class are thread-safe. Provided the properties are set and not changed - /// afterward, a single instance of this class may be used by an entire web application safely. - /// </remarks> - public class WebConsumer : ConsumerBase { - /// <summary> - /// Initializes a new instance of the <see cref="WebConsumer"/> class. - /// </summary> - /// <param name="serviceDescription">The endpoints and behavior of the Service Provider.</param> - /// <param name="tokenManager">The host's method of storing and recalling tokens and secrets.</param> - public WebConsumer(ServiceProviderDescription serviceDescription, IConsumerTokenManager tokenManager) - : base(serviceDescription, tokenManager) { - } - - /// <summary> - /// Begins an OAuth authorization request and redirects the user to the Service Provider - /// to provide that authorization. Upon successful authorization, the user is redirected - /// back to the current page. - /// </summary> - /// <returns>The pending user agent redirect based message to be sent as an HttpResponse.</returns> - /// <remarks> - /// Requires HttpContext.Current. - /// </remarks> - public UserAuthorizationRequest PrepareRequestUserAuthorization() { - Uri callback = this.Channel.GetRequestFromContext().UrlBeforeRewriting.StripQueryArgumentsWithPrefix(Protocol.ParameterPrefix); - return this.PrepareRequestUserAuthorization(callback, null, null); - } - - /// <summary> - /// Prepares an OAuth message that begins an authorization request that will - /// redirect the user to the Service Provider to provide that authorization. - /// </summary> - /// <param name="callback"> - /// An optional Consumer URL that the Service Provider should redirect the - /// User Agent to upon successful authorization. - /// </param> - /// <param name="requestParameters">Extra parameters to add to the request token message. Optional.</param> - /// <param name="redirectParameters">Extra parameters to add to the redirect to Service Provider message. Optional.</param> - /// <returns>The pending user agent redirect based message to be sent as an HttpResponse.</returns> - public UserAuthorizationRequest PrepareRequestUserAuthorization(Uri callback, IDictionary<string, string> requestParameters, IDictionary<string, string> redirectParameters) { - string token; - return this.PrepareRequestUserAuthorization(callback, requestParameters, redirectParameters, out token); - } - - /// <summary> - /// Processes an incoming authorization-granted message from an SP and obtains an access token. - /// </summary> - /// <returns>The access token, or null if no incoming authorization message was recognized.</returns> - /// <remarks> - /// Requires HttpContext.Current. - /// </remarks> - public AuthorizedTokenResponse ProcessUserAuthorization() { - return this.ProcessUserAuthorization(this.Channel.GetRequestFromContext()); - } - - /// <summary> - /// Attaches an OAuth authorization request to an outgoing OpenID authentication request. - /// </summary> - /// <param name="openIdAuthenticationRequest">The OpenID authentication request.</param> - /// <param name="scope">The scope of access that is requested of the service provider.</param> - public void AttachAuthorizationRequest(IAuthenticationRequest openIdAuthenticationRequest, string scope) { - Contract.Requires<ArgumentNullException>(openIdAuthenticationRequest != null); - - var authorizationRequest = new AuthorizationRequest { - Consumer = this.ConsumerKey, - Scope = scope, - }; - - openIdAuthenticationRequest.AddExtension(authorizationRequest); - } - - /// <summary> - /// Processes an incoming authorization-granted message from an SP and obtains an access token. - /// </summary> - /// <param name="openIdAuthenticationResponse">The OpenID authentication response that may be carrying an authorized request token.</param> - /// <returns> - /// The access token, or null if OAuth authorization was denied by the user or service provider. - /// </returns> - /// <remarks> - /// The access token, if granted, is automatically stored in the <see cref="ConsumerBase.TokenManager"/>. - /// The token manager instance must implement <see cref="IOpenIdOAuthTokenManager"/>. - /// </remarks> - public AuthorizedTokenResponse ProcessUserAuthorization(IAuthenticationResponse openIdAuthenticationResponse) { - Contract.Requires<ArgumentNullException>(openIdAuthenticationResponse != null); - Contract.Requires<InvalidOperationException>(this.TokenManager is IOpenIdOAuthTokenManager); - var openidTokenManager = this.TokenManager as IOpenIdOAuthTokenManager; - ErrorUtilities.VerifyOperation(openidTokenManager != null, OAuthStrings.OpenIdOAuthExtensionRequiresSpecialTokenManagerInterface, typeof(IOpenIdOAuthTokenManager).FullName); - - // The OAuth extension is only expected in positive assertion responses. - if (openIdAuthenticationResponse.Status != AuthenticationStatus.Authenticated) { - return null; - } - - // Retrieve the OAuth extension - var positiveAuthorization = openIdAuthenticationResponse.GetExtension<AuthorizationApprovedResponse>(); - if (positiveAuthorization == null) { - return null; - } - - // Prepare a message to exchange the request token for an access token. - // We are careful to use a v1.0 message version so that the oauth_verifier is not required. - var requestAccess = new AuthorizedTokenRequest(this.ServiceProvider.AccessTokenEndpoint, Protocol.V10.Version) { - RequestToken = positiveAuthorization.RequestToken, - ConsumerKey = this.ConsumerKey, - }; - - // Retrieve the access token and store it in the token manager. - openidTokenManager.StoreOpenIdAuthorizedRequestToken(this.ConsumerKey, positiveAuthorization); - var grantAccess = this.Channel.Request<AuthorizedTokenResponse>(requestAccess); - this.TokenManager.ExpireRequestTokenAndStoreNewAccessToken(this.ConsumerKey, positiveAuthorization.RequestToken, grantAccess.AccessToken, grantAccess.TokenSecret); - - // Provide the caller with the access token so it may be associated with the user - // that is logging in. - return grantAccess; - } - - /// <summary> - /// Processes an incoming authorization-granted message from an SP and obtains an access token. - /// </summary> - /// <param name="request">The incoming HTTP request.</param> - /// <returns>The access token, or null if no incoming authorization message was recognized.</returns> - public AuthorizedTokenResponse ProcessUserAuthorization(HttpRequestInfo request) { - Contract.Requires<ArgumentNullException>(request != null); - - UserAuthorizationResponse authorizationMessage; - if (this.Channel.TryReadFromRequest<UserAuthorizationResponse>(request, out authorizationMessage)) { - string requestToken = authorizationMessage.RequestToken; - string verifier = authorizationMessage.VerificationCode; - return this.ProcessUserAuthorization(requestToken, verifier); - } else { - return null; - } - } - } -} diff --git a/src/DotNetOpenAuth/OAuth2/AuthorizationServer.cs b/src/DotNetOpenAuth/OAuth2/AuthorizationServer.cs deleted file mode 100644 index ad40fa5..0000000 --- a/src/DotNetOpenAuth/OAuth2/AuthorizationServer.cs +++ /dev/null @@ -1,258 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="AuthorizationServer.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; - using DotNetOpenAuth.OAuth2.Messages; - - /// <summary> - /// Authorization Server supporting the web server flow. - /// </summary> - public class AuthorizationServer { - /// <summary> - /// Initializes a new instance of the <see cref="AuthorizationServer"/> class. - /// </summary> - /// <param name="authorizationServer">The authorization server.</param> - public AuthorizationServer(IAuthorizationServer authorizationServer) { - Contract.Requires<ArgumentNullException>(authorizationServer != null); - this.OAuthChannel = new OAuth2AuthorizationServerChannel(authorizationServer); - } - - /// <summary> - /// Gets the channel. - /// </summary> - /// <value>The channel.</value> - public Channel Channel { - get { return this.OAuthChannel; } - } - - /// <summary> - /// Gets the authorization server. - /// </summary> - /// <value>The authorization server.</value> - public IAuthorizationServer AuthorizationServerServices { - get { return this.OAuthChannel.AuthorizationServer; } - } - - /// <summary> - /// Gets the channel. - /// </summary> - internal OAuth2AuthorizationServerChannel OAuthChannel { get; private set; } - - /// <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> - /// <param name="request">The HTTP request to read from.</param> - /// <returns>The incoming request, or null if no OAuth message was attached.</returns> - /// <exception cref="ProtocolException">Thrown if an unexpected OAuth message is attached to the incoming request.</exception> - public EndUserAuthorizationRequest ReadAuthorizationRequest(HttpRequestInfo request = null) { - if (request == null) { - request = this.Channel.GetRequestFromContext(); - } - - EndUserAuthorizationRequest message; - if (this.Channel.TryReadFromRequest(request, out message)) { - 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); - } - } - - return message; - } - - /// <summary> - /// Approves an authorization request and sends an HTTP response to the user agent to redirect the user back to the Client. - /// </summary> - /// <param name="authorizationRequest">The authorization request to approve.</param> - /// <param name="userName">The username of the account that approved the request (or whose data will be accessed by the client).</param> - /// <param name="scopes">The scope of access the client should be granted. If <c>null</c>, all scopes in the original request will be granted.</param> - /// <param name="callback">The Client callback URL to use when formulating the redirect to send the user agent back to the Client.</param> - public void ApproveAuthorizationRequest(EndUserAuthorizationRequest authorizationRequest, string userName, IEnumerable<string> scopes = null, Uri callback = null) { - Contract.Requires<ArgumentNullException>(authorizationRequest != null); - - var response = this.PrepareApproveAuthorizationRequest(authorizationRequest, userName, scopes, callback); - this.Channel.Respond(response); - } - - /// <summary> - /// Rejects an authorization request and sends an HTTP response to the user agent to redirect the user back to the Client. - /// </summary> - /// <param name="authorizationRequest">The authorization request to disapprove.</param> - /// <param name="callback">The Client callback URL to use when formulating the redirect to send the user agent back to the Client.</param> - public void RejectAuthorizationRequest(EndUserAuthorizationRequest authorizationRequest, Uri callback = null) { - Contract.Requires<ArgumentNullException>(authorizationRequest != null); - - var response = this.PrepareRejectAuthorizationRequest(authorizationRequest, callback); - this.Channel.Respond(response); - } - - /// <summary> - /// Checks the incoming HTTP request for an access token request and prepares a response if the request message was found. - /// </summary> - /// <param name="response">The formulated response, or <c>null</c> if the request was not found..</param> - /// <returns>A value indicating whether any access token request was found in the HTTP request.</returns> - /// <remarks> - /// This method assumes that the authorization server and the resource server are the same and that they share a single - /// asymmetric key for signing and encrypting the access token. If this is not true, use the <see cref="ReadAccessTokenRequest"/> method instead. - /// </remarks> - public bool TryPrepareAccessTokenResponse(out IDirectResponseProtocolMessage response) { - return this.TryPrepareAccessTokenResponse(this.Channel.GetRequestFromContext(), out response); - } - - /// <summary> - /// Checks the incoming HTTP request for an access token request and prepares a response if the request message was found. - /// </summary> - /// <param name="httpRequestInfo">The HTTP request info.</param> - /// <param name="response">The formulated response, or <c>null</c> if the request was not found..</param> - /// <returns>A value indicating whether any access token request was found in the HTTP request.</returns> - /// <remarks> - /// This method assumes that the authorization server and the resource server are the same and that they share a single - /// asymmetric key for signing and encrypting the access token. If this is not true, use the <see cref="ReadAccessTokenRequest"/> method instead. - /// </remarks> - public bool TryPrepareAccessTokenResponse(HttpRequestInfo httpRequestInfo, out IDirectResponseProtocolMessage response) { - Contract.Requires<ArgumentNullException>(httpRequestInfo != null); - Contract.Ensures(Contract.Result<bool>() == (Contract.ValueAtReturn<IDirectResponseProtocolMessage>(out response) != null)); - - var request = this.ReadAccessTokenRequest(httpRequestInfo); - if (request != null) { - response = this.PrepareAccessTokenResponse(request); - return true; - } - - response = null; - return false; - } - - /// <summary> - /// Reads the access token request. - /// </summary> - /// <param name="requestInfo">The request info.</param> - /// <returns>The Client's request for an access token; or <c>null</c> if no such message was found in the request.</returns> - public AccessTokenRequestBase ReadAccessTokenRequest(HttpRequestInfo requestInfo = null) { - if (requestInfo == null) { - requestInfo = this.Channel.GetRequestFromContext(); - } - - AccessTokenRequestBase request; - this.Channel.TryReadFromRequest(requestInfo, out request); - return request; - } - - /// <summary> - /// Prepares a response to inform the Client that the user has rejected the Client's authorization request. - /// </summary> - /// <param name="authorizationRequest">The authorization request.</param> - /// <param name="callback">The Client callback URL to use when formulating the redirect to send the user agent back to the Client.</param> - /// <returns>The authorization response message to send to the Client.</returns> - public EndUserAuthorizationFailedResponse PrepareRejectAuthorizationRequest(EndUserAuthorizationRequest authorizationRequest, Uri callback = null) { - Contract.Requires<ArgumentNullException>(authorizationRequest != null); - Contract.Ensures(Contract.Result<EndUserAuthorizationFailedResponse>() != null); - - if (callback == null) { - callback = this.GetCallback(authorizationRequest); - } - - var response = new EndUserAuthorizationFailedResponse(callback, authorizationRequest); - return response; - } - - /// <summary> - /// Approves an authorization request. - /// </summary> - /// <param name="authorizationRequest">The authorization request to approve.</param> - /// <param name="userName">The username of the account that approved the request (or whose data will be accessed by the client).</param> - /// <param name="scopes">The scope of access the client should be granted. If <c>null</c>, all scopes in the original request will be granted.</param> - /// <param name="callback">The Client callback URL to use when formulating the redirect to send the user agent back to the Client.</param> - /// <returns>The authorization response message to send to the Client.</returns> - public EndUserAuthorizationSuccessResponseBase PrepareApproveAuthorizationRequest(EndUserAuthorizationRequest authorizationRequest, string userName, IEnumerable<string> scopes = null, Uri callback = null) { - Contract.Requires<ArgumentNullException>(authorizationRequest != null); - Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(userName)); - Contract.Ensures(Contract.Result<EndUserAuthorizationSuccessResponseBase>() != null); - - if (callback == null) { - callback = this.GetCallback(authorizationRequest); - } - - var client = this.AuthorizationServerServices.GetClientOrThrow(authorizationRequest.ClientIdentifier); - EndUserAuthorizationSuccessResponseBase response; - switch (authorizationRequest.ResponseType) { - case EndUserAuthorizationResponseType.AccessToken: - var accessTokenResponse = new EndUserAuthorizationSuccessAccessTokenResponse(callback, authorizationRequest); - accessTokenResponse.Lifetime = this.AuthorizationServerServices.GetAccessTokenLifetime(authorizationRequest); - response = accessTokenResponse; - break; - case EndUserAuthorizationResponseType.AuthorizationCode: - response = new EndUserAuthorizationSuccessAuthCodeResponse(callback, authorizationRequest); - break; - default: - throw ErrorUtilities.ThrowInternal("Unexpected response type."); - } - - response.AuthorizingUsername = userName; - - // Customize the approved scope if the authorization server has decided to do so. - if (scopes != null) { - response.Scope.ResetContents(scopes); - } - - return response; - } - - /// <summary> - /// 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> - /// <returns>The response message to send to the client.</returns> - public virtual IDirectResponseProtocolMessage PrepareAccessTokenResponse(AccessTokenRequestBase request, bool includeRefreshToken = true) { - Contract.Requires<ArgumentNullException>(request != null); - - var tokenRequest = (IAuthorizationCarryingRequest)request; - var response = new AccessTokenSuccessResponse(request) { - Lifetime = this.AuthorizationServerServices.GetAccessTokenLifetime(request), - HasRefreshToken = includeRefreshToken, - }; - response.Scope.ResetContents(tokenRequest.AuthorizationDescription.Scope); - return response; - } - - /// <summary> - /// Gets the redirect URL to use for a particular authorization request. - /// </summary> - /// <param name="authorizationRequest">The authorization request.</param> - /// <returns>The URL to redirect to. Never <c>null</c>.</returns> - /// <exception cref="ProtocolException">Thrown if no callback URL could be determined.</exception> - protected Uri GetCallback(EndUserAuthorizationRequest authorizationRequest) { - Contract.Requires<ArgumentNullException>(authorizationRequest != null); - Contract.Ensures(Contract.Result<Uri>() != null); - - var client = this.AuthorizationServerServices.GetClientOrThrow(authorizationRequest.ClientIdentifier); - - // Prefer a request-specific callback to the pre-registered one (if any). - if (authorizationRequest.Callback != null) { - // The OAuth channel has already validated the callback parameter against - // the authorization server's whitelist for this client. - return authorizationRequest.Callback; - } - - // 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); - return defaultCallback; - } - } -} diff --git a/src/DotNetOpenAuth/OAuth2/ChannelElements/AccessToken.cs b/src/DotNetOpenAuth/OAuth2/ChannelElements/AccessToken.cs deleted file mode 100644 index 6278828..0000000 --- a/src/DotNetOpenAuth/OAuth2/ChannelElements/AccessToken.cs +++ /dev/null @@ -1,102 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="AccessToken.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OAuth2.ChannelElements { - using System; - using System.Collections.Generic; - using System.Diagnostics.Contracts; - using System.Security.Cryptography; - using DotNetOpenAuth.Messaging; - using DotNetOpenAuth.Messaging.Bindings; - - /// <summary> - /// A short-lived token that accompanies HTTP requests to protected data to authorize the request. - /// </summary> - internal class AccessToken : AuthorizationDataBag { - /// <summary> - /// Initializes a new instance of the <see cref="AccessToken"/> class. - /// </summary> - public AccessToken() { - } - - /// <summary> - /// Initializes a new instance of the <see cref="AccessToken"/> class. - /// </summary> - /// <param name="authorization">The authorization to be described by the access token.</param> - /// <param name="lifetime">The lifetime of the access token.</param> - internal AccessToken(IAuthorizationDescription authorization, TimeSpan? lifetime) { - Contract.Requires<ArgumentNullException>(authorization != null); - - this.ClientIdentifier = authorization.ClientIdentifier; - this.UtcCreationDate = authorization.UtcIssued; - this.User = authorization.User; - this.Scope.ResetContents(authorization.Scope); - this.Lifetime = lifetime; - } - - /// <summary> - /// Initializes a new instance of the <see cref="AccessToken"/> class. - /// </summary> - /// <param name="clientIdentifier">The client identifier.</param> - /// <param name="scopes">The scopes.</param> - /// <param name="username">The username of the account that authorized this token.</param> - /// <param name="lifetime">The lifetime for this access token.</param> - internal AccessToken(string clientIdentifier, IEnumerable<string> scopes, string username, TimeSpan? lifetime) { - Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(clientIdentifier)); - - this.ClientIdentifier = clientIdentifier; - this.Scope.ResetContents(scopes); - this.User = username; - this.Lifetime = lifetime; - this.UtcCreationDate = DateTime.UtcNow; - } - - /// <summary> - /// Gets or sets the lifetime of the access token. - /// </summary> - /// <value>The lifetime.</value> - [MessagePart(Encoder = typeof(TimespanSecondsEncoder))] - internal TimeSpan? Lifetime { get; set; } - - /// <summary> - /// Creates a formatter capable of serializing/deserializing an access token. - /// </summary> - /// <param name="signingKey">The crypto service provider with the authorization server's private key used to asymmetrically sign the access token.</param> - /// <param name="encryptingKey">The crypto service provider with the resource server's public key used to encrypt the access token.</param> - /// <returns>An access token serializer.</returns> - internal static IDataBagFormatter<AccessToken> CreateFormatter(RSACryptoServiceProvider signingKey, RSACryptoServiceProvider encryptingKey) { - Contract.Requires(signingKey != null || !signingKey.PublicOnly); - Contract.Requires(encryptingKey != null); - Contract.Ensures(Contract.Result<IDataBagFormatter<AccessToken>>() != null); - - return new UriStyleMessageFormatter<AccessToken>(signingKey, encryptingKey); - } - - /// <summary> - /// Checks the message state for conformity to the protocol specification - /// and throws an exception if the message is invalid. - /// </summary> - /// <remarks> - /// <para>Some messages have required fields, or combinations of fields that must relate to each other - /// in specialized ways. After deserializing a message, this method checks the state of the - /// message to see if it conforms to the protocol.</para> - /// <para>Note that this property should <i>not</i> check signatures or perform any state checks - /// outside this scope of this particular message.</para> - /// </remarks> - /// <exception cref="ProtocolException">Thrown if the message is invalid.</exception> - protected override void EnsureValidMessage() { - base.EnsureValidMessage(); - - // Has this token expired? - if (this.Lifetime.HasValue) { - DateTime expirationDate = this.UtcCreationDate + this.Lifetime.Value; - if (expirationDate < DateTime.UtcNow) { - throw new ExpiredMessageException(expirationDate, this.ContainingMessage); - } - } - } - } -} diff --git a/src/DotNetOpenAuth/OAuth2/ChannelElements/AuthorizationCode.cs b/src/DotNetOpenAuth/OAuth2/ChannelElements/AuthorizationCode.cs deleted file mode 100644 index 03732bb..0000000 --- a/src/DotNetOpenAuth/OAuth2/ChannelElements/AuthorizationCode.cs +++ /dev/null @@ -1,100 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="AuthorizationCode.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OAuth2.ChannelElements { - using System; - using System.Collections.Generic; - 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.</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) { - Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(clientIdentifier)); - Contract.Requires<ArgumentNullException>(callback != null); - - this.ClientIdentifier = clientIdentifier; - this.CallbackHash = CalculateCallbackHash(callback); - this.Scope.ResetContents(scopes); - this.User = username; - this.UtcCreationDate = DateTime.UtcNow; - } - - /// <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(IAuthorizationServer authorizationServer) { - Contract.Requires<ArgumentNullException>(authorizationServer != null); - Contract.Ensures(Contract.Result<IDataBagFormatter<AuthorizationCode>>() != null); - - return new UriStyleMessageFormatter<AuthorizationCode>( - authorizationServer.CryptoKeyStore, - AuthorizationCodeKeyBucket, - signed: true, - encrypted: true, - compressed: false, - maximumAge: AuthorizationCodeBindingElement.MaximumMessageAge, - decodeOnceOnly: authorizationServer.VerificationCodeNonceStore); - } - - /// <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> - internal void VerifyCallback(Uri callback) { - ErrorUtilities.VerifyProtocol(MessagingUtilities.AreEquivalent(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> - private static byte[] CalculateCallbackHash(Uri callback) { - using (var hasher = new SHA256Managed()) { - return hasher.ComputeHash(Encoding.UTF8.GetBytes(callback.AbsoluteUri)); - } - } - } -} diff --git a/src/DotNetOpenAuth/OAuth2/ChannelElements/AuthorizationCodeBindingElement.cs b/src/DotNetOpenAuth/OAuth2/ChannelElements/AuthorizationCodeBindingElement.cs deleted file mode 100644 index 4569c93..0000000 --- a/src/DotNetOpenAuth/OAuth2/ChannelElements/AuthorizationCodeBindingElement.cs +++ /dev/null @@ -1,101 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="AuthorizationCodeBindingElement.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 Messages; - using Messaging; - using Messaging.Bindings; - - /// <summary> - /// A binding element for OAuth 2.0 authorization servers that create/verify - /// issued authorization codes as part of obtaining access/refresh tokens. - /// </summary> - internal class AuthorizationCodeBindingElement : AuthServerBindingElementBase { - /// <summary> - /// Initializes a new instance of the <see cref="AuthorizationCodeBindingElement"/> class. - /// </summary> - internal AuthorizationCodeBindingElement() { - } - - /// <summary> - /// Gets the protection commonly offered (if any) by this binding element. - /// </summary> - /// <value>Always <c>MessageProtections.None</c></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> - /// 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.Configuration.Messaging.MaximumMessageLifetimeNoSkew; } - } - - /// <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 response = message as EndUserAuthorizationSuccessAuthCodeResponse; - if (response != null) { - var directResponse = (IDirectResponseProtocolMessage)response; - var request = (EndUserAuthorizationRequest)directResponse.OriginatingRequest; - IAuthorizationCarryingRequest tokenCarryingResponse = response; - tokenCarryingResponse.AuthorizationDescription = new AuthorizationCode(request.ClientIdentifier, request.Callback, response.Scope, response.AuthorizingUsername); - - return MessageProtections.None; - } - - 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) { - var request = message as AccessTokenAuthorizationCodeRequest; - if (request != null) { - IAuthorizationCarryingRequest tokenRequest = request; - ((AuthorizationCode)tokenRequest.AuthorizationDescription).VerifyCallback(request.Callback); - - return MessageProtections.None; - } - - return null; - } - } -} diff --git a/src/DotNetOpenAuth/OAuth2/ChannelElements/OAuth2AuthorizationServerChannel.cs b/src/DotNetOpenAuth/OAuth2/ChannelElements/OAuth2AuthorizationServerChannel.cs deleted file mode 100644 index 888830e..0000000 --- a/src/DotNetOpenAuth/OAuth2/ChannelElements/OAuth2AuthorizationServerChannel.cs +++ /dev/null @@ -1,108 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="OAuth2AuthorizationServerChannel.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. 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; - - /// <summary> - /// The channel for the OAuth protocol. - /// </summary> - internal class OAuth2AuthorizationServerChannel : OAuth2ChannelBase { - /// <summary> - /// Initializes a new instance of the <see cref="OAuth2AuthorizationServerChannel"/> class. - /// </summary> - /// <param name="authorizationServer">The authorization server.</param> - protected internal OAuth2AuthorizationServerChannel(IAuthorizationServer authorizationServer) - : base(InitializeBindingElements(authorizationServer)) { - Contract.Requires<ArgumentNullException>(authorizationServer != null); - this.AuthorizationServer = authorizationServer; - } - - /// <summary> - /// Gets the authorization server. - /// </summary> - /// <value>The authorization server.</value> - public IAuthorizationServer AuthorizationServer { get; private 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(); - 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(HttpRequestInfo 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> - /// <returns> - /// An array of binding elements used to initialize the channel. - /// </returns> - private static IChannelBindingElement[] InitializeBindingElements(IAuthorizationServer authorizationServer) { - Contract.Requires<ArgumentNullException>(authorizationServer != null); - var bindingElements = new List<IChannelBindingElement>(); - - bindingElements.Add(new AuthServerAllFlowsBindingElement()); - bindingElements.Add(new AuthorizationCodeBindingElement()); - bindingElements.Add(new AccessTokenBindingElement()); - bindingElements.Add(new AccessRequestBindingElement()); - - return bindingElements.ToArray(); - } - } -} diff --git a/src/DotNetOpenAuth/OAuth2/ChannelElements/OAuth2ResourceServerChannel.cs b/src/DotNetOpenAuth/OAuth2/ChannelElements/OAuth2ResourceServerChannel.cs deleted file mode 100644 index 94df1a8..0000000 --- a/src/DotNetOpenAuth/OAuth2/ChannelElements/OAuth2ResourceServerChannel.cs +++ /dev/null @@ -1,153 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="OAuth2ResourceServerChannel.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OAuth2.ChannelElements { - using System; - using System.Collections.Generic; - using System.Diagnostics.Contracts; - using System.Linq; - using System.Net; - using System.Net.Mime; - using System.Text; - using System.Web; - using DotNetOpenAuth.Messaging; - using DotNetOpenAuth.Messaging.Reflection; - using DotNetOpenAuth.OAuth2.Messages; - - /// <summary> - /// The channel for the OAuth protocol. - /// </summary> - internal class OAuth2ResourceServerChannel : StandardMessageFactoryChannel { - /// <summary> - /// The messages receivable by this channel. - /// </summary> - private static readonly Type[] MessageTypes = new Type[] { - typeof(Messages.AccessProtectedResourceRequest), - }; - - /// <summary> - /// The protocol versions supported by this channel. - /// </summary> - private static readonly Version[] Versions = Protocol.AllVersions.Select(v => v.Version).ToArray(); - - /// <summary> - /// Initializes a new instance of the <see cref="OAuth2ResourceServerChannel"/> class. - /// </summary> - protected internal OAuth2ResourceServerChannel() - : base(MessageTypes, Versions) { - // TODO: add signing (authenticated request) binding element. - } - - /// <summary> - /// Gets the protocol message that may be embedded in the given HTTP request. - /// </summary> - /// <param name="request">The request to search for an embedded message.</param> - /// <returns> - /// The deserialized message, if one is found. Null otherwise. - /// </returns> - protected override IDirectedProtocolMessage ReadFromRequestCore(HttpRequestInfo request) { - var fields = new Dictionary<string, string>(); - string accessToken; - if ((accessToken = SearchForBearerAccessTokenInRequest(request)) != null) { - fields["token_type"] = Protocol.AccessTokenTypes.Bearer; - fields["access_token"] = accessToken; - } - - if (fields.Count > 0) { - MessageReceivingEndpoint recipient; - try { - recipient = request.GetRecipient(); - } catch (ArgumentException ex) { - Logger.OAuth.WarnFormat("Unrecognized HTTP request: " + ex.ToString()); - return null; - } - - // Deserialize the message using all the data we've collected. - var message = (IDirectedProtocolMessage)this.Receive(fields, recipient); - return message; - } - - return null; - } - - /// <summary> - /// Gets the protocol message that may be in the given HTTP response. - /// </summary> - /// <param name="response">The response that is anticipated to contain an protocol message.</param> - /// <returns> - /// The deserialized message parts, if found. Null otherwise. - /// </returns> - /// <exception cref="ProtocolException">Thrown when the response is not valid.</exception> - protected override IDictionary<string, string> ReadFromResponseCore(IncomingWebResponse response) { - // We never expect resource servers to send out direct requests, - // and therefore won't have direct responses. - throw new NotImplementedException(); - } - - /// <summary> - /// Queues a message for sending in the response stream where the fields - /// are sent in the response stream in querystring style. - /// </summary> - /// <param name="response">The message to send as a response.</param> - /// <returns> - /// The pending user agent redirect based message to be sent as an HttpResponse. - /// </returns> - /// <remarks> - /// This method implements spec OAuth V1.0 section 5.3. - /// </remarks> - protected override OutgoingWebResponse PrepareDirectResponse(IProtocolMessage response) { - var webResponse = new OutgoingWebResponse(); - - // The only direct response from a resource server is a 401 Unauthorized error. - var unauthorizedResponse = response as UnauthorizedResponse; - ErrorUtilities.VerifyInternal(unauthorizedResponse != null, "Only unauthorized responses are expected."); - - // First initialize based on the specifics within the message. - var httpResponse = response as IHttpDirectResponse; - webResponse.Status = httpResponse != null ? httpResponse.HttpStatusCode : HttpStatusCode.Unauthorized; - foreach (string headerName in httpResponse.Headers) { - webResponse.Headers.Add(headerName, httpResponse.Headers[headerName]); - } - - // Now serialize all the message parts into the WWW-Authenticate header. - var fields = this.MessageDescriptions.GetAccessor(response); - webResponse.Headers[HttpResponseHeader.WwwAuthenticate] = MessagingUtilities.AssembleAuthorizationHeader(Protocol.BearerHttpAuthorizationScheme, fields); - return webResponse; - } - - /// <summary> - /// Searches for a bearer access token in the request. - /// </summary> - /// <param name="request">The request.</param> - /// <returns>The bearer access token, if one exists. Otherwise <c>null</c>.</returns> - private static string SearchForBearerAccessTokenInRequest(HttpRequestInfo request) { - Contract.Requires<ArgumentNullException>(request != null, "request"); - - // First search the authorization header. - string authorizationHeader = request.Headers[HttpRequestHeader.Authorization]; - if (!string.IsNullOrEmpty(authorizationHeader) && authorizationHeader.StartsWith(Protocol.BearerHttpAuthorizationSchemeWithTrailingSpace, StringComparison.OrdinalIgnoreCase)) { - return authorizationHeader.Substring(Protocol.BearerHttpAuthorizationSchemeWithTrailingSpace.Length); - } - - // Failing that, scan the entity - if (!string.IsNullOrEmpty(request.Headers[HttpRequestHeader.ContentType])) { - var contentType = new ContentType(request.Headers[HttpRequestHeader.ContentType]); - if (string.Equals(contentType.MediaType, HttpFormUrlEncoded, StringComparison.Ordinal)) { - if (request.Form[Protocol.BearerTokenEncodedUrlParameterName] != null) { - return request.Form[Protocol.BearerTokenEncodedUrlParameterName]; - } - } - } - - // Finally, check the least desirable location: the query string - if (!String.IsNullOrEmpty(request.QueryStringBeforeRewriting[Protocol.BearerTokenEncodedUrlParameterName])) { - return request.QueryStringBeforeRewriting[Protocol.BearerTokenEncodedUrlParameterName]; - } - - return null; - } - } -} diff --git a/src/DotNetOpenAuth/OAuth2/ChannelElements/RefreshToken.cs b/src/DotNetOpenAuth/OAuth2/ChannelElements/RefreshToken.cs deleted file mode 100644 index 9fe54c5..0000000 --- a/src/DotNetOpenAuth/OAuth2/ChannelElements/RefreshToken.cs +++ /dev/null @@ -1,56 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="RefreshToken.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. 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) { - Contract.Requires<ArgumentNullException>(authorization != null); - - 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) { - Contract.Requires<ArgumentNullException>(cryptoKeyStore != null); - Contract.Ensures(Contract.Result<IDataBagFormatter<RefreshToken>>() != null); - - return new UriStyleMessageFormatter<RefreshToken>(cryptoKeyStore, RefreshTokenKeyBucket, signed: true, encrypted: true); - } - } -} diff --git a/src/DotNetOpenAuth/OAuth2/ClientAuthorizationView.cs b/src/DotNetOpenAuth/OAuth2/ClientAuthorizationView.cs deleted file mode 100644 index ffa217b..0000000 --- a/src/DotNetOpenAuth/OAuth2/ClientAuthorizationView.cs +++ /dev/null @@ -1,192 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="ClientAuthorizationView.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OAuth2 { - using System; - using System.Collections.Generic; - using System.ComponentModel; - using System.Data; - using System.Diagnostics.Contracts; - using System.Drawing; - using System.Linq; - using System.Text; - using System.Windows.Forms; - using DotNetOpenAuth.Messaging; - - /// <summary> - /// A WinForms control that hosts a mini-browser for hosting by native applications to - /// allow the user to authorize the client without leaving the application. - /// </summary> - public partial class ClientAuthorizationView : UserControl { - /// <summary> - /// Initializes a new instance of the <see cref="ClientAuthorizationView"/> class. - /// </summary> - public ClientAuthorizationView() { - this.InitializeComponent(); - - this.Authorization = new AuthorizationState(); - } - - /// <summary> - /// Occurs when the authorization flow has completed. - /// </summary> - public event EventHandler<ClientAuthorizationCompleteEventArgs> Completed; - - /// <summary> - /// Gets the authorization tracking object. - /// </summary> - public IAuthorizationState Authorization { get; private set; } - - /// <summary> - /// Gets or sets the client used to coordinate the authorization flow. - /// </summary> - public UserAgentClient Client { get; set; } - - /// <summary> - /// Gets the set of scopes that describe the requested level of access. - /// </summary> - public HashSet<string> Scope { - get { return this.Authorization.Scope; } - } - - /// <summary> - /// Gets or sets the callback URL used to indicate the flow has completed. - /// </summary> - public Uri Callback { - get { return this.Authorization.Callback; } - set { this.Authorization.Callback = value; } - } - - /// <summary> - /// Gets a value indicating whether the authorization flow has been completed. - /// </summary> - public bool IsCompleted { - get { return this.Authorization == null || this.Authorization.AccessToken != null; } - } - - /// <summary> - /// Gets a value indicating whether authorization has been granted. - /// </summary> - /// <value>Null if <see cref="IsCompleted"/> is <c>false</c></value> - public bool? IsGranted { - get { - if (this.Authorization == null) { - return false; - } - - return this.Authorization.AccessToken != null ? (bool?)true : null; - } - } - - /// <summary> - /// Gets a value indicating whether authorization has been rejected. - /// </summary> - /// <value>Null if <see cref="IsCompleted"/> is <c>false</c></value> - public bool? IsRejected { - get { - bool? granted = this.IsGranted; - return granted.HasValue ? (bool?)(!granted.Value) : null; - } - } - - /// <summary> - /// Called when the authorization flow has been completed. - /// </summary> - protected virtual void OnCompleted() { - var completed = this.Completed; - if (completed != null) { - completed(this, new ClientAuthorizationCompleteEventArgs(this.Authorization)); - } - } - - /// <summary> - /// Raises the <see cref="E:System.Windows.Forms.UserControl.Load"/> event. - /// </summary> - /// <param name="e">An <see cref="T:System.EventArgs"/> that contains the event data.</param> - protected override void OnLoad(EventArgs e) { - base.OnLoad(e); - - Uri authorizationUrl = this.Client.RequestUserAuthorization(this.Authorization); - this.webBrowser1.Navigate(authorizationUrl.AbsoluteUri); // use AbsoluteUri to workaround bug in WebBrowser that calls Uri.ToString instead of Uri.AbsoluteUri leading to escaping errors. - } - - /// <summary> - /// Tests whether two URLs are equal for purposes of detecting the conclusion of authorization. - /// </summary> - /// <param name="location1">The first location.</param> - /// <param name="location2">The second location.</param> - /// <param name="components">The components to compare.</param> - /// <returns><c>true</c> if the given components are equal.</returns> - private static bool SignificantlyEqual(Uri location1, Uri location2, UriComponents components) { - string value1 = location1.GetComponents(components, UriFormat.Unescaped); - string value2 = location2.GetComponents(components, UriFormat.Unescaped); - return string.Equals(value1, value2, StringComparison.Ordinal); - } - - /// <summary> - /// Handles the Navigating event of the webBrowser1 control. - /// </summary> - /// <param name="sender">The source of the event.</param> - /// <param name="e">The <see cref="System.Windows.Forms.WebBrowserNavigatingEventArgs"/> instance containing the event data.</param> - private void WebBrowser1_Navigating(object sender, WebBrowserNavigatingEventArgs e) { - this.ProcessLocationChanged(e.Url); - } - - /// <summary> - /// Processes changes in the URL the browser has navigated to. - /// </summary> - /// <param name="location">The location.</param> - private void ProcessLocationChanged(Uri location) { - if (SignificantlyEqual(location, this.Authorization.Callback, UriComponents.SchemeAndServer | UriComponents.Path)) { - try { - this.Client.ProcessUserAuthorization(location, this.Authorization); - } catch (ProtocolException ex) { - MessageBox.Show(ex.ToStringDescriptive()); - } finally { - this.OnCompleted(); - } - } - } - - /// <summary> - /// Handles the Navigated event of the webBrowser1 control. - /// </summary> - /// <param name="sender">The source of the event.</param> - /// <param name="e">The <see cref="System.Windows.Forms.WebBrowserNavigatedEventArgs"/> instance containing the event data.</param> - private void WebBrowser1_Navigated(object sender, WebBrowserNavigatedEventArgs e) { - this.ProcessLocationChanged(e.Url); - } - - /// <summary> - /// Handles the LocationChanged event of the webBrowser1 control. - /// </summary> - /// <param name="sender">The source of the event.</param> - /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param> - private void WebBrowser1_LocationChanged(object sender, EventArgs e) { - this.ProcessLocationChanged(this.webBrowser1.Url); - } - - /// <summary> - /// Describes the results of a completed authorization flow. - /// </summary> - public class ClientAuthorizationCompleteEventArgs : EventArgs { - /// <summary> - /// Initializes a new instance of the <see cref="ClientAuthorizationCompleteEventArgs"/> class. - /// </summary> - /// <param name="authorization">The authorization.</param> - public ClientAuthorizationCompleteEventArgs(IAuthorizationState authorization) { - Contract.Requires<ArgumentNullException>(authorization != null); - this.Authorization = authorization; - } - - /// <summary> - /// Gets the authorization tracking object. - /// </summary> - /// <value>Null if authorization was rejected by the user.</value> - public IAuthorizationState Authorization { get; private set; } - } - } -} diff --git a/src/DotNetOpenAuth/OAuth2/ClientBase.cs b/src/DotNetOpenAuth/OAuth2/ClientBase.cs deleted file mode 100644 index 51aac39..0000000 --- a/src/DotNetOpenAuth/OAuth2/ClientBase.cs +++ /dev/null @@ -1,256 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="ClientBase.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.Globalization; - using System.Linq; - using System.Net; - using System.Text; - using DotNetOpenAuth.Messaging; - using DotNetOpenAuth.OAuth2.ChannelElements; - using DotNetOpenAuth.OAuth2.Messages; - - /// <summary> - /// A base class for common OAuth Client behaviors. - /// </summary> - public class ClientBase { - /// <summary> - /// Initializes a new instance of the <see cref="ClientBase"/> class. - /// </summary> - /// <param name="authorizationServer">The token issuer.</param> - /// <param name="clientIdentifier">The client identifier.</param> - /// <param name="clientSecret">The client secret.</param> - protected ClientBase(AuthorizationServerDescription authorizationServer, string clientIdentifier = null, string clientSecret = null) { - Contract.Requires<ArgumentNullException>(authorizationServer != null); - this.AuthorizationServer = authorizationServer; - this.Channel = new OAuth2ClientChannel(); - this.ClientIdentifier = clientIdentifier; - this.ClientSecret = clientSecret; - } - - /// <summary> - /// Gets the token issuer. - /// </summary> - /// <value>The token issuer.</value> - public AuthorizationServerDescription AuthorizationServer { get; private set; } - - /// <summary> - /// Gets the OAuth channel. - /// </summary> - /// <value>The channel.</value> - public Channel Channel { get; private set; } - - /// <summary> - /// Gets or sets the identifier by which this client is known to the Authorization Server. - /// </summary> - public string ClientIdentifier { get; set; } - - /// <summary> - /// Gets or sets the client secret shared with the Authorization Server. - /// </summary> - public string ClientSecret { get; set; } - - /// <summary> - /// Adds the necessary HTTP Authorization header to an HTTP request for protected resources - /// so that the Service Provider will allow the request through. - /// </summary> - /// <param name="request">The request for protected resources from the service provider.</param> - /// <param name="accessToken">The access token previously obtained from the Authorization Server.</param> - public static void AuthorizeRequest(HttpWebRequest request, string accessToken) { - Contract.Requires<ArgumentNullException>(request != null); - Contract.Requires<ArgumentException>(!string.IsNullOrEmpty(accessToken)); - - OAuthUtilities.AuthorizeWithBearerToken(request, accessToken); - } - - /// <summary> - /// Adds the OAuth authorization token to an outgoing HTTP request, renewing a - /// (nearly) expired access token if necessary. - /// </summary> - /// <param name="request">The request for protected resources from the service provider.</param> - /// <param name="authorization">The authorization for this request previously obtained via OAuth.</param> - public void AuthorizeRequest(HttpWebRequest request, IAuthorizationState authorization) { - Contract.Requires<ArgumentNullException>(request != null); - Contract.Requires<ArgumentNullException>(authorization != null); - Contract.Requires<ArgumentException>(!string.IsNullOrEmpty(authorization.AccessToken)); - Contract.Requires<ProtocolException>(!authorization.AccessTokenExpirationUtc.HasValue || authorization.AccessTokenExpirationUtc < DateTime.UtcNow || authorization.RefreshToken != null); - - if (authorization.AccessTokenExpirationUtc.HasValue && authorization.AccessTokenExpirationUtc.Value < DateTime.UtcNow) { - ErrorUtilities.VerifyProtocol(authorization.RefreshToken != null, "Access token has expired and cannot be automatically refreshed."); - this.RefreshAuthorization(authorization); - } - - AuthorizeRequest(request, authorization.AccessToken); - } - - /// <summary> - /// Refreshes a short-lived access token using a longer-lived refresh token - /// with a new access token that has the same scope as the refresh token. - /// The refresh token itself may also be refreshed. - /// </summary> - /// <param name="authorization">The authorization to update.</param> - /// <param name="skipIfUsefulLifeExceeds">If given, the access token will <em>not</em> be refreshed if its remaining lifetime exceeds this value.</param> - /// <returns>A value indicating whether the access token was actually renewed; <c>true</c> if it was renewed, or <c>false</c> if it still had useful life remaining.</returns> - /// <remarks> - /// This method may modify the value of the <see cref="IAuthorizationState.RefreshToken"/> property on - /// the <paramref name="authorization"/> parameter if the authorization server has cycled out your refresh token. - /// If the parameter value was updated, this method calls <see cref="IAuthorizationState.SaveChanges"/> on that instance. - /// </remarks> - public bool RefreshAuthorization(IAuthorizationState authorization, TimeSpan? skipIfUsefulLifeExceeds = null) { - Contract.Requires<ArgumentNullException>(authorization != null); - Contract.Requires<ArgumentException>(!string.IsNullOrEmpty(authorization.RefreshToken)); - - if (skipIfUsefulLifeExceeds.HasValue && authorization.AccessTokenExpirationUtc.HasValue) { - TimeSpan usefulLifeRemaining = authorization.AccessTokenExpirationUtc.Value - DateTime.UtcNow; - if (usefulLifeRemaining > skipIfUsefulLifeExceeds.Value) { - // There is useful life remaining in the access token. Don't refresh. - Logger.OAuth.DebugFormat("Skipping token refresh step because access token's remaining life is {0}, which exceeds {1}.", usefulLifeRemaining, skipIfUsefulLifeExceeds.Value); - return false; - } - } - - var request = new AccessTokenRefreshRequest(this.AuthorizationServer) { - ClientIdentifier = this.ClientIdentifier, - ClientSecret = this.ClientSecret, - RefreshToken = authorization.RefreshToken, - }; - - var response = this.Channel.Request<AccessTokenSuccessResponse>(request); - UpdateAuthorizationWithResponse(authorization, response); - return true; - } - - /// <summary> - /// Gets an access token that may be used for only a subset of the scope for which a given - /// refresh token is authorized. - /// </summary> - /// <param name="refreshToken">The refresh token.</param> - /// <param name="scope">The scope subset desired in the access token.</param> - /// <returns>A description of the obtained access token, and possibly a new refresh token.</returns> - /// <remarks> - /// If the return value includes a new refresh token, the old refresh token should be discarded and - /// replaced with the new one. - /// </remarks> - public IAuthorizationState GetScopedAccessToken(string refreshToken, HashSet<string> scope) { - Contract.Requires<ArgumentException>(!string.IsNullOrEmpty(refreshToken)); - Contract.Requires<ArgumentNullException>(scope != null); - Contract.Ensures(Contract.Result<IAuthorizationState>() != null); - - var request = new AccessTokenRefreshRequest(this.AuthorizationServer) { - ClientIdentifier = this.ClientIdentifier, - ClientSecret = this.ClientSecret, - RefreshToken = refreshToken, - }; - - var response = this.Channel.Request<AccessTokenSuccessResponse>(request); - var authorization = new AuthorizationState(); - UpdateAuthorizationWithResponse(authorization, response); - - return authorization; - } - - /// <summary> - /// Updates the authorization state maintained by the client with the content of an outgoing response. - /// </summary> - /// <param name="authorizationState">The authorization state maintained by the client.</param> - /// <param name="accessTokenSuccess">The access token containing response message.</param> - internal static void UpdateAuthorizationWithResponse(IAuthorizationState authorizationState, AccessTokenSuccessResponse accessTokenSuccess) { - Contract.Requires<ArgumentNullException>(authorizationState != null); - Contract.Requires<ArgumentNullException>(accessTokenSuccess != null); - - authorizationState.AccessToken = accessTokenSuccess.AccessToken; - authorizationState.AccessTokenExpirationUtc = DateTime.UtcNow + accessTokenSuccess.Lifetime; - authorizationState.AccessTokenIssueDateUtc = DateTime.UtcNow; - - // The authorization server MAY choose to renew the refresh token itself. - if (accessTokenSuccess.RefreshToken != null) { - authorizationState.RefreshToken = accessTokenSuccess.RefreshToken; - } - - // An included scope parameter in the response only describes the access token's scope. - // Don't update the whole authorization state object with that scope because that represents - // the refresh token's original scope. - if ((authorizationState.Scope == null || authorizationState.Scope.Count == 0) && accessTokenSuccess.Scope != null) { - authorizationState.Scope.ResetContents(accessTokenSuccess.Scope); - } - - authorizationState.SaveChanges(); - } - - /// <summary> - /// Updates the authorization state maintained by the client with the content of an outgoing response. - /// </summary> - /// <param name="authorizationState">The authorization state maintained by the client.</param> - /// <param name="accessTokenSuccess">The access token containing response message.</param> - internal static void UpdateAuthorizationWithResponse(IAuthorizationState authorizationState, EndUserAuthorizationSuccessAccessTokenResponse accessTokenSuccess) { - Contract.Requires<ArgumentNullException>(authorizationState != null); - Contract.Requires<ArgumentNullException>(accessTokenSuccess != null); - - authorizationState.AccessToken = accessTokenSuccess.AccessToken; - authorizationState.AccessTokenExpirationUtc = DateTime.UtcNow + accessTokenSuccess.Lifetime; - authorizationState.AccessTokenIssueDateUtc = DateTime.UtcNow; - if (accessTokenSuccess.Scope != null && accessTokenSuccess.Scope != authorizationState.Scope) { - if (authorizationState.Scope != null) { - Logger.OAuth.InfoFormat( - "Requested scope of \"{0}\" changed to \"{1}\" by authorization server.", - authorizationState.Scope, - accessTokenSuccess.Scope); - } - - authorizationState.Scope.ResetContents(accessTokenSuccess.Scope); - } - - authorizationState.SaveChanges(); - } - - /// <summary> - /// Updates authorization state with a success response from the Authorization Server. - /// </summary> - /// <param name="authorizationState">The authorization state to update.</param> - /// <param name="authorizationSuccess">The authorization success message obtained from the authorization server.</param> - internal void UpdateAuthorizationWithResponse(IAuthorizationState authorizationState, EndUserAuthorizationSuccessAuthCodeResponse authorizationSuccess) { - Contract.Requires<ArgumentNullException>(authorizationState != null); - Contract.Requires<ArgumentNullException>(authorizationSuccess != null); - - var accessTokenRequest = new AccessTokenAuthorizationCodeRequest(this.AuthorizationServer) { - ClientIdentifier = this.ClientIdentifier, - ClientSecret = this.ClientSecret, - Callback = authorizationState.Callback, - AuthorizationCode = authorizationSuccess.AuthorizationCode, - }; - IProtocolMessage accessTokenResponse = this.Channel.Request(accessTokenRequest); - var accessTokenSuccess = accessTokenResponse as AccessTokenSuccessResponse; - var failedAccessTokenResponse = accessTokenResponse as AccessTokenFailedResponse; - if (accessTokenSuccess != null) { - UpdateAuthorizationWithResponse(authorizationState, accessTokenSuccess); - } else { - authorizationState.Delete(); - string error = failedAccessTokenResponse != null ? failedAccessTokenResponse.Error : "(unknown)"; - ErrorUtilities.ThrowProtocol(OAuthStrings.CannotObtainAccessTokenWithReason, error); - } - } - - /// <summary> - /// Calculates the fraction of life remaining in an access token. - /// </summary> - /// <param name="authorization">The authorization to measure.</param> - /// <returns>A fractional number no greater than 1. Could be negative if the access token has already expired.</returns> - private static double ProportionalLifeRemaining(IAuthorizationState authorization) { - Contract.Requires<ArgumentNullException>(authorization != null); - Contract.Requires<ArgumentException>(authorization.AccessTokenIssueDateUtc.HasValue); - Contract.Requires<ArgumentException>(authorization.AccessTokenExpirationUtc.HasValue); - - // Calculate what % of the total life this access token has left. - TimeSpan totalLifetime = authorization.AccessTokenExpirationUtc.Value - authorization.AccessTokenIssueDateUtc.Value; - TimeSpan elapsedLifetime = DateTime.UtcNow - authorization.AccessTokenIssueDateUtc.Value; - double proportionLifetimeRemaining = 1 - (elapsedLifetime.TotalSeconds / totalLifetime.TotalSeconds); - return proportionLifetimeRemaining; - } - } -} diff --git a/src/DotNetOpenAuth/OAuth2/IAccessTokenAnalyzer.cs b/src/DotNetOpenAuth/OAuth2/IAccessTokenAnalyzer.cs deleted file mode 100644 index 74b8ebb..0000000 --- a/src/DotNetOpenAuth/OAuth2/IAccessTokenAnalyzer.cs +++ /dev/null @@ -1,64 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="IAccessTokenAnalyzer.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OAuth2 { - using System; - using System.Collections.Generic; - using System.Diagnostics.CodeAnalysis; - using System.Diagnostics.Contracts; - using System.Linq; - using System.Text; - using DotNetOpenAuth.Messaging; - - /// <summary> - /// An interface that resource server hosts should implement if they accept access tokens - /// issued by non-DotNetOpenAuth authorization servers. - /// </summary> - [ContractClass((typeof(IAccessTokenAnalyzerContract)))] - public interface IAccessTokenAnalyzer { - /// <summary> - /// Reads an access token to find out what data it authorizes access to. - /// </summary> - /// <param name="message">The message carrying the access token.</param> - /// <param name="accessToken">The access token.</param> - /// <param name="user">The user whose data is accessible with this access token.</param> - /// <param name="scope">The scope of access authorized by this access token.</param> - /// <returns>A value indicating whether this access token is valid.</returns> - [SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "1#", Justification = "Try pattern")] - [SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "2#", Justification = "Try pattern")] - bool TryValidateAccessToken(IDirectedProtocolMessage message, string accessToken, out string user, out HashSet<string> scope); - } - - /// <summary> - /// Code contract for the <see cref="IAccessTokenAnalyzer"/> interface. - /// </summary> - [ContractClassFor(typeof(IAccessTokenAnalyzer))] - internal abstract class IAccessTokenAnalyzerContract : IAccessTokenAnalyzer { - /// <summary> - /// Prevents a default instance of the <see cref="IAccessTokenAnalyzerContract"/> class from being created. - /// </summary> - private IAccessTokenAnalyzerContract() { - } - - /// <summary> - /// Reads an access token to find out what data it authorizes access to. - /// </summary> - /// <param name="message">The message carrying the access token.</param> - /// <param name="accessToken">The access token.</param> - /// <param name="user">The user whose data is accessible with this access token.</param> - /// <param name="scope">The scope of access authorized by this access token.</param> - /// <returns> - /// A value indicating whether this access token is valid. - /// </returns> - bool IAccessTokenAnalyzer.TryValidateAccessToken(IDirectedProtocolMessage message, string accessToken, out string user, out HashSet<string> scope) { - Contract.Requires<ArgumentNullException>(message != null); - Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(accessToken)); - Contract.Ensures(Contract.Result<bool>() == (Contract.ValueAtReturn<string>(out user) != null)); - - throw new NotImplementedException(); - } - } -} diff --git a/src/DotNetOpenAuth/OAuth2/IAuthorizationServer.cs b/src/DotNetOpenAuth/OAuth2/IAuthorizationServer.cs deleted file mode 100644 index 883f40c..0000000 --- a/src/DotNetOpenAuth/OAuth2/IAuthorizationServer.cs +++ /dev/null @@ -1,238 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="IAuthorizationServer.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.Messaging.Bindings; - using DotNetOpenAuth.OAuth2.ChannelElements; - using DotNetOpenAuth.OAuth2.Messages; - - /// <summary> - /// Provides host-specific authorization server services needed by this library. - /// </summary> - [ContractClass(typeof(IAuthorizationServerContract))] - public interface IAuthorizationServer { - /// <summary> - /// Gets the store for storeing 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 VerificationCodeNonceStore { get; } - - /// <summary> - /// Gets 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> - RSACryptoServiceProvider AccessTokenSigningKey { get; } - - /// <summary> - /// Obtains the lifetime for a new 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> - /// Receives the lifetime for this access token. Note that within this lifetime, authorization <i>may</i> not be revokable. - /// Short lifetimes are recommended (i.e. one hour), particularly when the client is not authenticated or - /// the resources to which access is being granted are sensitive. - /// </returns> - TimeSpan GetAccessTokenLifetime(IAccessTokenRequest accessTokenRequestMessage); - - /// <summary> - /// Obtains the encryption key for an access token being created. - /// </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> - /// The crypto service provider with the asymmetric public key to use for encrypting access tokens for a specific resource server. - /// The caller is responsible to dispose of this value. - /// </returns> - /// <remarks> - /// The caller is responsible to dispose of the returned value. - /// </remarks> - RSACryptoServiceProvider GetResourceServerEncryptionKey(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> - IConsumerDescription 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> - /// Code Contract for the <see cref="IAuthorizationServer"/> interface. - /// </summary> - [ContractClassFor(typeof(IAuthorizationServer))] - internal abstract class IAuthorizationServerContract : IAuthorizationServer { - /// <summary> - /// Prevents a default instance of the <see cref="IAuthorizationServerContract"/> class from being created. - /// </summary> - private IAuthorizationServerContract() { - } - - /// <summary> - /// Gets the store for storeing crypto keys used to symmetrically encrypt and sign authorization codes and refresh tokens. - /// </summary> - ICryptoKeyStore IAuthorizationServer.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 IAuthorizationServer.VerificationCodeNonceStore { - get { - Contract.Ensures(Contract.Result<INonceStore>() != null); - throw new NotImplementedException(); - } - } - - /// <summary> - /// Gets the crypto service provider with the asymmetric private key to use for signing access tokens. - /// </summary> - /// <value> - /// Must not be null, and must contain the private key. - /// </value> - /// <returns>A crypto service provider instance that contains the private key.</returns> - RSACryptoServiceProvider IAuthorizationServer.AccessTokenSigningKey { - get { - Contract.Ensures(Contract.Result<RSACryptoServiceProvider>() != null); - Contract.Ensures(!Contract.Result<RSACryptoServiceProvider>().PublicOnly); - throw new NotImplementedException(); - } - } - - /// <summary> - /// Obtains the lifetime for a new 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> - /// Receives the lifetime for this access token. Note that within this lifetime, authorization <i>may</i> not be revokable. - /// Short lifetimes are recommended (i.e. one hour), particularly when the client is not authenticated or - /// the resources to which access is being granted are sensitive. - /// </returns> - TimeSpan IAuthorizationServer.GetAccessTokenLifetime(IAccessTokenRequest accessTokenRequestMessage) { - Contract.Requires<ArgumentNullException>(accessTokenRequestMessage != null); - throw new NotImplementedException(); - } - - /// <summary> - /// Obtains the encryption key for an access token being created. - /// </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> - /// The crypto service provider with the asymmetric public key to use for encrypting access tokens for a specific resource server. - /// The caller is responsible to dispose of this value. - /// </returns> - RSACryptoServiceProvider IAuthorizationServer.GetResourceServerEncryptionKey(IAccessTokenRequest accessTokenRequestMessage) { - Contract.Requires<ArgumentNullException>(accessTokenRequestMessage != null); - Contract.Ensures(Contract.Result<RSACryptoServiceProvider>() != 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> - IConsumerDescription IAuthorizationServer.GetClient(string clientIdentifier) { - Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(clientIdentifier)); - Contract.Ensures(Contract.Result<IConsumerDescription>() != 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 IAuthorizationServer.IsAuthorizationValid(IAuthorizationDescription authorization) { - Contract.Requires<ArgumentNullException>(authorization != null); - throw new NotImplementedException(); - } - } -} diff --git a/src/DotNetOpenAuth/OAuth2/IClientAuthorizationTracker.cs b/src/DotNetOpenAuth/OAuth2/IClientAuthorizationTracker.cs deleted file mode 100644 index 97294e6..0000000 --- a/src/DotNetOpenAuth/OAuth2/IClientAuthorizationTracker.cs +++ /dev/null @@ -1,53 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="IClientAuthorizationTracker.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OAuth2 { - using System; - using System.Diagnostics.Contracts; - - /// <summary> - /// A token manager implemented by some clients to assist in tracking authorization state. - /// </summary> - [ContractClass(typeof(IClientAuthorizationTrackerContract))] - public interface IClientAuthorizationTracker { - /// <summary> - /// Gets the state of the authorization for a given callback URL and client state. - /// </summary> - /// <param name="callbackUrl">The callback URL.</param> - /// <param name="clientState">State of the client stored at the beginning of an authorization request.</param> - /// <returns>The authorization state; may be <c>null</c> if no authorization state matches.</returns> - IAuthorizationState GetAuthorizationState(Uri callbackUrl, string clientState); - } - - /// <summary> - /// Contract class for the <see cref="IClientAuthorizationTracker"/> interface. - /// </summary> - [ContractClassFor(typeof(IClientAuthorizationTracker))] - internal abstract class IClientAuthorizationTrackerContract : IClientAuthorizationTracker { - /// <summary> - /// Prevents a default instance of the <see cref="IClientAuthorizationTrackerContract"/> class from being created. - /// </summary> - private IClientAuthorizationTrackerContract() { - } - - #region IClientTokenManager Members - - /// <summary> - /// Gets the state of the authorization for a given callback URL and client state. - /// </summary> - /// <param name="callbackUrl">The callback URL.</param> - /// <param name="clientState">State of the client stored at the beginning of an authorization request.</param> - /// <returns> - /// The authorization state; may be <c>null</c> if no authorization state matches. - /// </returns> - IAuthorizationState IClientAuthorizationTracker.GetAuthorizationState(Uri callbackUrl, string clientState) { - Contract.Requires<ArgumentNullException>(callbackUrl != null); - throw new NotImplementedException(); - } - - #endregion - } -} diff --git a/src/DotNetOpenAuth/OAuth2/IConsumerDescription.cs b/src/DotNetOpenAuth/OAuth2/IConsumerDescription.cs deleted file mode 100644 index 14f3523..0000000 --- a/src/DotNetOpenAuth/OAuth2/IConsumerDescription.cs +++ /dev/null @@ -1,101 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="IConsumerDescription.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; - - /// <summary> - /// A description of a client from an Authorization Server's point of view. - /// </summary> - [ContractClass(typeof(IConsumerDescriptionContract))] - public interface IConsumerDescription { - /// <summary> - /// Gets the client secret. - /// </summary> - string Secret { get; } - - /// <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> - Uri DefaultCallback { get; } - - /// <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.</param> - /// <returns> - /// <c>true</c> if the callback URL is allowable for this client; otherwise, <c>false</c>. - /// </returns> - /// <remarks> - /// <para> - /// At the point this method is invoked, the identity of the client has <em>not</em> - /// been confirmed. To avoid open redirector attacks, the alleged client's identity - /// is used to lookup a list of allowable callback URLs to make sure that the callback URL - /// the actual client is requesting is one of the expected ones. - /// </para> - /// <para> - /// From OAuth 2.0 section 2.1: - /// The authorization server SHOULD require the client to pre-register - /// their redirection URI or at least certain components such as the - /// scheme, host, port and path. If a redirection URI was registered, - /// the authorization server MUST compare any redirection URI received at - /// the authorization endpoint with the registered URI. - /// </para> - /// </remarks> - bool IsCallbackAllowed(Uri callback); - } - - /// <summary> - /// Contract class for the <see cref="IConsumerDescription"/> interface. - /// </summary> - [ContractClassFor(typeof(IConsumerDescription))] - internal abstract class IConsumerDescriptionContract : IConsumerDescription { - #region IConsumerDescription Members - - /// <summary> - /// Gets the client secret. - /// </summary> - /// <value></value> - string IConsumerDescription.Secret { - get { throw new NotImplementedException(); } - } - - /// <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> - Uri IConsumerDescription.DefaultCallback { - get { - Contract.Ensures(Contract.Result<Uri>() == null || Contract.Result<Uri>().IsAbsoluteUri); - throw new NotImplementedException(); - } - } - - /// <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 requested callback URI.</param> - /// <returns> - /// <c>true</c> if the callback is allowed; otherwise, <c>false</c>. - /// </returns> - bool IConsumerDescription.IsCallbackAllowed(Uri callback) { - Contract.Requires<ArgumentNullException>(callback != null); - Contract.Requires<ArgumentException>(callback.IsAbsoluteUri); - throw new NotImplementedException(); - } - - #endregion - } -} diff --git a/src/DotNetOpenAuth/OAuth2/Messages/AccessTokenAuthorizationCodeRequest.cs b/src/DotNetOpenAuth/OAuth2/Messages/AccessTokenAuthorizationCodeRequest.cs deleted file mode 100644 index 22dad1d..0000000 --- a/src/DotNetOpenAuth/OAuth2/Messages/AccessTokenAuthorizationCodeRequest.cs +++ /dev/null @@ -1,94 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="AccessTokenAuthorizationCodeRequest.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OAuth2.Messages { - using System; - using System.Collections.Generic; - using System.Diagnostics.Contracts; - using System.Linq; - using System.Text; - - using DotNetOpenAuth.Messaging; - 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 AccessTokenAuthorizationCodeRequest : AccessTokenRequestBase, IAuthorizationCarryingRequest { - /// <summary> - /// Initializes a new instance of the <see cref="AccessTokenAuthorizationCodeRequest"/> class. - /// </summary> - /// <param name="tokenEndpoint">The Authorization Server's access token endpoint URL.</param> - /// <param name="version">The version.</param> - internal AccessTokenAuthorizationCodeRequest(Uri tokenEndpoint, Version version) - : base(tokenEndpoint, version) { - } - - /// <summary> - /// Initializes a new instance of the <see cref="AccessTokenAuthorizationCodeRequest"/> class. - /// </summary> - /// <param name="authorizationServer">The authorization server.</param> - internal AccessTokenAuthorizationCodeRequest(AuthorizationServerDescription authorizationServer) - : this(authorizationServer.TokenEndpoint, authorizationServer.Version) { - Contract.Requires<ArgumentNullException>(authorizationServer != null); - } - - /// <summary> - /// Gets the type of the code or token. - /// </summary> - /// <value>The type of the code or token.</value> - CodeOrTokenType IAuthorizationCarryingRequest.CodeOrTokenType { - get { return CodeOrTokenType.AuthorizationCode; } - } - - /// <summary> - /// Gets or sets the verification code or refresh/access token. - /// </summary> - /// <value>The code or token.</value> - string IAuthorizationCarryingRequest.CodeOrToken { - get { return this.AuthorizationCode; } - set { this.AuthorizationCode = value; } - } - - /// <summary> - /// Gets or sets the authorization that the token describes. - /// </summary> - IAuthorizationDescription IAuthorizationCarryingRequest.AuthorizationDescription { get; set; } - - /// <summary> - /// Gets the type of the grant. - /// </summary> - /// <value>The type of the grant.</value> - internal override GrantType GrantType { - get { return Messages.GrantType.AuthorizationCode; } - } - - /// <summary> - /// Gets or sets the verification code previously communicated to the Client - /// in <see cref="EndUserAuthorizationSuccessAuthCodeResponse.AuthorizationCode"/>. - /// </summary> - /// <value>The verification code received from the authorization server.</value> - [MessagePart(Protocol.code, IsRequired = true)] - internal string AuthorizationCode { get; set; } - - /// <summary> - /// Gets or sets the callback URL used in <see cref="EndUserAuthorizationRequest.Callback"/> - /// </summary> - /// <value> - /// The Callback URL used to obtain the Verification Code. - /// </value> - [MessagePart(Protocol.redirect_uri, IsRequired = true)] - internal Uri Callback { get; set; } - - /// <summary> - /// Gets the scope of operations the client is allowed to invoke. - /// </summary> - protected override HashSet<string> RequestedScope { - get { return ((IAuthorizationCarryingRequest)this).AuthorizationDescription.Scope; } - } - } -} diff --git a/src/DotNetOpenAuth/OAuth2/Messages/AccessTokenRequestBase.cs b/src/DotNetOpenAuth/OAuth2/Messages/AccessTokenRequestBase.cs deleted file mode 100644 index 10dc231..0000000 --- a/src/DotNetOpenAuth/OAuth2/Messages/AccessTokenRequestBase.cs +++ /dev/null @@ -1,78 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="AccessTokenRequestBase.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.Configuration; - using DotNetOpenAuth.Messaging; - using DotNetOpenAuth.OAuth2.ChannelElements; - - /// <summary> - /// A message sent from the client to the authorization server to exchange a previously obtained grant for an access token. - /// </summary> - public abstract class AccessTokenRequestBase : AuthenticatedClientRequestBase, IAccessTokenRequest { - /// <summary> - /// Initializes a new instance of the <see cref="AccessTokenRequestBase"/> class. - /// </summary> - /// <param name="tokenEndpoint">The Authorization Server's access token endpoint URL.</param> - /// <param name="version">The version.</param> - protected AccessTokenRequestBase(Uri tokenEndpoint, Version version) - : base(tokenEndpoint, version) { - this.HttpMethods = HttpDeliveryMethods.PostRequest; - } - - /// <summary> - /// Gets the scope of operations the client is allowed to invoke. - /// </summary> - HashSet<string> IAccessTokenRequest.Scope { - get { return this.RequestedScope; } - } - - /// <summary> - /// Gets a value indicating whether the client requesting the access token has authenticated itself. - /// </summary> - /// <value> - /// Always true, because of our base class. - /// </value> - bool IAccessTokenRequest.ClientAuthenticated { - get { return true; } - } - - /// <summary> - /// Gets the type of the grant. - /// </summary> - /// <value>The type of the grant.</value> - [MessagePart(Protocol.grant_type, IsRequired = true, Encoder = typeof(GrantTypeEncoder))] - internal abstract GrantType GrantType { get; } - - /// <summary> - /// Gets the scope of operations the client is allowed to invoke. - /// </summary> - protected abstract HashSet<string> RequestedScope { get; } - - /// <summary> - /// Checks the message state for conformity to the protocol specification - /// and throws an exception if the message is invalid. - /// </summary> - /// <remarks> - /// <para>Some messages have required fields, or combinations of fields that must relate to each other - /// in specialized ways. After deserializing a message, this method checks the state of the - /// message to see if it conforms to the protocol.</para> - /// <para>Note that this property should <i>not</i> check signatures or perform any state checks - /// outside this scope of this particular message.</para> - /// </remarks> - /// <exception cref="ProtocolException">Thrown if the message is invalid.</exception> - protected override void EnsureValidMessage() { - base.EnsureValidMessage(); - ErrorUtilities.VerifyProtocol( - DotNetOpenAuthSection.Configuration.Messaging.RelaxSslRequirements || this.Recipient.IsTransportSecure(), - OAuthStrings.HttpsRequired); - } - } -} diff --git a/src/DotNetOpenAuth/OAuth2/Messages/EndUserAuthorizationFailedResponse.cs b/src/DotNetOpenAuth/OAuth2/Messages/EndUserAuthorizationFailedResponse.cs deleted file mode 100644 index 5497e1b..0000000 --- a/src/DotNetOpenAuth/OAuth2/Messages/EndUserAuthorizationFailedResponse.cs +++ /dev/null @@ -1,81 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="EndUserAuthorizationFailedResponse.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OAuth2.Messages { - using System; - using System.Collections.Generic; - using System.Diagnostics.Contracts; - using System.Linq; - using System.Runtime.Remoting.Messaging; - using System.Text; - using DotNetOpenAuth.Messaging; - - /// <summary> - /// The message that an Authorization Server responds to a Client with when the user denies a user authorization request. - /// </summary> - public class EndUserAuthorizationFailedResponse : MessageBase, IMessageWithClientState { - /// <summary> - /// Initializes a new instance of the <see cref="EndUserAuthorizationFailedResponse"/> 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 EndUserAuthorizationFailedResponse(Uri clientCallback, Version version) - : base(version, MessageTransport.Indirect, clientCallback) { - Contract.Requires<ArgumentNullException>(version != null); - Contract.Requires<ArgumentNullException>(clientCallback != null); - } - - /// <summary> - /// Initializes a new instance of the <see cref="EndUserAuthorizationFailedResponse"/> 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 EndUserAuthorizationFailedResponse(Uri clientCallback, EndUserAuthorizationRequest request) - : base(((IProtocolMessage)request).Version, MessageTransport.Indirect, clientCallback) { - Contract.Requires<ArgumentNullException>(request != null); - ((IMessageWithClientState)this).ClientState = request.ClientState; - } - - /// <summary> - /// Gets or sets the error. - /// </summary> - /// <value> - /// One of the values given in <see cref="Protocol.EndUserAuthorizationRequestErrorCodes"/>. - /// OR a numerical HTTP status code from the 4xx or 5xx - /// range, with the exception of the 400 (Bad Request) and - /// 401 (Unauthorized) status codes. For example, if the - /// service is temporarily unavailable, the authorization - /// server MAY return an error response with "error" set to - /// "503". - /// </value> - [MessagePart(Protocol.error, IsRequired = true)] - public string Error { get; set; } - - /// <summary> - /// Gets or sets a human readable description of the error. - /// </summary> - /// <value>Human-readable text providing additional information, used to assist in the understanding and resolution of the error that occurred.</value> - [MessagePart(Protocol.error_description, IsRequired = false)] - public string ErrorDescription { get; set; } - - /// <summary> - /// Gets or sets the location of the web page that describes the error and possible resolution. - /// </summary> - /// <value>A URI identifying a human-readable web page with information about the error, used to provide the end-user with additional information about the error.</value> - [MessagePart(Protocol.error_uri, IsRequired = false)] - public Uri ErrorUri { get; set; } - - /// <summary> - /// Gets or sets some state as provided by the client in the authorization request. - /// </summary> - /// <value>An opaque value defined by the client.</value> - /// <remarks> - /// REQUIRED if the Client sent the value in the <see cref="EndUserAuthorizationRequest"/>. - /// </remarks> - [MessagePart(Protocol.state, IsRequired = false)] - string IMessageWithClientState.ClientState { get; set; } - } -} diff --git a/src/DotNetOpenAuth/OAuth2/Messages/EndUserAuthorizationRequest.cs b/src/DotNetOpenAuth/OAuth2/Messages/EndUserAuthorizationRequest.cs deleted file mode 100644 index 856fe22..0000000 --- a/src/DotNetOpenAuth/OAuth2/Messages/EndUserAuthorizationRequest.cs +++ /dev/null @@ -1,118 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="EndUserAuthorizationRequest.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OAuth2.Messages { - using System; - using System.Collections.Generic; - using System.Diagnostics.CodeAnalysis; - using System.Diagnostics.Contracts; - using DotNetOpenAuth.Configuration; - using DotNetOpenAuth.Messaging; - using DotNetOpenAuth.OAuth2.ChannelElements; - - /// <summary> - /// A message sent by a web application Client to the AuthorizationServer - /// via the user agent to obtain authorization from the user and prepare - /// to issue an access token to the Consumer if permission is granted. - /// </summary> - [Serializable] - public class EndUserAuthorizationRequest : MessageBase, IAccessTokenRequest { - /// <summary> - /// Initializes a new instance of the <see cref="EndUserAuthorizationRequest"/> class. - /// </summary> - /// <param name="authorizationEndpoint">The Authorization Server's user authorization URL to direct the user to.</param> - /// <param name="version">The protocol version.</param> - internal EndUserAuthorizationRequest(Uri authorizationEndpoint, Version version) - : base(version, MessageTransport.Indirect, authorizationEndpoint) { - Contract.Requires<ArgumentNullException>(authorizationEndpoint != null); - Contract.Requires<ArgumentNullException>(version != null); - this.HttpMethods = HttpDeliveryMethods.GetRequest | HttpDeliveryMethods.PostRequest; - this.Scope = new HashSet<string>(OAuthUtilities.ScopeStringComparer); - this.ResponseType = EndUserAuthorizationResponseType.AuthorizationCode; - } - - /// <summary> - /// Initializes a new instance of the <see cref="EndUserAuthorizationRequest"/> class. - /// </summary> - /// <param name="authorizationServer">The authorization server.</param> - internal EndUserAuthorizationRequest(AuthorizationServerDescription authorizationServer) - : this(authorizationServer.AuthorizationEndpoint, authorizationServer.Version) { - Contract.Requires<ArgumentNullException>(authorizationServer != null); - Contract.Requires<ArgumentException>(authorizationServer.Version != null); - Contract.Requires<ArgumentException>(authorizationServer.AuthorizationEndpoint != null); - } - - /// <summary> - /// Gets or sets the grant type that the client expects of the authorization server. - /// </summary> - /// <value>Always <see cref="EndUserAuthorizationResponseType.AuthorizationCode"/>. Other response types are not supported.</value> - [MessagePart(Protocol.response_type, IsRequired = true, Encoder = typeof(EndUserAuthorizationResponseTypeEncoder))] - public EndUserAuthorizationResponseType ResponseType { get; set; } - - /// <summary> - /// Gets or sets the identifier by which this client is known to the Authorization Server. - /// </summary> - [MessagePart(Protocol.client_id, IsRequired = true)] - public string ClientIdentifier { get; set; } - - /// <summary> - /// Gets a value indicating whether the client requesting the access token has authenticated itself. - /// </summary> - /// <value> - /// Always false because authorization requests only include the client_id, without a secret. - /// </value> - bool IAccessTokenRequest.ClientAuthenticated { - get { return false; } - } - - /// <summary> - /// Gets or sets the callback URL. - /// </summary> - /// <value> - /// An absolute URL to which the Authorization Server will redirect the User back after - /// the user has approved the authorization request. - /// </value> - /// <remarks> - /// REQUIRED unless a redirection URI has been established between the client and authorization server via other means. An absolute URI to which the authorization server will redirect the user-agent to when the end-user authorization step is completed. The authorization server MAY require the client to pre-register their redirection URI. The redirection URI MUST NOT include a query component as defined by [RFC3986] (Berners-Lee, T., Fielding, R., and L. Masinter, “Uniform Resource Identifier (URI): Generic Syntax,” January 2005.) section 3 if the state parameter is present. - /// </remarks> - [MessagePart(Protocol.redirect_uri, IsRequired = false)] - public Uri Callback { get; set; } - - /// <summary> - /// Gets or sets state of the client that should be sent back with the authorization response. - /// </summary> - /// <value> - /// An opaque value that Clients can use to maintain state associated with this request. - /// </value> - /// <remarks> - /// This data is proprietary to the client and should be considered an opaque string to the - /// authorization server. - /// </remarks> - [MessagePart(Protocol.state, IsRequired = false)] - public string ClientState { get; set; } - - /// <summary> - /// Gets the scope of access being requested. - /// </summary> - /// <value>The scope of the access request expressed as a list of space-delimited strings. The value of the scope parameter is defined by the authorization server. If the value contains multiple space-delimited strings, their order does not matter, and each string adds an additional access range to the requested scope.</value> - [MessagePart(Protocol.scope, IsRequired = false, Encoder = typeof(ScopeEncoder))] - public HashSet<string> Scope { get; private set; } - - /// <summary> - /// Checks the message state for conformity to the protocol specification - /// and throws an exception if the message is invalid. - /// </summary> - /// <exception cref="ProtocolException">Thrown if the message is invalid.</exception> - protected override void EnsureValidMessage() { - base.EnsureValidMessage(); - - ErrorUtilities.VerifyProtocol( - DotNetOpenAuthSection.Configuration.Messaging.RelaxSslRequirements || this.Recipient.IsTransportSecure(), - OAuthStrings.HttpsRequired); - ErrorUtilities.VerifyProtocol(this.Callback == null || this.Callback.IsAbsoluteUri, this, OAuthStrings.AbsoluteUriRequired, Protocol.redirect_uri); - } - } -} diff --git a/src/DotNetOpenAuth/OAuth2/Messages/EndUserAuthorizationSuccessAccessTokenResponse.cs b/src/DotNetOpenAuth/OAuth2/Messages/EndUserAuthorizationSuccessAccessTokenResponse.cs deleted file mode 100644 index c9373eb..0000000 --- a/src/DotNetOpenAuth/OAuth2/Messages/EndUserAuthorizationSuccessAccessTokenResponse.cs +++ /dev/null @@ -1,121 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="EndUserAuthorizationSuccessAccessTokenResponse.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OAuth2.Messages { - using System; - using System.Collections.Generic; - using System.Diagnostics.Contracts; - using System.Linq; - using System.Text; - - using DotNetOpenAuth.Messaging; - 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 only an access token, - /// and to return the user to the Client where they started their experience. - /// </summary> - internal class EndUserAuthorizationSuccessAccessTokenResponse : EndUserAuthorizationSuccessResponseBase, IAuthorizationCarryingRequest, IHttpIndirectResponse { - /// <summary> - /// Initializes a new instance of the <see cref="EndUserAuthorizationSuccessAccessTokenResponse"/> 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 EndUserAuthorizationSuccessAccessTokenResponse(Uri clientCallback, Version version) - : base(clientCallback, version) { - Contract.Requires<ArgumentNullException>(version != null); - Contract.Requires<ArgumentNullException>(clientCallback != null); - this.TokenType = Protocol.AccessTokenTypes.Bearer; - } - - /// <summary> - /// Initializes a new instance of the <see cref="EndUserAuthorizationSuccessAccessTokenResponse"/> 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 EndUserAuthorizationSuccessAccessTokenResponse(Uri clientCallback, EndUserAuthorizationRequest request) - : base(clientCallback, request) { - Contract.Requires<ArgumentNullException>(clientCallback != null); - Contract.Requires<ArgumentNullException>(request != null); - ((IMessageWithClientState)this).ClientState = request.ClientState; - this.TokenType = Protocol.AccessTokenTypes.Bearer; - } - - #region IAuthorizationCarryingRequest Members - - /// <summary> - /// Gets or sets the verification code or refresh/access token. - /// </summary> - /// <value>The code or token.</value> - string IAuthorizationCarryingRequest.CodeOrToken { - get { return this.AccessToken; } - set { this.AccessToken = value; } - } - - /// <summary> - /// Gets the type of the code or token. - /// </summary> - /// <value>The type of the code or token.</value> - CodeOrTokenType IAuthorizationCarryingRequest.CodeOrTokenType { - get { return CodeOrTokenType.AccessToken; } - } - - /// <summary> - /// Gets or sets the authorization that the token describes. - /// </summary> - /// <value></value> - IAuthorizationDescription IAuthorizationCarryingRequest.AuthorizationDescription { get; set; } - - #endregion - - #region IHttpIndirectResponse Members - - /// <summary> - /// Gets a value indicating whether the payload for the message should be included - /// in the redirect fragment instead of the query string or POST entity. - /// </summary> - bool IHttpIndirectResponse.Include301RedirectPayloadInFragment { - get { return true; } - } - - #endregion - - /// <summary> - /// Gets or sets the token type. - /// </summary> - /// <value>Usually "bearer".</value> - /// <remarks> - /// Described in OAuth 2.0 section 7.1. - /// </remarks> - [MessagePart(Protocol.token_type, IsRequired = true)] - public string TokenType { get; internal set; } - - /// <summary> - /// Gets or sets the access token. - /// </summary> - /// <value>The access token.</value> - [MessagePart(Protocol.access_token, IsRequired = true)] - public string AccessToken { get; set; } - - /// <summary> - /// Gets or sets the scope of the <see cref="AccessToken"/> if one is given; otherwise the scope of the authorization code. - /// </summary> - /// <value>The scope.</value> - [MessagePart(Protocol.scope, IsRequired = false, Encoder = typeof(ScopeEncoder))] - public new ICollection<string> Scope { - get { return base.Scope; } - protected set { base.Scope = value; } - } - - /// <summary> - /// Gets or sets the lifetime of the authorization. - /// </summary> - /// <value>The lifetime.</value> - [MessagePart(Protocol.expires_in, IsRequired = false, Encoder = typeof(TimespanSecondsEncoder))] - internal TimeSpan? Lifetime { get; set; } - } -} diff --git a/src/DotNetOpenAuth/OAuth2/Messages/EndUserAuthorizationSuccessAuthCodeResponse.cs b/src/DotNetOpenAuth/OAuth2/Messages/EndUserAuthorizationSuccessAuthCodeResponse.cs deleted file mode 100644 index 65965ef..0000000 --- a/src/DotNetOpenAuth/OAuth2/Messages/EndUserAuthorizationSuccessAuthCodeResponse.cs +++ /dev/null @@ -1,76 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="EndUserAuthorizationSuccessAuthCodeResponse.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OAuth2.Messages { - using System; - using System.Diagnostics.Contracts; - - using DotNetOpenAuth.Messaging; - 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 EndUserAuthorizationSuccessAuthCodeResponse : EndUserAuthorizationSuccessResponseBase, IAuthorizationCarryingRequest { - /// <summary> - /// Initializes a new instance of the <see cref="EndUserAuthorizationSuccessAuthCodeResponse"/> 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 EndUserAuthorizationSuccessAuthCodeResponse(Uri clientCallback, Version version) - : base(clientCallback, version) { - Contract.Requires<ArgumentNullException>(version != null); - Contract.Requires<ArgumentNullException>(clientCallback != null); - } - - /// <summary> - /// Initializes a new instance of the <see cref="EndUserAuthorizationSuccessAuthCodeResponse"/> 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 EndUserAuthorizationSuccessAuthCodeResponse(Uri clientCallback, EndUserAuthorizationRequest request) - : base(clientCallback, request) { - Contract.Requires<ArgumentNullException>(clientCallback != null); - Contract.Requires<ArgumentNullException>(request != null); - ((IMessageWithClientState)this).ClientState = request.ClientState; - } - - #region IAuthorizationCarryingRequest Members - - /// <summary> - /// Gets or sets the verification code or refresh/access token. - /// </summary> - /// <value>The code or token.</value> - string IAuthorizationCarryingRequest.CodeOrToken { - get { return this.AuthorizationCode; } - set { this.AuthorizationCode = value; } - } - - /// <summary> - /// Gets the type of the code or token. - /// </summary> - /// <value>The type of the code or token.</value> - CodeOrTokenType IAuthorizationCarryingRequest.CodeOrTokenType { - get { return CodeOrTokenType.AuthorizationCode; } - } - - /// <summary> - /// Gets or sets the authorization that the token describes. - /// </summary> - IAuthorizationDescription IAuthorizationCarryingRequest.AuthorizationDescription { get; set; } - - #endregion - - /// <summary> - /// Gets or sets the authorization code. - /// </summary> - /// <value>The authorization code.</value> - [MessagePart(Protocol.code, IsRequired = true)] - internal string AuthorizationCode { get; set; } - } -} diff --git a/src/DotNetOpenAuth/OAuth2/Messages/EndUserAuthorizationSuccessResponseBase.cs b/src/DotNetOpenAuth/OAuth2/Messages/EndUserAuthorizationSuccessResponseBase.cs deleted file mode 100644 index d3c32e8..0000000 --- a/src/DotNetOpenAuth/OAuth2/Messages/EndUserAuthorizationSuccessResponseBase.cs +++ /dev/null @@ -1,69 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="EndUserAuthorizationSuccessResponseBase.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OAuth2.Messages { - using System; - using System.Collections.Generic; - using System.Diagnostics.Contracts; - using System.Security.Cryptography; - - using DotNetOpenAuth.Messaging; - 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, and to return the user - /// to the Client where they started their experience. - /// </summary> - public abstract class EndUserAuthorizationSuccessResponseBase : MessageBase, IMessageWithClientState { - /// <summary> - /// Initializes a new instance of the <see cref="EndUserAuthorizationSuccessResponseBase"/> 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 EndUserAuthorizationSuccessResponseBase(Uri clientCallback, Version version) - : base(version, MessageTransport.Indirect, clientCallback) { - Contract.Requires<ArgumentNullException>(version != null); - Contract.Requires<ArgumentNullException>(clientCallback != null); - this.Scope = new HashSet<string>(OAuthUtilities.ScopeStringComparer); - } - - /// <summary> - /// Initializes a new instance of the <see cref="EndUserAuthorizationSuccessResponseBase"/> 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 EndUserAuthorizationSuccessResponseBase(Uri clientCallback, EndUserAuthorizationRequest request) - : base(request, clientCallback) { - Contract.Requires<ArgumentNullException>(clientCallback != null); - Contract.Requires<ArgumentNullException>(request != null); - ((IMessageWithClientState)this).ClientState = request.ClientState; - this.Scope = new HashSet<string>(OAuthUtilities.ScopeStringComparer); - this.Scope.ResetContents(request.Scope); - } - - /// <summary> - /// Gets or sets some state as provided by the client in the authorization request. - /// </summary> - /// <value>An opaque value defined by the client.</value> - /// <remarks> - /// REQUIRED if the Client sent the value in the <see cref="EndUserAuthorizationRequest"/>. - /// </remarks> - [MessagePart(Protocol.state, IsRequired = false)] - string IMessageWithClientState.ClientState { get; set; } - - /// <summary> - /// Gets or sets the scope of the <see cref="AccessToken"/> if one is given; otherwise the scope of the authorization code. - /// </summary> - /// <value>The scope.</value> - public ICollection<string> Scope { get; protected set; } - - /// <summary> - /// Gets or sets the authorizing user's account name. - /// </summary> - internal string AuthorizingUsername { get; set; } - } -} diff --git a/src/DotNetOpenAuth/OAuth2/Messages/MessageBase.cs b/src/DotNetOpenAuth/OAuth2/Messages/MessageBase.cs deleted file mode 100644 index e390d6e..0000000 --- a/src/DotNetOpenAuth/OAuth2/Messages/MessageBase.cs +++ /dev/null @@ -1,239 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="MessageBase.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OAuth2.Messages { - using System; - using System.Collections.Generic; - using System.Diagnostics.CodeAnalysis; - using System.Diagnostics.Contracts; - using DotNetOpenAuth.Messaging; - - /// <summary> - /// A common message base class for OAuth messages. - /// </summary> - [Serializable] - public class MessageBase : IDirectedProtocolMessage, IDirectResponseProtocolMessage { - /// <summary> - /// A dictionary to contain extra message data. - /// </summary> - private Dictionary<string, string> extraData = new Dictionary<string, string>(); - - /// <summary> - /// The originating request. - /// </summary> - private IDirectedProtocolMessage originatingRequest; - - /// <summary> - /// The backing field for the <see cref="IMessage.Version"/> property. - /// </summary> - private Version version; - - /// <summary> - /// A value indicating whether this message is a direct or indirect message. - /// </summary> - private MessageTransport messageTransport; - - /// <summary> - /// Initializes a new instance of the <see cref="MessageBase"/> class - /// that is used for direct response messages. - /// </summary> - /// <param name="version">The version.</param> - protected MessageBase(Version version) { - Contract.Requires<ArgumentNullException>(version != null); - this.messageTransport = MessageTransport.Direct; - this.version = version; - this.HttpMethods = HttpDeliveryMethods.GetRequest; - } - - /// <summary> - /// Initializes a new instance of the <see cref="MessageBase"/> class. - /// </summary> - /// <param name="request">The originating request.</param> - /// <param name="recipient">The recipient of the directed message. Null if not applicable.</param> - protected MessageBase(IDirectedProtocolMessage request, Uri recipient = null) { - Contract.Requires<ArgumentNullException>(request != null); - this.originatingRequest = request; - this.messageTransport = request.Transport; - this.version = request.Version; - this.Recipient = recipient; - this.HttpMethods = HttpDeliveryMethods.GetRequest; - } - - /// <summary> - /// Initializes a new instance of the <see cref="MessageBase"/> class - /// that is used for directed messages. - /// </summary> - /// <param name="version">The version.</param> - /// <param name="messageTransport">The message transport.</param> - /// <param name="recipient">The recipient.</param> - protected MessageBase(Version version, MessageTransport messageTransport, Uri recipient) { - Contract.Requires<ArgumentNullException>(version != null); - Contract.Requires<ArgumentNullException>(recipient != null); - - this.version = version; - this.messageTransport = messageTransport; - this.Recipient = recipient; - this.HttpMethods = HttpDeliveryMethods.GetRequest | HttpDeliveryMethods.PostRequest; - } - - #region IMessage Properties - - /// <summary> - /// Gets the version of the protocol or extension this message is prepared to implement. - /// </summary> - /// <remarks> - /// Implementations of this interface should ensure that this property never returns null. - /// </remarks> - Version IMessage.Version { - get { return this.Version; } - } - - /// <summary> - /// Gets the extra, non-standard Protocol parameters included in the message. - /// </summary> - /// <value></value> - /// <remarks> - /// Implementations of this interface should ensure that this property never returns null. - /// </remarks> - public IDictionary<string, string> ExtraData { - get { return this.extraData; } - } - - #endregion - - #region IProtocolMessage Members - - /// <summary> - /// Gets the level of protection this message requires. - /// </summary> - /// <value><see cref="MessageProtections.None"/></value> - MessageProtections IProtocolMessage.RequiredProtection { - get { return RequiredProtection; } - } - - /// <summary> - /// Gets a value indicating whether this is a direct or indirect message. - /// </summary> - /// <value></value> - MessageTransport IProtocolMessage.Transport { - get { return this.Transport; } - } - - #endregion - - #region IDirectedProtocolMessage Members - - /// <summary> - /// Gets the preferred method of transport for the message. - /// </summary> - /// <remarks> - /// For indirect messages this will likely be GET+POST, which both can be simulated in the user agent: - /// the GET with a simple 301 Redirect, and the POST with an HTML form in the response with javascript - /// to automate submission. - /// </remarks> - HttpDeliveryMethods IDirectedProtocolMessage.HttpMethods { - get { return this.HttpMethods; } - } - - /// <summary> - /// Gets the URL of the intended receiver of this message. - /// </summary> - Uri IDirectedProtocolMessage.Recipient { - get { return this.Recipient; } - } - - #endregion - - #region IDirectResponseProtocolMessage Members - - /// <summary> - /// Gets the originating request message that caused this response to be formed. - /// </summary> - IDirectedProtocolMessage IDirectResponseProtocolMessage.OriginatingRequest { - get { return this.OriginatingRequest; } - } - - #endregion - - /// <summary> - /// Gets the level of protection this message requires. - /// </summary> - protected static MessageProtections RequiredProtection { - get { return MessageProtections.None; } - } - - /// <summary> - /// Gets a value indicating whether this is a direct or indirect message. - /// </summary> - protected MessageTransport Transport { - get { return this.messageTransport; } - } - - /// <summary> - /// Gets the version of the protocol or extension this message is prepared to implement. - /// </summary> - protected Version Version { - get { return this.version; } - } - - /// <summary> - /// Gets or sets the preferred method of transport for the message. - /// </summary> - /// <remarks> - /// For indirect messages this will likely be GET+POST, which both can be simulated in the user agent: - /// the GET with a simple 301 Redirect, and the POST with an HTML form in the response with javascript - /// to automate submission. - /// </remarks> - protected HttpDeliveryMethods HttpMethods { get; set; } - - /// <summary> - /// Gets the originating request message that caused this response to be formed. - /// </summary> - protected IDirectedProtocolMessage OriginatingRequest { - get { return this.originatingRequest; } - } - - /// <summary> - /// Gets the URL of the intended receiver of this message. - /// </summary> - protected Uri Recipient { get; private set; } - - #region IMessage Methods - - /// <summary> - /// Checks the message state for conformity to the protocol specification - /// and throws an exception if the message is invalid. - /// </summary> - /// <remarks> - /// <para>Some messages have required fields, or combinations of fields that must relate to each other - /// in specialized ways. After deserializing a message, this method checks the state of the - /// message to see if it conforms to the protocol.</para> - /// <para>Note that this property should <i>not</i> check signatures or perform any state checks - /// outside this scope of this particular message.</para> - /// </remarks> - /// <exception cref="ProtocolException">Thrown if the message is invalid.</exception> - void IMessage.EnsureValidMessage() { - this.EnsureValidMessage(); - } - - #endregion - - /// <summary> - /// Checks the message state for conformity to the protocol specification - /// and throws an exception if the message is invalid. - /// </summary> - /// <remarks> - /// <para>Some messages have required fields, or combinations of fields that must relate to each other - /// in specialized ways. After deserializing a message, this method checks the state of the - /// message to see if it conforms to the protocol.</para> - /// <para>Note that this property should <i>not</i> check signatures or perform any state checks - /// outside this scope of this particular message.</para> - /// </remarks> - /// <exception cref="ProtocolException">Thrown if the message is invalid.</exception> - protected virtual void EnsureValidMessage() { - } - } -} diff --git a/src/DotNetOpenAuth/OAuth2/Messages/UnauthorizedResponse.cs b/src/DotNetOpenAuth/OAuth2/Messages/UnauthorizedResponse.cs deleted file mode 100644 index d79c00b..0000000 --- a/src/DotNetOpenAuth/OAuth2/Messages/UnauthorizedResponse.cs +++ /dev/null @@ -1,115 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="UnauthorizedResponse.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OAuth2.Messages { - using System; - using System.Diagnostics.Contracts; - using System.Net; - using System.Text; - using DotNetOpenAuth.Messaging; - - /// <summary> - /// A direct response that is simply a 401 Unauthorized with an - /// WWW-Authenticate: OAuth header. - /// </summary> - internal class UnauthorizedResponse : MessageBase, IHttpDirectResponse { - /// <summary> - /// Initializes a new instance of the <see cref="UnauthorizedResponse"/> class. - /// </summary> - /// <param name="exception">The exception.</param> - /// <param name="version">The protocol version.</param> - internal UnauthorizedResponse(ProtocolException exception, Version version = null) - : base(version ?? Protocol.Default.Version) { - Contract.Requires<ArgumentNullException>(exception != null); - this.ErrorMessage = exception.Message; - } - - /// <summary> - /// Initializes a new instance of the <see cref="UnauthorizedResponse"/> class. - /// </summary> - /// <param name="request">The request.</param> - internal UnauthorizedResponse(IDirectedProtocolMessage request) - : base(request) { - this.Realm = "Service"; - } - - /// <summary> - /// Initializes a new instance of the <see cref="UnauthorizedResponse"/> class. - /// </summary> - /// <param name="request">The request.</param> - /// <param name="exception">The exception.</param> - internal UnauthorizedResponse(IDirectedProtocolMessage request, ProtocolException exception) - : this(request) { - Contract.Requires<ArgumentNullException>(exception != null); - this.ErrorMessage = exception.Message; - } - - #region IHttpDirectResponse Members - - /// <summary> - /// Gets the HTTP status code that the direct response should be sent with. - /// </summary> - HttpStatusCode IHttpDirectResponse.HttpStatusCode { - get { return HttpStatusCode.Unauthorized; } - } - - /// <summary> - /// Gets the HTTP headers to add to the response. - /// </summary> - /// <value>May be an empty collection, but must not be <c>null</c>.</value> - WebHeaderCollection IHttpDirectResponse.Headers { - get { - return new WebHeaderCollection() { - { HttpResponseHeader.WwwAuthenticate, Protocol.BearerHttpAuthorizationScheme }, - }; - } - } - - #endregion - - /// <summary> - /// Gets or sets the error message. - /// </summary> - /// <value>The error message.</value> - [MessagePart("error")] - internal string ErrorMessage { get; set; } - - /// <summary> - /// Gets or sets the realm. - /// </summary> - /// <value>The realm.</value> - [MessagePart("realm")] - internal string Realm { get; set; } - - /// <summary> - /// Gets or sets the scope. - /// </summary> - /// <value>The scope.</value> - [MessagePart("scope")] - internal string Scope { get; set; } - - /// <summary> - /// Gets or sets the algorithms. - /// </summary> - /// <value>The algorithms.</value> - [MessagePart("algorithms")] - internal string Algorithms { get; set; } - - /// <summary> - /// Gets or sets the user endpoint. - /// </summary> - /// <value>The user endpoint.</value> - [MessagePart("user-uri")] - internal Uri UserEndpoint { get; set; } - - /// <summary> - /// Gets or sets the token endpoint. - /// </summary> - /// <value>The token endpoint.</value> - [MessagePart("token-uri")] - internal Uri TokenEndpoint { get; set; } - } -} diff --git a/src/DotNetOpenAuth/OAuth2/OAuthUtilities.cs b/src/DotNetOpenAuth/OAuth2/OAuthUtilities.cs deleted file mode 100644 index c2b2f5f..0000000 --- a/src/DotNetOpenAuth/OAuth2/OAuthUtilities.cs +++ /dev/null @@ -1,123 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="OAuthUtilities.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.Globalization; - using System.Linq; - using System.Net; - using System.Text; - using DotNetOpenAuth.Messaging; - - /// <summary> - /// Some common utility methods for OAuth 2.0. - /// </summary> - public static class OAuthUtilities { - /// <summary> - /// The <see cref="StringComparer"/> instance to use when comparing scope equivalence. - /// </summary> - public static readonly StringComparer ScopeStringComparer = StringComparer.Ordinal; - - /// <summary> - /// The delimiter between scope elements. - /// </summary> - private static char[] scopeDelimiter = new char[] { ' ' }; - - /// <summary> - /// The characters that may appear in an access token that is included in an HTTP Authorization header. - /// </summary> - /// <remarks> - /// This is defined in OAuth 2.0 DRAFT 10, section 5.1.1. (http://tools.ietf.org/id/draft-ietf-oauth-v2-10.html#authz-header) - /// </remarks> - private static string accessTokenAuthorizationHeaderAllowedCharacters = MessagingUtilities.UppercaseLetters + - MessagingUtilities.LowercaseLetters + - MessagingUtilities.Digits + - @"!#$%&'()*+-./:<=>?@[]^_`{|}~\,;"; - - /// <summary> - /// Determines whether one given scope is a subset of another scope. - /// </summary> - /// <param name="requestedScope">The requested scope, which may be a subset of <paramref name="grantedScope"/>.</param> - /// <param name="grantedScope">The granted scope, the suspected superset.</param> - /// <returns> - /// <c>true</c> if all the elements that appear in <paramref name="requestedScope"/> also appear in <paramref name="grantedScope"/>; - /// <c>false</c> otherwise. - /// </returns> - public static bool IsScopeSubset(string requestedScope, string grantedScope) { - if (string.IsNullOrEmpty(requestedScope)) { - return true; - } - - if (string.IsNullOrEmpty(grantedScope)) { - return false; - } - - var requestedScopes = new HashSet<string>(requestedScope.Split(scopeDelimiter, StringSplitOptions.RemoveEmptyEntries)); - var grantedScopes = new HashSet<string>(grantedScope.Split(scopeDelimiter, StringSplitOptions.RemoveEmptyEntries)); - return requestedScopes.IsSubsetOf(grantedScopes); - } - - /// <summary> - /// Identifies individual scope elements - /// </summary> - /// <param name="scope">The space-delimited list of scopes.</param> - /// <returns>A set of individual scopes, with any duplicates removed.</returns> - public static HashSet<string> SplitScopes(string scope) { - if (string.IsNullOrEmpty(scope)) { - return new HashSet<string>(); - } - - return new HashSet<string>(scope.Split(scopeDelimiter, StringSplitOptions.RemoveEmptyEntries), ScopeStringComparer); - } - - /// <summary> - /// Serializes a set of scopes as a space-delimited list. - /// </summary> - /// <param name="scopes">The scopes to serialize.</param> - /// <returns>A space-delimited list.</returns> - public static string JoinScopes(HashSet<string> scopes) { - Contract.Requires<ArgumentNullException>(scopes != null); - return string.Join(" ", scopes.ToArray()); - } - - /// <summary> - /// Authorizes an HTTP request using an OAuth 2.0 access token in an HTTP Authorization header. - /// </summary> - /// <param name="request">The request to authorize.</param> - /// <param name="accessToken">The access token previously obtained from the Authorization Server.</param> - internal static void AuthorizeWithBearerToken(this HttpWebRequest request, string accessToken) { - Contract.Requires<ArgumentNullException>(request != null); - Contract.Requires<ArgumentException>(!string.IsNullOrEmpty(accessToken)); - ErrorUtilities.VerifyProtocol(accessToken.All(ch => accessTokenAuthorizationHeaderAllowedCharacters.IndexOf(ch) >= 0), "The access token contains characters that must not appear in the HTTP Authorization header."); - - request.Headers[HttpRequestHeader.Authorization] = string.Format( - CultureInfo.InvariantCulture, - Protocol.BearerHttpAuthorizationHeaderFormat, - accessToken); - } - - /// <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 IConsumerDescription GetClientOrThrow(this IAuthorizationServer authorizationServer, string clientIdentifier) { - Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(clientIdentifier)); - Contract.Ensures(Contract.Result<IConsumerDescription>() != null); - - try { - return authorizationServer.GetClient(clientIdentifier); - } catch (KeyNotFoundException ex) { - throw ErrorUtilities.Wrap(ex, OAuth.OAuthStrings.ConsumerOrTokenSecretNotFound); - } catch (ArgumentException ex) { - throw ErrorUtilities.Wrap(ex, OAuth.OAuthStrings.ConsumerOrTokenSecretNotFound); - } - } - } -} diff --git a/src/DotNetOpenAuth/OAuth2/ResourceServer.cs b/src/DotNetOpenAuth/OAuth2/ResourceServer.cs deleted file mode 100644 index a5baef0..0000000 --- a/src/DotNetOpenAuth/OAuth2/ResourceServer.cs +++ /dev/null @@ -1,136 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="ResourceServer.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OAuth2 { - using System; - using System.Collections.Generic; - using System.Diagnostics.CodeAnalysis; - using System.Diagnostics.Contracts; - using System.Linq; - using System.Net; - using System.Security.Principal; - using System.ServiceModel.Channels; - using System.Text; - using System.Text.RegularExpressions; - using System.Web; - using ChannelElements; - using Messages; - using Messaging; - - /// <summary> - /// Provides services for validating OAuth access tokens. - /// </summary> - public class ResourceServer { - /// <summary> - /// Initializes a new instance of the <see cref="ResourceServer"/> class. - /// </summary> - /// <param name="accessTokenAnalyzer">The access token analyzer.</param> - public ResourceServer(IAccessTokenAnalyzer accessTokenAnalyzer) { - Contract.Requires<ArgumentNullException>(accessTokenAnalyzer != null); - - this.AccessTokenAnalyzer = accessTokenAnalyzer; - this.Channel = new OAuth2ResourceServerChannel(); - } - - /// <summary> - /// Gets the access token analyzer. - /// </summary> - /// <value>The access token analyzer.</value> - public IAccessTokenAnalyzer AccessTokenAnalyzer { get; private set; } - - /// <summary> - /// Gets the channel. - /// </summary> - /// <value>The channel.</value> - internal OAuth2ResourceServerChannel Channel { get; private set; } - - /// <summary> - /// Discovers what access the client should have considering the access token in the current request. - /// </summary> - /// <param name="userName">The name on the account the client has access to.</param> - /// <param name="scope">The set of operations the client is authorized for.</param> - /// <returns>An error to return to the client if access is not authorized; <c>null</c> if access is granted.</returns> - [SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "0#", Justification = "Try pattern")] - [SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "1#", Justification = "Try pattern")] - public OutgoingWebResponse VerifyAccess(out string userName, out HashSet<string> scope) { - return this.VerifyAccess(this.Channel.GetRequestFromContext(), out userName, out scope); - } - - /// <summary> - /// Discovers what access the client should have considering the access token in the current request. - /// </summary> - /// <param name="httpRequestInfo">The HTTP request info.</param> - /// <param name="userName">The name on the account the client has access to.</param> - /// <param name="scope">The set of operations the client is authorized for.</param> - /// <returns> - /// An error to return to the client if access is not authorized; <c>null</c> if access is granted. - /// </returns> - [SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "1#", Justification = "Try pattern")] - [SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "2#", Justification = "Try pattern")] - public virtual OutgoingWebResponse VerifyAccess(HttpRequestInfo httpRequestInfo, out string userName, out HashSet<string> scope) { - Contract.Requires<ArgumentNullException>(httpRequestInfo != null); - - AccessProtectedResourceRequest request = null; - try { - if (this.Channel.TryReadFromRequest<AccessProtectedResourceRequest>(httpRequestInfo, out request)) { - if (this.AccessTokenAnalyzer.TryValidateAccessToken(request, request.AccessToken, out userName, out scope)) { - // No errors to return. - return null; - } - - throw ErrorUtilities.ThrowProtocol("Bad access token"); - } else { - var response = new UnauthorizedResponse(new ProtocolException("Missing access token")); - - userName = null; - scope = null; - return this.Channel.PrepareResponse(response); - } - } catch (ProtocolException ex) { - var response = request != null ? new UnauthorizedResponse(request, ex) : new UnauthorizedResponse(ex); - - userName = null; - scope = null; - return this.Channel.PrepareResponse(response); - } - } - - /// <summary> - /// Discovers what access the client should have considering the access token in the current request. - /// </summary> - /// <param name="httpRequestInfo">The HTTP request info.</param> - /// <param name="principal">The principal that contains the user and roles that the access token is authorized for.</param> - /// <returns> - /// An error to return to the client if access is not authorized; <c>null</c> if access is granted. - /// </returns> - [SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "1#", Justification = "Try pattern")] - public virtual OutgoingWebResponse VerifyAccess(HttpRequestInfo httpRequestInfo, out IPrincipal principal) { - string username; - HashSet<string> scope; - var result = this.VerifyAccess(httpRequestInfo, out username, out scope); - principal = result == null ? new OAuth.ChannelElements.OAuthPrincipal(username, scope != null ? scope.ToArray() : new string[0]) : null; - return result; - } - - /// <summary> - /// Discovers what access the client should have considering the access token in the current request. - /// </summary> - /// <param name="request">HTTP details from an incoming WCF message.</param> - /// <param name="requestUri">The URI of the WCF service endpoint.</param> - /// <param name="principal">The principal that contains the user and roles that the access token is authorized for.</param> - /// <returns> - /// An error to return to the client if access is not authorized; <c>null</c> if access is granted. - /// </returns> - [SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "1#", Justification = "Try pattern")] - [SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "2#", Justification = "Try pattern")] - public virtual OutgoingWebResponse VerifyAccess(HttpRequestMessageProperty request, Uri requestUri, out IPrincipal principal) { - Contract.Requires<ArgumentNullException>(request != null); - Contract.Requires<ArgumentNullException>(requestUri != null); - - return this.VerifyAccess(new HttpRequestInfo(request, requestUri), out principal); - } - } -} diff --git a/src/DotNetOpenAuth/OAuth2/StandardAccessTokenAnalyzer.cs b/src/DotNetOpenAuth/OAuth2/StandardAccessTokenAnalyzer.cs deleted file mode 100644 index 1f59e52..0000000 --- a/src/DotNetOpenAuth/OAuth2/StandardAccessTokenAnalyzer.cs +++ /dev/null @@ -1,66 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="StandardAccessTokenAnalyzer.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.Security.Cryptography; - using DotNetOpenAuth.Messaging; - using DotNetOpenAuth.OAuth2.ChannelElements; - - /// <summary> - /// An access token reader that understands DotNetOpenAuth authorization server issued tokens. - /// </summary> - public class StandardAccessTokenAnalyzer : IAccessTokenAnalyzer { - /// <summary> - /// Initializes a new instance of the <see cref="StandardAccessTokenAnalyzer"/> class. - /// </summary> - /// <param name="authorizationServerPublicSigningKey">The crypto service provider with the authorization server public signing key.</param> - /// <param name="resourceServerPrivateEncryptionKey">The crypto service provider with the resource server private encryption key.</param> - public StandardAccessTokenAnalyzer(RSACryptoServiceProvider authorizationServerPublicSigningKey, RSACryptoServiceProvider resourceServerPrivateEncryptionKey) { - Contract.Requires<ArgumentNullException>(authorizationServerPublicSigningKey != null); - Contract.Requires<ArgumentNullException>(resourceServerPrivateEncryptionKey != null); - Contract.Requires<ArgumentException>(!resourceServerPrivateEncryptionKey.PublicOnly); - this.AuthorizationServerPublicSigningKey = authorizationServerPublicSigningKey; - this.ResourceServerPrivateEncryptionKey = resourceServerPrivateEncryptionKey; - } - - /// <summary> - /// Gets the authorization server public signing key. - /// </summary> - /// <value>The authorization server public signing key.</value> - public RSACryptoServiceProvider AuthorizationServerPublicSigningKey { get; private set; } - - /// <summary> - /// Gets the resource server private encryption key. - /// </summary> - /// <value>The resource server private encryption key.</value> - public RSACryptoServiceProvider ResourceServerPrivateEncryptionKey { get; private set; } - - /// <summary> - /// Reads an access token to find out what data it authorizes access to. - /// </summary> - /// <param name="message">The message carrying the access token.</param> - /// <param name="accessToken">The access token.</param> - /// <param name="user">The user whose data is accessible with this access token.</param> - /// <param name="scope">The scope of access authorized by this access token.</param> - /// <returns> - /// A value indicating whether this access token is valid. - /// </returns> - /// <remarks> - /// This method also responsible to throw a <see cref="ProtocolException"/> or return - /// <c>false</c> when the access token is expired, invalid, or from an untrusted authorization server. - /// </remarks> - public virtual bool TryValidateAccessToken(IDirectedProtocolMessage message, string accessToken, out string user, out HashSet<string> scope) { - var accessTokenFormatter = AccessToken.CreateFormatter(this.AuthorizationServerPublicSigningKey, this.ResourceServerPrivateEncryptionKey); - var token = accessTokenFormatter.Deserialize(message, accessToken); - user = token.User; - scope = new HashSet<string>(token.Scope, OAuthUtilities.ScopeStringComparer); - return true; - } - } -} diff --git a/src/DotNetOpenAuth/OAuth2/UserAgentClient.cs b/src/DotNetOpenAuth/OAuth2/UserAgentClient.cs deleted file mode 100644 index e23eca4..0000000 --- a/src/DotNetOpenAuth/OAuth2/UserAgentClient.cs +++ /dev/null @@ -1,123 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="UserAgentClient.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.Text; - using DotNetOpenAuth.Messaging; - using DotNetOpenAuth.OAuth2.Messages; - - /// <summary> - /// The OAuth client for the user-agent flow, providing services for installed apps - /// and in-browser Javascript widgets. - /// </summary> - public class UserAgentClient : ClientBase { - /// <summary> - /// Initializes a new instance of the <see cref="UserAgentClient"/> class. - /// </summary> - /// <param name="authorizationServer">The token issuer.</param> - /// <param name="clientIdentifier">The client identifier.</param> - /// <param name="clientSecret">The client secret.</param> - public UserAgentClient(AuthorizationServerDescription authorizationServer, string clientIdentifier = null, string clientSecret = null) - : base(authorizationServer, clientIdentifier, clientSecret) { - } - - /// <summary> - /// Initializes a new instance of the <see cref="UserAgentClient"/> class. - /// </summary> - /// <param name="authorizationEndpoint">The authorization endpoint.</param> - /// <param name="tokenEndpoint">The token endpoint.</param> - /// <param name="clientIdentifier">The client identifier.</param> - /// <param name="clientSecret">The client secret.</param> - public UserAgentClient(Uri authorizationEndpoint, Uri tokenEndpoint, string clientIdentifier = null, string clientSecret = null) - : this(new AuthorizationServerDescription { AuthorizationEndpoint = authorizationEndpoint, TokenEndpoint = tokenEndpoint }, clientIdentifier, clientSecret) { - Contract.Requires<ArgumentNullException>(authorizationEndpoint != null); - Contract.Requires<ArgumentNullException>(tokenEndpoint != null); - } - - /// <summary> - /// Generates a URL that the user's browser can be directed to in order to authorize - /// this client to access protected data at some resource server. - /// </summary> - /// <param name="scope">The scope of authorized access requested.</param> - /// <param name="state">The client state that should be returned with the authorization response.</param> - /// <param name="returnTo">The URL that the authorization response should be sent to via a user-agent redirect.</param> - /// <returns> - /// A fully-qualified URL suitable to initiate the authorization flow. - /// </returns> - public Uri RequestUserAuthorization(IEnumerable<string> scope = null, string state = null, Uri returnTo = null) { - var authorization = new AuthorizationState(scope) { - Callback = returnTo, - }; - - return this.RequestUserAuthorization(authorization); - } - - /// <summary> - /// Generates a URL that the user's browser can be directed to in order to authorize - /// this client to access protected data at some resource server. - /// </summary> - /// <param name="authorization">The authorization state that is tracking this particular request. Optional.</param> - /// <param name="state">The client state that should be returned with the authorization response.</param> - /// <returns> - /// A fully-qualified URL suitable to initiate the authorization flow. - /// </returns> - public Uri RequestUserAuthorization(IAuthorizationState authorization, string state = null) { - Contract.Requires<ArgumentNullException>(authorization != null); - Contract.Requires<InvalidOperationException>(!string.IsNullOrEmpty(this.ClientIdentifier)); - - if (authorization.Callback == null) { - authorization.Callback = new Uri("http://localhost/"); - } - - var request = new EndUserAuthorizationRequest(this.AuthorizationServer) { - ClientIdentifier = this.ClientIdentifier, - Callback = authorization.Callback, - ClientState = state, - }; - request.Scope.ResetContents(authorization.Scope); - - return this.Channel.PrepareResponse(request).GetDirectUriRequest(this.Channel); - } - - /// <summary> - /// Scans the incoming request for an authorization response message. - /// </summary> - /// <param name="actualRedirectUrl">The actual URL of the incoming HTTP request.</param> - /// <param name="authorizationState">The authorization.</param> - /// <returns>The granted authorization, or <c>null</c> if the incoming HTTP request did not contain an authorization server response or authorization was rejected.</returns> - public IAuthorizationState ProcessUserAuthorization(Uri actualRedirectUrl, IAuthorizationState authorizationState = null) { - Contract.Requires<ArgumentNullException>(actualRedirectUrl != null); - - if (authorizationState == null) { - authorizationState = new AuthorizationState(); - } - - var carrier = new HttpRequestInfo("GET", actualRedirectUrl, actualRedirectUrl.PathAndQuery, new System.Net.WebHeaderCollection(), null); - IDirectedProtocolMessage response = this.Channel.ReadFromRequest(carrier); - if (response == null) { - return null; - } - - EndUserAuthorizationSuccessAccessTokenResponse accessTokenSuccess; - EndUserAuthorizationSuccessAuthCodeResponse authCodeSuccess; - EndUserAuthorizationFailedResponse failure; - if ((accessTokenSuccess = response as EndUserAuthorizationSuccessAccessTokenResponse) != null) { - UpdateAuthorizationWithResponse(authorizationState, accessTokenSuccess); - } else if ((authCodeSuccess = response as EndUserAuthorizationSuccessAuthCodeResponse) != null) { - this.UpdateAuthorizationWithResponse(authorizationState, authCodeSuccess); - } else if ((failure = response as EndUserAuthorizationFailedResponse) != null) { - authorizationState.Delete(); - return null; - } - - return authorizationState; - } - } -} diff --git a/src/DotNetOpenAuth/OAuth2/WebServerClient.cs b/src/DotNetOpenAuth/OAuth2/WebServerClient.cs deleted file mode 100644 index 0063ae0..0000000 --- a/src/DotNetOpenAuth/OAuth2/WebServerClient.cs +++ /dev/null @@ -1,130 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="WebServerClient.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.Net; - using System.Text; - using System.Web; - using DotNetOpenAuth.Messaging; - using DotNetOpenAuth.OAuth2.Messages; - - /// <summary> - /// An OAuth 2.0 consumer designed for web applications. - /// </summary> - public class WebServerClient : ClientBase { - /// <summary> - /// Initializes a new instance of the <see cref="WebServerClient"/> class. - /// </summary> - /// <param name="authorizationServer">The authorization server.</param> - /// <param name="clientIdentifier">The client identifier.</param> - /// <param name="clientSecret">The client secret.</param> - public WebServerClient(AuthorizationServerDescription authorizationServer, string clientIdentifier = null, string clientSecret = null) - : base(authorizationServer, clientIdentifier, clientSecret) { - } - - /// <summary> - /// Gets or sets an optional component that gives you greater control to record and influence the authorization process. - /// </summary> - /// <value>The authorization tracker.</value> - public IClientAuthorizationTracker AuthorizationTracker { get; set; } - - /// <summary> - /// Prepares a request for user authorization from an authorization server. - /// </summary> - /// <param name="scope">The scope of authorized access requested.</param> - /// <param name="state">The state of the client that should be sent back with the authorization response.</param> - /// <param name="returnTo">The URL the authorization server should redirect the browser (typically on this site) to when the authorization is completed. If null, the current request's URL will be used.</param> - public void RequestUserAuthorization(IEnumerable<string> scope = null, string state = null, Uri returnTo = null) { - var authorizationState = new AuthorizationState(scope) { - Callback = returnTo, - }; - this.PrepareRequestUserAuthorization(authorizationState, state).Send(); - } - - /// <summary> - /// Prepares a request for user authorization from an authorization server. - /// </summary> - /// <param name="scopes">The scope of authorized access requested.</param> - /// <param name="state">The state of the client that should be sent back with the authorization response.</param> - /// <returns>The authorization request.</returns> - public OutgoingWebResponse PrepareRequestUserAuthorization(IEnumerable<string> scopes = null, string state = null) { - var authorizationState = new AuthorizationState(scopes); - return this.PrepareRequestUserAuthorization(authorizationState, state); - } - - /// <summary> - /// Prepares a request for user authorization from an authorization server. - /// </summary> - /// <param name="authorization">The authorization state to associate with this particular request.</param> - /// <param name="state">The state of the client that should be sent back with the authorization response.</param> - /// <returns>The authorization request.</returns> - public OutgoingWebResponse PrepareRequestUserAuthorization(IAuthorizationState authorization, string state = null) { - Contract.Requires<ArgumentNullException>(authorization != null); - Contract.Requires<InvalidOperationException>(authorization.Callback != null || (HttpContext.Current != null && HttpContext.Current.Request != null), MessagingStrings.HttpContextRequired); - Contract.Requires<InvalidOperationException>(!string.IsNullOrEmpty(this.ClientIdentifier)); - Contract.Ensures(Contract.Result<OutgoingWebResponse>() != null); - - if (authorization.Callback == null) { - authorization.Callback = this.Channel.GetRequestFromContext().UrlBeforeRewriting - .StripMessagePartsFromQueryString(this.Channel.MessageDescriptions.Get(typeof(EndUserAuthorizationSuccessResponseBase), Protocol.Default.Version)) - .StripMessagePartsFromQueryString(this.Channel.MessageDescriptions.Get(typeof(EndUserAuthorizationFailedResponse), Protocol.Default.Version)); - authorization.SaveChanges(); - } - - var request = new EndUserAuthorizationRequest(this.AuthorizationServer) { - ClientIdentifier = this.ClientIdentifier, - Callback = authorization.Callback, - ClientState = state, - }; - request.Scope.ResetContents(authorization.Scope); - - return this.Channel.PrepareResponse(request); - } - - /// <summary> - /// Processes the authorization response from an authorization server, if available. - /// </summary> - /// <param name="request">The incoming HTTP request that may carry an authorization response.</param> - /// <returns>The authorization state that contains the details of the authorization.</returns> - public IAuthorizationState ProcessUserAuthorization(HttpRequestInfo request = null) { - Contract.Requires<InvalidOperationException>(!string.IsNullOrEmpty(this.ClientIdentifier)); - Contract.Requires<InvalidOperationException>(!string.IsNullOrEmpty(this.ClientSecret)); - - if (request == null) { - request = this.Channel.GetRequestFromContext(); - } - - IMessageWithClientState response; - if (this.Channel.TryReadFromRequest<IMessageWithClientState>(request, out response)) { - Uri callback = MessagingUtilities.StripMessagePartsFromQueryString(request.UrlBeforeRewriting, this.Channel.MessageDescriptions.Get(response)); - IAuthorizationState authorizationState; - if (this.AuthorizationTracker != null) { - authorizationState = this.AuthorizationTracker.GetAuthorizationState(callback, response.ClientState); - ErrorUtilities.VerifyProtocol(authorizationState != null, "Unexpected OAuth authorization response received with callback and client state that does not match an expected value."); - } else { - authorizationState = new AuthorizationState { Callback = callback }; - } - var success = response as EndUserAuthorizationSuccessAuthCodeResponse; - var failure = response as EndUserAuthorizationFailedResponse; - ErrorUtilities.VerifyProtocol(success != null || failure != null, MessagingStrings.UnexpectedMessageReceivedOfMany); - if (success != null) { - this.UpdateAuthorizationWithResponse(authorizationState, success); - } else { // failure - Logger.OAuth.Info("User refused to grant the requested authorization at the Authorization Server."); - authorizationState.Delete(); - } - - return authorizationState; - } - - return null; - } - } -} diff --git a/src/DotNetOpenAuth/OpenId/Association.cs b/src/DotNetOpenAuth/OpenId/Association.cs deleted file mode 100644 index 5b97ad4..0000000 --- a/src/DotNetOpenAuth/OpenId/Association.cs +++ /dev/null @@ -1,308 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="Association.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OpenId { - using System; - using System.Diagnostics; - using System.Diagnostics.CodeAnalysis; - using System.Diagnostics.Contracts; - using System.IO; - using System.Security.Cryptography; - using System.Text; - using DotNetOpenAuth.Configuration; - using DotNetOpenAuth.Messaging; - using DotNetOpenAuth.OpenId.RelyingParty; - - /// <summary> - /// Stores a secret used in signing and verifying messages. - /// </summary> - /// <remarks> - /// OpenID associations may be shared between Provider and Relying Party (smart - /// associations), or be a way for a Provider to recall its own secret for later - /// (dumb associations). - /// </remarks> - [DebuggerDisplay("Handle = {Handle}, Expires = {Expires}")] - [ContractVerification(true)] - [ContractClass(typeof(AssociationContract))] - public abstract class Association { - /// <summary> - /// Initializes a new instance of the <see cref="Association"/> class. - /// </summary> - /// <param name="handle">The handle.</param> - /// <param name="secret">The secret.</param> - /// <param name="totalLifeLength">How long the association will be useful.</param> - /// <param name="issued">The UTC time of when this association was originally issued by the Provider.</param> - protected Association(string handle, byte[] secret, TimeSpan totalLifeLength, DateTime issued) { - Contract.Requires<ArgumentNullException>(!string.IsNullOrEmpty(handle)); - Contract.Requires<ArgumentNullException>(secret != null); - Contract.Requires<ArgumentOutOfRangeException>(totalLifeLength > TimeSpan.Zero); - Contract.Requires<ArgumentException>(issued.Kind == DateTimeKind.Utc); - Contract.Requires<ArgumentOutOfRangeException>(issued <= DateTime.UtcNow); - Contract.Ensures(this.TotalLifeLength == totalLifeLength); - - this.Handle = handle; - this.SecretKey = secret; - this.TotalLifeLength = totalLifeLength; - this.Issued = OpenIdUtilities.CutToSecond(issued); - } - - /// <summary> - /// Gets a unique handle by which this <see cref="Association"/> may be stored or retrieved. - /// </summary> - public string Handle { get; internal set; } - - /// <summary> - /// Gets the UTC time when this <see cref="Association"/> will expire. - /// </summary> - public DateTime Expires { - get { return this.Issued + this.TotalLifeLength; } - } - - /// <summary> - /// Gets a value indicating whether this <see cref="Association"/> has already expired. - /// </summary> - public bool IsExpired { - get { return this.Expires < DateTime.UtcNow; } - } - - /// <summary> - /// Gets the length (in bits) of the hash this association creates when signing. - /// </summary> - public abstract int HashBitLength { get; } - - /// <summary> - /// Gets a value indicating whether this instance has useful life remaining. - /// </summary> - /// <value> - /// <c>true</c> if this instance has useful life remaining; otherwise, <c>false</c>. - /// </value> - internal bool HasUsefulLifeRemaining { - get { return this.TimeTillExpiration >= MinimumUsefulAssociationLifetime; } - } - - /// <summary> - /// Gets or sets the UTC time that this <see cref="Association"/> was first created. - /// </summary> - [MessagePart] - internal DateTime Issued { get; set; } - - /// <summary> - /// Gets the number of seconds until this <see cref="Association"/> expires. - /// Never negative (counter runs to zero). - /// </summary> - protected internal long SecondsTillExpiration { - get { - Contract.Ensures(Contract.Result<long>() >= 0); - return Math.Max(0, (long)this.TimeTillExpiration.TotalSeconds); - } - } - - /// <summary> - /// Gets the shared secret key between the consumer and provider. - /// </summary> - [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification = "It is a buffer.")] - [MessagePart("key")] - protected internal byte[] SecretKey { get; private set; } - - /// <summary> - /// Gets the duration a secret key used for signing dumb client requests will be good for. - /// </summary> - protected static TimeSpan DumbSecretLifetime { - get { - Contract.Ensures(Contract.Result<TimeSpan>() > TimeSpan.Zero); - return DotNetOpenAuthSection.Configuration.OpenId.MaxAuthenticationTime; - } - } - - /// <summary> - /// Gets the lifetime the OpenID provider permits this <see cref="Association"/>. - /// </summary> - [MessagePart("ttl")] - protected TimeSpan TotalLifeLength { get; private set; } - - /// <summary> - /// Gets the minimum lifetime an association must still be good for in order for it to be used for a future authentication. - /// </summary> - /// <remarks> - /// Associations that are not likely to last the duration of a user login are not worth using at all. - /// </remarks> - private static TimeSpan MinimumUsefulAssociationLifetime { - get { - Contract.Ensures(Contract.Result<TimeSpan>() > TimeSpan.Zero); - return DotNetOpenAuthSection.Configuration.OpenId.MaxAuthenticationTime; - } - } - - /// <summary> - /// Gets the TimeSpan till this association expires. - /// </summary> - private TimeSpan TimeTillExpiration { - get { return this.Expires - DateTime.UtcNow; } - } - - /// <summary> - /// Re-instantiates an <see cref="Association"/> previously persisted in a database or some - /// other shared store. - /// </summary> - /// <param name="handle"> - /// The <see cref="Handle"/> property of the previous <see cref="Association"/> instance. - /// </param> - /// <param name="expiresUtc"> - /// The UTC value of the <see cref="Expires"/> property of the previous <see cref="Association"/> instance. - /// </param> - /// <param name="privateData"> - /// The byte array returned by a call to <see cref="SerializePrivateData"/> on the previous - /// <see cref="Association"/> instance. - /// </param> - /// <returns> - /// The newly dehydrated <see cref="Association"/>, which can be returned - /// from a custom association store's - /// <see cref="IRelyingPartyAssociationStore.GetAssociation(Uri, SecuritySettings)"/> method. - /// </returns> - public static Association Deserialize(string handle, DateTime expiresUtc, byte[] privateData) { - Contract.Requires<ArgumentNullException>(!String.IsNullOrEmpty(handle)); - Contract.Requires<ArgumentNullException>(privateData != null); - Contract.Ensures(Contract.Result<Association>() != null); - - expiresUtc = expiresUtc.ToUniversalTimeSafe(); - TimeSpan remainingLifeLength = expiresUtc - DateTime.UtcNow; - byte[] secret = privateData; // the whole of privateData is the secret key for now. - // We figure out what derived type to instantiate based on the length of the secret. - try { - return HmacShaAssociation.Create(handle, secret, remainingLifeLength); - } catch (ArgumentException ex) { - throw new ArgumentException(OpenIdStrings.BadAssociationPrivateData, "privateData", ex); - } - } - - /// <summary> - /// Returns private data required to persist this <see cref="Association"/> in - /// permanent storage (a shared database for example) for deserialization later. - /// </summary> - /// <returns> - /// An opaque byte array that must be stored and returned exactly as it is provided here. - /// The byte array may vary in length depending on the specific type of <see cref="Association"/>, - /// but in current versions are no larger than 256 bytes. - /// </returns> - /// <remarks> - /// Values of public properties on the base class <see cref="Association"/> are not included - /// in this byte array, as they are useful for fast database lookup and are persisted separately. - /// </remarks> - public byte[] SerializePrivateData() { - Contract.Ensures(Contract.Result<byte[]>() != null); - - // We may want to encrypt this secret using the machine.config private key, - // and add data regarding which Association derivative will need to be - // re-instantiated on deserialization. - // For now, we just send out the secret key. We can derive the type from the length later. - byte[] secretKeyCopy = new byte[this.SecretKey.Length]; - if (this.SecretKey.Length > 0) { - this.SecretKey.CopyTo(secretKeyCopy, 0); - } - return secretKeyCopy; - } - - /// <summary> - /// Tests equality of two <see cref="Association"/> objects. - /// </summary> - /// <param name="obj">The <see cref="T:System.Object"/> to compare with the current <see cref="T:System.Object"/>.</param> - /// <returns> - /// true if the specified <see cref="T:System.Object"/> is equal to the current <see cref="T:System.Object"/>; otherwise, false. - /// </returns> - public override bool Equals(object obj) { - Association a = obj as Association; - if (a == null) { - return false; - } - if (a.GetType() != GetType()) { - return false; - } - - if (a.Handle != this.Handle || - a.Issued != this.Issued || - !MessagingUtilities.Equals(a.TotalLifeLength, this.TotalLifeLength, TimeSpan.FromSeconds(1))) { - return false; - } - - if (!MessagingUtilities.AreEquivalent(a.SecretKey, this.SecretKey)) { - return false; - } - - return true; - } - - /// <summary> - /// Returns the hash code. - /// </summary> - /// <returns> - /// A hash code for the current <see cref="T:System.Object"/>. - /// </returns> - public override int GetHashCode() { - HMACSHA1 hmac = new HMACSHA1(this.SecretKey); - try { - CryptoStream cs = new CryptoStream(Stream.Null, hmac, CryptoStreamMode.Write); - - byte[] hbytes = ASCIIEncoding.ASCII.GetBytes(this.Handle); - - cs.Write(hbytes, 0, hbytes.Length); - cs.Close(); - - byte[] hash = hmac.Hash; - hmac.Clear(); - - long val = 0; - for (int i = 0; i < hash.Length; i++) { - val = val ^ (long)hash[i]; - } - - val = val ^ this.Expires.ToFileTimeUtc(); - - return (int)val; - } finally { - ((IDisposable)hmac).Dispose(); - } - } - - /// <summary> - /// The string to pass as the assoc_type value in the OpenID protocol. - /// </summary> - /// <param name="protocol">The protocol version of the message that the assoc_type value will be included in.</param> - /// <returns>The value that should be used for the openid.assoc_type parameter.</returns> - internal abstract string GetAssociationType(Protocol protocol); - - /// <summary> - /// Generates a signature from a given blob of data. - /// </summary> - /// <param name="data">The data to sign. This data will not be changed (the signature is the return value).</param> - /// <returns>The calculated signature of the data.</returns> - protected internal byte[] Sign(byte[] data) { - Contract.Requires<ArgumentNullException>(data != null); - using (HashAlgorithm hasher = this.CreateHasher()) { - return hasher.ComputeHash(data); - } - } - - /// <summary> - /// Returns the specific hash algorithm used for message signing. - /// </summary> - /// <returns>The hash algorithm used for message signing.</returns> - protected abstract HashAlgorithm CreateHasher(); - -#if CONTRACTS_FULL - /// <summary> - /// Verifies conditions that should be true for any valid state of this object. - /// </summary> - [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Called by code contracts.")] - [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called by code contracts.")] - [ContractInvariantMethod] - private void ObjectInvariant() { - Contract.Invariant(!string.IsNullOrEmpty(this.Handle)); - Contract.Invariant(this.TotalLifeLength > TimeSpan.Zero); - Contract.Invariant(this.SecretKey != null); - } -#endif - } -} diff --git a/src/DotNetOpenAuth/OpenId/AssociationContract.cs b/src/DotNetOpenAuth/OpenId/AssociationContract.cs deleted file mode 100644 index 57f4fd9..0000000 --- a/src/DotNetOpenAuth/OpenId/AssociationContract.cs +++ /dev/null @@ -1,65 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="AssociationContract.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OpenId { - using System; - using System.Diagnostics; - using System.Diagnostics.CodeAnalysis; - using System.Diagnostics.Contracts; - using System.IO; - using System.Security.Cryptography; - using System.Text; - using DotNetOpenAuth.Configuration; - using DotNetOpenAuth.Messaging; - - /// <summary> - /// Code contract for the <see cref="Association"/> class. - /// </summary> - [ContractClassFor(typeof(Association))] - internal abstract class AssociationContract : Association { - /// <summary> - /// Prevents a default instance of the <see cref="AssociationContract"/> class from being created. - /// </summary> - private AssociationContract() - : base(null, null, TimeSpan.Zero, DateTime.Now) { - } - - /// <summary> - /// Gets the length (in bits) of the hash this association creates when signing. - /// </summary> - public override int HashBitLength { - get { - Contract.Ensures(Contract.Result<int>() > 0); - throw new NotImplementedException(); - } - } - - /// <summary> - /// The string to pass as the assoc_type value in the OpenID protocol. - /// </summary> - /// <param name="protocol">The protocol version of the message that the assoc_type value will be included in.</param> - /// <returns> - /// The value that should be used for the openid.assoc_type parameter. - /// </returns> - [Pure] - internal override string GetAssociationType(Protocol protocol) { - Contract.Requires<ArgumentNullException>(protocol != null); - throw new NotImplementedException(); - } - - /// <summary> - /// Returns the specific hash algorithm used for message signing. - /// </summary> - /// <returns> - /// The hash algorithm used for message signing. - /// </returns> - [Pure] - protected override HashAlgorithm CreateHasher() { - Contract.Ensures(Contract.Result<HashAlgorithm>() != null); - throw new NotImplementedException(); - } - } -} diff --git a/src/DotNetOpenAuth/OpenId/Behaviors/AXFetchAsSregTransform.cs b/src/DotNetOpenAuth/OpenId/Behaviors/AXFetchAsSregTransform.cs deleted file mode 100644 index 01b74a1..0000000 --- a/src/DotNetOpenAuth/OpenId/Behaviors/AXFetchAsSregTransform.cs +++ /dev/null @@ -1,139 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="AXFetchAsSregTransform.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OpenId.Behaviors { - using System; - using System.Collections.Generic; - using System.Diagnostics.CodeAnalysis; - using System.Linq; - using System.Text; - using DotNetOpenAuth.Messaging; - using DotNetOpenAuth.OpenId.Extensions; - using DotNetOpenAuth.OpenId.Extensions.AttributeExchange; - using DotNetOpenAuth.OpenId.Extensions.SimpleRegistration; - using DotNetOpenAuth.OpenId.Provider; - using DotNetOpenAuth.OpenId.RelyingParty; - - /// <summary> - /// An Attribute Exchange and Simple Registration filter to make all incoming attribute - /// requests look like Simple Registration requests, and to convert the response - /// to the originally requested extension and format. - /// </summary> - [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Sreg", Justification = "Abbreviation")] - public sealed class AXFetchAsSregTransform : IRelyingPartyBehavior, IProviderBehavior { - /// <summary> - /// Initializes static members of the <see cref="AXFetchAsSregTransform"/> class. - /// </summary> - static AXFetchAsSregTransform() { - AXFormats = AXAttributeFormats.Common; - } - - /// <summary> - /// Initializes a new instance of the <see cref="AXFetchAsSregTransform"/> class. - /// </summary> - public AXFetchAsSregTransform() { - } - - /// <summary> - /// Gets or sets the AX attribute type URI formats this transform is willing to work with. - /// </summary> - public static AXAttributeFormats AXFormats { get; set; } - - #region IRelyingPartyBehavior Members - - /// <summary> - /// Applies a well known set of security requirements to a default set of security settings. - /// </summary> - /// <param name="securitySettings">The security settings to enhance with the requirements of this profile.</param> - /// <remarks> - /// Care should be taken to never decrease security when applying a profile. - /// Profiles should only enhance security requirements to avoid being - /// incompatible with each other. - /// </remarks> - void IRelyingPartyBehavior.ApplySecuritySettings(RelyingPartySecuritySettings securitySettings) { - } - - /// <summary> - /// Called when an authentication request is about to be sent. - /// </summary> - /// <param name="request">The request.</param> - /// <remarks> - /// Implementations should be prepared to be called multiple times on the same outgoing message - /// without malfunctioning. - /// </remarks> - void IRelyingPartyBehavior.OnOutgoingAuthenticationRequest(RelyingParty.IAuthenticationRequest request) { - // Don't create AX extensions for OpenID 1.x messages, since AX requires OpenID 2.0. - if (request.Provider.Version.Major >= 2) { - request.SpreadSregToAX(AXFormats); - } - } - - /// <summary> - /// Called when an incoming positive assertion is received. - /// </summary> - /// <param name="assertion">The positive assertion.</param> - void IRelyingPartyBehavior.OnIncomingPositiveAssertion(IAuthenticationResponse assertion) { - if (assertion.GetExtension<ClaimsResponse>() == null) { - ClaimsResponse sreg = assertion.UnifyExtensionsAsSreg(true); - ((PositiveAnonymousResponse)assertion).Response.Extensions.Add(sreg); - } - } - - #endregion - - #region IProviderBehavior Members - - /// <summary> - /// Applies a well known set of security requirements to a default set of security settings. - /// </summary> - /// <param name="securitySettings">The security settings to enhance with the requirements of this profile.</param> - /// <remarks> - /// Care should be taken to never decrease security when applying a profile. - /// Profiles should only enhance security requirements to avoid being - /// incompatible with each other. - /// </remarks> - void IProviderBehavior.ApplySecuritySettings(ProviderSecuritySettings securitySettings) { - // Nothing to do here. - } - - /// <summary> - /// Called when a request is received by the Provider. - /// </summary> - /// <param name="request">The incoming request.</param> - /// <returns> - /// <c>true</c> if this behavior owns this request and wants to stop other behaviors - /// from handling it; <c>false</c> to allow other behaviors to process this request. - /// </returns> - /// <remarks> - /// Implementations may set a new value to <see cref="IRequest.SecuritySettings"/> but - /// should not change the properties on the instance of <see cref="ProviderSecuritySettings"/> - /// itself as that instance may be shared across many requests. - /// </remarks> - bool IProviderBehavior.OnIncomingRequest(IRequest request) { - var extensionRequest = request as Provider.HostProcessedRequest; - if (extensionRequest != null) { - extensionRequest.UnifyExtensionsAsSreg(); - } - - return false; - } - - /// <summary> - /// Called when the Provider is preparing to send a response to an authentication request. - /// </summary> - /// <param name="request">The request that is configured to generate the outgoing response.</param> - /// <returns> - /// <c>true</c> if this behavior owns this request and wants to stop other behaviors - /// from handling it; <c>false</c> to allow other behaviors to process this request. - /// </returns> - bool IProviderBehavior.OnOutgoingResponse(Provider.IAuthenticationRequest request) { - request.ConvertSregToMatchRequest(); - return false; - } - - #endregion - } -} diff --git a/src/DotNetOpenAuth/OpenId/Behaviors/GsaIcamProfile.cs b/src/DotNetOpenAuth/OpenId/Behaviors/GsaIcamProfile.cs deleted file mode 100644 index 66ac276..0000000 --- a/src/DotNetOpenAuth/OpenId/Behaviors/GsaIcamProfile.cs +++ /dev/null @@ -1,294 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="GsaIcamProfile.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OpenId.Behaviors { - using System; - using System.Diagnostics.CodeAnalysis; - using System.Diagnostics.Contracts; - using System.Linq; - using DotNetOpenAuth.Configuration; - using DotNetOpenAuth.Messaging; - using DotNetOpenAuth.OpenId.Extensions.AttributeExchange; - using DotNetOpenAuth.OpenId.Extensions.ProviderAuthenticationPolicy; - using DotNetOpenAuth.OpenId.Extensions.SimpleRegistration; - using DotNetOpenAuth.OpenId.Provider; - using DotNetOpenAuth.OpenId.RelyingParty; - - /// <summary> - /// Implements the Identity, Credential, & Access Management (ICAM) OpenID 2.0 Profile - /// for the General Services Administration (GSA). - /// </summary> - /// <remarks> - /// <para>Relying parties that include this profile are always held to the terms required by the profile, - /// but Providers are only affected by the special behaviors of the profile when the RP specifically - /// indicates that they want to use this profile. </para> - /// </remarks> - [Serializable] - [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Icam", Justification = "Acronym")] - public sealed class GsaIcamProfile : IRelyingPartyBehavior, IProviderBehavior { - /// <summary> - /// The maximum time a shared association can live. - /// </summary> - private static readonly TimeSpan MaximumAssociationLifetime = TimeSpan.FromSeconds(86400); - - /// <summary> - /// Backing field for the <see cref="DisableSslRequirement"/> static property. - /// </summary> - private static bool disableSslRequirement = DotNetOpenAuthSection.Configuration.Messaging.RelaxSslRequirements; - - /// <summary> - /// Initializes a new instance of the <see cref="GsaIcamProfile"/> class. - /// </summary> - public GsaIcamProfile() { - if (DisableSslRequirement) { - Logger.OpenId.Warn("GSA level 1 behavior has its RequireSsl requirement disabled."); - } - } - - /// <summary> - /// Gets or sets the provider for generating PPID identifiers. - /// </summary> - [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Ppid", Justification = "Acronym")] - public static IDirectedIdentityIdentifierProvider PpidIdentifierProvider { get; set; } - - /// <summary> - /// Gets or sets a value indicating whether PII is allowed to be requested or received via OpenID. - /// </summary> - /// <value>The default value is <c>false</c>.</value> - public static bool AllowPersonallyIdentifiableInformation { get; set; } - - /// <summary> - /// Gets or sets a value indicating whether to ignore the SSL requirement (for testing purposes only). - /// </summary> - public static bool DisableSslRequirement { // not an auto-property because it has a default value, and FxCop doesn't want us using static constructors. - get { return disableSslRequirement; } - set { disableSslRequirement = value; } - } - - #region IRelyingPartyBehavior Members - - /// <summary> - /// Applies a well known set of security requirements. - /// </summary> - /// <param name="securitySettings">The security settings to enhance with the requirements of this profile.</param> - /// <remarks> - /// Care should be taken to never decrease security when applying a profile. - /// Profiles should only enhance security requirements to avoid being - /// incompatible with each other. - /// </remarks> - void IRelyingPartyBehavior.ApplySecuritySettings(RelyingPartySecuritySettings securitySettings) { - if (securitySettings.MaximumHashBitLength < 256) { - securitySettings.MaximumHashBitLength = 256; - } - - securitySettings.RequireSsl = !DisableSslRequirement; - securitySettings.RequireDirectedIdentity = true; - securitySettings.RequireAssociation = true; - securitySettings.RejectDelegatingIdentifiers = true; - securitySettings.IgnoreUnsignedExtensions = true; - securitySettings.MinimumRequiredOpenIdVersion = ProtocolVersion.V20; - } - - /// <summary> - /// Called when an authentication request is about to be sent. - /// </summary> - /// <param name="request">The request.</param> - void IRelyingPartyBehavior.OnOutgoingAuthenticationRequest(RelyingParty.IAuthenticationRequest request) { - RelyingParty.AuthenticationRequest requestInternal = (RelyingParty.AuthenticationRequest)request; - ErrorUtilities.VerifyProtocol(string.Equals(request.Realm.Scheme, Uri.UriSchemeHttps, StringComparison.Ordinal) || DisableSslRequirement, BehaviorStrings.RealmMustBeHttps); - - var pape = requestInternal.AppliedExtensions.OfType<PolicyRequest>().SingleOrDefault(); - if (pape == null) { - request.AddExtension(pape = new PolicyRequest()); - } - - if (!pape.PreferredPolicies.Contains(AuthenticationPolicies.PrivatePersonalIdentifier)) { - pape.PreferredPolicies.Add(AuthenticationPolicies.PrivatePersonalIdentifier); - } - - if (!pape.PreferredPolicies.Contains(AuthenticationPolicies.USGovernmentTrustLevel1)) { - pape.PreferredPolicies.Add(AuthenticationPolicies.USGovernmentTrustLevel1); - } - - if (!AllowPersonallyIdentifiableInformation && !pape.PreferredPolicies.Contains(AuthenticationPolicies.NoPersonallyIdentifiableInformation)) { - pape.PreferredPolicies.Add(AuthenticationPolicies.NoPersonallyIdentifiableInformation); - } - - if (pape.PreferredPolicies.Contains(AuthenticationPolicies.NoPersonallyIdentifiableInformation)) { - ErrorUtilities.VerifyProtocol( - (!requestInternal.AppliedExtensions.OfType<ClaimsRequest>().Any() && - !requestInternal.AppliedExtensions.OfType<FetchRequest>().Any()), - BehaviorStrings.PiiIncludedWithNoPiiPolicy); - } - - Reporting.RecordEventOccurrence(this, "RP"); - } - - /// <summary> - /// Called when an incoming positive assertion is received. - /// </summary> - /// <param name="assertion">The positive assertion.</param> - void IRelyingPartyBehavior.OnIncomingPositiveAssertion(IAuthenticationResponse assertion) { - PolicyResponse pape = assertion.GetExtension<PolicyResponse>(); - ErrorUtilities.VerifyProtocol( - pape != null && - pape.ActualPolicies.Contains(AuthenticationPolicies.USGovernmentTrustLevel1) && - pape.ActualPolicies.Contains(AuthenticationPolicies.PrivatePersonalIdentifier), - BehaviorStrings.PapeResponseOrRequiredPoliciesMissing); - - ErrorUtilities.VerifyProtocol(AllowPersonallyIdentifiableInformation || pape.ActualPolicies.Contains(AuthenticationPolicies.NoPersonallyIdentifiableInformation), BehaviorStrings.PapeResponseOrRequiredPoliciesMissing); - - if (pape.ActualPolicies.Contains(AuthenticationPolicies.NoPersonallyIdentifiableInformation)) { - ErrorUtilities.VerifyProtocol( - assertion.GetExtension<ClaimsResponse>() == null && - assertion.GetExtension<FetchResponse>() == null, - BehaviorStrings.PiiIncludedWithNoPiiPolicy); - } - } - - #endregion - - #region IProviderBehavior Members - - /// <summary> - /// Adapts the default security settings to the requirements of this behavior. - /// </summary> - /// <param name="securitySettings">The original security settings.</param> - void IProviderBehavior.ApplySecuritySettings(ProviderSecuritySettings securitySettings) { - if (securitySettings.MaximumHashBitLength < 256) { - securitySettings.MaximumHashBitLength = 256; - } - - SetMaximumAssociationLifetimeToNotExceed(Protocol.Default.Args.SignatureAlgorithm.HMAC_SHA256, MaximumAssociationLifetime, securitySettings); - SetMaximumAssociationLifetimeToNotExceed(Protocol.Default.Args.SignatureAlgorithm.HMAC_SHA1, MaximumAssociationLifetime, securitySettings); - } - - /// <summary> - /// Called when a request is received by the Provider. - /// </summary> - /// <param name="request">The incoming request.</param> - /// <returns> - /// <c>true</c> if this behavior owns this request and wants to stop other behaviors - /// from handling it; <c>false</c> to allow other behaviors to process this request. - /// </returns> - /// <remarks> - /// Implementations may set a new value to <see cref="IRequest.SecuritySettings"/> but - /// should not change the properties on the instance of <see cref="ProviderSecuritySettings"/> - /// itself as that instance may be shared across many requests. - /// </remarks> - bool IProviderBehavior.OnIncomingRequest(IRequest request) { - var hostProcessedRequest = request as IHostProcessedRequest; - if (hostProcessedRequest != null) { - // Only apply our special policies if the RP requested it. - var papeRequest = request.GetExtension<PolicyRequest>(); - if (papeRequest != null) { - if (papeRequest.PreferredPolicies.Contains(AuthenticationPolicies.USGovernmentTrustLevel1)) { - // Whenever we see this GSA policy requested, we MUST also see the PPID policy requested. - ErrorUtilities.VerifyProtocol(papeRequest.PreferredPolicies.Contains(AuthenticationPolicies.PrivatePersonalIdentifier), BehaviorStrings.PapeRequestMissingRequiredPolicies); - ErrorUtilities.VerifyProtocol(string.Equals(hostProcessedRequest.Realm.Scheme, Uri.UriSchemeHttps, StringComparison.Ordinal) || DisableSslRequirement, BehaviorStrings.RealmMustBeHttps); - - // Apply GSA-specific security to this individual request. - request.SecuritySettings.RequireSsl = !DisableSslRequirement; - return true; - } - } - } - - return false; - } - - /// <summary> - /// Called when the Provider is preparing to send a response to an authentication request. - /// </summary> - /// <param name="request">The request that is configured to generate the outgoing response.</param> - /// <returns> - /// <c>true</c> if this behavior owns this request and wants to stop other behaviors - /// from handling it; <c>false</c> to allow other behaviors to process this request. - /// </returns> - bool IProviderBehavior.OnOutgoingResponse(Provider.IAuthenticationRequest request) { - bool result = false; - - // Nothing to do for negative assertions. - if (!request.IsAuthenticated.Value) { - return result; - } - - var requestInternal = (Provider.AuthenticationRequest)request; - var responseMessage = (IProtocolMessageWithExtensions)requestInternal.Response; - - // Only apply our special policies if the RP requested it. - var papeRequest = request.GetExtension<PolicyRequest>(); - if (papeRequest != null) { - var papeResponse = responseMessage.Extensions.OfType<PolicyResponse>().SingleOrDefault(); - if (papeResponse == null) { - request.AddResponseExtension(papeResponse = new PolicyResponse()); - } - - if (papeRequest.PreferredPolicies.Contains(AuthenticationPolicies.USGovernmentTrustLevel1)) { - result = true; - if (!papeResponse.ActualPolicies.Contains(AuthenticationPolicies.USGovernmentTrustLevel1)) { - papeResponse.ActualPolicies.Add(AuthenticationPolicies.USGovernmentTrustLevel1); - } - - // The spec requires that the OP perform discovery and if that fails, it must either sternly - // warn the user of a potential threat or just abort the authentication. - // We can't verify that the OP displayed anything to the user at this level, but we can - // at least verify that the OP performed the discovery on the realm and halt things if it didn't. - ErrorUtilities.VerifyHost(requestInternal.HasRealmDiscoveryBeenPerformed, BehaviorStrings.RealmDiscoveryNotPerformed); - } - - if (papeRequest.PreferredPolicies.Contains(AuthenticationPolicies.PrivatePersonalIdentifier)) { - ErrorUtilities.VerifyProtocol(request.ClaimedIdentifier == request.LocalIdentifier, OpenIdStrings.DelegatingIdentifiersNotAllowed); - - // Mask the user's identity with a PPID. - ErrorUtilities.VerifyHost(PpidIdentifierProvider != null, BehaviorStrings.PpidProviderNotGiven); - Identifier ppidIdentifier = PpidIdentifierProvider.GetIdentifier(request.LocalIdentifier, request.Realm); - requestInternal.ResetClaimedAndLocalIdentifiers(ppidIdentifier); - - // Indicate that the RP is receiving a PPID claimed_id - if (!papeResponse.ActualPolicies.Contains(AuthenticationPolicies.PrivatePersonalIdentifier)) { - papeResponse.ActualPolicies.Add(AuthenticationPolicies.PrivatePersonalIdentifier); - } - } - - if (papeRequest.PreferredPolicies.Contains(AuthenticationPolicies.NoPersonallyIdentifiableInformation)) { - ErrorUtilities.VerifyProtocol( - !responseMessage.Extensions.OfType<ClaimsResponse>().Any() && - !responseMessage.Extensions.OfType<FetchResponse>().Any(), - BehaviorStrings.PiiIncludedWithNoPiiPolicy); - - // If no PII is given in extensions, and the claimed_id is a PPID, then we can state we issue no PII. - if (papeResponse.ActualPolicies.Contains(AuthenticationPolicies.PrivatePersonalIdentifier)) { - if (!papeResponse.ActualPolicies.Contains(AuthenticationPolicies.NoPersonallyIdentifiableInformation)) { - papeResponse.ActualPolicies.Add(AuthenticationPolicies.NoPersonallyIdentifiableInformation); - } - } - } - - Reporting.RecordEventOccurrence(this, "OP"); - } - - return result; - } - - #endregion - - /// <summary> - /// Ensures the maximum association lifetime does not exceed a given limit. - /// </summary> - /// <param name="associationType">Type of the association.</param> - /// <param name="maximumLifetime">The maximum lifetime.</param> - /// <param name="securitySettings">The security settings to adjust.</param> - private static void SetMaximumAssociationLifetimeToNotExceed(string associationType, TimeSpan maximumLifetime, ProviderSecuritySettings securitySettings) { - Contract.Requires(!String.IsNullOrEmpty(associationType)); - Contract.Requires(maximumLifetime.TotalSeconds > 0); - if (!securitySettings.AssociationLifetimes.ContainsKey(associationType) || - securitySettings.AssociationLifetimes[associationType] > maximumLifetime) { - securitySettings.AssociationLifetimes[associationType] = maximumLifetime; - } - } - } -} diff --git a/src/DotNetOpenAuth/OpenId/Behaviors/PpidGeneration.cs b/src/DotNetOpenAuth/OpenId/Behaviors/PpidGeneration.cs deleted file mode 100644 index a465611..0000000 --- a/src/DotNetOpenAuth/OpenId/Behaviors/PpidGeneration.cs +++ /dev/null @@ -1,121 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="PpidGeneration.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OpenId.Behaviors { - using System; - using System.Diagnostics.CodeAnalysis; - using System.Diagnostics.Contracts; - using System.Linq; - using DotNetOpenAuth.Messaging; - using DotNetOpenAuth.OpenId.Extensions.ProviderAuthenticationPolicy; - using DotNetOpenAuth.OpenId.Provider; - - /// <summary> - /// Offers OpenID Providers automatic PPID Claimed Identifier generation when requested - /// by a PAPE request. - /// </summary> - /// <remarks> - /// <para>PPIDs are set on positive authentication responses when the PAPE request includes - /// the <see cref="AuthenticationPolicies.PrivatePersonalIdentifier"/> authentication policy.</para> - /// <para>The static member <see cref="PpidGeneration.PpidIdentifierProvider"/> MUST - /// be set prior to any PPID requests come in. Typically this should be set in the - /// <c>Application_Start</c> method in the global.asax.cs file.</para> - /// </remarks> - [Serializable] - [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Ppid", Justification = "Abbreviation")] - public sealed class PpidGeneration : IProviderBehavior { - /// <summary> - /// Gets or sets the provider for generating PPID identifiers. - /// </summary> - [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Ppid", Justification = "Abbreviation")] - public static IDirectedIdentityIdentifierProvider PpidIdentifierProvider { get; set; } - - #region IProviderBehavior Members - - /// <summary> - /// Applies a well known set of security requirements to a default set of security settings. - /// </summary> - /// <param name="securitySettings">The security settings to enhance with the requirements of this profile.</param> - /// <remarks> - /// Care should be taken to never decrease security when applying a profile. - /// Profiles should only enhance security requirements to avoid being - /// incompatible with each other. - /// </remarks> - void IProviderBehavior.ApplySecuritySettings(ProviderSecuritySettings securitySettings) { - // No special security to apply here. - } - - /// <summary> - /// Called when a request is received by the Provider. - /// </summary> - /// <param name="request">The incoming request.</param> - /// <returns> - /// <c>true</c> if this behavior owns this request and wants to stop other behaviors - /// from handling it; <c>false</c> to allow other behaviors to process this request. - /// </returns> - /// <remarks> - /// Implementations may set a new value to <see cref="IRequest.SecuritySettings"/> but - /// should not change the properties on the instance of <see cref="ProviderSecuritySettings"/> - /// itself as that instance may be shared across many requests. - /// </remarks> - bool IProviderBehavior.OnIncomingRequest(IRequest request) { - return false; - } - - /// <summary> - /// Called when the Provider is preparing to send a response to an authentication request. - /// </summary> - /// <param name="request">The request that is configured to generate the outgoing response.</param> - /// <returns> - /// <c>true</c> if this behavior owns this request and wants to stop other behaviors - /// from handling it; <c>false</c> to allow other behaviors to process this request. - /// </returns> - bool IProviderBehavior.OnOutgoingResponse(IAuthenticationRequest request) { - // Nothing to do for negative assertions. - if (!request.IsAuthenticated.Value) { - return false; - } - - var requestInternal = (Provider.AuthenticationRequest)request; - var responseMessage = (IProtocolMessageWithExtensions)requestInternal.Response; - - // Only apply our special policies if the RP requested it. - var papeRequest = request.GetExtension<PolicyRequest>(); - if (papeRequest != null) { - if (papeRequest.PreferredPolicies.Contains(AuthenticationPolicies.PrivatePersonalIdentifier)) { - ErrorUtilities.VerifyProtocol(request.ClaimedIdentifier == request.LocalIdentifier, OpenIdStrings.DelegatingIdentifiersNotAllowed); - - if (PpidIdentifierProvider == null) { - Logger.OpenId.Error(BehaviorStrings.PpidProviderNotGiven); - return false; - } - - // Mask the user's identity with a PPID. - if (PpidIdentifierProvider.IsUserLocalIdentifier(request.LocalIdentifier)) { - Identifier ppidIdentifier = PpidIdentifierProvider.GetIdentifier(request.LocalIdentifier, request.Realm); - requestInternal.ResetClaimedAndLocalIdentifiers(ppidIdentifier); - } - - // Indicate that the RP is receiving a PPID claimed_id - var papeResponse = responseMessage.Extensions.OfType<PolicyResponse>().SingleOrDefault(); - if (papeResponse == null) { - request.AddResponseExtension(papeResponse = new PolicyResponse()); - } - - if (!papeResponse.ActualPolicies.Contains(AuthenticationPolicies.PrivatePersonalIdentifier)) { - papeResponse.ActualPolicies.Add(AuthenticationPolicies.PrivatePersonalIdentifier); - } - - Reporting.RecordEventOccurrence(this, string.Empty); - } - } - - return false; - } - - #endregion - } -} diff --git a/src/DotNetOpenAuth/OpenId/ChannelElements/ExtensionsBindingElement.cs b/src/DotNetOpenAuth/OpenId/ChannelElements/ExtensionsBindingElement.cs deleted file mode 100644 index c516e8f..0000000 --- a/src/DotNetOpenAuth/OpenId/ChannelElements/ExtensionsBindingElement.cs +++ /dev/null @@ -1,252 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="ExtensionsBindingElement.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OpenId.ChannelElements { - using System; - using System.Collections.Generic; - using System.Diagnostics.CodeAnalysis; - using System.Diagnostics.Contracts; - using System.Linq; - using System.Text; - using DotNetOpenAuth.Messaging; - using DotNetOpenAuth.Messaging.Reflection; - using DotNetOpenAuth.OpenId.Extensions; - using DotNetOpenAuth.OpenId.Messages; - using DotNetOpenAuth.OpenId.Provider; - using DotNetOpenAuth.OpenId.RelyingParty; - - /// <summary> - /// The binding element that serializes/deserializes OpenID extensions to/from - /// their carrying OpenID messages. - /// </summary> - internal class ExtensionsBindingElement : IChannelBindingElement { - /// <summary> - /// The security settings that apply to this relying party, if it is a relying party. - /// </summary> - private readonly RelyingPartySecuritySettings relyingPartySecuritySettings; - - /// <summary> - /// Initializes a new instance of the <see cref="ExtensionsBindingElement"/> class. - /// </summary> - /// <param name="extensionFactory">The extension factory.</param> - /// <param name="securitySettings">The security settings.</param> - internal ExtensionsBindingElement(IOpenIdExtensionFactory extensionFactory, SecuritySettings securitySettings) { - Contract.Requires<ArgumentNullException>(extensionFactory != null); - Contract.Requires<ArgumentNullException>(securitySettings != null); - - this.ExtensionFactory = extensionFactory; - this.relyingPartySecuritySettings = securitySettings as RelyingPartySecuritySettings; - } - - #region IChannelBindingElement Members - - /// <summary> - /// Gets or sets the channel that this binding element belongs to. - /// </summary> - /// <value></value> - /// <remarks> - /// This property is set by the channel when it is first constructed. - /// </remarks> - public Channel Channel { get; set; } - - /// <summary> - /// Gets the extension factory. - /// </summary> - public IOpenIdExtensionFactory ExtensionFactory { get; private set; } - - /// <summary> - /// Gets the protection offered (if any) by this binding element. - /// </summary> - /// <value><see cref="MessageProtections.None"/></value> - public 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> - [SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Justification = "It doesn't look too bad to me. :)")] - public MessageProtections? ProcessOutgoingMessage(IProtocolMessage message) { - var extendableMessage = message as IProtocolMessageWithExtensions; - if (extendableMessage != null) { - Protocol protocol = Protocol.Lookup(message.Version); - MessageDictionary baseMessageDictionary = this.Channel.MessageDescriptions.GetAccessor(message); - - // We have a helper class that will do all the heavy-lifting of organizing - // all the extensions, their aliases, and their parameters. - var extensionManager = ExtensionArgumentsManager.CreateOutgoingExtensions(protocol); - foreach (IExtensionMessage protocolExtension in extendableMessage.Extensions) { - var extension = protocolExtension as IOpenIdMessageExtension; - if (extension != null) { - Reporting.RecordFeatureUse(protocolExtension); - - // Give extensions that require custom serialization a chance to do their work. - var customSerializingExtension = extension as IMessageWithEvents; - if (customSerializingExtension != null) { - customSerializingExtension.OnSending(); - } - - // OpenID 2.0 Section 12 forbids two extensions with the same TypeURI in the same message. - ErrorUtilities.VerifyProtocol(!extensionManager.ContainsExtension(extension.TypeUri), OpenIdStrings.ExtensionAlreadyAddedWithSameTypeURI, extension.TypeUri); - - // Ensure that we're sending out a valid extension. - var extensionDescription = this.Channel.MessageDescriptions.Get(extension); - var extensionDictionary = extensionDescription.GetDictionary(extension).Serialize(); - extensionDescription.EnsureMessagePartsPassBasicValidation(extensionDictionary); - - // Add the extension to the outgoing message payload. - extensionManager.AddExtensionArguments(extension.TypeUri, extensionDictionary); - } else { - Logger.OpenId.WarnFormat("Unexpected extension type {0} did not implement {1}.", protocolExtension.GetType(), typeof(IOpenIdMessageExtension).Name); - } - } - - // We use a cheap trick (for now at least) to determine whether the 'openid.' prefix - // belongs on the parameters by just looking at what other parameters do. - // Technically, direct message responses from Provider to Relying Party are the only - // messages that leave off the 'openid.' prefix. - bool includeOpenIdPrefix = baseMessageDictionary.Keys.Any(key => key.StartsWith(protocol.openid.Prefix, StringComparison.Ordinal)); - - // Add the extension parameters to the base message for transmission. - baseMessageDictionary.AddExtraParameters(extensionManager.GetArgumentsToSend(includeOpenIdPrefix)); - return MessageProtections.None; - } - - 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 MessageProtections? ProcessIncomingMessage(IProtocolMessage message) { - var extendableMessage = message as IProtocolMessageWithExtensions; - if (extendableMessage != null) { - // First add the extensions that are signed by the Provider. - foreach (IOpenIdMessageExtension signedExtension in this.GetExtensions(extendableMessage, true, null)) { - Reporting.RecordFeatureUse(signedExtension); - signedExtension.IsSignedByRemoteParty = true; - extendableMessage.Extensions.Add(signedExtension); - } - - // Now search again, considering ALL extensions whether they are signed or not, - // skipping the signed ones and adding the new ones as unsigned extensions. - if (this.relyingPartySecuritySettings == null || !this.relyingPartySecuritySettings.IgnoreUnsignedExtensions) { - Func<string, bool> isNotSigned = typeUri => !extendableMessage.Extensions.Cast<IOpenIdMessageExtension>().Any(ext => ext.TypeUri == typeUri); - foreach (IOpenIdMessageExtension unsignedExtension in this.GetExtensions(extendableMessage, false, isNotSigned)) { - Reporting.RecordFeatureUse(unsignedExtension); - unsignedExtension.IsSignedByRemoteParty = false; - extendableMessage.Extensions.Add(unsignedExtension); - } - } - - return MessageProtections.None; - } - - return null; - } - - #endregion - - /// <summary> - /// Gets the extensions on a message. - /// </summary> - /// <param name="message">The carrier of the extensions.</param> - /// <param name="ignoreUnsigned">If set to <c>true</c> only signed extensions will be available.</param> - /// <param name="extensionFilter">A optional filter that takes an extension type URI and - /// returns a value indicating whether that extension should be deserialized and - /// returned in the sequence. May be null.</param> - /// <returns>A sequence of extensions in the message.</returns> - private IEnumerable<IOpenIdMessageExtension> GetExtensions(IProtocolMessageWithExtensions message, bool ignoreUnsigned, Func<string, bool> extensionFilter) { - bool isAtProvider = message is SignedResponseRequest; - - // We have a helper class that will do all the heavy-lifting of organizing - // all the extensions, their aliases, and their parameters. - var extensionManager = ExtensionArgumentsManager.CreateIncomingExtensions(this.GetExtensionsDictionary(message, ignoreUnsigned)); - foreach (string typeUri in extensionManager.GetExtensionTypeUris()) { - // Our caller may have already obtained a signed version of this extension, - // so skip it if they don't want this one. - if (extensionFilter != null && !extensionFilter(typeUri)) { - continue; - } - - var extensionData = extensionManager.GetExtensionArguments(typeUri); - - // Initialize this particular extension. - IOpenIdMessageExtension extension = this.ExtensionFactory.Create(typeUri, extensionData, message, isAtProvider); - if (extension != null) { - try { - // Make sure the extension fulfills spec requirements before deserializing it. - MessageDescription messageDescription = this.Channel.MessageDescriptions.Get(extension); - messageDescription.EnsureMessagePartsPassBasicValidation(extensionData); - - // Deserialize the extension. - MessageDictionary extensionDictionary = messageDescription.GetDictionary(extension); - foreach (var pair in extensionData) { - extensionDictionary[pair.Key] = pair.Value; - } - - // Give extensions that require custom serialization a chance to do their work. - var customSerializingExtension = extension as IMessageWithEvents; - if (customSerializingExtension != null) { - customSerializingExtension.OnReceiving(); - } - } catch (ProtocolException ex) { - Logger.OpenId.ErrorFormat(OpenIdStrings.BadExtension, extension.GetType(), ex); - extension = null; - } - - if (extension != null) { - yield return extension; - } - } else { - Logger.OpenId.DebugFormat("Extension with type URI '{0}' ignored because it is not a recognized extension.", typeUri); - } - } - } - - /// <summary> - /// Gets the dictionary of message parts that should be deserialized into extensions. - /// </summary> - /// <param name="message">The message.</param> - /// <param name="ignoreUnsigned">If set to <c>true</c> only signed extensions will be available.</param> - /// <returns> - /// A dictionary of message parts, including only signed parts when appropriate. - /// </returns> - private IDictionary<string, string> GetExtensionsDictionary(IProtocolMessage message, bool ignoreUnsigned) { - Contract.Requires<InvalidOperationException>(this.Channel != null); - - IndirectSignedResponse signedResponse = message as IndirectSignedResponse; - if (signedResponse != null && ignoreUnsigned) { - return signedResponse.GetSignedMessageParts(this.Channel); - } else { - return this.Channel.MessageDescriptions.GetAccessor(message); - } - } - } -} diff --git a/src/DotNetOpenAuth/OpenId/ChannelElements/IOpenIdExtensionFactory.cs b/src/DotNetOpenAuth/OpenId/ChannelElements/IOpenIdExtensionFactory.cs deleted file mode 100644 index 762fc9a..0000000 --- a/src/DotNetOpenAuth/OpenId/ChannelElements/IOpenIdExtensionFactory.cs +++ /dev/null @@ -1,50 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="IOpenIdExtensionFactory.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OpenId.ChannelElements { - using System.Collections.Generic; - using DotNetOpenAuth.Messaging; - using DotNetOpenAuth.OpenId.Messages; - using DotNetOpenAuth.OpenId.Provider; - using DotNetOpenAuth.OpenId.RelyingParty; - - /// <summary> - /// OpenID extension factory class for creating extensions based on received Type URIs. - /// </summary> - /// <remarks> - /// OpenID extension factories must be registered with the library. This can be - /// done by adding a factory to <see cref="OpenIdRelyingParty.ExtensionFactories"/> - /// or <see cref="OpenIdProvider.ExtensionFactories"/>, or by adding a snippet - /// such as the following to your web.config file: - /// <example> - /// <dotNetOpenAuth> - /// <openid> - /// <extensionFactories> - /// <add type="DotNetOpenAuth.ApplicationBlock.CustomExtensions.Acme, DotNetOpenAuth.ApplicationBlock" /> - /// </extensionFactories> - /// </openid> - /// </dotNetOpenAuth> - /// </example> - /// </remarks> - public interface IOpenIdExtensionFactory { - /// <summary> - /// Creates a new instance of some extension based on the received extension parameters. - /// </summary> - /// <param name="typeUri">The type URI of the extension.</param> - /// <param name="data">The parameters associated specifically with this extension.</param> - /// <param name="baseMessage">The OpenID message carrying this extension.</param> - /// <param name="isProviderRole">A value indicating whether this extension is being received at the OpenID Provider.</param> - /// <returns> - /// An instance of <see cref="IOpenIdMessageExtension"/> if the factory recognizes - /// the extension described in the input parameters; <c>null</c> otherwise. - /// </returns> - /// <remarks> - /// This factory method need only initialize properties in the instantiated extension object - /// that are not bound using <see cref="MessagePartAttribute"/>. - /// </remarks> - IOpenIdMessageExtension Create(string typeUri, IDictionary<string, string> data, IProtocolMessageWithExtensions baseMessage, bool isProviderRole); - } -} diff --git a/src/DotNetOpenAuth/OpenId/ChannelElements/KeyValueFormEncoding.cs b/src/DotNetOpenAuth/OpenId/ChannelElements/KeyValueFormEncoding.cs deleted file mode 100644 index 46c2139..0000000 --- a/src/DotNetOpenAuth/OpenId/ChannelElements/KeyValueFormEncoding.cs +++ /dev/null @@ -1,169 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="KeyValueFormEncoding.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OpenId.ChannelElements { - using System; - using System.Collections.Generic; - using System.Diagnostics.CodeAnalysis; - using System.Diagnostics.Contracts; - using System.Globalization; - using System.IO; - using System.Text; - using DotNetOpenAuth.Messaging; - - /// <summary> - /// Indicates the level of strictness to require when decoding a - /// Key-Value Form encoded dictionary. - /// </summary> - public enum KeyValueFormConformanceLevel { - /// <summary> - /// Be as forgiving as possible to errors made while encoding. - /// </summary> - Loose, - - /// <summary> - /// Allow for certain errors in encoding attributable to ambiguities - /// in the OpenID 1.1 spec's description of the encoding. - /// </summary> - OpenId11, - - /// <summary> - /// The strictest mode. The decoder requires the encoded dictionary - /// to be in strict compliance with OpenID 2.0's description of - /// the encoding. - /// </summary> - OpenId20, - } - - /// <summary> - /// Performs conversion to and from the Key-Value Form Encoding defined by - /// OpenID Authentication 2.0 section 4.1.1. - /// http://openid.net/specs/openid-authentication-2_0.html#anchor4 - /// </summary> - /// <remarks> - /// This class is thread safe and immutable. - /// </remarks> - internal class KeyValueFormEncoding { - /// <summary> - /// Characters that must not appear in parameter names. - /// </summary> - private static readonly char[] IllegalKeyCharacters = { '\n', ':' }; - - /// <summary> - /// Characters that must not appaer in parameter values. - /// </summary> - private static readonly char[] IllegalValueCharacters = { '\n' }; - - /// <summary> - /// The newline character sequence to use. - /// </summary> - private const string NewLineCharacters = "\n"; - - /// <summary> - /// The character encoding to use. - /// </summary> - private static readonly Encoding textEncoding = new UTF8Encoding(false); - - /// <summary> - /// Initializes a new instance of the <see cref="KeyValueFormEncoding"/> class. - /// </summary> - public KeyValueFormEncoding() { - this.ConformanceLevel = KeyValueFormConformanceLevel.Loose; - } - - /// <summary> - /// Initializes a new instance of the <see cref="KeyValueFormEncoding"/> class. - /// </summary> - /// <param name="conformanceLevel">How strictly an incoming Key-Value Form message will be held to the spec.</param> - public KeyValueFormEncoding(KeyValueFormConformanceLevel conformanceLevel) { - this.ConformanceLevel = conformanceLevel; - } - - /// <summary> - /// Gets a value controlling how strictly an incoming Key-Value Form message will be held to the spec. - /// </summary> - public KeyValueFormConformanceLevel ConformanceLevel { get; private set; } - - /// <summary> - /// Encodes key/value pairs to Key-Value Form. - /// </summary> - /// <param name="keysAndValues"> - /// The dictionary of key/value pairs to convert to a byte stream. - /// </param> - /// <returns>The UTF8 byte array.</returns> - /// <remarks> - /// Enumerating a Dictionary<TKey, TValue> has undeterministic ordering. - /// If ordering of the key=value pairs is important, a deterministic enumerator must - /// be used. - /// </remarks> - [SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times", Justification = "Not a problem for this type.")] - public static byte[] GetBytes(IEnumerable<KeyValuePair<string, string>> keysAndValues) { - Contract.Requires<ArgumentNullException>(keysAndValues != null); - - using (MemoryStream ms = new MemoryStream()) { - using (StreamWriter sw = new StreamWriter(ms, textEncoding)) { - sw.NewLine = NewLineCharacters; - foreach (var pair in keysAndValues) { - if (pair.Key.IndexOfAny(IllegalKeyCharacters) >= 0) { - throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, OpenIdStrings.InvalidCharacterInKeyValueFormInput, pair.Key)); - } - if (pair.Value.IndexOfAny(IllegalValueCharacters) >= 0) { - throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, OpenIdStrings.InvalidCharacterInKeyValueFormInput, pair.Value)); - } - - sw.Write(pair.Key); - sw.Write(':'); - sw.Write(pair.Value); - sw.WriteLine(); - } - } - - return ms.ToArray(); - } - } - - /// <summary> - /// Decodes bytes in Key-Value Form to key/value pairs. - /// </summary> - /// <param name="data">The stream of Key-Value Form encoded bytes.</param> - /// <returns>The deserialized dictionary.</returns> - /// <exception cref="FormatException">Thrown when the data is not in the expected format.</exception> - public IDictionary<string, string> GetDictionary(Stream data) { - using (StreamReader reader = new StreamReader(data, textEncoding)) { - var dict = new Dictionary<string, string>(); - int line_num = 0; - string line; - while ((line = reader.ReadLine()) != null) { - line_num++; - if (this.ConformanceLevel == KeyValueFormConformanceLevel.Loose) { - line = line.Trim(); - if (line.Length == 0) { - continue; - } - } - string[] parts = line.Split(new[] { ':' }, 2); - ErrorUtilities.VerifyFormat(parts.Length == 2, OpenIdStrings.InvalidKeyValueFormCharacterMissing, ':', line_num, line); - if (this.ConformanceLevel > KeyValueFormConformanceLevel.Loose) { - ErrorUtilities.VerifyFormat(!(char.IsWhiteSpace(parts[0], parts[0].Length - 1) || char.IsWhiteSpace(parts[1], 0)), OpenIdStrings.InvalidCharacterInKeyValueFormInput, ' ', line_num, line); - } - if (this.ConformanceLevel < KeyValueFormConformanceLevel.OpenId20) { - parts[0] = parts[0].Trim(); - parts[1] = parts[1].Trim(); - } - - // calling Add method will throw if a key is encountered twice, - // which we should do. - dict.Add(parts[0], parts[1]); - } - if (this.ConformanceLevel > KeyValueFormConformanceLevel.Loose) { - reader.BaseStream.Seek(-1, SeekOrigin.End); - ErrorUtilities.VerifyFormat(reader.BaseStream.ReadByte() == '\n', OpenIdStrings.InvalidKeyValueFormCharacterMissing, "\\n", line_num, line); - } - return dict; - } - } - } -} diff --git a/src/DotNetOpenAuth/OpenId/ChannelElements/OpenIdChannel.cs b/src/DotNetOpenAuth/OpenId/ChannelElements/OpenIdChannel.cs deleted file mode 100644 index d9a0e50..0000000 --- a/src/DotNetOpenAuth/OpenId/ChannelElements/OpenIdChannel.cs +++ /dev/null @@ -1,386 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="OpenIdChannel.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OpenId.ChannelElements { - using System; - using System.Collections.Generic; - using System.Diagnostics.CodeAnalysis; - using System.Diagnostics.Contracts; - using System.Globalization; - using System.IO; - using System.Linq; - using System.Net; - using System.Text; - using DotNetOpenAuth.Messaging; - using DotNetOpenAuth.Messaging.Bindings; - using DotNetOpenAuth.OpenId.Extensions; - using DotNetOpenAuth.OpenId.Messages; - using DotNetOpenAuth.OpenId.Provider; - using DotNetOpenAuth.OpenId.RelyingParty; - - /// <summary> - /// A channel that knows how to send and receive OpenID messages. - /// </summary> - [ContractVerification(true)] - internal class OpenIdChannel : Channel { - /// <summary> - /// The HTTP Content-Type to use in Key-Value Form responses. - /// </summary> - /// <remarks> - /// OpenID 2.0 section 5.1.2 says this SHOULD be text/plain. But this value - /// does not prevent free hosters like GoDaddy from tacking on their ads - /// to the end of the direct response, corrupting the data. So we deviate - /// from the spec a bit here to improve the story for free Providers. - /// </remarks> - internal const string KeyValueFormContentType = "application/x-openid-kvf"; - - /// <summary> - /// The encoder that understands how to read and write Key-Value Form. - /// </summary> - private KeyValueFormEncoding keyValueForm = new KeyValueFormEncoding(); - - /// <summary> - /// Initializes a new instance of the <see cref="OpenIdChannel"/> class - /// for use by a Relying Party. - /// </summary> - /// <param name="cryptoKeyStore">The association store to use.</param> - /// <param name="nonceStore">The nonce store to use.</param> - /// <param name="securitySettings">The security settings to apply.</param> - internal OpenIdChannel(ICryptoKeyStore cryptoKeyStore, INonceStore nonceStore, RelyingPartySecuritySettings securitySettings) - : this(cryptoKeyStore, nonceStore, new OpenIdMessageFactory(), securitySettings, false) { - Contract.Requires<ArgumentNullException>(securitySettings != null); - } - - /// <summary> - /// Initializes a new instance of the <see cref="OpenIdChannel"/> class - /// for use by a Provider. - /// </summary> - /// <param name="cryptoKeyStore">The OpenID Provider's association store or handle encoder.</param> - /// <param name="nonceStore">The nonce store to use.</param> - /// <param name="securitySettings">The security settings.</param> - internal OpenIdChannel(IProviderAssociationStore cryptoKeyStore, INonceStore nonceStore, ProviderSecuritySettings securitySettings) - : this(cryptoKeyStore, nonceStore, new OpenIdMessageFactory(), securitySettings) { - Contract.Requires<ArgumentNullException>(cryptoKeyStore != null); - Contract.Requires<ArgumentNullException>(securitySettings != null); - } - - /// <summary> - /// Initializes a new instance of the <see cref="OpenIdChannel"/> class - /// for use by a Relying Party. - /// </summary> - /// <param name="cryptoKeyStore">The association store to use.</param> - /// <param name="nonceStore">The nonce store to use.</param> - /// <param name="messageTypeProvider">An object that knows how to distinguish the various OpenID message types for deserialization purposes.</param> - /// <param name="securitySettings">The security settings to apply.</param> - /// <param name="nonVerifying">A value indicating whether the channel is set up with no functional security binding elements.</param> - private OpenIdChannel(ICryptoKeyStore cryptoKeyStore, INonceStore nonceStore, IMessageFactory messageTypeProvider, RelyingPartySecuritySettings securitySettings, bool nonVerifying) : - this(messageTypeProvider, InitializeBindingElements(cryptoKeyStore, nonceStore, securitySettings, nonVerifying)) { - Contract.Requires<ArgumentNullException>(messageTypeProvider != null); - Contract.Requires<ArgumentNullException>(securitySettings != null); - Contract.Requires<ArgumentException>(!nonVerifying || securitySettings is RelyingPartySecuritySettings); - } - - /// <summary> - /// Initializes a new instance of the <see cref="OpenIdChannel"/> class - /// for use by a Provider. - /// </summary> - /// <param name="cryptoKeyStore">The association store to use.</param> - /// <param name="nonceStore">The nonce store to use.</param> - /// <param name="messageTypeProvider">An object that knows how to distinguish the various OpenID message types for deserialization purposes.</param> - /// <param name="securitySettings">The security settings.</param> - private OpenIdChannel(IProviderAssociationStore cryptoKeyStore, INonceStore nonceStore, IMessageFactory messageTypeProvider, ProviderSecuritySettings securitySettings) : - this(messageTypeProvider, InitializeBindingElements(cryptoKeyStore, nonceStore, securitySettings)) { - Contract.Requires<ArgumentNullException>(cryptoKeyStore != null); - Contract.Requires<ArgumentNullException>(messageTypeProvider != null); - Contract.Requires<ArgumentNullException>(securitySettings != null); - } - - /// <summary> - /// Initializes a new instance of the <see cref="OpenIdChannel"/> class. - /// </summary> - /// <param name="messageTypeProvider">A class prepared to analyze incoming messages and indicate what concrete - /// message types can deserialize from it.</param> - /// <param name="bindingElements">The binding elements to use in sending and receiving messages.</param> - private OpenIdChannel(IMessageFactory messageTypeProvider, IChannelBindingElement[] bindingElements) - : base(messageTypeProvider, bindingElements) { - Contract.Requires<ArgumentNullException>(messageTypeProvider != null); - - // Customize the binding element order, since we play some tricks for higher - // security and backward compatibility with older OpenID versions. - var outgoingBindingElements = new List<IChannelBindingElement>(bindingElements); - var incomingBindingElements = new List<IChannelBindingElement>(bindingElements); - incomingBindingElements.Reverse(); - - // Customize the order of the incoming elements by moving the return_to elements in front. - var backwardCompatibility = incomingBindingElements.OfType<BackwardCompatibilityBindingElement>().SingleOrDefault(); - var returnToSign = incomingBindingElements.OfType<ReturnToSignatureBindingElement>().SingleOrDefault(); - if (backwardCompatibility != null) { - incomingBindingElements.MoveTo(0, backwardCompatibility); - } - if (returnToSign != null) { - // Yes, this is intentionally, shifting the backward compatibility - // binding element to second position. - incomingBindingElements.MoveTo(0, returnToSign); - } - - this.CustomizeBindingElementOrder(outgoingBindingElements, incomingBindingElements); - - // Change out the standard web request handler to reflect the standard - // OpenID pattern that outgoing web requests are to unknown and untrusted - // servers on the Internet. - this.WebRequestHandler = new UntrustedWebRequestHandler(); - } - - /// <summary> - /// A value indicating whether the channel is set up - /// with no functional security binding elements. - /// </summary> - /// <returns>A new <see cref="OpenIdChannel"/> instance that will not perform verification on incoming messages or apply any security to outgoing messages.</returns> - /// <remarks> - /// <para>A value of <c>true</c> allows the relying party to preview incoming - /// messages without invalidating nonces or checking signatures.</para> - /// <para>Setting this to <c>true</c> poses a great security risk and is only - /// present to support the <see cref="OpenIdAjaxTextBox"/> which needs to preview - /// messages, and will validate them later.</para> - /// </remarks> - internal static OpenIdChannel CreateNonVerifyingChannel() { - Contract.Ensures(Contract.Result<OpenIdChannel>() != null); - - return new OpenIdChannel(null, null, new OpenIdMessageFactory(), new RelyingPartySecuritySettings(), true); - } - - /// <summary> - /// Verifies the integrity and applicability of an incoming message. - /// </summary> - /// <param name="message">The message just received.</param> - /// <exception cref="ProtocolException"> - /// Thrown when the message is somehow invalid, except for check_authentication messages. - /// This can be due to tampering, replay attack or expiration, among other things. - /// </exception> - protected override void ProcessIncomingMessage(IProtocolMessage message) { - var checkAuthRequest = message as CheckAuthenticationRequest; - if (checkAuthRequest != null) { - IndirectSignedResponse originalResponse = new IndirectSignedResponse(checkAuthRequest, this); - try { - base.ProcessIncomingMessage(originalResponse); - checkAuthRequest.IsValid = true; - } catch (ProtocolException) { - checkAuthRequest.IsValid = false; - } - } else { - base.ProcessIncomingMessage(message); - } - - // Convert an OpenID indirect error message, which we never expect - // between two good OpenID implementations, into an exception. - // We don't process DirectErrorResponse because associate negotiations - // commonly get a derivative of that message type and handle it. - var errorMessage = message as IndirectErrorResponse; - if (errorMessage != null) { - string exceptionMessage = string.Format( - CultureInfo.CurrentCulture, - OpenIdStrings.IndirectErrorFormattedMessage, - errorMessage.ErrorMessage, - errorMessage.Contact, - errorMessage.Reference); - throw new ProtocolException(exceptionMessage, message); - } - } - - /// <summary> - /// Prepares an HTTP request that carries a given message. - /// </summary> - /// <param name="request">The message to send.</param> - /// <returns> - /// The <see cref="HttpWebRequest"/> prepared to send the request. - /// </returns> - protected override HttpWebRequest CreateHttpRequest(IDirectedProtocolMessage request) { - return this.InitializeRequestAsPost(request); - } - - /// <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) { - try { - return this.keyValueForm.GetDictionary(response.ResponseStream); - } catch (FormatException ex) { - throw ErrorUtilities.Wrap(ex, ex.Message); - } - } - - /// <summary> - /// Called when receiving a direct response message, before deserialization begins. - /// </summary> - /// <param name="response">The HTTP direct response.</param> - /// <param name="message">The newly instantiated message, prior to deserialization.</param> - protected override void OnReceivingDirectResponse(IncomingWebResponse response, IDirectResponseProtocolMessage message) { - base.OnReceivingDirectResponse(response, message); - - // Verify that the expected HTTP status code was used for the message, - // per OpenID 2.0 section 5.1.2.2. - // Note: The v1.1 spec doesn't require 400 responses for some error messages - if (message.Version.Major >= 2) { - var httpDirectResponse = message as IHttpDirectResponse; - if (httpDirectResponse != null) { - ErrorUtilities.VerifyProtocol( - httpDirectResponse.HttpStatusCode == response.Status, - MessagingStrings.UnexpectedHttpStatusCode, - (int)httpDirectResponse.HttpStatusCode, - (int)response.Status); - } - } - } - - /// <summary> - /// Queues a message for sending in the response stream where the fields - /// are sent in the response stream in querystring style. - /// </summary> - /// <param name="response">The message to send as a response.</param> - /// <returns> - /// The pending user agent redirect based message to be sent as an HttpResponse. - /// </returns> - /// <remarks> - /// This method implements spec V1.0 section 5.3. - /// </remarks> - protected override OutgoingWebResponse PrepareDirectResponse(IProtocolMessage response) { - var messageAccessor = this.MessageDescriptions.GetAccessor(response); - var fields = messageAccessor.Serialize(); - byte[] keyValueEncoding = KeyValueFormEncoding.GetBytes(fields); - - OutgoingWebResponse preparedResponse = new OutgoingWebResponse(); - preparedResponse.Headers.Add(HttpResponseHeader.ContentType, KeyValueFormContentType); - preparedResponse.OriginalMessage = response; - preparedResponse.ResponseStream = new MemoryStream(keyValueEncoding); - - IHttpDirectResponse httpMessage = response as IHttpDirectResponse; - if (httpMessage != null) { - preparedResponse.Status = httpMessage.HttpStatusCode; - } - - return preparedResponse; - } - - /// <summary> - /// Gets the direct response of a direct HTTP request. - /// </summary> - /// <param name="webRequest">The web request.</param> - /// <returns>The response to the web request.</returns> - /// <exception cref="ProtocolException">Thrown on network or protocol errors.</exception> - protected override IncomingWebResponse GetDirectResponse(HttpWebRequest webRequest) { - IncomingWebResponse response = this.WebRequestHandler.GetResponse(webRequest, DirectWebRequestOptions.AcceptAllHttpResponses); - - // Filter the responses to the allowable set of HTTP status codes. - if (response.Status != HttpStatusCode.OK && response.Status != HttpStatusCode.BadRequest) { - if (Logger.Channel.IsErrorEnabled) { - using (var reader = new StreamReader(response.ResponseStream)) { - Logger.Channel.ErrorFormat( - "Unexpected HTTP status code {0} {1} received in direct response:{2}{3}", - (int)response.Status, - response.Status, - Environment.NewLine, - reader.ReadToEnd()); - } - } - - // Call dispose before throwing since we're not including the response in the - // exception we're throwing. - response.Dispose(); - - ErrorUtilities.ThrowProtocol(OpenIdStrings.UnexpectedHttpStatusCode, (int)response.Status, response.Status); - } - - return response; - } - - /// <summary> - /// Initializes the binding elements. - /// </summary> - /// <param name="cryptoKeyStore">The crypto key store.</param> - /// <param name="nonceStore">The nonce store to use.</param> - /// <param name="securitySettings">The security settings to apply. Must be an instance of either <see cref="RelyingPartySecuritySettings"/> or <see cref="ProviderSecuritySettings"/>.</param> - /// <param name="nonVerifying">A value indicating whether the channel is set up with no functional security binding elements.</param> - /// <returns> - /// An array of binding elements which may be used to construct the channel. - /// </returns> - private static IChannelBindingElement[] InitializeBindingElements(ICryptoKeyStore cryptoKeyStore, INonceStore nonceStore, RelyingPartySecuritySettings securitySettings, bool nonVerifying) { - Contract.Requires<ArgumentNullException>(securitySettings != null); - - SigningBindingElement signingElement; - signingElement = nonVerifying ? null : new SigningBindingElement(new CryptoKeyStoreAsRelyingPartyAssociationStore(cryptoKeyStore ?? new MemoryCryptoKeyStore())); - - var extensionFactory = OpenIdExtensionFactoryAggregator.LoadFromConfiguration(); - - List<IChannelBindingElement> elements = new List<IChannelBindingElement>(8); - elements.Add(new ExtensionsBindingElement(extensionFactory, securitySettings)); - elements.Add(new RelyingPartySecurityOptions(securitySettings)); - elements.Add(new BackwardCompatibilityBindingElement()); - ReturnToNonceBindingElement requestNonceElement = null; - - if (cryptoKeyStore != null) { - if (nonceStore != null) { - // There is no point in having a ReturnToNonceBindingElement without - // a ReturnToSignatureBindingElement because the nonce could be - // artificially changed without it. - requestNonceElement = new ReturnToNonceBindingElement(nonceStore, securitySettings); - elements.Add(requestNonceElement); - } - - // It is important that the return_to signing element comes last - // so that the nonce is included in the signature. - elements.Add(new ReturnToSignatureBindingElement(cryptoKeyStore)); - } - - ErrorUtilities.VerifyOperation(!securitySettings.RejectUnsolicitedAssertions || requestNonceElement != null, OpenIdStrings.UnsolicitedAssertionRejectionRequiresNonceStore); - - if (nonVerifying) { - elements.Add(new SkipSecurityBindingElement()); - } else { - if (nonceStore != null) { - elements.Add(new StandardReplayProtectionBindingElement(nonceStore, true)); - } - - elements.Add(new StandardExpirationBindingElement()); - elements.Add(signingElement); - } - - return elements.ToArray(); - } - - /// <summary> - /// Initializes the binding elements. - /// </summary> - /// <param name="cryptoKeyStore">The OpenID Provider's crypto key store.</param> - /// <param name="nonceStore">The nonce store to use.</param> - /// <param name="securitySettings">The security settings to apply. Must be an instance of either <see cref="RelyingPartySecuritySettings"/> or <see cref="ProviderSecuritySettings"/>.</param> - /// <returns> - /// An array of binding elements which may be used to construct the channel. - /// </returns> - private static IChannelBindingElement[] InitializeBindingElements(IProviderAssociationStore cryptoKeyStore, INonceStore nonceStore, ProviderSecuritySettings securitySettings) { - Contract.Requires<ArgumentNullException>(cryptoKeyStore != null); - Contract.Requires<ArgumentNullException>(securitySettings != null); - Contract.Requires<ArgumentNullException>(nonceStore != null); - - SigningBindingElement signingElement; - signingElement = new SigningBindingElement(cryptoKeyStore, securitySettings); - - var extensionFactory = OpenIdExtensionFactoryAggregator.LoadFromConfiguration(); - - List<IChannelBindingElement> elements = new List<IChannelBindingElement>(8); - elements.Add(new ExtensionsBindingElement(extensionFactory, securitySettings)); - elements.Add(new StandardReplayProtectionBindingElement(nonceStore, true)); - elements.Add(new StandardExpirationBindingElement()); - elements.Add(signingElement); - - return elements.ToArray(); - } - } -} diff --git a/src/DotNetOpenAuth/OpenId/ChannelElements/OpenIdMessageFactory.cs b/src/DotNetOpenAuth/OpenId/ChannelElements/OpenIdMessageFactory.cs deleted file mode 100644 index 1e5ea4c..0000000 --- a/src/DotNetOpenAuth/OpenId/ChannelElements/OpenIdMessageFactory.cs +++ /dev/null @@ -1,146 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="OpenIdMessageFactory.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OpenId.ChannelElements { - using System; - using System.Collections.Generic; - using System.Diagnostics.Contracts; - using System.Linq; - using System.Text; - using DotNetOpenAuth.Messaging; - using DotNetOpenAuth.OpenId.Messages; - using DotNetOpenAuth.OpenId.RelyingParty; - - /// <summary> - /// Distinguishes the various OpenID message types for deserialization purposes. - /// </summary> - internal class OpenIdMessageFactory : IMessageFactory { - #region IMessageFactory Members - - /// <summary> - /// Analyzes an incoming request message payload to discover what kind of - /// message is embedded in it and returns the type, or null if no match is found. - /// </summary> - /// <param name="recipient">The intended or actual recipient of the request message.</param> - /// <param name="fields">The name/value pairs that make up the message payload.</param> - /// <returns> - /// A newly instantiated <see cref="IProtocolMessage"/>-derived object that this message can - /// deserialize to. Null if the request isn't recognized as a valid protocol message. - /// </returns> - public IDirectedProtocolMessage GetNewRequestMessage(MessageReceivingEndpoint recipient, IDictionary<string, string> fields) { - RequestBase message = null; - - // Discern the OpenID version of the message. - Protocol protocol = Protocol.V11; - string ns; - if (fields.TryGetValue(Protocol.V20.openid.ns, out ns)) { - ErrorUtilities.VerifyProtocol(string.Equals(ns, Protocol.OpenId2Namespace, StringComparison.Ordinal), MessagingStrings.UnexpectedMessagePartValue, Protocol.V20.openid.ns, ns); - protocol = Protocol.V20; - } - - string mode; - if (fields.TryGetValue(protocol.openid.mode, out mode)) { - if (string.Equals(mode, protocol.Args.Mode.associate)) { - if (fields.ContainsKey(protocol.openid.dh_consumer_public)) { - message = new AssociateDiffieHellmanRequest(protocol.Version, recipient.Location); - } else { - message = new AssociateUnencryptedRequest(protocol.Version, recipient.Location); - } - } else if (string.Equals(mode, protocol.Args.Mode.checkid_setup) || - string.Equals(mode, protocol.Args.Mode.checkid_immediate)) { - AuthenticationRequestMode authMode = string.Equals(mode, protocol.Args.Mode.checkid_immediate) ? AuthenticationRequestMode.Immediate : AuthenticationRequestMode.Setup; - if (fields.ContainsKey(protocol.openid.identity)) { - message = new CheckIdRequest(protocol.Version, recipient.Location, authMode); - } else { - ErrorUtilities.VerifyProtocol(!fields.ContainsKey(protocol.openid.claimed_id), OpenIdStrings.IdentityAndClaimedIdentifierMustBeBothPresentOrAbsent); - message = new SignedResponseRequest(protocol.Version, recipient.Location, authMode); - } - } else if (string.Equals(mode, protocol.Args.Mode.cancel) || - (string.Equals(mode, protocol.Args.Mode.setup_needed) && (protocol.Version.Major >= 2 || fields.ContainsKey(protocol.openid.user_setup_url)))) { - message = new NegativeAssertionResponse(protocol.Version, recipient.Location, mode); - } else if (string.Equals(mode, protocol.Args.Mode.id_res)) { - if (fields.ContainsKey(protocol.openid.identity)) { - message = new PositiveAssertionResponse(protocol.Version, recipient.Location); - } else { - ErrorUtilities.VerifyProtocol(!fields.ContainsKey(protocol.openid.claimed_id), OpenIdStrings.IdentityAndClaimedIdentifierMustBeBothPresentOrAbsent); - message = new IndirectSignedResponse(protocol.Version, recipient.Location); - } - } else if (string.Equals(mode, protocol.Args.Mode.check_authentication)) { - message = new CheckAuthenticationRequest(protocol.Version, recipient.Location); - } else if (string.Equals(mode, protocol.Args.Mode.error)) { - message = new IndirectErrorResponse(protocol.Version, recipient.Location); - } else { - ErrorUtilities.ThrowProtocol(MessagingStrings.UnexpectedMessagePartValue, protocol.openid.mode, mode); - } - } - - if (message != null) { - message.SetAsIncoming(); - } - - return message; - } - - /// <summary> - /// Analyzes an incoming request message payload to discover what kind of - /// message is embedded in it and returns the type, or null if no match is found. - /// </summary> - /// <param name="request">The message that was sent as a request that resulted in the response.</param> - /// <param name="fields">The name/value pairs that make up the message payload.</param> - /// <returns> - /// A newly instantiated <see cref="IProtocolMessage"/>-derived object that this message can - /// deserialize to. Null if the request isn't recognized as a valid protocol message. - /// </returns> - public IDirectResponseProtocolMessage GetNewResponseMessage(IDirectedProtocolMessage request, IDictionary<string, string> fields) { - DirectResponseBase message = null; - - // Discern the OpenID version of the message. - Protocol protocol = Protocol.V11; - string ns; - if (fields.TryGetValue(Protocol.V20.openidnp.ns, out ns)) { - ErrorUtilities.VerifyProtocol(string.Equals(ns, Protocol.OpenId2Namespace, StringComparison.Ordinal), MessagingStrings.UnexpectedMessagePartValue, Protocol.V20.openidnp.ns, ns); - protocol = Protocol.V20; - } - - // Handle error messages generally. - if (fields.ContainsKey(protocol.openidnp.error)) { - message = new DirectErrorResponse(protocol.Version, request); - } - - var associateRequest = request as AssociateRequest; - if (associateRequest != null) { - if (protocol.Version.Major >= 2 && fields.ContainsKey(protocol.openidnp.error_code)) { - // This is a special recognized error case that we create a special message for. - message = new AssociateUnsuccessfulResponse(protocol.Version, associateRequest); - } else if (message == null) { - var associateDiffieHellmanRequest = request as AssociateDiffieHellmanRequest; - var associateUnencryptedRequest = request as AssociateUnencryptedRequest; - - if (associateDiffieHellmanRequest != null) { - message = new AssociateDiffieHellmanResponse(protocol.Version, associateDiffieHellmanRequest); - } - - if (associateUnencryptedRequest != null) { - message = new AssociateUnencryptedResponse(protocol.Version, associateUnencryptedRequest); - } - } - } - - var checkAuthenticationRequest = request as CheckAuthenticationRequest; - if (checkAuthenticationRequest != null && message == null) { - message = new CheckAuthenticationResponse(protocol.Version, checkAuthenticationRequest); - } - - if (message != null) { - message.SetAsIncoming(); - } - - return message; - } - - #endregion - } -} diff --git a/src/DotNetOpenAuth/OpenId/ChannelElements/ReturnToNonceBindingElement.cs b/src/DotNetOpenAuth/OpenId/ChannelElements/ReturnToNonceBindingElement.cs deleted file mode 100644 index 3649543..0000000 --- a/src/DotNetOpenAuth/OpenId/ChannelElements/ReturnToNonceBindingElement.cs +++ /dev/null @@ -1,291 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="ReturnToNonceBindingElement.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OpenId.ChannelElements { - using System; - using System.Collections.Generic; - using System.Diagnostics.Contracts; - using System.Linq; - using System.Text; - using DotNetOpenAuth.Messaging; - using DotNetOpenAuth.Messaging.Bindings; - using DotNetOpenAuth.OpenId.Messages; - using DotNetOpenAuth.OpenId.RelyingParty; - - /// <summary> - /// This binding element adds a nonce to a Relying Party's outgoing - /// authentication request when working against an OpenID 1.0 Provider - /// in order to protect against replay attacks or on all authentication - /// requests to distinguish solicited from unsolicited assertions. - /// </summary> - /// <remarks> - /// <para>This nonce goes beyond the OpenID 1.x spec, but adds to security. - /// Since this library's Provider implementation also provides special nonce - /// protection for 1.0 messages, this security feature overlaps with that one. - /// This means that if an RP from this library were talking to an OP from this - /// library, but the Identifier being authenticated advertised the OP as a 1.x - /// OP, then both RP and OP might try to use a nonce for protecting the assertion. - /// There's no problem with that--it will still all work out. And it would be a - /// very rare combination of elements anyway. - /// </para> - /// <para> - /// This binding element deactivates itself for OpenID 2.0 (or later) messages - /// since they are automatically protected in the protocol by the Provider's - /// openid.response_nonce parameter. The exception to this is when - /// <see cref="RelyingPartySecuritySettings.RejectUnsolicitedAssertions"/> is - /// set to <c>true</c>, which will not only add a request nonce to every outgoing - /// authentication request but also require that it be present in positive - /// assertions, effectively disabling unsolicited assertions. - /// </para> - /// <para>In the messaging stack, this binding element looks like an ordinary - /// transform-type of binding element rather than a protection element, - /// due to its required order in the channel stack and that it exists - /// only on the RP side and only on some messages.</para> - /// </remarks> - internal class ReturnToNonceBindingElement : IChannelBindingElement { - /// <summary> - /// The parameter of the callback parameter we tack onto the return_to URL - /// to store the replay-detection nonce. - /// </summary> - internal const string NonceParameter = OpenIdUtilities.CustomParameterPrefix + "request_nonce"; - - /// <summary> - /// The context within which return_to nonces must be unique -- they all go into the same bucket. - /// </summary> - private const string ReturnToNonceContext = "https://localhost/dnoa/return_to_nonce"; - - /// <summary> - /// The length of the generated nonce's random part. - /// </summary> - private const int NonceByteLength = 128 / 8; // 128-bit nonce - - /// <summary> - /// The nonce store that will allow us to recall which nonces we've seen before. - /// </summary> - private INonceStore nonceStore; - - /// <summary> - /// The security settings at the RP. - /// </summary> - private RelyingPartySecuritySettings securitySettings; - - /// <summary> - /// Backing field for the <see cref="Channel"/> property. - /// </summary> - private Channel channel; - - /// <summary> - /// Initializes a new instance of the <see cref="ReturnToNonceBindingElement"/> class. - /// </summary> - /// <param name="nonceStore">The nonce store to use.</param> - /// <param name="securitySettings">The security settings of the RP.</param> - internal ReturnToNonceBindingElement(INonceStore nonceStore, RelyingPartySecuritySettings securitySettings) { - Contract.Requires<ArgumentNullException>(nonceStore != null); - Contract.Requires<ArgumentNullException>(securitySettings != null); - - this.nonceStore = nonceStore; - this.securitySettings = securitySettings; - } - - #region IChannelBindingElement Properties - - /// <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 { - return this.channel; - } - - set { - if (this.channel == value) { - return; - } - - this.channel = value; - } - } - - /// <summary> - /// Gets the protection offered (if any) by this binding element. - /// </summary> - public MessageProtections Protection { - get { return MessageProtections.ReplayProtection; } - } - - #endregion - - /// <summary> - /// Gets the maximum message age from the standard expiration binding element. - /// </summary> - private static TimeSpan MaximumMessageAge { - get { return StandardExpirationBindingElement.MaximumMessageAge; } - } - - #region IChannelBindingElement Methods - - /// <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 MessageProtections? ProcessOutgoingMessage(IProtocolMessage message) { - // We only add a nonce to some auth requests. - SignedResponseRequest request = message as SignedResponseRequest; - if (this.UseRequestNonce(request)) { - request.AddReturnToArguments(NonceParameter, CustomNonce.NewNonce().Serialize()); - request.SignReturnTo = true; // a nonce without a signature is completely pointless - - return MessageProtections.ReplayProtection; - } - - 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 MessageProtections? ProcessIncomingMessage(IProtocolMessage message) { - IndirectSignedResponse response = message as IndirectSignedResponse; - if (this.UseRequestNonce(response)) { - if (!response.ReturnToParametersSignatureValidated) { - Logger.OpenId.Error("Incoming message is expected to have a nonce, but the return_to parameter is not signed."); - } - - string nonceValue = response.GetReturnToArgument(NonceParameter); - ErrorUtilities.VerifyProtocol( - nonceValue != null && response.ReturnToParametersSignatureValidated, - this.securitySettings.RejectUnsolicitedAssertions ? OpenIdStrings.UnsolicitedAssertionsNotAllowed : OpenIdStrings.UnsolicitedAssertionsNotAllowedFrom1xOPs); - - CustomNonce nonce = CustomNonce.Deserialize(nonceValue); - DateTime expirationDate = nonce.CreationDateUtc + MaximumMessageAge; - if (expirationDate < DateTime.UtcNow) { - throw new ExpiredMessageException(expirationDate, message); - } - - IReplayProtectedProtocolMessage replayResponse = response; - if (!this.nonceStore.StoreNonce(ReturnToNonceContext, nonce.RandomPartAsString, nonce.CreationDateUtc)) { - Logger.OpenId.ErrorFormat("Replayed nonce detected ({0} {1}). Rejecting message.", replayResponse.Nonce, replayResponse.UtcCreationDate); - throw new ReplayedMessageException(message); - } - - return MessageProtections.ReplayProtection; - } - - return null; - } - - #endregion - - /// <summary> - /// Determines whether a request nonce should be applied the request - /// or should be expected in the response. - /// </summary> - /// <param name="message">The authentication request or the positive assertion response.</param> - /// <returns> - /// <c>true</c> if the message exchanged with an OpenID 1.x provider - /// or if unsolicited assertions should be rejected at the RP; otherwise <c>false</c>. - /// </returns> - private bool UseRequestNonce(IMessage message) { - return message != null && (this.securitySettings.RejectUnsolicitedAssertions || - (message.Version.Major < 2 && this.securitySettings.ProtectDownlevelReplayAttacks)); - } - - /// <summary> - /// A special DotNetOpenAuth-only nonce used by the RP when talking to 1.0 OPs in order - /// to protect against replay attacks. - /// </summary> - private class CustomNonce { - /// <summary> - /// The random bits generated for the nonce. - /// </summary> - private byte[] randomPart; - - /// <summary> - /// Initializes a new instance of the <see cref="CustomNonce"/> class. - /// </summary> - /// <param name="creationDate">The creation date of the nonce.</param> - /// <param name="randomPart">The random bits that help make the nonce unique.</param> - private CustomNonce(DateTime creationDate, byte[] randomPart) { - this.CreationDateUtc = creationDate; - this.randomPart = randomPart; - } - - /// <summary> - /// Gets the creation date. - /// </summary> - internal DateTime CreationDateUtc { get; private set; } - - /// <summary> - /// Gets the random part of the nonce as a base64 encoded string. - /// </summary> - internal string RandomPartAsString { - get { return Convert.ToBase64String(this.randomPart); } - } - - /// <summary> - /// Creates a new nonce. - /// </summary> - /// <returns>The newly instantiated instance.</returns> - internal static CustomNonce NewNonce() { - return new CustomNonce(DateTime.UtcNow, MessagingUtilities.GetCryptoRandomData(NonceByteLength)); - } - - /// <summary> - /// Deserializes a nonce from the return_to parameter. - /// </summary> - /// <param name="value">The base64-encoded value of the nonce.</param> - /// <returns>The instantiated and initialized nonce.</returns> - internal static CustomNonce Deserialize(string value) { - Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(value)); - - byte[] nonce = MessagingUtilities.FromBase64WebSafeString(value); - Contract.Assume(nonce != null); - DateTime creationDateUtc = new DateTime(BitConverter.ToInt64(nonce, 0), DateTimeKind.Utc); - byte[] randomPart = new byte[NonceByteLength]; - Array.Copy(nonce, sizeof(long), randomPart, 0, NonceByteLength); - return new CustomNonce(creationDateUtc, randomPart); - } - - /// <summary> - /// Serializes the entire nonce for adding to the return_to URL. - /// </summary> - /// <returns>The base64-encoded string representing the nonce.</returns> - internal string Serialize() { - byte[] timestamp = BitConverter.GetBytes(this.CreationDateUtc.Ticks); - byte[] nonce = new byte[timestamp.Length + this.randomPart.Length]; - timestamp.CopyTo(nonce, 0); - this.randomPart.CopyTo(nonce, timestamp.Length); - string base64Nonce = MessagingUtilities.ConvertToBase64WebSafeString(nonce); - return base64Nonce; - } - } - } -} diff --git a/src/DotNetOpenAuth/OpenId/ChannelElements/ReturnToSignatureBindingElement.cs b/src/DotNetOpenAuth/OpenId/ChannelElements/ReturnToSignatureBindingElement.cs deleted file mode 100644 index 30358e0..0000000 --- a/src/DotNetOpenAuth/OpenId/ChannelElements/ReturnToSignatureBindingElement.cs +++ /dev/null @@ -1,211 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="ReturnToSignatureBindingElement.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OpenId.ChannelElements { - using System; - using System.Collections.Generic; - using System.Collections.Specialized; - using System.Diagnostics.Contracts; - using System.Security.Cryptography; - using System.Web; - using DotNetOpenAuth.Configuration; - using DotNetOpenAuth.Messaging; - using DotNetOpenAuth.Messaging.Bindings; - using DotNetOpenAuth.OpenId.Messages; - using DotNetOpenAuth.OpenId.RelyingParty; - - /// <summary> - /// This binding element signs a Relying Party's openid.return_to parameter - /// so that upon return, it can verify that it hasn't been tampered with. - /// </summary> - /// <remarks> - /// <para>Since Providers can send unsolicited assertions, not all openid.return_to - /// values will be signed. But those that are signed will be validated, and - /// any invalid or missing signatures will cause this library to not trust - /// the parameters in the return_to URL.</para> - /// <para>In the messaging stack, this binding element looks like an ordinary - /// transform-type of binding element rather than a protection element, - /// due to its required order in the channel stack and that it doesn't sign - /// anything except a particular message part.</para> - /// </remarks> - internal class ReturnToSignatureBindingElement : IChannelBindingElement { - /// <summary> - /// The name of the callback parameter we'll tack onto the return_to value - /// to store our signature on the return_to parameter. - /// </summary> - private const string ReturnToSignatureParameterName = OpenIdUtilities.CustomParameterPrefix + "return_to_sig"; - - /// <summary> - /// The name of the callback parameter we'll tack onto the return_to value - /// to store the handle of the association we use to sign the return_to parameter. - /// </summary> - private const string ReturnToSignatureHandleParameterName = OpenIdUtilities.CustomParameterPrefix + "return_to_sig_handle"; - - /// <summary> - /// The URI to use for private associations at this RP. - /// </summary> - private static readonly Uri SecretUri = new Uri("https://localhost/dnoa/secret"); - - /// <summary> - /// The key store used to generate the private signature on the return_to parameter. - /// </summary> - private ICryptoKeyStore cryptoKeyStore; - - /// <summary> - /// Initializes a new instance of the <see cref="ReturnToSignatureBindingElement"/> class. - /// </summary> - /// <param name="cryptoKeyStore">The crypto key store.</param> - internal ReturnToSignatureBindingElement(ICryptoKeyStore cryptoKeyStore) { - Contract.Requires<ArgumentNullException>(cryptoKeyStore != null); - - this.cryptoKeyStore = cryptoKeyStore; - } - - #region IChannelBindingElement Members - - /// <summary> - /// Gets or sets the channel that this binding element belongs to. - /// </summary> - /// <value></value> - /// <remarks> - /// This property is set by the channel when it is first constructed. - /// </remarks> - public Channel Channel { get; set; } - - /// <summary> - /// Gets the protection offered (if any) by this binding element. - /// </summary> - /// <value><see cref="MessageProtections.None"/></value> - /// <remarks> - /// No message protection is reported because this binding element - /// does not protect the entire message -- only a part. - /// </remarks> - public 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 MessageProtections? ProcessOutgoingMessage(IProtocolMessage message) { - SignedResponseRequest request = message as SignedResponseRequest; - if (request != null && request.ReturnTo != null && request.SignReturnTo) { - var cryptoKeyPair = this.cryptoKeyStore.GetCurrentKey(SecretUri.AbsoluteUri, DotNetOpenAuthSection.Configuration.OpenId.MaxAuthenticationTime); - request.AddReturnToArguments(ReturnToSignatureHandleParameterName, cryptoKeyPair.Key); - string signature = Convert.ToBase64String(this.GetReturnToSignature(request.ReturnTo, cryptoKeyPair.Value)); - request.AddReturnToArguments(ReturnToSignatureParameterName, signature); - - // We return none because we are not signing the entire message (only a part). - return MessageProtections.None; - } - - 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 MessageProtections? ProcessIncomingMessage(IProtocolMessage message) { - IndirectSignedResponse response = message as IndirectSignedResponse; - - if (response != null) { - // We can't use response.GetReturnToArgument(string) because that relies - // on us already having validated this signature. - NameValueCollection returnToParameters = HttpUtility.ParseQueryString(response.ReturnTo.Query); - - // Only check the return_to signature if one is present. - if (returnToParameters[ReturnToSignatureHandleParameterName] != null) { - // Set the safety flag showing whether the return_to url had a valid signature. - byte[] expectedBytes = this.GetReturnToSignature(response.ReturnTo); - string actual = returnToParameters[ReturnToSignatureParameterName]; - actual = OpenIdUtilities.FixDoublyUriDecodedBase64String(actual); - byte[] actualBytes = Convert.FromBase64String(actual); - response.ReturnToParametersSignatureValidated = MessagingUtilities.AreEquivalentConstantTime(actualBytes, expectedBytes); - if (!response.ReturnToParametersSignatureValidated) { - Logger.Bindings.WarnFormat("The return_to signature failed verification."); - } - - return MessageProtections.None; - } - } - - return null; - } - - #endregion - - /// <summary> - /// Gets the return to signature. - /// </summary> - /// <param name="returnTo">The return to.</param> - /// <param name="cryptoKey">The crypto key.</param> - /// <returns> - /// The generated signature. - /// </returns> - /// <remarks> - /// Only the parameters in the return_to URI are signed, rather than the base URI - /// itself, in order that OPs that might change the return_to's implicit port :80 part - /// or other minor changes do not invalidate the signature. - /// </remarks> - private byte[] GetReturnToSignature(Uri returnTo, CryptoKey cryptoKey = null) { - Contract.Requires<ArgumentNullException>(returnTo != null); - - // Assemble the dictionary to sign, taking care to remove the signature itself - // in order to accurately reproduce the original signature (which of course didn't include - // the signature). - // Also we need to sort the dictionary's keys so that we sign in the same order as we did - // the last time. - var returnToParameters = HttpUtility.ParseQueryString(returnTo.Query); - returnToParameters.Remove(ReturnToSignatureParameterName); - var sortedReturnToParameters = new SortedDictionary<string, string>(StringComparer.OrdinalIgnoreCase); - foreach (string key in returnToParameters) { - sortedReturnToParameters.Add(key, returnToParameters[key]); - } - - Logger.Bindings.DebugFormat("ReturnTo signed data: {0}{1}", Environment.NewLine, sortedReturnToParameters.ToStringDeferred()); - - // Sign the parameters. - byte[] bytesToSign = KeyValueFormEncoding.GetBytes(sortedReturnToParameters); - byte[] signature; - try { - if (cryptoKey == null) { - cryptoKey = this.cryptoKeyStore.GetKey(SecretUri.AbsoluteUri, returnToParameters[ReturnToSignatureHandleParameterName]); - } - - using (var signer = new HMACSHA256(cryptoKey.Key)) { - signature = signer.ComputeHash(bytesToSign); - } - } catch (ProtocolException ex) { - throw ErrorUtilities.Wrap(ex, OpenIdStrings.MaximumAuthenticationTimeExpired); - } - - return signature; - } - } -} diff --git a/src/DotNetOpenAuth/OpenId/ChannelElements/SigningBindingElement.cs b/src/DotNetOpenAuth/OpenId/ChannelElements/SigningBindingElement.cs deleted file mode 100644 index e301a3e..0000000 --- a/src/DotNetOpenAuth/OpenId/ChannelElements/SigningBindingElement.cs +++ /dev/null @@ -1,410 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="SigningBindingElement.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OpenId.ChannelElements { - using System; - using System.Collections.Generic; - using System.Diagnostics; - using System.Diagnostics.Contracts; - using System.Globalization; - using System.Linq; - using System.Net.Security; - using System.Web; - using DotNetOpenAuth.Loggers; - using DotNetOpenAuth.Messaging; - using DotNetOpenAuth.Messaging.Bindings; - using DotNetOpenAuth.Messaging.Reflection; - using DotNetOpenAuth.OpenId.Messages; - using DotNetOpenAuth.OpenId.Provider; - using DotNetOpenAuth.OpenId.RelyingParty; - - /// <summary> - /// Signs and verifies authentication assertions. - /// </summary> - internal class SigningBindingElement : IChannelBindingElement { - /// <summary> - /// The association store used by Relying Parties to look up the secrets needed for signing. - /// </summary> - private readonly IRelyingPartyAssociationStore rpAssociations; - - /// <summary> - /// The association store used by Providers to look up the secrets needed for signing. - /// </summary> - private readonly IProviderAssociationStore opAssociations; - - /// <summary> - /// The security settings at the Provider. - /// Only defined when this element is instantiated to service a Provider. - /// </summary> - private readonly ProviderSecuritySettings opSecuritySettings; - - /// <summary> - /// Initializes a new instance of the SigningBindingElement class for use by a Relying Party. - /// </summary> - /// <param name="associationStore">The association store used to look up the secrets needed for signing. May be null for dumb Relying Parties.</param> - internal SigningBindingElement(IRelyingPartyAssociationStore associationStore) { - this.rpAssociations = associationStore; - } - - /// <summary> - /// Initializes a new instance of the SigningBindingElement class for use by a Provider. - /// </summary> - /// <param name="associationStore">The association store used to look up the secrets needed for signing.</param> - /// <param name="securitySettings">The security settings.</param> - internal SigningBindingElement(IProviderAssociationStore associationStore, ProviderSecuritySettings securitySettings) { - Contract.Requires<ArgumentNullException>(associationStore != null); - Contract.Requires<ArgumentNullException>(securitySettings != null); - - this.opAssociations = associationStore; - this.opSecuritySettings = securitySettings; - } - - #region IChannelBindingElement Properties - - /// <summary> - /// Gets the protection offered (if any) by this binding element. - /// </summary> - /// <value><see cref="MessageProtections.TamperProtection"/></value> - public MessageProtections Protection { - get { return MessageProtections.TamperProtection; } - } - - /// <summary> - /// Gets or sets the channel that this binding element belongs to. - /// </summary> - public Channel Channel { get; set; } - - #endregion - - /// <summary> - /// Gets a value indicating whether this binding element is on a Provider channel. - /// </summary> - private bool IsOnProvider { - get { return this.opSecuritySettings != null; } - } - - #region IChannelBindingElement Methods - - /// <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> - public MessageProtections? ProcessOutgoingMessage(IProtocolMessage message) { - var signedMessage = message as ITamperResistantOpenIdMessage; - if (signedMessage != null) { - Logger.Bindings.DebugFormat("Signing {0} message.", message.GetType().Name); - Association association = this.GetAssociation(signedMessage); - signedMessage.AssociationHandle = association.Handle; - signedMessage.SignedParameterOrder = this.GetSignedParameterOrder(signedMessage); - signedMessage.Signature = this.GetSignature(signedMessage, association); - return MessageProtections.TamperProtection; - } - - 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> - public MessageProtections? ProcessIncomingMessage(IProtocolMessage message) { - var signedMessage = message as ITamperResistantOpenIdMessage; - if (signedMessage != null) { - Logger.Bindings.DebugFormat("Verifying incoming {0} message signature of: {1}", message.GetType().Name, signedMessage.Signature); - MessageProtections protectionsApplied = MessageProtections.TamperProtection; - - this.EnsureParametersRequiringSignatureAreSigned(signedMessage); - - Association association = this.GetSpecificAssociation(signedMessage); - if (association != null) { - string signature = this.GetSignature(signedMessage, association); - if (!MessagingUtilities.EqualsConstantTime(signedMessage.Signature, signature)) { - Logger.Bindings.Error("Signature verification failed."); - throw new InvalidSignatureException(message); - } - } else { - ErrorUtilities.VerifyInternal(this.Channel != null, "Cannot verify private association signature because we don't have a channel."); - - // If we're on the Provider, then the RP sent us a check_auth with a signature - // we don't have an association for. (It may have expired, or it may be a faulty RP). - if (this.IsOnProvider) { - throw new InvalidSignatureException(message); - } - - // We did not recognize the association the provider used to sign the message. - // Ask the provider to check the signature then. - var indirectSignedResponse = (IndirectSignedResponse)signedMessage; - var checkSignatureRequest = new CheckAuthenticationRequest(indirectSignedResponse, this.Channel); - var checkSignatureResponse = this.Channel.Request<CheckAuthenticationResponse>(checkSignatureRequest); - if (!checkSignatureResponse.IsValid) { - Logger.Bindings.Error("Provider reports signature verification failed."); - throw new InvalidSignatureException(message); - } - - // If the OP confirms that a handle should be invalidated as well, do that. - if (!string.IsNullOrEmpty(checkSignatureResponse.InvalidateHandle)) { - if (this.rpAssociations != null) { - this.rpAssociations.RemoveAssociation(indirectSignedResponse.ProviderEndpoint, checkSignatureResponse.InvalidateHandle); - } - } - - // When we're in dumb mode we can't provide our own replay protection, - // but for OpenID 2.0 Providers we can rely on them providing it as part - // of signature verification. - if (message.Version.Major >= 2) { - protectionsApplied |= MessageProtections.ReplayProtection; - } - } - - return protectionsApplied; - } - - return null; - } - - #endregion - - /// <summary> - /// Determines whether the relying party sending an authentication request is - /// vulnerable to replay attacks. - /// </summary> - /// <param name="request">The request message from the Relying Party. Useful, but may be null for conservative estimate results.</param> - /// <param name="response">The response message to be signed.</param> - /// <returns> - /// <c>true</c> if the relying party is vulnerable; otherwise, <c>false</c>. - /// </returns> - private static bool IsRelyingPartyVulnerableToReplays(SignedResponseRequest request, IndirectSignedResponse response) { - Contract.Requires<ArgumentNullException>(response != null); - - // OpenID 2.0 includes replay protection as part of the protocol. - if (response.Version.Major >= 2) { - return false; - } - - // This library's RP may be on the remote end, and may be using 1.x merely because - // discovery on the Claimed Identifier suggested this was a 1.x OP. - // Since this library's RP has a built-in request_nonce parameter for replay - // protection, we'll allow for that. - var returnToArgs = HttpUtility.ParseQueryString(response.ReturnTo.Query); - if (!string.IsNullOrEmpty(returnToArgs[ReturnToNonceBindingElement.NonceParameter])) { - return false; - } - - // If the OP endpoint _AND_ RP return_to URL uses HTTPS then no one - // can steal and replay the positive assertion. - // We can only ascertain this if the request message was handed to us - // so we know what our own OP endpoint is. If we don't have a request - // message, then we'll default to assuming it's insecure. - if (request != null) { - if (request.Recipient.IsTransportSecure() && response.Recipient.IsTransportSecure()) { - return false; - } - } - - // Nothing left to protect against replays. RP is vulnerable. - return true; - } - - /// <summary> - /// Ensures that all message parameters that must be signed are in fact included - /// in the signature. - /// </summary> - /// <param name="signedMessage">The signed message.</param> - private void EnsureParametersRequiringSignatureAreSigned(ITamperResistantOpenIdMessage signedMessage) { - // Verify that the signed parameter order includes the mandated fields. - // We do this in such a way that derived classes that add mandated fields automatically - // get included in the list of checked parameters. - Protocol protocol = Protocol.Lookup(signedMessage.Version); - var partsRequiringProtection = from part in this.Channel.MessageDescriptions.Get(signedMessage).Mapping.Values - where part.RequiredProtection != ProtectionLevel.None - where part.IsRequired || part.IsNondefaultValueSet(signedMessage) - select part.Name; - ErrorUtilities.VerifyInternal(partsRequiringProtection.All(name => name.StartsWith(protocol.openid.Prefix, StringComparison.Ordinal)), "Signing only works when the parameters start with the 'openid.' prefix."); - string[] signedParts = signedMessage.SignedParameterOrder.Split(','); - var unsignedParts = from partName in partsRequiringProtection - where !signedParts.Contains(partName.Substring(protocol.openid.Prefix.Length)) - select partName; - ErrorUtilities.VerifyProtocol(!unsignedParts.Any(), OpenIdStrings.SignatureDoesNotIncludeMandatoryParts, string.Join(", ", unsignedParts.ToArray())); - } - - /// <summary> - /// Calculates the signature for a given message. - /// </summary> - /// <param name="signedMessage">The message to sign or verify.</param> - /// <param name="association">The association to use to sign the message.</param> - /// <returns>The calculated signature of the method.</returns> - private string GetSignature(ITamperResistantOpenIdMessage signedMessage, Association association) { - Contract.Requires<ArgumentNullException>(signedMessage != null); - Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(signedMessage.SignedParameterOrder)); - Contract.Requires<ArgumentNullException>(association != null); - - // Prepare the parts to sign, taking care to replace an openid.mode value - // of check_authentication with its original id_res so the signature matches. - MessageDictionary dictionary = this.Channel.MessageDescriptions.GetAccessor(signedMessage); - var parametersToSign = from name in signedMessage.SignedParameterOrder.Split(',') - let prefixedName = Protocol.V20.openid.Prefix + name - select new KeyValuePair<string, string>(name, dictionary[prefixedName]); - - byte[] dataToSign = KeyValueFormEncoding.GetBytes(parametersToSign); - string signature = Convert.ToBase64String(association.Sign(dataToSign)); - - if (Logger.Signatures.IsDebugEnabled) { - Logger.Signatures.DebugFormat( - "Signing these message parts: {0}{1}{0}Base64 representation of signed data: {2}{0}Signature: {3}", - Environment.NewLine, - parametersToSign.ToStringDeferred(), - Convert.ToBase64String(dataToSign), - signature); - } - - return signature; - } - - /// <summary> - /// Gets the value to use for the openid.signed parameter. - /// </summary> - /// <param name="signedMessage">The signable message.</param> - /// <returns> - /// A comma-delimited list of parameter names, omitting the 'openid.' prefix, that determines - /// the inclusion and order of message parts that will be signed. - /// </returns> - private string GetSignedParameterOrder(ITamperResistantOpenIdMessage signedMessage) { - Contract.Requires<InvalidOperationException>(this.Channel != null); - Contract.Requires<ArgumentNullException>(signedMessage != null); - - Protocol protocol = Protocol.Lookup(signedMessage.Version); - - MessageDescription description = this.Channel.MessageDescriptions.Get(signedMessage); - var signedParts = from part in description.Mapping.Values - where (part.RequiredProtection & System.Net.Security.ProtectionLevel.Sign) != 0 - && part.GetValue(signedMessage) != null - select part.Name; - string prefix = Protocol.V20.openid.Prefix; - ErrorUtilities.VerifyInternal(signedParts.All(name => name.StartsWith(prefix, StringComparison.Ordinal)), "All signed message parts must start with 'openid.'."); - - if (this.opSecuritySettings.SignOutgoingExtensions) { - // Tack on any ExtraData parameters that start with 'openid.'. - List<string> extraSignedParameters = new List<string>(signedMessage.ExtraData.Count); - foreach (string key in signedMessage.ExtraData.Keys) { - if (key.StartsWith(protocol.openid.Prefix, StringComparison.Ordinal)) { - extraSignedParameters.Add(key); - } else { - Logger.Signatures.DebugFormat("The extra parameter '{0}' will not be signed because it does not start with 'openid.'.", key); - } - } - signedParts = signedParts.Concat(extraSignedParameters); - } - - int skipLength = prefix.Length; - string signedFields = string.Join(",", signedParts.Select(name => name.Substring(skipLength)).ToArray()); - return signedFields; - } - - /// <summary> - /// Gets the association to use to sign or verify a message. - /// </summary> - /// <param name="signedMessage">The message to sign or verify.</param> - /// <returns>The association to use to sign or verify the message.</returns> - private Association GetAssociation(ITamperResistantOpenIdMessage signedMessage) { - Contract.Requires<ArgumentNullException>(signedMessage != null); - - if (this.IsOnProvider) { - // We're on a Provider to either sign (smart/dumb) or verify a dumb signature. - bool signing = string.IsNullOrEmpty(signedMessage.Signature); - - if (signing) { - // If the RP has no replay protection, coerce use of a private association - // instead of a shared one (if security settings indicate) - // to protect the authenticating user from replay attacks. - bool forcePrivateAssociation = this.opSecuritySettings.ProtectDownlevelReplayAttacks - && IsRelyingPartyVulnerableToReplays(null, (IndirectSignedResponse)signedMessage); - - if (forcePrivateAssociation) { - if (!string.IsNullOrEmpty(signedMessage.AssociationHandle)) { - Logger.Signatures.Info("An OpenID 1.x authentication request with a shared association handle will be responded to with a private association in order to provide OP-side replay protection."); - } - - return this.GetDumbAssociationForSigning(); - } else { - return this.GetSpecificAssociation(signedMessage) ?? this.GetDumbAssociationForSigning(); - } - } else { - return this.GetSpecificAssociation(signedMessage); - } - } else { - // We're on a Relying Party verifying a signature. - IDirectedProtocolMessage directedMessage = (IDirectedProtocolMessage)signedMessage; - if (this.rpAssociations != null) { - return this.rpAssociations.GetAssociation(directedMessage.Recipient, signedMessage.AssociationHandle); - } else { - return null; - } - } - } - - /// <summary> - /// Gets a specific association referenced in a given message's association handle. - /// </summary> - /// <param name="signedMessage">The signed message whose association handle should be used to lookup the association to return.</param> - /// <returns>The referenced association; or <c>null</c> if such an association cannot be found.</returns> - /// <remarks> - /// If the association handle set in the message does not match any valid association, - /// the association handle property is cleared, and the - /// <see cref="ITamperResistantOpenIdMessage.InvalidateHandle"/> property is set to the - /// handle that could not be found. - /// </remarks> - private Association GetSpecificAssociation(ITamperResistantOpenIdMessage signedMessage) { - Association association = null; - - if (!string.IsNullOrEmpty(signedMessage.AssociationHandle)) { - IndirectSignedResponse indirectSignedMessage = signedMessage as IndirectSignedResponse; - if (this.IsOnProvider) { - // Since we have an association handle, we're either signing with a smart association, - // or verifying a dumb one. - bool signing = string.IsNullOrEmpty(signedMessage.Signature); - bool isPrivateAssociation = !signing; - association = this.opAssociations.Deserialize(signedMessage, isPrivateAssociation, signedMessage.AssociationHandle); - if (association == null) { - // There was no valid association with the requested handle. - // Let's tell the RP to forget about that association. - signedMessage.InvalidateHandle = signedMessage.AssociationHandle; - signedMessage.AssociationHandle = null; - } - } else if (this.rpAssociations != null) { // if on a smart RP - Uri providerEndpoint = indirectSignedMessage.ProviderEndpoint; - association = this.rpAssociations.GetAssociation(providerEndpoint, signedMessage.AssociationHandle); - } - } - - return association; - } - - /// <summary> - /// Gets a private Provider association used for signing messages in "dumb" mode. - /// </summary> - /// <returns>An existing or newly created association.</returns> - private Association GetDumbAssociationForSigning() { - // If no assoc_handle was given or it was invalid, the only thing - // left to do is sign a message using a 'dumb' mode association. - Protocol protocol = Protocol.Default; - Association association = HmacShaAssociation.Create(protocol, protocol.Args.SignatureAlgorithm.HMAC_SHA256, AssociationRelyingPartyType.Dumb, this.opAssociations, this.opSecuritySettings); - return association; - } - } -} diff --git a/src/DotNetOpenAuth/OpenId/DiffieHellmanUtilities.cs b/src/DotNetOpenAuth/OpenId/DiffieHellmanUtilities.cs deleted file mode 100644 index 249f1f3..0000000 --- a/src/DotNetOpenAuth/OpenId/DiffieHellmanUtilities.cs +++ /dev/null @@ -1,149 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="DiffieHellmanUtilities.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OpenId { - using System; - using System.Collections.Generic; - using System.Diagnostics.Contracts; - using System.Globalization; - using System.Linq; - using System.Security.Cryptography; - using System.Text; - using DotNetOpenAuth.Messaging; - using Org.Mentalis.Security.Cryptography; - - /// <summary> - /// Diffie-Hellman encryption methods used by both the relying party and provider. - /// </summary> - internal class DiffieHellmanUtilities { - /// <summary> - /// An array of known Diffie Hellman sessions, sorted by decreasing hash size. - /// </summary> - private static DHSha[] diffieHellmanSessionTypes = new List<DHSha> { - new DHSha(new SHA512Managed(), protocol => protocol.Args.SessionType.DH_SHA512), - new DHSha(new SHA384Managed(), protocol => protocol.Args.SessionType.DH_SHA384), - new DHSha(new SHA256Managed(), protocol => protocol.Args.SessionType.DH_SHA256), - new DHSha(new SHA1Managed(), protocol => protocol.Args.SessionType.DH_SHA1), - } .ToArray(); - - /// <summary> - /// Finds the hashing algorithm to use given an openid.session_type value. - /// </summary> - /// <param name="protocol">The protocol version of the message that named the session_type to be used.</param> - /// <param name="sessionType">The value of the openid.session_type parameter.</param> - /// <returns>The hashing algorithm to use.</returns> - /// <exception cref="ProtocolException">Thrown if no match could be found for the given <paramref name="sessionType"/>.</exception> - public static HashAlgorithm Lookup(Protocol protocol, string sessionType) { - Contract.Requires<ArgumentNullException>(protocol != null); - Contract.Requires<ArgumentNullException>(sessionType != null); - - // We COULD use just First instead of FirstOrDefault, but we want to throw ProtocolException instead of InvalidOperationException. - DHSha match = diffieHellmanSessionTypes.FirstOrDefault(dhsha => String.Equals(dhsha.GetName(protocol), sessionType, StringComparison.Ordinal)); - ErrorUtilities.VerifyProtocol(match != null, OpenIdStrings.NoSessionTypeFound, sessionType, protocol.Version); - return match.Algorithm; - } - - /// <summary> - /// Looks up the value to be used for the openid.session_type parameter. - /// </summary> - /// <param name="protocol">The protocol version that is to be used.</param> - /// <param name="hashSizeInBits">The hash size (in bits) that the DH session must have.</param> - /// <returns>The value to be used for the openid.session_type parameter, or null if no match was found.</returns> - internal static string GetNameForSize(Protocol protocol, int hashSizeInBits) { - Contract.Requires<ArgumentNullException>(protocol != null); - DHSha match = diffieHellmanSessionTypes.FirstOrDefault(dhsha => dhsha.Algorithm.HashSize == hashSizeInBits); - return match != null ? match.GetName(protocol) : null; - } - - /// <summary> - /// Encrypts/decrypts a shared secret. - /// </summary> - /// <param name="hasher">The hashing algorithm that is agreed by both parties to use as part of the secret exchange.</param> - /// <param name="dh"> - /// If the secret is being encrypted, this is the new Diffie Hellman object to use. - /// If the secret is being decrypted, this must be the same Diffie Hellman object used to send the original request message. - /// </param> - /// <param name="remotePublicKey">The public key of the remote party.</param> - /// <param name="plainOrEncryptedSecret">The secret to encode, or the encoded secret. Whichever one is given will generate the opposite in the return value.</param> - /// <returns> - /// The encrypted version of the secret if the secret itself was given in <paramref name="remotePublicKey"/>. - /// The secret itself if the encrypted version of the secret was given in <paramref name="remotePublicKey"/>. - /// </returns> - internal static byte[] SHAHashXorSecret(HashAlgorithm hasher, DiffieHellman dh, byte[] remotePublicKey, byte[] plainOrEncryptedSecret) { - Contract.Requires<ArgumentNullException>(hasher != null); - Contract.Requires<ArgumentNullException>(dh != null); - Contract.Requires<ArgumentNullException>(remotePublicKey != null); - Contract.Requires<ArgumentNullException>(plainOrEncryptedSecret != null); - - byte[] sharedBlock = dh.DecryptKeyExchange(remotePublicKey); - byte[] sharedBlockHash = hasher.ComputeHash(EnsurePositive(sharedBlock)); - ErrorUtilities.VerifyProtocol(sharedBlockHash.Length == plainOrEncryptedSecret.Length, OpenIdStrings.AssociationSecretHashLengthMismatch, plainOrEncryptedSecret.Length, sharedBlockHash.Length); - - byte[] secret = new byte[plainOrEncryptedSecret.Length]; - for (int i = 0; i < plainOrEncryptedSecret.Length; i++) { - secret[i] = (byte)(plainOrEncryptedSecret[i] ^ sharedBlockHash[i]); - } - return secret; - } - - /// <summary> - /// Ensures that the big integer represented by a given series of bytes - /// is a positive integer. - /// </summary> - /// <param name="inputBytes">The bytes that make up the big integer.</param> - /// <returns> - /// A byte array (possibly new if a change was required) whose - /// integer is guaranteed to be positive. - /// </returns> - /// <remarks> - /// This is to be consistent with OpenID spec section 4.2. - /// </remarks> - internal static byte[] EnsurePositive(byte[] inputBytes) { - Contract.Requires<ArgumentNullException>(inputBytes != null); - if (inputBytes.Length == 0) { - throw new ArgumentException(MessagingStrings.UnexpectedEmptyArray, "inputBytes"); - } - - int i = (int)inputBytes[0]; - if (i > 127) { - byte[] nowPositive = new byte[inputBytes.Length + 1]; - nowPositive[0] = 0; - inputBytes.CopyTo(nowPositive, 1); - return nowPositive; - } - - return inputBytes; - } - - /// <summary> - /// Provides access to a Diffie-Hellman session algorithm and its name. - /// </summary> - private class DHSha { - /// <summary> - /// Initializes a new instance of the <see cref="DHSha"/> class. - /// </summary> - /// <param name="algorithm">The hashing algorithm used in this particular Diffie-Hellman session type.</param> - /// <param name="getName">A function that will return the value of the openid.session_type parameter for a given version of OpenID.</param> - public DHSha(HashAlgorithm algorithm, Func<Protocol, string> getName) { - Contract.Requires<ArgumentNullException>(algorithm != null); - Contract.Requires<ArgumentNullException>(getName != null); - - this.GetName = getName; - this.Algorithm = algorithm; - } - - /// <summary> - /// Gets the function that will return the value of the openid.session_type parameter for a given version of OpenID. - /// </summary> - internal Func<Protocol, string> GetName { get; private set; } - - /// <summary> - /// Gets the hashing algorithm used in this particular Diffie-Hellman session type - /// </summary> - internal HashAlgorithm Algorithm { get; private set; } - } - } -} diff --git a/src/DotNetOpenAuth/OpenId/Extensions/AliasManager.cs b/src/DotNetOpenAuth/OpenId/Extensions/AliasManager.cs deleted file mode 100644 index 0a84266..0000000 --- a/src/DotNetOpenAuth/OpenId/Extensions/AliasManager.cs +++ /dev/null @@ -1,187 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="AliasManager.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OpenId.Extensions { - using System; - using System.Collections.Generic; - using System.Diagnostics; - using System.Diagnostics.Contracts; - using System.Globalization; - using System.Text; - using DotNetOpenAuth.Messaging; - - /// <summary> - /// Manages a fast, two-way mapping between type URIs and their aliases. - /// </summary> - internal class AliasManager { - /// <summary> - /// The format of auto-generated aliases. - /// </summary> - private const string AliasFormat = "alias{0}"; - - /// <summary> - /// Tracks extension Type URIs and aliases assigned to them. - /// </summary> - private Dictionary<string, string> typeUriToAliasMap = new Dictionary<string, string>(); - - /// <summary> - /// Tracks extension aliases and Type URIs assigned to them. - /// </summary> - private Dictionary<string, string> aliasToTypeUriMap = new Dictionary<string, string>(); - - /// <summary> - /// Gets the aliases that have been set. - /// </summary> - public IEnumerable<string> Aliases { - get { return this.aliasToTypeUriMap.Keys; } - } - - /// <summary> - /// Gets an alias assigned for a given Type URI. A new alias is assigned if necessary. - /// </summary> - /// <param name="typeUri">The type URI.</param> - /// <returns>The alias assigned to this type URI. Never null.</returns> - public string GetAlias(string typeUri) { - Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(typeUri)); - string alias; - return this.typeUriToAliasMap.TryGetValue(typeUri, out alias) ? alias : this.AssignNewAlias(typeUri); - } - - /// <summary> - /// Sets an alias and the value that will be returned by <see cref="ResolveAlias"/>. - /// </summary> - /// <param name="alias">The alias.</param> - /// <param name="typeUri">The type URI.</param> - public void SetAlias(string alias, string typeUri) { - Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(alias)); - Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(typeUri)); - this.aliasToTypeUriMap.Add(alias, typeUri); - this.typeUriToAliasMap.Add(typeUri, alias); - } - - /// <summary> - /// Takes a sequence of type URIs and assigns aliases for all of them. - /// </summary> - /// <param name="typeUris">The type URIs to create aliases for.</param> - /// <param name="preferredTypeUriToAliases">An optional dictionary of URI/alias pairs that suggest preferred aliases to use if available for certain type URIs.</param> - public void AssignAliases(IEnumerable<string> typeUris, IDictionary<string, string> preferredTypeUriToAliases) { - Contract.Requires<ArgumentNullException>(typeUris != null); - - // First go through the actually used type URIs and see which ones have matching preferred aliases. - if (preferredTypeUriToAliases != null) { - foreach (string typeUri in typeUris) { - if (this.typeUriToAliasMap.ContainsKey(typeUri)) { - // this Type URI is already mapped to an alias. - continue; - } - - string preferredAlias; - if (preferredTypeUriToAliases.TryGetValue(typeUri, out preferredAlias) && !this.IsAliasUsed(preferredAlias)) { - this.SetAlias(preferredAlias, typeUri); - } - } - } - - // Now go through the whole list again and assign whatever is left now that the preferred ones - // have gotten their picks where available. - foreach (string typeUri in typeUris) { - if (this.typeUriToAliasMap.ContainsKey(typeUri)) { - // this Type URI is already mapped to an alias. - continue; - } - - this.AssignNewAlias(typeUri); - } - } - - /// <summary> - /// Sets up aliases for any Type URIs in a dictionary that do not yet have aliases defined, - /// and where the given preferred alias is still available. - /// </summary> - /// <param name="preferredTypeUriToAliases">A dictionary of type URI keys and alias values.</param> - public void SetPreferredAliasesWhereNotSet(IDictionary<string, string> preferredTypeUriToAliases) { - Contract.Requires<ArgumentNullException>(preferredTypeUriToAliases != null); - - foreach (var pair in preferredTypeUriToAliases) { - if (this.typeUriToAliasMap.ContainsKey(pair.Key)) { - // type URI is already mapped - continue; - } - - if (this.aliasToTypeUriMap.ContainsKey(pair.Value)) { - // alias is already mapped - continue; - } - - // The type URI and alias are as yet unset, so go ahead and assign them. - this.SetAlias(pair.Value, pair.Key); - } - } - - /// <summary> - /// Gets the Type Uri encoded by a given alias. - /// </summary> - /// <param name="alias">The alias.</param> - /// <returns>The Type URI.</returns> - /// <exception cref="ArgumentOutOfRangeException">Thrown if the given alias does not have a matching TypeURI.</exception> - public string ResolveAlias(string alias) { - Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(alias)); - string typeUri = this.TryResolveAlias(alias); - if (typeUri == null) { - throw new ArgumentOutOfRangeException("alias"); - } - return typeUri; - } - - /// <summary> - /// Gets the Type Uri encoded by a given alias. - /// </summary> - /// <param name="alias">The alias.</param> - /// <returns>The Type URI for the given alias, or null if none for that alias exist.</returns> - public string TryResolveAlias(string alias) { - Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(alias)); - string typeUri = null; - this.aliasToTypeUriMap.TryGetValue(alias, out typeUri); - return typeUri; - } - - /// <summary> - /// Returns a value indicating whether an alias has already been assigned to a type URI. - /// </summary> - /// <param name="alias">The alias in question.</param> - /// <returns>True if the alias has already been assigned. False otherwise.</returns> - public bool IsAliasUsed(string alias) { - Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(alias)); - return this.aliasToTypeUriMap.ContainsKey(alias); - } - - /// <summary> - /// Determines whether a given TypeURI has an associated alias assigned to it. - /// </summary> - /// <param name="typeUri">The type URI.</param> - /// <returns> - /// <c>true</c> if the given type URI already has an alias assigned; <c>false</c> otherwise. - /// </returns> - public bool IsAliasAssignedTo(string typeUri) { - Contract.Requires<ArgumentNullException>(typeUri != null); - return this.typeUriToAliasMap.ContainsKey(typeUri); - } - - /// <summary> - /// Assigns a new alias to a given Type URI. - /// </summary> - /// <param name="typeUri">The type URI to assign a new alias to.</param> - /// <returns>The newly generated alias.</returns> - private string AssignNewAlias(string typeUri) { - Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(typeUri)); - ErrorUtilities.VerifyInternal(!this.typeUriToAliasMap.ContainsKey(typeUri), "Oops! This type URI already has an alias!"); - string alias = string.Format(CultureInfo.InvariantCulture, AliasFormat, this.typeUriToAliasMap.Count + 1); - this.typeUriToAliasMap.Add(typeUri, alias); - this.aliasToTypeUriMap.Add(alias, typeUri); - return alias; - } - } -} diff --git a/src/DotNetOpenAuth/OpenId/Extensions/AttributeExchange/AXUtilities.cs b/src/DotNetOpenAuth/OpenId/Extensions/AttributeExchange/AXUtilities.cs deleted file mode 100644 index 2b947f7..0000000 --- a/src/DotNetOpenAuth/OpenId/Extensions/AttributeExchange/AXUtilities.cs +++ /dev/null @@ -1,144 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="AXUtilities.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OpenId.Extensions.AttributeExchange { - using System; - using System.Collections.Generic; - using System.Diagnostics.Contracts; - using System.Globalization; - using DotNetOpenAuth.Messaging; - - /// <summary> - /// Helper methods shared by multiple messages in the Attribute Exchange extension. - /// </summary> - public static class AXUtilities { - /// <summary> - /// Adds a request for an attribute considering it 'required'. - /// </summary> - /// <param name="collection">The attribute request collection.</param> - /// <param name="typeUri">The type URI of the required attribute.</param> - public static void AddRequired(this ICollection<AttributeRequest> collection, string typeUri) { - Contract.Requires<ArgumentNullException>(collection != null); - collection.Add(new AttributeRequest(typeUri, true)); - } - - /// <summary> - /// Adds a request for an attribute without considering it 'required'. - /// </summary> - /// <param name="collection">The attribute request collection.</param> - /// <param name="typeUri">The type URI of the requested attribute.</param> - public static void AddOptional(this ICollection<AttributeRequest> collection, string typeUri) { - Contract.Requires<ArgumentNullException>(collection != null); - collection.Add(new AttributeRequest(typeUri, false)); - } - - /// <summary> - /// Adds a given attribute with one or more values to the request for storage. - /// Applicable to Relying Parties only. - /// </summary> - /// <param name="collection">The collection of <see cref="AttributeValues"/> to add to.</param> - /// <param name="typeUri">The type URI of the attribute.</param> - /// <param name="values">The attribute values.</param> - public static void Add(this ICollection<AttributeValues> collection, string typeUri, params string[] values) { - Contract.Requires<ArgumentNullException>(collection != null); - collection.Add(new AttributeValues(typeUri, values)); - } - - /// <summary> - /// Serializes a set of attribute values to a dictionary of fields to send in the message. - /// </summary> - /// <param name="fields">The dictionary to fill with serialized attributes.</param> - /// <param name="attributes">The attributes.</param> - internal static void SerializeAttributes(IDictionary<string, string> fields, IEnumerable<AttributeValues> attributes) { - Contract.Requires<ArgumentNullException>(fields != null); - Contract.Requires<ArgumentNullException>(attributes != null); - - AliasManager aliasManager = new AliasManager(); - foreach (var att in attributes) { - string alias = aliasManager.GetAlias(att.TypeUri); - fields.Add("type." + alias, att.TypeUri); - if (att.Values == null) { - continue; - } - if (att.Values.Count != 1) { - fields.Add("count." + alias, att.Values.Count.ToString(CultureInfo.InvariantCulture)); - for (int i = 0; i < att.Values.Count; i++) { - fields.Add(string.Format(CultureInfo.InvariantCulture, "value.{0}.{1}", alias, i + 1), att.Values[i]); - } - } else { - fields.Add("value." + alias, att.Values[0]); - } - } - } - - /// <summary> - /// Deserializes attribute values from an incoming set of message data. - /// </summary> - /// <param name="fields">The data coming in with the message.</param> - /// <returns>The attribute values found in the message.</returns> - internal static IEnumerable<AttributeValues> DeserializeAttributes(IDictionary<string, string> fields) { - AliasManager aliasManager = ParseAliases(fields); - foreach (string alias in aliasManager.Aliases) { - AttributeValues att = new AttributeValues(aliasManager.ResolveAlias(alias)); - int count = 1; - bool countSent = false; - string countString; - if (fields.TryGetValue("count." + alias, out countString)) { - if (!int.TryParse(countString, out count) || count < 0) { - Logger.OpenId.ErrorFormat("Failed to parse count.{0} value to a non-negative integer.", alias); - continue; - } - countSent = true; - } - if (countSent) { - for (int i = 1; i <= count; i++) { - string value; - if (fields.TryGetValue(string.Format(CultureInfo.InvariantCulture, "value.{0}.{1}", alias, i), out value)) { - att.Values.Add(value); - } else { - Logger.OpenId.ErrorFormat("Missing value for attribute '{0}'.", att.TypeUri); - continue; - } - } - } else { - string value; - if (fields.TryGetValue("value." + alias, out value)) { - att.Values.Add(value); - } else { - Logger.OpenId.ErrorFormat("Missing value for attribute '{0}'.", att.TypeUri); - continue; - } - } - yield return att; - } - } - - /// <summary> - /// Reads through the attributes included in the response to discover - /// the alias-TypeURI relationships. - /// </summary> - /// <param name="fields">The data included in the extension message.</param> - /// <returns>The alias manager that provides lookup between aliases and type URIs.</returns> - private static AliasManager ParseAliases(IDictionary<string, string> fields) { - Contract.Requires<ArgumentNullException>(fields != null); - - AliasManager aliasManager = new AliasManager(); - const string TypePrefix = "type."; - foreach (var pair in fields) { - if (!pair.Key.StartsWith(TypePrefix, StringComparison.Ordinal)) { - continue; - } - string alias = pair.Key.Substring(TypePrefix.Length); - if (alias.IndexOfAny(FetchRequest.IllegalAliasCharacters) >= 0) { - Logger.OpenId.ErrorFormat("Illegal characters in alias name '{0}'.", alias); - continue; - } - aliasManager.SetAlias(alias, pair.Value); - } - return aliasManager; - } - } -} diff --git a/src/DotNetOpenAuth/OpenId/Extensions/AttributeExchange/AttributeRequest.cs b/src/DotNetOpenAuth/OpenId/Extensions/AttributeExchange/AttributeRequest.cs deleted file mode 100644 index 2dc9c69..0000000 --- a/src/DotNetOpenAuth/OpenId/Extensions/AttributeExchange/AttributeRequest.cs +++ /dev/null @@ -1,156 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="AttributeRequest.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OpenId.Extensions.AttributeExchange { - using System; - using System.Diagnostics; - using System.Diagnostics.Contracts; - using DotNetOpenAuth.Messaging; - - /// <summary> - /// An individual attribute to be requested of the OpenID Provider using - /// the Attribute Exchange extension. - /// </summary> - [Serializable] - [DebuggerDisplay("{TypeUri} (required: {IsRequired}) ({Count})")] - public class AttributeRequest { - /// <summary> - /// Backing field for the <see cref="Count"/> property. - /// </summary> - [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private int count = 1; - - /// <summary> - /// Initializes a new instance of the <see cref="AttributeRequest"/> class - /// with <see cref="IsRequired"/> = false, <see cref="Count"/> = 1. - /// </summary> - public AttributeRequest() { - } - - /// <summary> - /// Initializes a new instance of the <see cref="AttributeRequest"/> class - /// with <see cref="IsRequired"/> = false, <see cref="Count"/> = 1. - /// </summary> - /// <param name="typeUri">The unique TypeURI for that describes the attribute being sought.</param> - public AttributeRequest(string typeUri) { - Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(typeUri)); - this.TypeUri = typeUri; - } - - /// <summary> - /// Initializes a new instance of the <see cref="AttributeRequest"/> class - /// with <see cref="Count"/> = 1. - /// </summary> - /// <param name="typeUri">The unique TypeURI for that describes the attribute being sought.</param> - /// <param name="isRequired">A value indicating whether the Relying Party considers this attribute to be required for registration.</param> - public AttributeRequest(string typeUri, bool isRequired) - : this(typeUri) { - this.IsRequired = isRequired; - } - - /// <summary> - /// Initializes a new instance of the <see cref="AttributeRequest"/> class. - /// </summary> - /// <param name="typeUri">The unique TypeURI for that describes the attribute being sought.</param> - /// <param name="isRequired">A value indicating whether the Relying Party considers this attribute to be required for registration.</param> - /// <param name="count">The maximum number of values for this attribute the Relying Party is prepared to receive.</param> - public AttributeRequest(string typeUri, bool isRequired, int count) - : this(typeUri, isRequired) { - this.Count = count; - } - - /// <summary> - /// Gets or sets the URI uniquely identifying the attribute being requested. - /// </summary> - public string TypeUri { get; set; } - - /// <summary> - /// Gets or sets a value indicating whether the relying party considers this a required field. - /// Note that even if set to true, the Provider may not provide the value. - /// </summary> - public bool IsRequired { get; set; } - - /// <summary> - /// Gets or sets the maximum number of values for this attribute the - /// Relying Party wishes to receive from the OpenID Provider. - /// A value of int.MaxValue is considered infinity. - /// </summary> - public int Count { - get { - return this.count; - } - - set { - Contract.Requires<ArgumentOutOfRangeException>(value > 0); - this.count = value; - } - } - - /// <summary> - /// Used by a Provider to create a response to a request for an attribute's value(s) - /// using a given array of strings. - /// </summary> - /// <param name="values">The values for the requested attribute.</param> - /// <returns> - /// The newly created <see cref="AttributeValues"/> object that should be added to - /// the <see cref="FetchResponse"/> object. - /// </returns> - public AttributeValues Respond(params string[] values) { - Contract.Requires<ArgumentNullException>(values != null); - Contract.Requires<ArgumentException>(values.Length <= this.Count); - return new AttributeValues(this.TypeUri, values); - } - - /// <summary> - /// Determines whether the specified <see cref="T:System.Object"/> is equal to the current <see cref="T:System.Object"/>. - /// </summary> - /// <param name="obj">The <see cref="T:System.Object"/> to compare with the current <see cref="T:System.Object"/>.</param> - /// <returns> - /// true if the specified <see cref="T:System.Object"/> is equal to the current <see cref="T:System.Object"/>; otherwise, false. - /// </returns> - /// <exception cref="T:System.NullReferenceException"> - /// The <paramref name="obj"/> parameter is null. - /// </exception> - public override bool Equals(object obj) { - AttributeRequest other = obj as AttributeRequest; - if (other == null) { - return false; - } - - if (this.TypeUri != other.TypeUri) { - return false; - } - - if (this.Count != other.Count) { - return false; - } - - if (this.IsRequired != other.IsRequired) { - return false; - } - - return true; - } - - /// <summary> - /// Serves as a hash function for a particular type. - /// </summary> - /// <returns> - /// A hash code for the current <see cref="T:System.Object"/>. - /// </returns> - public override int GetHashCode() { - int hashCode = this.IsRequired ? 1 : 0; - unchecked { - hashCode += this.Count; - if (this.TypeUri != null) { - hashCode += this.TypeUri.GetHashCode(); - } - } - - return hashCode; - } - } -} diff --git a/src/DotNetOpenAuth/OpenId/Extensions/AttributeExchange/AttributeValues.cs b/src/DotNetOpenAuth/OpenId/Extensions/AttributeExchange/AttributeValues.cs deleted file mode 100644 index b2fc1fe..0000000 --- a/src/DotNetOpenAuth/OpenId/Extensions/AttributeExchange/AttributeValues.cs +++ /dev/null @@ -1,114 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="AttributeValues.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OpenId.Extensions.AttributeExchange { - using System; - using System.Collections.Generic; - using System.Diagnostics; - using System.Diagnostics.Contracts; - using DotNetOpenAuth.Messaging; - - /// <summary> - /// An individual attribute's value(s) as supplied by an OpenID Provider - /// in response to a prior request by an OpenID Relying Party as part of - /// a fetch request, or by a relying party as part of a store request. - /// </summary> - [Serializable] - [DebuggerDisplay("{TypeUri}")] - public class AttributeValues { - /// <summary> - /// Initializes a new instance of the <see cref="AttributeValues"/> class. - /// </summary> - /// <param name="typeUri">The TypeURI that uniquely identifies the attribute.</param> - /// <param name="values">The values for the attribute.</param> - public AttributeValues(string typeUri, params string[] values) { - Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(typeUri)); - - this.TypeUri = typeUri; - this.Values = (IList<string>)values ?? EmptyList<string>.Instance; - } - - /// <summary> - /// Initializes a new instance of the <see cref="AttributeValues"/> class. - /// </summary> - /// <remarks> - /// This is internal because web sites should be using the - /// <see cref="AttributeRequest.Respond"/> method to instantiate. - /// </remarks> - internal AttributeValues() { - this.Values = new List<string>(1); - } - - /// <summary> - /// Initializes a new instance of the <see cref="AttributeValues"/> class. - /// </summary> - /// <param name="typeUri">The TypeURI of the attribute whose values are being provided.</param> - internal AttributeValues(string typeUri) { - Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(typeUri)); - - this.TypeUri = typeUri; - this.Values = new List<string>(1); - } - - /// <summary> - /// Gets the URI uniquely identifying the attribute whose value is being supplied. - /// </summary> - public string TypeUri { get; internal set; } - - /// <summary> - /// Gets the values supplied by the Provider. - /// </summary> - public IList<string> Values { get; private set; } - - /// <summary> - /// Determines whether the specified <see cref="T:System.Object"/> is equal to the current <see cref="T:System.Object"/>. - /// </summary> - /// <param name="obj">The <see cref="T:System.Object"/> to compare with the current <see cref="T:System.Object"/>.</param> - /// <returns> - /// true if the specified <see cref="T:System.Object"/> is equal to the current <see cref="T:System.Object"/>; otherwise, false. - /// </returns> - /// <exception cref="T:System.NullReferenceException"> - /// The <paramref name="obj"/> parameter is null. - /// </exception> - public override bool Equals(object obj) { - AttributeValues other = obj as AttributeValues; - if (other == null) { - return false; - } - - if (this.TypeUri != other.TypeUri) { - return false; - } - - if (!MessagingUtilities.AreEquivalent<string>(this.Values, other.Values)) { - return false; - } - - return true; - } - - /// <summary> - /// Serves as a hash function for a particular type. - /// </summary> - /// <returns> - /// A hash code for the current <see cref="T:System.Object"/>. - /// </returns> - public override int GetHashCode() { - int hashCode = 0; - unchecked { - if (this.TypeUri != null) { - hashCode += this.TypeUri.GetHashCode(); - } - - foreach (string value in this.Values) { - hashCode += value.GetHashCode(); - } - } - - return hashCode; - } - } -} diff --git a/src/DotNetOpenAuth/OpenId/Extensions/ExtensionArgumentsManager.cs b/src/DotNetOpenAuth/OpenId/Extensions/ExtensionArgumentsManager.cs deleted file mode 100644 index 0a78df1..0000000 --- a/src/DotNetOpenAuth/OpenId/Extensions/ExtensionArgumentsManager.cs +++ /dev/null @@ -1,223 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="ExtensionArgumentsManager.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OpenId.Extensions { - using System; - using System.Collections.Generic; - using System.Diagnostics.Contracts; - using System.Globalization; - using System.Text; - using DotNetOpenAuth.Messaging; - - /// <summary> - /// Manages the processing and construction of OpenID extensions parts. - /// </summary> - internal class ExtensionArgumentsManager { - /// <summary> - /// This contains a set of aliases that we must be willing to implicitly - /// match to namespaces for backward compatibility with other OpenID libraries. - /// </summary> - private static readonly Dictionary<string, string> typeUriToAliasAffinity = new Dictionary<string, string> { - { Extensions.SimpleRegistration.Constants.sreg_ns, Extensions.SimpleRegistration.Constants.sreg_compatibility_alias }, - { Extensions.ProviderAuthenticationPolicy.Constants.TypeUri, Extensions.ProviderAuthenticationPolicy.Constants.CompatibilityAlias }, - }; - - /// <summary> - /// The version of OpenID that the message is using. - /// </summary> - private Protocol protocol; - - /// <summary> - /// Whether extensions are being read or written. - /// </summary> - private bool isReadMode; - - /// <summary> - /// The alias manager that will track Type URI to alias mappings. - /// </summary> - private AliasManager aliasManager = new AliasManager(); - - /// <summary> - /// A complex dictionary where the key is the Type URI of the extension, - /// and the value is another dictionary of the name/value args of the extension. - /// </summary> - private Dictionary<string, IDictionary<string, string>> extensions = new Dictionary<string, IDictionary<string, string>>(); - - /// <summary> - /// Prevents a default instance of the <see cref="ExtensionArgumentsManager"/> class from being created. - /// </summary> - private ExtensionArgumentsManager() { } - - /// <summary> - /// Gets a value indicating whether the extensions are being read (as opposed to written). - /// </summary> - internal bool ReadMode { - get { return this.isReadMode; } - } - - /// <summary> - /// Creates a <see cref="ExtensionArgumentsManager"/> instance to process incoming extensions. - /// </summary> - /// <param name="query">The parameters in the OpenID message.</param> - /// <returns>The newly created instance of <see cref="ExtensionArgumentsManager"/>.</returns> - public static ExtensionArgumentsManager CreateIncomingExtensions(IDictionary<string, string> query) { - Contract.Requires<ArgumentNullException>(query != null); - var mgr = new ExtensionArgumentsManager(); - mgr.protocol = Protocol.Detect(query); - mgr.isReadMode = true; - string aliasPrefix = mgr.protocol.openid.ns + "."; - - // First pass looks for namespace aliases - foreach (var pair in query) { - if (pair.Key.StartsWith(aliasPrefix, StringComparison.Ordinal)) { - mgr.aliasManager.SetAlias(pair.Key.Substring(aliasPrefix.Length), pair.Value); - } - } - - // For backwards compatibility, add certain aliases if they aren't defined. - if (mgr.protocol.Version.Major < 2) { - foreach (var pair in typeUriToAliasAffinity) { - if (!mgr.aliasManager.IsAliasAssignedTo(pair.Key) && - !mgr.aliasManager.IsAliasUsed(pair.Value)) { - mgr.aliasManager.SetAlias(pair.Value, pair.Key); - } - } - } - - // Second pass looks for extensions using those aliases - foreach (var pair in query) { - if (!pair.Key.StartsWith(mgr.protocol.openid.Prefix, StringComparison.Ordinal)) { - continue; - } - string possibleAlias = pair.Key.Substring(mgr.protocol.openid.Prefix.Length); - int periodIndex = possibleAlias.IndexOf(".", StringComparison.Ordinal); - if (periodIndex >= 0) { - possibleAlias = possibleAlias.Substring(0, periodIndex); - } - string typeUri; - if ((typeUri = mgr.aliasManager.TryResolveAlias(possibleAlias)) != null) { - if (!mgr.extensions.ContainsKey(typeUri)) { - mgr.extensions[typeUri] = new Dictionary<string, string>(); - } - string key = periodIndex >= 0 ? pair.Key.Substring(mgr.protocol.openid.Prefix.Length + possibleAlias.Length + 1) : string.Empty; - mgr.extensions[typeUri].Add(key, pair.Value); - } - } - return mgr; - } - - /// <summary> - /// Creates a <see cref="ExtensionArgumentsManager"/> instance to prepare outgoing extensions. - /// </summary> - /// <param name="protocol">The protocol version used for the outgoing message.</param> - /// <returns> - /// The newly created instance of <see cref="ExtensionArgumentsManager"/>. - /// </returns> - public static ExtensionArgumentsManager CreateOutgoingExtensions(Protocol protocol) { - var mgr = new ExtensionArgumentsManager(); - mgr.protocol = protocol; - - // Affinity for certain alias for backwards compatibility - foreach (var pair in typeUriToAliasAffinity) { - mgr.aliasManager.SetAlias(pair.Value, pair.Key); - } - return mgr; - } - - /// <summary> - /// Adds query parameters for OpenID extensions to the request directed - /// at the OpenID provider. - /// </summary> - /// <param name="extensionTypeUri">The extension type URI.</param> - /// <param name="arguments">The arguments for this extension to add to the message.</param> - public void AddExtensionArguments(string extensionTypeUri, IDictionary<string, string> arguments) { - Contract.Requires<InvalidOperationException>(!this.ReadMode); - Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(extensionTypeUri)); - Contract.Requires<ArgumentNullException>(arguments != null); - if (arguments.Count == 0) { - return; - } - - IDictionary<string, string> extensionArgs; - if (!this.extensions.TryGetValue(extensionTypeUri, out extensionArgs)) { - this.extensions.Add(extensionTypeUri, extensionArgs = new Dictionary<string, string>(arguments.Count)); - } - - ErrorUtilities.VerifyProtocol(extensionArgs.Count == 0, OpenIdStrings.ExtensionAlreadyAddedWithSameTypeURI, extensionTypeUri); - foreach (var pair in arguments) { - extensionArgs.Add(pair.Key, pair.Value); - } - } - - /// <summary> - /// Gets the actual arguments to add to a querystring or other response, - /// where type URI, alias, and actual key/values are all defined. - /// </summary> - /// <param name="includeOpenIdPrefix"> - /// <c>true</c> if the generated parameter names should include the 'openid.' prefix. - /// This should be <c>true</c> for all but direct response messages. - /// </param> - /// <returns>A dictionary of key=value pairs to add to the message to carry the extension.</returns> - internal IDictionary<string, string> GetArgumentsToSend(bool includeOpenIdPrefix) { - Contract.Requires<InvalidOperationException>(!this.ReadMode); - Dictionary<string, string> args = new Dictionary<string, string>(); - foreach (var typeUriAndExtension in this.extensions) { - string typeUri = typeUriAndExtension.Key; - var extensionArgs = typeUriAndExtension.Value; - if (extensionArgs.Count == 0) { - continue; - } - string alias = this.aliasManager.GetAlias(typeUri); - - // send out the alias declaration - string openidPrefix = includeOpenIdPrefix ? this.protocol.openid.Prefix : string.Empty; - args.Add(openidPrefix + this.protocol.openidnp.ns + "." + alias, typeUri); - string prefix = openidPrefix + alias; - foreach (var pair in extensionArgs) { - string key = prefix; - if (pair.Key.Length > 0) { - key += "." + pair.Key; - } - args.Add(key, pair.Value); - } - } - return args; - } - - /// <summary> - /// Gets the fields carried by a given OpenId extension. - /// </summary> - /// <param name="extensionTypeUri">The type URI of the extension whose fields are being queried for.</param> - /// <returns> - /// The fields included in the given extension, or null if the extension is not present. - /// </returns> - internal IDictionary<string, string> GetExtensionArguments(string extensionTypeUri) { - Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(extensionTypeUri)); - Contract.Requires<InvalidOperationException>(this.ReadMode); - - IDictionary<string, string> extensionArgs; - this.extensions.TryGetValue(extensionTypeUri, out extensionArgs); - return extensionArgs; - } - - /// <summary> - /// Gets whether any arguments for a given extension are present. - /// </summary> - /// <param name="extensionTypeUri">The extension Type URI in question.</param> - /// <returns><c>true</c> if this extension is present; <c>false</c> otherwise.</returns> - internal bool ContainsExtension(string extensionTypeUri) { - return this.extensions.ContainsKey(extensionTypeUri); - } - - /// <summary> - /// Gets the type URIs of all discovered extensions in the message. - /// </summary> - /// <returns>A sequence of the type URIs.</returns> - internal IEnumerable<string> GetExtensionTypeUris() { - return this.extensions.Keys; - } - } -} diff --git a/src/DotNetOpenAuth/OpenId/Extensions/ExtensionsInteropHelper.cs b/src/DotNetOpenAuth/OpenId/Extensions/ExtensionsInteropHelper.cs deleted file mode 100644 index 5e1003a..0000000 --- a/src/DotNetOpenAuth/OpenId/Extensions/ExtensionsInteropHelper.cs +++ /dev/null @@ -1,383 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="ExtensionsInteropHelper.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OpenId.Extensions { - using System; - using System.Collections.Generic; - using System.Diagnostics.CodeAnalysis; - using System.Diagnostics.Contracts; - using System.Linq; - using DotNetOpenAuth.Messaging; - using DotNetOpenAuth.OpenId.Extensions.AttributeExchange; - using DotNetOpenAuth.OpenId.Extensions.SimpleRegistration; - using DotNetOpenAuth.OpenId.Messages; - - /// <summary> - /// A set of methods designed to assist in improving interop across different - /// OpenID implementations and their extensions. - /// </summary> - public static class ExtensionsInteropHelper { - /// <summary> - /// The gender decoder to translate AX genders to Sreg. - /// </summary> - private static GenderEncoder genderEncoder = new GenderEncoder(); - - /// <summary> - /// Adds an Attribute Exchange (AX) extension to the authentication request - /// that asks for the same attributes as the Simple Registration (sreg) extension - /// that is already applied. - /// </summary> - /// <param name="request">The authentication request.</param> - /// <param name="attributeFormats">The attribute formats to use in the AX request.</param> - /// <remarks> - /// <para>If discovery on the user-supplied identifier yields hints regarding which - /// extensions and attribute formats the Provider supports, this method MAY ignore the - /// <paramref name="attributeFormats"/> argument and accomodate the Provider to minimize - /// the size of the request.</para> - /// <para>If the request does not carry an sreg extension, the method logs a warning but - /// otherwise quietly returns doing nothing.</para> - /// </remarks> - [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Sreg", Justification = "Abbreviation")] - public static void SpreadSregToAX(this RelyingParty.IAuthenticationRequest request, AXAttributeFormats attributeFormats) { - Contract.Requires<ArgumentNullException>(request != null); - - var req = (RelyingParty.AuthenticationRequest)request; - var sreg = req.AppliedExtensions.OfType<ClaimsRequest>().SingleOrDefault(); - if (sreg == null) { - Logger.OpenId.Debug("No Simple Registration (ClaimsRequest) extension present in the request to spread to AX."); - return; - } - - if (req.DiscoveryResult.IsExtensionSupported<ClaimsRequest>()) { - Logger.OpenId.Debug("Skipping generation of AX request because the Identifier advertises the Provider supports the Sreg extension."); - return; - } - - var ax = req.AppliedExtensions.OfType<FetchRequest>().SingleOrDefault(); - if (ax == null) { - ax = new FetchRequest(); - req.AddExtension(ax); - } - - // Try to use just one AX Type URI format if we can figure out which type the OP accepts. - AXAttributeFormats detectedFormat; - if (TryDetectOPAttributeFormat(request, out detectedFormat)) { - Logger.OpenId.Debug("Detected OP support for AX but not for Sreg. Removing Sreg extension request and using AX instead."); - attributeFormats = detectedFormat; - req.Extensions.Remove(sreg); - } else { - Logger.OpenId.Debug("Could not determine whether OP supported Sreg or AX. Using both extensions."); - } - - foreach (AXAttributeFormats format in ForEachFormat(attributeFormats)) { - FetchAttribute(ax, format, WellKnownAttributes.BirthDate.WholeBirthDate, sreg.BirthDate); - FetchAttribute(ax, format, WellKnownAttributes.Contact.HomeAddress.Country, sreg.Country); - FetchAttribute(ax, format, WellKnownAttributes.Contact.Email, sreg.Email); - FetchAttribute(ax, format, WellKnownAttributes.Name.FullName, sreg.FullName); - FetchAttribute(ax, format, WellKnownAttributes.Person.Gender, sreg.Gender); - FetchAttribute(ax, format, WellKnownAttributes.Preferences.Language, sreg.Language); - FetchAttribute(ax, format, WellKnownAttributes.Name.Alias, sreg.Nickname); - FetchAttribute(ax, format, WellKnownAttributes.Contact.HomeAddress.PostalCode, sreg.PostalCode); - FetchAttribute(ax, format, WellKnownAttributes.Preferences.TimeZone, sreg.TimeZone); - } - } - - /// <summary> - /// Looks for Simple Registration and Attribute Exchange (all known formats) - /// response extensions and returns them as a Simple Registration extension. - /// </summary> - /// <param name="response">The authentication response.</param> - /// <param name="allowUnsigned">if set to <c>true</c> unsigned extensions will be included in the search.</param> - /// <returns> - /// The Simple Registration response if found, - /// or a fabricated one based on the Attribute Exchange extension if found, - /// or just an empty <see cref="ClaimsResponse"/> if there was no data. - /// Never <c>null</c>.</returns> - [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Sreg", Justification = "Abbreviation")] - public static ClaimsResponse UnifyExtensionsAsSreg(this RelyingParty.IAuthenticationResponse response, bool allowUnsigned) { - Contract.Requires<ArgumentNullException>(response != null); - - var resp = (RelyingParty.IAuthenticationResponse)response; - var sreg = allowUnsigned ? resp.GetUntrustedExtension<ClaimsResponse>() : resp.GetExtension<ClaimsResponse>(); - if (sreg != null) { - return sreg; - } - - AXAttributeFormats formats = AXAttributeFormats.All; - sreg = new ClaimsResponse(); - var fetchResponse = allowUnsigned ? resp.GetUntrustedExtension<FetchResponse>() : resp.GetExtension<FetchResponse>(); - if (fetchResponse != null) { - ((IOpenIdMessageExtension)sreg).IsSignedByRemoteParty = fetchResponse.IsSignedByProvider; - sreg.BirthDateRaw = fetchResponse.GetAttributeValue(WellKnownAttributes.BirthDate.WholeBirthDate, formats); - sreg.Country = fetchResponse.GetAttributeValue(WellKnownAttributes.Contact.HomeAddress.Country, formats); - sreg.PostalCode = fetchResponse.GetAttributeValue(WellKnownAttributes.Contact.HomeAddress.PostalCode, formats); - sreg.Email = fetchResponse.GetAttributeValue(WellKnownAttributes.Contact.Email, formats); - sreg.FullName = fetchResponse.GetAttributeValue(WellKnownAttributes.Name.FullName, formats); - sreg.Language = fetchResponse.GetAttributeValue(WellKnownAttributes.Preferences.Language, formats); - sreg.Nickname = fetchResponse.GetAttributeValue(WellKnownAttributes.Name.Alias, formats); - sreg.TimeZone = fetchResponse.GetAttributeValue(WellKnownAttributes.Preferences.TimeZone, formats); - string gender = fetchResponse.GetAttributeValue(WellKnownAttributes.Person.Gender, formats); - if (gender != null) { - sreg.Gender = (Gender)genderEncoder.Decode(gender); - } - } - - return sreg; - } - - /// <summary> - /// Looks for Simple Registration and Attribute Exchange (all known formats) - /// request extensions and returns them as a Simple Registration extension, - /// and adds the new extension to the original request message if it was absent. - /// </summary> - /// <param name="request">The authentication request.</param> - /// <returns> - /// The Simple Registration request if found, - /// or a fabricated one based on the Attribute Exchange extension if found, - /// or <c>null</c> if no attribute extension request is found.</returns> - internal static ClaimsRequest UnifyExtensionsAsSreg(this Provider.IHostProcessedRequest request) { - Contract.Requires<ArgumentNullException>(request != null); - - var req = (Provider.HostProcessedRequest)request; - var sreg = req.GetExtension<ClaimsRequest>(); - if (sreg != null) { - return sreg; - } - - var ax = req.GetExtension<FetchRequest>(); - if (ax != null) { - sreg = new ClaimsRequest(SimpleRegistration.Constants.sreg_ns); - sreg.Synthesized = true; - ((IProtocolMessageWithExtensions)req.RequestMessage).Extensions.Add(sreg); - sreg.BirthDate = GetDemandLevelFor(ax, WellKnownAttributes.BirthDate.WholeBirthDate); - sreg.Country = GetDemandLevelFor(ax, WellKnownAttributes.Contact.HomeAddress.Country); - sreg.Email = GetDemandLevelFor(ax, WellKnownAttributes.Contact.Email); - sreg.FullName = GetDemandLevelFor(ax, WellKnownAttributes.Name.FullName); - sreg.Gender = GetDemandLevelFor(ax, WellKnownAttributes.Person.Gender); - sreg.Language = GetDemandLevelFor(ax, WellKnownAttributes.Preferences.Language); - sreg.Nickname = GetDemandLevelFor(ax, WellKnownAttributes.Name.Alias); - sreg.PostalCode = GetDemandLevelFor(ax, WellKnownAttributes.Contact.HomeAddress.PostalCode); - sreg.TimeZone = GetDemandLevelFor(ax, WellKnownAttributes.Preferences.TimeZone); - } - - return sreg; - } - - /// <summary> - /// Converts the Simple Registration extension response to whatever format the original - /// attribute request extension came in. - /// </summary> - /// <param name="request">The authentication request with the response extensions already added.</param> - /// <remarks> - /// If the original attribute request came in as AX, the Simple Registration extension is converted - /// to an AX response and then the Simple Registration extension is removed from the response. - /// </remarks> - internal static void ConvertSregToMatchRequest(this Provider.IHostProcessedRequest request) { - var req = (Provider.HostProcessedRequest)request; - var response = req.Response as IProtocolMessageWithExtensions; // negative responses don't support extensions. - var sregRequest = request.GetExtension<ClaimsRequest>(); - if (sregRequest != null && response != null) { - if (sregRequest.Synthesized) { - var axRequest = request.GetExtension<FetchRequest>(); - ErrorUtilities.VerifyInternal(axRequest != null, "How do we have a synthesized Sreg request without an AX request?"); - - var sregResponse = response.Extensions.OfType<ClaimsResponse>().SingleOrDefault(); - if (sregResponse == null) { - // No Sreg response to copy from. - return; - } - - // Remove the sreg response since the RP didn't ask for it. - response.Extensions.Remove(sregResponse); - - AXAttributeFormats format = DetectAXFormat(axRequest.Attributes.Select(att => att.TypeUri)); - if (format == AXAttributeFormats.None) { - // No recognized AX attributes were requested. - return; - } - - var axResponse = response.Extensions.OfType<FetchResponse>().SingleOrDefault(); - if (axResponse == null) { - axResponse = new FetchResponse(); - response.Extensions.Add(axResponse); - } - - AddAXAttributeValue(axResponse, WellKnownAttributes.BirthDate.WholeBirthDate, format, sregResponse.BirthDateRaw); - AddAXAttributeValue(axResponse, WellKnownAttributes.Contact.HomeAddress.Country, format, sregResponse.Country); - AddAXAttributeValue(axResponse, WellKnownAttributes.Contact.HomeAddress.PostalCode, format, sregResponse.PostalCode); - AddAXAttributeValue(axResponse, WellKnownAttributes.Contact.Email, format, sregResponse.Email); - AddAXAttributeValue(axResponse, WellKnownAttributes.Name.FullName, format, sregResponse.FullName); - AddAXAttributeValue(axResponse, WellKnownAttributes.Name.Alias, format, sregResponse.Nickname); - AddAXAttributeValue(axResponse, WellKnownAttributes.Preferences.TimeZone, format, sregResponse.TimeZone); - AddAXAttributeValue(axResponse, WellKnownAttributes.Preferences.Language, format, sregResponse.Language); - if (sregResponse.Gender.HasValue) { - AddAXAttributeValue(axResponse, WellKnownAttributes.Person.Gender, format, genderEncoder.Encode(sregResponse.Gender)); - } - } - } - } - - /// <summary> - /// Gets the attribute value if available. - /// </summary> - /// <param name="fetchResponse">The AX fetch response extension to look for the attribute value.</param> - /// <param name="typeUri">The type URI of the attribute, using the axschema.org format of <see cref="WellKnownAttributes"/>.</param> - /// <param name="formats">The AX type URI formats to search.</param> - /// <returns> - /// The first value of the attribute, if available. - /// </returns> - internal static string GetAttributeValue(this FetchResponse fetchResponse, string typeUri, AXAttributeFormats formats) { - return ForEachFormat(formats).Select(format => fetchResponse.GetAttributeValue(TransformAXFormat(typeUri, format))).FirstOrDefault(s => s != null); - } - - /// <summary> - /// Transforms an AX attribute type URI from the axschema.org format into a given format. - /// </summary> - /// <param name="axSchemaOrgFormatTypeUri">The ax schema org format type URI.</param> - /// <param name="targetFormat">The target format. Only one flag should be set.</param> - /// <returns>The AX attribute type URI in the target format.</returns> - internal static string TransformAXFormatTestHook(string axSchemaOrgFormatTypeUri, AXAttributeFormats targetFormat) { - return TransformAXFormat(axSchemaOrgFormatTypeUri, targetFormat); - } - - /// <summary> - /// Adds the AX attribute value to the response if it is non-empty. - /// </summary> - /// <param name="ax">The AX Fetch response to add the attribute value to.</param> - /// <param name="typeUri">The attribute type URI in axschema.org format.</param> - /// <param name="format">The target format of the actual attribute to write out.</param> - /// <param name="value">The value of the attribute.</param> - private static void AddAXAttributeValue(FetchResponse ax, string typeUri, AXAttributeFormats format, string value) { - if (!string.IsNullOrEmpty(value)) { - string targetTypeUri = TransformAXFormat(typeUri, format); - if (!ax.Attributes.Contains(targetTypeUri)) { - ax.Attributes.Add(targetTypeUri, value); - } - } - } - - /// <summary> - /// Gets the demand level for an AX attribute. - /// </summary> - /// <param name="ax">The AX fetch request to search for the attribute.</param> - /// <param name="typeUri">The type URI of the attribute in axschema.org format.</param> - /// <returns>The demand level for the attribute.</returns> - private static DemandLevel GetDemandLevelFor(FetchRequest ax, string typeUri) { - Contract.Requires<ArgumentNullException>(ax != null); - Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(typeUri)); - - foreach (AXAttributeFormats format in ForEachFormat(AXAttributeFormats.All)) { - string typeUriInFormat = TransformAXFormat(typeUri, format); - if (ax.Attributes.Contains(typeUriInFormat)) { - return ax.Attributes[typeUriInFormat].IsRequired ? DemandLevel.Require : DemandLevel.Request; - } - } - - return DemandLevel.NoRequest; - } - - /// <summary> - /// Tries to find the exact format of AX attribute Type URI supported by the Provider. - /// </summary> - /// <param name="request">The authentication request.</param> - /// <param name="attributeFormat">The attribute formats the RP will try if this discovery fails.</param> - /// <returns>The AX format(s) to use based on the Provider's advertised AX support.</returns> - private static bool TryDetectOPAttributeFormat(RelyingParty.IAuthenticationRequest request, out AXAttributeFormats attributeFormat) { - Contract.Requires<ArgumentNullException>(request != null); - attributeFormat = DetectAXFormat(request.DiscoveryResult.Capabilities); - return attributeFormat != AXAttributeFormats.None; - } - - /// <summary> - /// Detects the AX attribute type URI format from a given sample. - /// </summary> - /// <param name="typeURIs">The type URIs to scan for recognized formats.</param> - /// <returns>The first AX type URI format recognized in the list.</returns> - private static AXAttributeFormats DetectAXFormat(IEnumerable<string> typeURIs) { - Contract.Requires<ArgumentNullException>(typeURIs != null); - - if (typeURIs.Any(uri => uri.StartsWith("http://axschema.org/", StringComparison.Ordinal))) { - return AXAttributeFormats.AXSchemaOrg; - } - - if (typeURIs.Any(uri => uri.StartsWith("http://schema.openid.net/", StringComparison.Ordinal))) { - return AXAttributeFormats.SchemaOpenIdNet; - } - - if (typeURIs.Any(uri => uri.StartsWith("http://openid.net/schema/", StringComparison.Ordinal))) { - return AXAttributeFormats.OpenIdNetSchema; - } - - return AXAttributeFormats.None; - } - - /// <summary> - /// Transforms an AX attribute type URI from the axschema.org format into a given format. - /// </summary> - /// <param name="axSchemaOrgFormatTypeUri">The ax schema org format type URI.</param> - /// <param name="targetFormat">The target format. Only one flag should be set.</param> - /// <returns>The AX attribute type URI in the target format.</returns> - private static string TransformAXFormat(string axSchemaOrgFormatTypeUri, AXAttributeFormats targetFormat) { - Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(axSchemaOrgFormatTypeUri)); - - switch (targetFormat) { - case AXAttributeFormats.AXSchemaOrg: - return axSchemaOrgFormatTypeUri; - case AXAttributeFormats.SchemaOpenIdNet: - return axSchemaOrgFormatTypeUri.Replace("axschema.org", "schema.openid.net"); - case AXAttributeFormats.OpenIdNetSchema: - return axSchemaOrgFormatTypeUri.Replace("axschema.org", "openid.net/schema"); - default: - throw new ArgumentOutOfRangeException("targetFormat"); - } - } - - /// <summary> - /// Splits the AX attribute format flags into individual values for processing. - /// </summary> - /// <param name="formats">The formats to split up into individual flags.</param> - /// <returns>A sequence of individual flags.</returns> - private static IEnumerable<AXAttributeFormats> ForEachFormat(AXAttributeFormats formats) { - if ((formats & AXAttributeFormats.AXSchemaOrg) != 0) { - yield return AXAttributeFormats.AXSchemaOrg; - } - - if ((formats & AXAttributeFormats.OpenIdNetSchema) != 0) { - yield return AXAttributeFormats.OpenIdNetSchema; - } - - if ((formats & AXAttributeFormats.SchemaOpenIdNet) != 0) { - yield return AXAttributeFormats.SchemaOpenIdNet; - } - } - - /// <summary> - /// Adds an attribute fetch request if it is not already present in the AX request. - /// </summary> - /// <param name="ax">The AX request to add the attribute request to.</param> - /// <param name="format">The format of the attribute's Type URI to use.</param> - /// <param name="axSchemaOrgFormatAttribute">The attribute in axschema.org format.</param> - /// <param name="demandLevel">The demand level.</param> - private static void FetchAttribute(FetchRequest ax, AXAttributeFormats format, string axSchemaOrgFormatAttribute, DemandLevel demandLevel) { - Contract.Requires<ArgumentNullException>(ax != null); - Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(axSchemaOrgFormatAttribute)); - - string typeUri = TransformAXFormat(axSchemaOrgFormatAttribute, format); - if (!ax.Attributes.Contains(typeUri)) { - switch (demandLevel) { - case DemandLevel.Request: - ax.Attributes.AddOptional(typeUri); - break; - case DemandLevel.Require: - ax.Attributes.AddRequired(typeUri); - break; - default: - break; - } - } - } - } -} diff --git a/src/DotNetOpenAuth/OpenId/Extensions/IClientScriptExtensionResponse.cs b/src/DotNetOpenAuth/OpenId/Extensions/IClientScriptExtensionResponse.cs deleted file mode 100644 index c84f507..0000000 --- a/src/DotNetOpenAuth/OpenId/Extensions/IClientScriptExtensionResponse.cs +++ /dev/null @@ -1,33 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="IClientScriptExtensionResponse.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OpenId.Extensions { - using System.Collections.Generic; - using DotNetOpenAuth.Messaging; - using DotNetOpenAuth.OpenId.Messages; - using DotNetOpenAuth.OpenId.RelyingParty; - - /// <summary> - /// An interface that OpenID extensions can implement to allow authentication response - /// messages with included extensions to be processed by Javascript on the user agent. - /// </summary> - public interface IClientScriptExtensionResponse : IExtensionMessage { - /// <summary> - /// Reads the extension information on an authentication response from the provider. - /// </summary> - /// <param name="response">The incoming OpenID response carrying the extension.</param> - /// <returns> - /// A Javascript snippet that when executed on the user agent returns an object with - /// the information deserialized from the extension response. - /// </returns> - /// <remarks> - /// This method is called <b>before</b> the signature on the assertion response has been - /// verified. Therefore all information in these fields should be assumed unreliable - /// and potentially falsified. - /// </remarks> - string InitializeJavaScriptData(IProtocolMessageWithExtensions response); - } -} diff --git a/src/DotNetOpenAuth/OpenId/Extensions/OpenIdExtensionFactoryAggregator.cs b/src/DotNetOpenAuth/OpenId/Extensions/OpenIdExtensionFactoryAggregator.cs deleted file mode 100644 index 05e6687..0000000 --- a/src/DotNetOpenAuth/OpenId/Extensions/OpenIdExtensionFactoryAggregator.cs +++ /dev/null @@ -1,81 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="OpenIdExtensionFactoryAggregator.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OpenId.Extensions { - using System.Collections.Generic; - using System.Diagnostics.Contracts; - using DotNetOpenAuth.Messaging; - using DotNetOpenAuth.OpenId.ChannelElements; - using DotNetOpenAuth.OpenId.Messages; - - /// <summary> - /// An OpenID extension factory that only delegates extension - /// instantiation requests to other factories. - /// </summary> - internal class OpenIdExtensionFactoryAggregator : IOpenIdExtensionFactory { - /// <summary> - /// The list of factories this factory delegates to. - /// </summary> - private List<IOpenIdExtensionFactory> factories = new List<IOpenIdExtensionFactory>(2); - - /// <summary> - /// Initializes a new instance of the <see cref="OpenIdExtensionFactoryAggregator"/> class. - /// </summary> - internal OpenIdExtensionFactoryAggregator() { - } - - /// <summary> - /// Gets the extension factories that this aggregating factory delegates to. - /// </summary> - /// <value>A list of factories. May be empty, but never null.</value> - internal IList<IOpenIdExtensionFactory> Factories { - get { return this.factories; } - } - - #region IOpenIdExtensionFactory Members - - /// <summary> - /// Creates a new instance of some extension based on the received extension parameters. - /// </summary> - /// <param name="typeUri">The type URI of the extension.</param> - /// <param name="data">The parameters associated specifically with this extension.</param> - /// <param name="baseMessage">The OpenID message carrying this extension.</param> - /// <param name="isProviderRole">A value indicating whether this extension is being received at the OpenID Provider.</param> - /// <returns> - /// An instance of <see cref="IOpenIdMessageExtension"/> if the factory recognizes - /// the extension described in the input parameters; <c>null</c> otherwise. - /// </returns> - /// <remarks> - /// This factory method need only initialize properties in the instantiated extension object - /// that are not bound using <see cref="MessagePartAttribute"/>. - /// </remarks> - public IOpenIdMessageExtension Create(string typeUri, IDictionary<string, string> data, IProtocolMessageWithExtensions baseMessage, bool isProviderRole) { - foreach (var factory in this.factories) { - IOpenIdMessageExtension result = factory.Create(typeUri, data, baseMessage, isProviderRole); - if (result != null) { - return result; - } - } - - return null; - } - - #endregion - - /// <summary> - /// Loads the default factory and additional ones given by the configuration. - /// </summary> - /// <returns>A new instance of <see cref="OpenIdExtensionFactoryAggregator"/>.</returns> - internal static OpenIdExtensionFactoryAggregator LoadFromConfiguration() { - Contract.Ensures(Contract.Result<OpenIdExtensionFactoryAggregator>() != null); - var factoriesElement = DotNetOpenAuth.Configuration.DotNetOpenAuthSection.Configuration.OpenId.ExtensionFactories; - var aggregator = new OpenIdExtensionFactoryAggregator(); - aggregator.Factories.Add(new StandardOpenIdExtensionFactory()); - aggregator.factories.AddRange(factoriesElement.CreateInstances(false)); - return aggregator; - } - } -} diff --git a/src/DotNetOpenAuth/OpenId/Extensions/ProviderAuthenticationPolicy/PapeUtilities.cs b/src/DotNetOpenAuth/OpenId/Extensions/ProviderAuthenticationPolicy/PapeUtilities.cs deleted file mode 100644 index eeaea31..0000000 --- a/src/DotNetOpenAuth/OpenId/Extensions/ProviderAuthenticationPolicy/PapeUtilities.cs +++ /dev/null @@ -1,65 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="PapeUtilities.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OpenId.Extensions.ProviderAuthenticationPolicy { - using System; - using System.Collections.Generic; - using System.Diagnostics.Contracts; - using System.Globalization; - using System.Linq; - using System.Text; - using DotNetOpenAuth.Messaging; - - /// <summary> - /// Utility methods for use by the PAPE extension. - /// </summary> - internal static class PapeUtilities { - /// <summary> - /// Looks at the incoming fields and figures out what the aliases and name spaces for auth level types are. - /// </summary> - /// <param name="fields">The incoming message data in which to discover TypeURIs and aliases.</param> - /// <returns>The <see cref="AliasManager"/> initialized with the given data.</returns> - internal static AliasManager FindIncomingAliases(IDictionary<string, string> fields) { - AliasManager aliasManager = new AliasManager(); - - foreach (var pair in fields) { - if (!pair.Key.StartsWith(Constants.AuthLevelNamespaceDeclarationPrefix, StringComparison.Ordinal)) { - continue; - } - - string alias = pair.Key.Substring(Constants.AuthLevelNamespaceDeclarationPrefix.Length); - aliasManager.SetAlias(alias, pair.Value); - } - - aliasManager.SetPreferredAliasesWhereNotSet(Constants.AssuranceLevels.PreferredTypeUriToAliasMap); - - return aliasManager; - } - - /// <summary> - /// Concatenates a sequence of strings using a space as a separator. - /// </summary> - /// <param name="values">The elements to concatenate together..</param> - /// <returns>The concatenated string of elements.</returns> - /// <exception cref="FormatException">Thrown if any element in the sequence includes a space.</exception> - internal static string ConcatenateListOfElements(IEnumerable<string> values) { - Contract.Requires<ArgumentNullException>(values != null); - - StringBuilder valuesList = new StringBuilder(); - foreach (string value in values.Distinct()) { - if (value.Contains(" ")) { - throw new FormatException(string.Format(CultureInfo.CurrentCulture, OpenIdStrings.InvalidUri, value)); - } - valuesList.Append(value); - valuesList.Append(" "); - } - if (valuesList.Length > 0) { - valuesList.Length -= 1; // remove trailing space - } - return valuesList.ToString(); - } - } -} diff --git a/src/DotNetOpenAuth/OpenId/Extensions/ProviderAuthenticationPolicy/PolicyResponse.cs b/src/DotNetOpenAuth/OpenId/Extensions/ProviderAuthenticationPolicy/PolicyResponse.cs deleted file mode 100644 index 246ec07..0000000 --- a/src/DotNetOpenAuth/OpenId/Extensions/ProviderAuthenticationPolicy/PolicyResponse.cs +++ /dev/null @@ -1,282 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="PolicyResponse.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OpenId.Extensions.ProviderAuthenticationPolicy { - using System; - using System.Collections.Generic; - using System.Diagnostics.CodeAnalysis; - using System.Diagnostics.Contracts; - using System.Globalization; - using DotNetOpenAuth.Messaging; - using DotNetOpenAuth.OpenId.Messages; - - /// <summary> - /// The PAPE response part of an OpenID Authentication response message. - /// </summary> - [Serializable] - public sealed class PolicyResponse : ExtensionBase, IMessageWithEvents { - /// <summary> - /// The factory method that may be used in deserialization of this message. - /// </summary> - internal static readonly StandardOpenIdExtensionFactory.CreateDelegate Factory = (typeUri, data, baseMessage, isProviderRole) => { - if (typeUri == Constants.TypeUri && !isProviderRole) { - return new PolicyResponse(); - } - - return null; - }; - - /// <summary> - /// The first part of a parameter name that gives the custom string value for - /// the assurance level. The second part of the parameter name is the alias for - /// that assurance level. - /// </summary> - private const string AuthLevelAliasPrefix = "auth_level."; - - /// <summary> - /// One or more authentication policy URIs that the OP conformed to when authenticating the End User. - /// </summary> - /// <value>Space separated list of authentication policy URIs.</value> - /// <remarks> - /// If no policies were met though the OP wishes to convey other information in the response, this parameter MUST be included with the value of "none". - /// </remarks> - [MessagePart("auth_policies", IsRequired = true)] - private string actualPoliciesString; - - /// <summary> - /// Backing field for the <see cref="AuthenticationTimeUtc"/> property. - /// </summary> - private DateTime? authenticationTimeUtc; - - /// <summary> - /// Initializes a new instance of the <see cref="PolicyResponse"/> class. - /// </summary> - public PolicyResponse() - : base(new Version(1, 0), Constants.TypeUri, null) { - this.ActualPolicies = new List<string>(1); - this.AssuranceLevels = new Dictionary<string, string>(1); - } - - /// <summary> - /// Gets a list of authentication policy URIs that the - /// OP conformed to when authenticating the End User. - /// </summary> - public IList<string> ActualPolicies { get; private set; } - - /// <summary> - /// Gets or sets the most recent timestamp when the End User has - /// actively authenticated to the OP in a manner fitting the asserted policies. - /// </summary> - /// <remarks> - /// If the RP's request included the "openid.max_auth_age" parameter - /// then the OP MUST include "openid.auth_time" in its response. - /// If "openid.max_auth_age" was not requested, the OP MAY choose to include - /// "openid.auth_time" in its response. - /// </remarks> - [MessagePart("auth_time", Encoder = typeof(DateTimeEncoder))] - public DateTime? AuthenticationTimeUtc { - get { - return this.authenticationTimeUtc; - } - - set { - Contract.Requires<ArgumentException>(!value.HasValue || value.Value.Kind != DateTimeKind.Unspecified, OpenIdStrings.UnspecifiedDateTimeKindNotAllowed); - - // Make sure that whatever is set here, it becomes UTC time. - if (value.HasValue) { - // Convert to UTC and cut to the second, since the protocol only allows for - // that level of precision. - this.authenticationTimeUtc = OpenIdUtilities.CutToSecond(value.Value.ToUniversalTimeSafe()); - } else { - this.authenticationTimeUtc = null; - } - } - } - - /// <summary> - /// Gets or sets the Assurance Level as defined by the National - /// Institute of Standards and Technology (NIST) in Special Publication - /// 800-63 (Burr, W., Dodson, D., and W. Polk, Ed., “Electronic - /// Authentication Guideline,” April 2006.) [NIST_SP800‑63] corresponding - /// to the authentication method and policies employed by the OP when - /// authenticating the End User. - /// </summary> - /// <remarks> - /// See PAPE spec Appendix A.1.2 (NIST Assurance Levels) for high-level - /// example classifications of authentication methods within the defined - /// levels. - /// </remarks> - [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Nist", Justification = "Acronym")] - public NistAssuranceLevel? NistAssuranceLevel { - get { - string levelString; - if (this.AssuranceLevels.TryGetValue(Constants.AssuranceLevels.NistTypeUri, out levelString)) { - return (NistAssuranceLevel)Enum.Parse(typeof(NistAssuranceLevel), levelString); - } else { - return null; - } - } - - set { - if (value != null) { - this.AssuranceLevels[Constants.AssuranceLevels.NistTypeUri] = ((int)value).ToString(CultureInfo.InvariantCulture); - } else { - this.AssuranceLevels.Remove(Constants.AssuranceLevels.NistTypeUri); - } - } - } - - /// <summary> - /// Gets a dictionary where keys are the authentication level type URIs and - /// the values are the per authentication level defined custom value. - /// </summary> - /// <remarks> - /// A very common key is <see cref="Constants.AssuranceLevels.NistTypeUri"/> - /// and values for this key are available in <see cref="NistAssuranceLevel"/>. - /// </remarks> - public IDictionary<string, string> AssuranceLevels { get; private set; } - - /// <summary> - /// Gets a value indicating whether this extension is signed by the Provider. - /// </summary> - /// <value> - /// <c>true</c> if this instance is signed by the Provider; otherwise, <c>false</c>. - /// </value> - public bool IsSignedByProvider { - get { return this.IsSignedByRemoteParty; } - } - - #region IMessageWithEvents Members - - /// <summary> - /// Called when the message is about to be transmitted, - /// before it passes through the channel binding elements. - /// </summary> - void IMessageWithEvents.OnSending() { - var extraData = ((IMessage)this).ExtraData; - extraData.Clear(); - - this.actualPoliciesString = SerializePolicies(this.ActualPolicies); - - if (this.AssuranceLevels.Count > 0) { - AliasManager aliases = new AliasManager(); - aliases.AssignAliases(this.AssuranceLevels.Keys, Constants.AssuranceLevels.PreferredTypeUriToAliasMap); - - // Add a definition for each Auth Level Type alias. - foreach (string alias in aliases.Aliases) { - extraData.Add(Constants.AuthLevelNamespaceDeclarationPrefix + alias, aliases.ResolveAlias(alias)); - } - - // Now use the aliases for those type URIs to list the individual values. - foreach (var pair in this.AssuranceLevels) { - extraData.Add(AuthLevelAliasPrefix + aliases.GetAlias(pair.Key), pair.Value); - } - } - } - - /// <summary> - /// Called when the message has been received, - /// after it passes through the channel binding elements. - /// </summary> - void IMessageWithEvents.OnReceiving() { - var extraData = ((IMessage)this).ExtraData; - - this.ActualPolicies.Clear(); - string[] actualPolicies = this.actualPoliciesString.Split(' '); - foreach (string policy in actualPolicies) { - if (policy.Length > 0 && policy != AuthenticationPolicies.None) { - this.ActualPolicies.Add(policy); - } - } - - this.AssuranceLevels.Clear(); - AliasManager authLevelAliases = PapeUtilities.FindIncomingAliases(extraData); - foreach (string authLevelAlias in authLevelAliases.Aliases) { - string authValue; - if (extraData.TryGetValue(AuthLevelAliasPrefix + authLevelAlias, out authValue)) { - string authLevelType = authLevelAliases.ResolveAlias(authLevelAlias); - this.AssuranceLevels[authLevelType] = authValue; - } - } - } - - #endregion - - /// <summary> - /// Determines whether the specified <see cref="T:System.Object"/> is equal to the current <see cref="T:System.Object"/>. - /// </summary> - /// <param name="obj">The <see cref="T:System.Object"/> to compare with the current <see cref="T:System.Object"/>.</param> - /// <returns> - /// true if the specified <see cref="T:System.Object"/> is equal to the current <see cref="T:System.Object"/>; otherwise, false. - /// </returns> - /// <exception cref="T:System.NullReferenceException"> - /// The <paramref name="obj"/> parameter is null. - /// </exception> - public override bool Equals(object obj) { - PolicyResponse other = obj as PolicyResponse; - if (other == null) { - return false; - } - - if (this.AuthenticationTimeUtc != other.AuthenticationTimeUtc) { - return false; - } - - if (this.AssuranceLevels.Count != other.AssuranceLevels.Count) { - return false; - } - - foreach (var pair in this.AssuranceLevels) { - if (!other.AssuranceLevels.Contains(pair)) { - return false; - } - } - - if (this.ActualPolicies.Count != other.ActualPolicies.Count) { - return false; - } - - foreach (string policy in this.ActualPolicies) { - if (!other.ActualPolicies.Contains(policy)) { - return false; - } - } - - return true; - } - - /// <summary> - /// Serves as a hash function for a particular type. - /// </summary> - /// <returns> - /// A hash code for the current <see cref="T:System.Object"/>. - /// </returns> - public override int GetHashCode() { - // This is a poor hash function, but an site that cares will likely have a bunch - // of look-alike instances anyway, so a good hash function would still bunch - // all the instances into the same hash code. - if (this.AuthenticationTimeUtc.HasValue) { - return this.AuthenticationTimeUtc.Value.GetHashCode(); - } else { - return 1; - } - } - - /// <summary> - /// Serializes the applied policies for transmission from the Provider - /// to the Relying Party. - /// </summary> - /// <param name="policies">The applied policies.</param> - /// <returns>A space-delimited list of applied policies.</returns> - private static string SerializePolicies(IList<string> policies) { - if (policies.Count == 0) { - return AuthenticationPolicies.None; - } else { - return PapeUtilities.ConcatenateListOfElements(policies); - } - } - } -} diff --git a/src/DotNetOpenAuth/OpenId/Extensions/SimpleRegistration/ClaimsRequest.cs b/src/DotNetOpenAuth/OpenId/Extensions/SimpleRegistration/ClaimsRequest.cs deleted file mode 100644 index cec8042..0000000 --- a/src/DotNetOpenAuth/OpenId/Extensions/SimpleRegistration/ClaimsRequest.cs +++ /dev/null @@ -1,316 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="ClaimsRequest.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OpenId.Extensions.SimpleRegistration { - using System; - using System.Collections.Generic; - using System.Diagnostics; - using System.Diagnostics.CodeAnalysis; - using System.Diagnostics.Contracts; - using System.Globalization; - using System.Text; - using DotNetOpenAuth.Messaging; - using DotNetOpenAuth.OpenId.Messages; - - /// <summary> - /// Carries the request/require/none demand state of the simple registration fields. - /// </summary> - [Serializable] - public sealed class ClaimsRequest : ExtensionBase { - /// <summary> - /// The factory method that may be used in deserialization of this message. - /// </summary> - internal static readonly StandardOpenIdExtensionFactory.CreateDelegate Factory = (typeUri, data, baseMessage, isProviderRole) => { - if (typeUri == Constants.sreg_ns && isProviderRole) { - return new ClaimsRequest(typeUri); - } - - return null; - }; - - /// <summary> - /// The type URI that this particular (deserialized) extension was read in using, - /// allowing a response to alter be crafted using the same type URI. - /// </summary> - private string typeUriDeserializedFrom; - - /// <summary> - /// Initializes a new instance of the <see cref="ClaimsRequest"/> class. - /// </summary> - public ClaimsRequest() - : base(new Version(1, 0), Constants.sreg_ns, Constants.AdditionalTypeUris) { - } - - /// <summary> - /// Initializes a new instance of the <see cref="ClaimsRequest"/> class - /// by deserializing from a message. - /// </summary> - /// <param name="typeUri">The type URI this extension was recognized by in the OpenID message.</param> - internal ClaimsRequest(string typeUri) - : this() { - Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(typeUri)); - - this.typeUriDeserializedFrom = typeUri; - } - - /// <summary> - /// Gets or sets the URL the consumer site provides for the authenticating user to review - /// for how his claims will be used by the consumer web site. - /// </summary> - [MessagePart(Constants.policy_url, IsRequired = false)] - public Uri PolicyUrl { get; set; } - - /// <summary> - /// Gets or sets the level of interest a relying party has in the nickname of the user. - /// </summary> - public DemandLevel Nickname { get; set; } - - /// <summary> - /// Gets or sets the level of interest a relying party has in the email of the user. - /// </summary> - public DemandLevel Email { get; set; } - - /// <summary> - /// Gets or sets the level of interest a relying party has in the full name of the user. - /// </summary> - public DemandLevel FullName { get; set; } - - /// <summary> - /// Gets or sets the level of interest a relying party has in the birthdate of the user. - /// </summary> - public DemandLevel BirthDate { get; set; } - - /// <summary> - /// Gets or sets the level of interest a relying party has in the gender of the user. - /// </summary> - public DemandLevel Gender { get; set; } - - /// <summary> - /// Gets or sets the level of interest a relying party has in the postal code of the user. - /// </summary> - public DemandLevel PostalCode { get; set; } - - /// <summary> - /// Gets or sets the level of interest a relying party has in the Country of the user. - /// </summary> - public DemandLevel Country { get; set; } - - /// <summary> - /// Gets or sets the level of interest a relying party has in the language of the user. - /// </summary> - public DemandLevel Language { get; set; } - - /// <summary> - /// Gets or sets the level of interest a relying party has in the time zone of the user. - /// </summary> - public DemandLevel TimeZone { get; set; } - - /// <summary> - /// Gets or sets a value indicating whether this <see cref="ClaimsRequest"/> instance - /// is synthesized from an AX request at the Provider. - /// </summary> - internal bool Synthesized { get; set; } - - /// <summary> - /// Gets or sets the value of the sreg.required parameter. - /// </summary> - /// <value>A comma-delimited list of sreg fields.</value> - [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called by messaging framework via reflection.")] - [MessagePart(Constants.required, AllowEmpty = true)] - private string RequiredList { - get { return string.Join(",", this.AssembleProfileFields(DemandLevel.Require)); } - set { this.SetProfileRequestFromList(value.Split(','), DemandLevel.Require); } - } - - /// <summary> - /// Gets or sets the value of the sreg.optional parameter. - /// </summary> - /// <value>A comma-delimited list of sreg fields.</value> - [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called by messaging framework via reflection.")] - [MessagePart(Constants.optional, AllowEmpty = true)] - private string OptionalList { - get { return string.Join(",", this.AssembleProfileFields(DemandLevel.Request)); } - set { this.SetProfileRequestFromList(value.Split(','), DemandLevel.Request); } - } - - /// <summary> - /// Tests equality between two <see cref="ClaimsRequest"/> structs. - /// </summary> - /// <param name="one">One instance to compare.</param> - /// <param name="other">Another instance to compare.</param> - /// <returns>The result of the operator.</returns> - public static bool operator ==(ClaimsRequest one, ClaimsRequest other) { - return one.EqualsNullSafe(other); - } - - /// <summary> - /// Tests inequality between two <see cref="ClaimsRequest"/> structs. - /// </summary> - /// <param name="one">One instance to compare.</param> - /// <param name="other">Another instance to compare.</param> - /// <returns>The result of the operator.</returns> - public static bool operator !=(ClaimsRequest one, ClaimsRequest other) { - return !(one == other); - } - - /// <summary> - /// Tests equality between two <see cref="ClaimsRequest"/> structs. - /// </summary> - /// <param name="obj">The <see cref="T:System.Object"/> to compare with the current <see cref="T:System.Object"/>.</param> - /// <returns> - /// true if the specified <see cref="T:System.Object"/> is equal to the current <see cref="T:System.Object"/>; otherwise, false. - /// </returns> - /// <exception cref="T:System.NullReferenceException"> - /// The <paramref name="obj"/> parameter is null. - /// </exception> - public override bool Equals(object obj) { - ClaimsRequest other = obj as ClaimsRequest; - if (other == null) { - return false; - } - - return - this.BirthDate.Equals(other.BirthDate) && - this.Country.Equals(other.Country) && - this.Language.Equals(other.Language) && - this.Email.Equals(other.Email) && - this.FullName.Equals(other.FullName) && - this.Gender.Equals(other.Gender) && - this.Nickname.Equals(other.Nickname) && - this.PostalCode.Equals(other.PostalCode) && - this.TimeZone.Equals(other.TimeZone) && - this.PolicyUrl.EqualsNullSafe(other.PolicyUrl); - } - - /// <summary> - /// Serves as a hash function for a particular type. - /// </summary> - /// <returns> - /// A hash code for the current <see cref="T:System.Object"/>. - /// </returns> - public override int GetHashCode() { - // It's important that if Equals returns true that the hash code also equals, - // so returning base.GetHashCode() is a BAD option. - // Return 1 is simple and poor for dictionary storage, but considering that every - // ClaimsRequest formulated at a single RP will likely have all the same fields, - // even a good hash code function will likely generate the same hash code. So - // we just cut to the chase and return a simple one. - return 1; - } - - /// <summary> - /// Renders the requested information as a string. - /// </summary> - /// <returns> - /// A <see cref="T:System.String"/> that represents the current <see cref="T:System.Object"/>. - /// </returns> - public override string ToString() { - string format = @"Nickname = '{0}' -Email = '{1}' -FullName = '{2}' -Birthdate = '{3}' -Gender = '{4}' -PostalCode = '{5}' -Country = '{6}' -Language = '{7}' -TimeZone = '{8}'"; - return string.Format(CultureInfo.CurrentCulture, format, this.Nickname, this.Email, this.FullName, this.BirthDate, this.Gender, this.PostalCode, this.Country, this.Language, this.TimeZone); - } - - /// <summary> - /// Prepares a Simple Registration response extension that is compatible with the - /// version of Simple Registration used in the request message. - /// </summary> - /// <returns>The newly created <see cref="ClaimsResponse"/> instance.</returns> - public ClaimsResponse CreateResponse() { - if (this.typeUriDeserializedFrom == null) { - throw new InvalidOperationException(OpenIdStrings.CallDeserializeBeforeCreateResponse); - } - - return new ClaimsResponse(this.typeUriDeserializedFrom); - } - - /// <summary> - /// Sets the profile request properties according to a list of - /// field names that might have been passed in the OpenId query dictionary. - /// </summary> - /// <param name="fieldNames"> - /// The list of field names that should receive a given - /// <paramref name="requestLevel"/>. These field names should match - /// the OpenId specification for field names, omitting the 'openid.sreg' prefix. - /// </param> - /// <param name="requestLevel">The none/request/require state of the listed fields.</param> - internal void SetProfileRequestFromList(IEnumerable<string> fieldNames, DemandLevel requestLevel) { - foreach (string field in fieldNames) { - switch (field) { - case "": // this occurs for empty lists - break; - case Constants.nickname: - this.Nickname = requestLevel; - break; - case Constants.email: - this.Email = requestLevel; - break; - case Constants.fullname: - this.FullName = requestLevel; - break; - case Constants.dob: - this.BirthDate = requestLevel; - break; - case Constants.gender: - this.Gender = requestLevel; - break; - case Constants.postcode: - this.PostalCode = requestLevel; - break; - case Constants.country: - this.Country = requestLevel; - break; - case Constants.language: - this.Language = requestLevel; - break; - case Constants.timezone: - this.TimeZone = requestLevel; - break; - default: - Logger.OpenId.WarnFormat("ClaimsRequest.SetProfileRequestFromList: Unrecognized field name '{0}'.", field); - break; - } - } - } - - /// <summary> - /// Assembles the profile parameter names that have a given <see cref="DemandLevel"/>. - /// </summary> - /// <param name="level">The demand level (request, require, none).</param> - /// <returns>An array of the profile parameter names that meet the criteria.</returns> - [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called by messaging framework via reflection.")] - private string[] AssembleProfileFields(DemandLevel level) { - List<string> fields = new List<string>(10); - if (this.Nickname == level) { - fields.Add(Constants.nickname); - } if (this.Email == level) { - fields.Add(Constants.email); - } if (this.FullName == level) { - fields.Add(Constants.fullname); - } if (this.BirthDate == level) { - fields.Add(Constants.dob); - } if (this.Gender == level) { - fields.Add(Constants.gender); - } if (this.PostalCode == level) { - fields.Add(Constants.postcode); - } if (this.Country == level) { - fields.Add(Constants.country); - } if (this.Language == level) { - fields.Add(Constants.language); - } if (this.TimeZone == level) { - fields.Add(Constants.timezone); - } - - return fields.ToArray(); - } - } -} diff --git a/src/DotNetOpenAuth/OpenId/Extensions/SimpleRegistration/ClaimsResponse.cs b/src/DotNetOpenAuth/OpenId/Extensions/SimpleRegistration/ClaimsResponse.cs deleted file mode 100644 index d4df028..0000000 --- a/src/DotNetOpenAuth/OpenId/Extensions/SimpleRegistration/ClaimsResponse.cs +++ /dev/null @@ -1,357 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="ClaimsResponse.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OpenId.Extensions.SimpleRegistration { - using System; - using System.Collections.Generic; - using System.Diagnostics.CodeAnalysis; - using System.Diagnostics.Contracts; - using System.Globalization; - using System.Net.Mail; - using System.Text; - using System.Text.RegularExpressions; - using System.Xml.Serialization; - using DotNetOpenAuth.Messaging; - using DotNetOpenAuth.OpenId.Messages; - - /// <summary> - /// A struct storing Simple Registration field values describing an - /// authenticating user. - /// </summary> - [Serializable] - public sealed class ClaimsResponse : ExtensionBase, IClientScriptExtensionResponse, IMessageWithEvents { - /// <summary> - /// The factory method that may be used in deserialization of this message. - /// </summary> - internal static readonly StandardOpenIdExtensionFactory.CreateDelegate Factory = (typeUri, data, baseMessage, isProviderRole) => { - if ((typeUri == Constants.sreg_ns || Array.IndexOf(Constants.AdditionalTypeUris, typeUri) >= 0) && !isProviderRole) { - return new ClaimsResponse(typeUri); - } - - return null; - }; - - /// <summary> - /// The allowed format for birthdates. - /// </summary> - private static readonly Regex birthDateValidator = new Regex(@"^\d\d\d\d-\d\d-\d\d$"); - - /// <summary> - /// Storage for the raw string birthdate value. - /// </summary> - private string birthDateRaw; - - /// <summary> - /// Backing field for the <see cref="BirthDate"/> property. - /// </summary> - private DateTime? birthDate; - - /// <summary> - /// Backing field for the <see cref="Culture"/> property. - /// </summary> - private CultureInfo culture; - - /// <summary> - /// Initializes a new instance of the <see cref="ClaimsResponse"/> class. - /// </summary> - internal ClaimsResponse() - : this(Constants.sreg_ns) { - } - - /// <summary> - /// Initializes a new instance of the <see cref="ClaimsResponse"/> class. - /// </summary> - /// <param name="typeUriToUse"> - /// The type URI that must be used to identify this extension in the response message. - /// This value should be the same one the relying party used to send the extension request. - /// </param> - internal ClaimsResponse(string typeUriToUse) - : base(new Version(1, 0), typeUriToUse, Constants.AdditionalTypeUris) { - Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(typeUriToUse)); - } - - /// <summary> - /// Gets or sets the nickname the user goes by. - /// </summary> - [MessagePart(Constants.nickname)] - public string Nickname { get; set; } - - /// <summary> - /// Gets or sets the user's email address. - /// </summary> - [MessagePart(Constants.email)] - public string Email { get; set; } - - /// <summary> - /// Gets or sets the full name of a user as a single string. - /// </summary> - [MessagePart(Constants.fullname)] - public string FullName { get; set; } - - /// <summary> - /// Gets or sets the user's birthdate. - /// </summary> - public DateTime? BirthDate { - get { - return this.birthDate; - } - - set { - this.birthDate = value; - - // Don't use property accessor for peer property to avoid infinite loop between the two proeprty accessors. - if (value.HasValue) { - this.birthDateRaw = value.Value.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture); - } else { - this.birthDateRaw = null; - } - } - } - - /// <summary> - /// Gets or sets the raw birth date string given by the extension. - /// </summary> - /// <value>A string in the format yyyy-MM-dd.</value> - [MessagePart(Constants.dob)] - public string BirthDateRaw { - get { - return this.birthDateRaw; - } - - set { - ErrorUtilities.VerifyArgument(value == null || birthDateValidator.IsMatch(value), OpenIdStrings.SregInvalidBirthdate); - if (value != null) { - // Update the BirthDate property, if possible. - // Don't use property accessor for peer property to avoid infinite loop between the two proeprty accessors. - // Some valid sreg dob values like "2000-00-00" will not work as a DateTime struct, - // in which case we null it out, but don't show any error. - DateTime newBirthDate; - if (DateTime.TryParse(value, out newBirthDate)) { - this.birthDate = newBirthDate; - } else { - Logger.OpenId.WarnFormat("Simple Registration birthdate '{0}' could not be parsed into a DateTime and may not include month and/or day information. Setting BirthDate property to null.", value); - this.birthDate = null; - } - } else { - this.birthDate = null; - } - - this.birthDateRaw = value; - } - } - - /// <summary> - /// Gets or sets the gender of the user. - /// </summary> - [MessagePart(Constants.gender, Encoder = typeof(GenderEncoder))] - public Gender? Gender { get; set; } - - /// <summary> - /// Gets or sets the zip code / postal code of the user. - /// </summary> - [MessagePart(Constants.postcode)] - public string PostalCode { get; set; } - - /// <summary> - /// Gets or sets the country of the user. - /// </summary> - [MessagePart(Constants.country)] - public string Country { get; set; } - - /// <summary> - /// Gets or sets the primary/preferred language of the user. - /// </summary> - [MessagePart(Constants.language)] - public string Language { get; set; } - - /// <summary> - /// Gets or sets the user's timezone. - /// </summary> - [MessagePart(Constants.timezone)] - public string TimeZone { get; set; } - - /// <summary> - /// Gets a combination of the user's full name and email address. - /// </summary> - public MailAddress MailAddress { - get { - if (string.IsNullOrEmpty(this.Email)) { - return null; - } else if (string.IsNullOrEmpty(this.FullName)) { - return new MailAddress(this.Email); - } else { - return new MailAddress(this.Email, this.FullName); - } - } - } - - /// <summary> - /// Gets or sets a combination o the language and country of the user. - /// </summary> - [XmlIgnore] - public CultureInfo Culture { - get { - if (this.culture == null && !string.IsNullOrEmpty(this.Language)) { - string cultureString = string.Empty; - cultureString = this.Language; - if (!string.IsNullOrEmpty(this.Country)) { - cultureString += "-" + this.Country; - } - this.culture = CultureInfo.GetCultureInfo(cultureString); - } - - return this.culture; - } - - set { - this.culture = value; - this.Language = (value != null) ? value.TwoLetterISOLanguageName : null; - int indexOfHyphen = (value != null) ? value.Name.IndexOf('-') : -1; - this.Country = indexOfHyphen > 0 ? value.Name.Substring(indexOfHyphen + 1) : null; - } - } - - /// <summary> - /// Gets a value indicating whether this extension is signed by the Provider. - /// </summary> - /// <value> - /// <c>true</c> if this instance is signed by the Provider; otherwise, <c>false</c>. - /// </value> - public bool IsSignedByProvider { - get { return this.IsSignedByRemoteParty; } - } - - /// <summary> - /// Tests equality of two <see cref="ClaimsResponse"/> objects. - /// </summary> - /// <param name="one">One instance to compare.</param> - /// <param name="other">Another instance to compare.</param> - /// <returns>The result of the operator.</returns> - public static bool operator ==(ClaimsResponse one, ClaimsResponse other) { - return one.EqualsNullSafe(other); - } - - /// <summary> - /// Tests inequality of two <see cref="ClaimsResponse"/> objects. - /// </summary> - /// <param name="one">One instance to compare.</param> - /// <param name="other">Another instance to compare.</param> - /// <returns>The result of the operator.</returns> - public static bool operator !=(ClaimsResponse one, ClaimsResponse other) { - return !(one == other); - } - - /// <summary> - /// Tests equality of two <see cref="ClaimsResponse"/> objects. - /// </summary> - /// <param name="obj">The <see cref="T:System.Object"/> to compare with the current <see cref="T:System.Object"/>.</param> - /// <returns> - /// true if the specified <see cref="T:System.Object"/> is equal to the current <see cref="T:System.Object"/>; otherwise, false. - /// </returns> - /// <exception cref="T:System.NullReferenceException"> - /// The <paramref name="obj"/> parameter is null. - /// </exception> - public override bool Equals(object obj) { - ClaimsResponse other = obj as ClaimsResponse; - if (other == null) { - return false; - } - - return - this.BirthDateRaw.EqualsNullSafe(other.BirthDateRaw) && - this.Country.EqualsNullSafe(other.Country) && - this.Language.EqualsNullSafe(other.Language) && - this.Email.EqualsNullSafe(other.Email) && - this.FullName.EqualsNullSafe(other.FullName) && - this.Gender.Equals(other.Gender) && - this.Nickname.EqualsNullSafe(other.Nickname) && - this.PostalCode.EqualsNullSafe(other.PostalCode) && - this.TimeZone.EqualsNullSafe(other.TimeZone); - } - - /// <summary> - /// Serves as a hash function for a particular type. - /// </summary> - /// <returns> - /// A hash code for the current <see cref="T:System.Object"/>. - /// </returns> - public override int GetHashCode() { - return (this.Nickname != null) ? this.Nickname.GetHashCode() : base.GetHashCode(); - } - - #region IClientScriptExtension Members - - /// <summary> - /// Reads the extension information on an authentication response from the provider. - /// </summary> - /// <param name="response">The incoming OpenID response carrying the extension.</param> - /// <returns> - /// A Javascript snippet that when executed on the user agent returns an object with - /// the information deserialized from the extension response. - /// </returns> - /// <remarks> - /// This method is called <b>before</b> the signature on the assertion response has been - /// verified. Therefore all information in these fields should be assumed unreliable - /// and potentially falsified. - /// </remarks> - string IClientScriptExtensionResponse.InitializeJavaScriptData(IProtocolMessageWithExtensions response) { - var sreg = new Dictionary<string, string>(15); - - // Although we could probably whip up a trip with MessageDictionary - // to avoid explicitly setting each field, doing so would likely - // open ourselves up to security exploits from the OP as it would - // make possible sending arbitrary javascript in arbitrary field names. - sreg[Constants.nickname] = this.Nickname; - sreg[Constants.email] = this.Email; - sreg[Constants.fullname] = this.FullName; - sreg[Constants.dob] = this.BirthDateRaw; - sreg[Constants.gender] = this.Gender.HasValue ? this.Gender.Value.ToString() : null; - sreg[Constants.postcode] = this.PostalCode; - sreg[Constants.country] = this.Country; - sreg[Constants.language] = this.Language; - sreg[Constants.timezone] = this.TimeZone; - - return MessagingUtilities.CreateJsonObject(sreg, false); - } - - #endregion - - #region IMessageWithEvents Members - - /// <summary> - /// Called when the message is about to be transmitted, - /// before it passes through the channel binding elements. - /// </summary> - void IMessageWithEvents.OnSending() { - // Null out empty values so we don't send out a lot of empty parameters. - this.Country = EmptyToNull(this.Country); - this.Email = EmptyToNull(this.Email); - this.FullName = EmptyToNull(this.FullName); - this.Language = EmptyToNull(this.Language); - this.Nickname = EmptyToNull(this.Nickname); - this.PostalCode = EmptyToNull(this.PostalCode); - this.TimeZone = EmptyToNull(this.TimeZone); - } - - /// <summary> - /// Called when the message has been received, - /// after it passes through the channel binding elements. - /// </summary> - void IMessageWithEvents.OnReceiving() { - } - - #endregion - - /// <summary> - /// Translates an empty string value to null, or passes through non-empty values. - /// </summary> - /// <param name="value">The value to consider changing to null.</param> - /// <returns>Either null or a non-empty string.</returns> - private static string EmptyToNull(string value) { - return string.IsNullOrEmpty(value) ? null : value; - } - } -}
\ No newline at end of file diff --git a/src/DotNetOpenAuth/OpenId/Extensions/UI/UIRequest.cs b/src/DotNetOpenAuth/OpenId/Extensions/UI/UIRequest.cs deleted file mode 100644 index df36b5e..0000000 --- a/src/DotNetOpenAuth/OpenId/Extensions/UI/UIRequest.cs +++ /dev/null @@ -1,261 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="UIRequest.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OpenId.Extensions.UI { - using System; - using System.Collections.Generic; - using System.Diagnostics.CodeAnalysis; - using System.Diagnostics.Contracts; - using System.Globalization; - using System.Linq; - using DotNetOpenAuth.Messaging; - using DotNetOpenAuth.OpenId.Messages; - using DotNetOpenAuth.OpenId.Provider; - using DotNetOpenAuth.OpenId.RelyingParty; - using DotNetOpenAuth.Xrds; - - /// <summary> - /// OpenID User Interface extension 1.0 request message. - /// </summary> - /// <remarks> - /// <para>Implements the extension described by: http://wiki.openid.net/f/openid_ui_extension_draft01.html </para> - /// <para>This extension only applies to checkid_setup requests, since checkid_immediate requests display - /// no UI to the user. </para> - /// <para>For rules about how the popup window should be displayed, please see the documentation of - /// <see cref="UIModes.Popup"/>. </para> - /// <para>An RP may determine whether an arbitrary OP supports this extension (and thereby determine - /// whether to use a standard full window redirect or a popup) via the - /// <see cref="IdentifierDiscoveryResult.IsExtensionSupported<T>()"/> method.</para> - /// </remarks> - [Serializable] - public class UIRequest : IOpenIdMessageExtension, IMessageWithEvents { - /// <summary> - /// The factory method that may be used in deserialization of this message. - /// </summary> - internal static readonly StandardOpenIdExtensionFactory.CreateDelegate Factory = (typeUri, data, baseMessage, isProviderRole) => { - if (typeUri == UIConstants.UITypeUri && isProviderRole) { - return new UIRequest(); - } - - return null; - }; - - /// <summary> - /// Additional type URIs that this extension is sometimes known by remote parties. - /// </summary> - private static readonly string[] additionalTypeUris = new string[] { - UIConstants.LangPrefSupported, - UIConstants.PopupSupported, - UIConstants.IconSupported, - }; - - /// <summary> - /// Backing store for <see cref="ExtraData"/>. - /// </summary> - private Dictionary<string, string> extraData = new Dictionary<string, string>(); - - /// <summary> - /// Initializes a new instance of the <see cref="UIRequest"/> class. - /// </summary> - public UIRequest() { - this.LanguagePreference = new[] { CultureInfo.CurrentUICulture }; - this.Mode = UIModes.Popup; - } - - /// <summary> - /// Gets or sets the list of user's preferred languages, sorted in decreasing preferred order. - /// </summary> - /// <value>The default is the <see cref="CultureInfo.CurrentUICulture"/> of the thread that created this instance.</value> - /// <remarks> - /// The user's preferred languages as a [BCP 47] language priority list, represented as a comma-separated list of BCP 47 basic language ranges in descending priority order. For instance, the value "fr-CA,fr-FR,en-CA" represents the preference for French spoken in Canada, French spoken in France, followed by English spoken in Canada. - /// </remarks> - [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification = "By design.")] - [MessagePart("lang", AllowEmpty = false)] - public CultureInfo[] LanguagePreference { get; set; } - - /// <summary> - /// Gets or sets the style of UI that the RP is hosting the OP's authentication page in. - /// </summary> - /// <value>Some value from the <see cref="UIModes"/> class. Defaults to <see cref="UIModes.Popup"/>.</value> - [MessagePart("mode", AllowEmpty = false, IsRequired = true)] - public string Mode { get; set; } - - /// <summary> - /// Gets or sets a value indicating whether the Relying Party has an icon - /// it would like the Provider to display to the user while asking them - /// whether they would like to log in. - /// </summary> - /// <value><c>true</c> if the Provider should display an icon; otherwise, <c>false</c>.</value> - /// <remarks> - /// By default, the Provider displays the relying party's favicon.ico. - /// </remarks> - [MessagePart("icon", AllowEmpty = false, IsRequired = false)] - public bool? Icon { get; set; } - - #region IOpenIdMessageExtension Members - - /// <summary> - /// Gets the TypeURI the extension uses in the OpenID protocol and in XRDS advertisements. - /// </summary> - /// <value></value> - public string TypeUri { get { return UIConstants.UITypeUri; } } - - /// <summary> - /// Gets the additional TypeURIs that are supported by this extension, in preferred order. - /// May be empty if none other than <see cref="TypeUri"/> is supported, but - /// should not be null. - /// </summary> - /// <remarks> - /// Useful for reading in messages with an older version of an extension. - /// The value in the <see cref="TypeUri"/> property is always checked before - /// trying this list. - /// If you do support multiple versions of an extension using this method, - /// consider adding a CreateResponse method to your request extension class - /// so that the response can have the context it needs to remain compatible - /// given the version of the extension in the request message. - /// The <see cref="Extensions.SimpleRegistration.ClaimsRequest.CreateResponse"/> for an example. - /// </remarks> - public IEnumerable<string> AdditionalSupportedTypeUris { get { return additionalTypeUris; } } - - /// <summary> - /// Gets or sets a value indicating whether this extension was - /// signed by the sender. - /// </summary> - /// <value> - /// <c>true</c> if this instance is signed by the sender; otherwise, <c>false</c>. - /// </value> - public bool IsSignedByRemoteParty { get; set; } - - #endregion - - #region IMessage Properties - - /// <summary> - /// Gets the version of the protocol or extension this message is prepared to implement. - /// </summary> - /// <value>The value 1.0.</value> - /// <remarks> - /// Implementations of this interface should ensure that this property never returns null. - /// </remarks> - public Version Version { - get { return new Version(1, 0); } - } - - /// <summary> - /// Gets the extra, non-standard Protocol parameters included in the message. - /// </summary> - /// <remarks> - /// Implementations of this interface should ensure that this property never returns null. - /// </remarks> - public IDictionary<string, string> ExtraData { - get { return this.extraData; } - } - - #endregion - - /// <summary> - /// Gets the URL of the RP icon for the OP to display. - /// </summary> - /// <param name="realm">The realm of the RP where the authentication request originated.</param> - /// <param name="webRequestHandler">The web request handler to use for discovery. - /// Usually available via <see cref="Channel.WebRequestHandler">OpenIdProvider.Channel.WebRequestHandler</see>.</param> - /// <returns> - /// A sequence of the RP's icons it has available for the Provider to display, in decreasing preferred order. - /// </returns> - /// <value>The icon URL.</value> - /// <remarks> - /// This property is automatically set for the OP with the result of RP discovery. - /// RPs should set this value by including an entry such as this in their XRDS document. - /// <example> - /// <Service xmlns="xri://$xrd*($v*2.0)"> - /// <Type>http://specs.openid.net/extensions/ui/icon</Type> - /// <URI>http://consumer.example.com/images/image.jpg</URI> - /// </Service> - /// </example> - /// </remarks> - public static IEnumerable<Uri> GetRelyingPartyIconUrls(Realm realm, IDirectWebRequestHandler webRequestHandler) { - Contract.Requires(realm != null); - Contract.Requires(webRequestHandler != null); - ErrorUtilities.VerifyArgumentNotNull(realm, "realm"); - ErrorUtilities.VerifyArgumentNotNull(webRequestHandler, "webRequestHandler"); - - XrdsDocument xrds = realm.Discover(webRequestHandler, false); - if (xrds == null) { - return Enumerable.Empty<Uri>(); - } else { - return xrds.FindRelyingPartyIcons(); - } - } - - /// <summary> - /// Gets the URL of the RP icon for the OP to display. - /// </summary> - /// <param name="realm">The realm of the RP where the authentication request originated.</param> - /// <param name="provider">The Provider instance used to obtain the authentication request.</param> - /// <returns> - /// A sequence of the RP's icons it has available for the Provider to display, in decreasing preferred order. - /// </returns> - /// <value>The icon URL.</value> - /// <remarks> - /// This property is automatically set for the OP with the result of RP discovery. - /// RPs should set this value by including an entry such as this in their XRDS document. - /// <example> - /// <Service xmlns="xri://$xrd*($v*2.0)"> - /// <Type>http://specs.openid.net/extensions/ui/icon</Type> - /// <URI>http://consumer.example.com/images/image.jpg</URI> - /// </Service> - /// </example> - /// </remarks> - public static IEnumerable<Uri> GetRelyingPartyIconUrls(Realm realm, OpenIdProvider provider) { - Contract.Requires(realm != null); - Contract.Requires(provider != null); - ErrorUtilities.VerifyArgumentNotNull(realm, "realm"); - ErrorUtilities.VerifyArgumentNotNull(provider, "provider"); - - return GetRelyingPartyIconUrls(realm, provider.Channel.WebRequestHandler); - } - - #region IMessage methods - - /// <summary> - /// Checks the message state for conformity to the protocol specification - /// and throws an exception if the message is invalid. - /// </summary> - /// <remarks> - /// <para>Some messages have required fields, or combinations of fields that must relate to each other - /// in specialized ways. After deserializing a message, this method checks the state of the - /// message to see if it conforms to the protocol.</para> - /// <para>Note that this property should <i>not</i> check signatures or perform any state checks - /// outside this scope of this particular message.</para> - /// </remarks> - /// <exception cref="ProtocolException">Thrown if the message is invalid.</exception> - public void EnsureValidMessage() { - } - - #endregion - - #region IMessageWithEvents Members - - /// <summary> - /// Called when the message is about to be transmitted, - /// before it passes through the channel binding elements. - /// </summary> - public void OnSending() { - } - - /// <summary> - /// Called when the message has been received, - /// after it passes through the channel binding elements. - /// </summary> - public void OnReceiving() { - if (this.LanguagePreference != null) { - // TODO: see if we can change the CultureInfo.CurrentUICulture somehow - } - } - - #endregion - } -} diff --git a/src/DotNetOpenAuth/OpenId/Extensions/UI/UIUtilities.cs b/src/DotNetOpenAuth/OpenId/Extensions/UI/UIUtilities.cs deleted file mode 100644 index cee6882..0000000 --- a/src/DotNetOpenAuth/OpenId/Extensions/UI/UIUtilities.cs +++ /dev/null @@ -1,52 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="UIUtilities.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OpenId.Extensions.UI { - using System; - using System.Diagnostics.Contracts; - using System.Globalization; - using DotNetOpenAuth.Messaging; - using DotNetOpenAuth.OpenId.RelyingParty; - - /// <summary> - /// Constants used in implementing support for the UI extension. - /// </summary> - public static class UIUtilities { - /// <summary> - /// The required width of the popup window the relying party creates for the provider. - /// </summary> - public const int PopupWidth = 500; // UI extension calls for 450px, but Yahoo needs 500px - - /// <summary> - /// The required height of the popup window the relying party creates for the provider. - /// </summary> - public const int PopupHeight = 500; - - /// <summary> - /// Gets the <c>window.open</c> javascript snippet to use to open a popup window - /// compliant with the UI extension. - /// </summary> - /// <param name="relyingParty">The relying party.</param> - /// <param name="request">The authentication request to place in the window.</param> - /// <param name="windowName">The name to assign to the popup window.</param> - /// <returns>A string starting with 'window.open' and forming just that one method call.</returns> - internal static string GetWindowPopupScript(OpenIdRelyingParty relyingParty, IAuthenticationRequest request, string windowName) { - Contract.Requires<ArgumentNullException>(relyingParty != null); - Contract.Requires<ArgumentNullException>(request != null); - Contract.Requires<ArgumentException>(!string.IsNullOrEmpty(windowName)); - - Uri popupUrl = request.RedirectingResponse.GetDirectUriRequest(relyingParty.Channel); - - return string.Format( - CultureInfo.InvariantCulture, - "(window.showModalDialog ? window.showModalDialog({0}, {1}, 'status:0;resizable:1;scroll:1;center:1;dialogWidth:{2}px; dialogHeight:{3}') : window.open({0}, {1}, 'status=0,toolbar=0,location=1,resizable=1,scrollbars=1,left=' + ((screen.width - {2}) / 2) + ',top=' + ((screen.height - {3}) / 2) + ',width={2},height={3}'));", - MessagingUtilities.GetSafeJavascriptValue(popupUrl.AbsoluteUri), - MessagingUtilities.GetSafeJavascriptValue(windowName), - PopupWidth, - PopupHeight); - } - } -} diff --git a/src/DotNetOpenAuth/OpenId/HmacShaAssociation.cs b/src/DotNetOpenAuth/OpenId/HmacShaAssociation.cs deleted file mode 100644 index 29da1be..0000000 --- a/src/DotNetOpenAuth/OpenId/HmacShaAssociation.cs +++ /dev/null @@ -1,306 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="HmacShaAssociation.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OpenId { - using System; - using System.Collections.Generic; - using System.Diagnostics; - using System.Diagnostics.Contracts; - using System.Globalization; - using System.Linq; - using System.Security.Cryptography; - using DotNetOpenAuth.Messaging; - using DotNetOpenAuth.OpenId; - using DotNetOpenAuth.OpenId.Messages; - using DotNetOpenAuth.OpenId.Provider; - - /// <summary> - /// An association that uses the HMAC-SHA family of algorithms for message signing. - /// </summary> - [ContractVerification(true)] - internal class HmacShaAssociation : Association { - /// <summary> - /// The default lifetime of a shared association when no lifetime is given - /// for a specific association type. - /// </summary> - private static readonly TimeSpan DefaultMaximumLifetime = TimeSpan.FromDays(14); - - /// <summary> - /// A list of HMAC-SHA algorithms in order of decreasing bit lengths. - /// </summary> - private static HmacSha[] hmacShaAssociationTypes = new List<HmacSha> { - new HmacSha { - CreateHasher = secretKey => new HMACSHA512(secretKey), - GetAssociationType = protocol => protocol.Args.SignatureAlgorithm.HMAC_SHA512, - BaseHashAlgorithm = new SHA512Managed(), - }, - new HmacSha { - CreateHasher = secretKey => new HMACSHA384(secretKey), - GetAssociationType = protocol => protocol.Args.SignatureAlgorithm.HMAC_SHA384, - BaseHashAlgorithm = new SHA384Managed(), - }, - new HmacSha { - CreateHasher = secretKey => new HMACSHA256(secretKey), - GetAssociationType = protocol => protocol.Args.SignatureAlgorithm.HMAC_SHA256, - BaseHashAlgorithm = new SHA256Managed(), - }, - new HmacSha { - CreateHasher = secretKey => new HMACSHA1(secretKey), - GetAssociationType = protocol => protocol.Args.SignatureAlgorithm.HMAC_SHA1, - BaseHashAlgorithm = new SHA1Managed(), - }, - } .ToArray(); - - /// <summary> - /// The specific variety of HMAC-SHA this association is based on (whether it be HMAC-SHA1, HMAC-SHA256, etc.) - /// </summary> - private HmacSha typeIdentity; - - /// <summary> - /// Initializes a new instance of the <see cref="HmacShaAssociation"/> class. - /// </summary> - /// <param name="typeIdentity">The specific variety of HMAC-SHA this association is based on (whether it be HMAC-SHA1, HMAC-SHA256, etc.)</param> - /// <param name="handle">The association handle.</param> - /// <param name="secret">The association secret.</param> - /// <param name="totalLifeLength">The time duration the association will be good for.</param> - private HmacShaAssociation(HmacSha typeIdentity, string handle, byte[] secret, TimeSpan totalLifeLength) - : base(handle, secret, totalLifeLength, DateTime.UtcNow) { - Contract.Requires<ArgumentNullException>(typeIdentity != null); - Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(handle)); - Contract.Requires<ArgumentNullException>(secret != null); - Contract.Requires<ArgumentOutOfRangeException>(totalLifeLength > TimeSpan.Zero); - Contract.Ensures(this.TotalLifeLength == totalLifeLength); - ErrorUtilities.VerifyProtocol(secret.Length == typeIdentity.SecretLength, OpenIdStrings.AssociationSecretAndTypeLengthMismatch, secret.Length, typeIdentity.GetAssociationType(Protocol.Default)); - - this.typeIdentity = typeIdentity; - } - - /// <summary> - /// Gets the length (in bits) of the hash this association creates when signing. - /// </summary> - public override int HashBitLength { - get { - Protocol protocol = Protocol.Default; - return HmacShaAssociation.GetSecretLength(protocol, this.GetAssociationType(protocol)) * 8; - } - } - - /// <summary> - /// Creates an HMAC-SHA association. - /// </summary> - /// <param name="protocol">The OpenID protocol version that the request for an association came in on.</param> - /// <param name="associationType">The value of the openid.assoc_type parameter.</param> - /// <param name="handle">The association handle.</param> - /// <param name="secret">The association secret.</param> - /// <param name="totalLifeLength">How long the association will be good for.</param> - /// <returns>The newly created association.</returns> - public static HmacShaAssociation Create(Protocol protocol, string associationType, string handle, byte[] secret, TimeSpan totalLifeLength) { - Contract.Requires<ArgumentNullException>(protocol != null); - Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(associationType)); - Contract.Requires<ArgumentNullException>(secret != null); - Contract.Ensures(Contract.Result<HmacShaAssociation>() != null); - HmacSha match = hmacShaAssociationTypes.FirstOrDefault(sha => String.Equals(sha.GetAssociationType(protocol), associationType, StringComparison.Ordinal)); - ErrorUtilities.VerifyProtocol(match != null, OpenIdStrings.NoAssociationTypeFoundByName, associationType); - return new HmacShaAssociation(match, handle, secret, totalLifeLength); - } - - /// <summary> - /// Creates an association with the specified handle, secret, and lifetime. - /// </summary> - /// <param name="handle">The handle.</param> - /// <param name="secret">The secret.</param> - /// <param name="totalLifeLength">Total lifetime.</param> - /// <returns>The newly created association.</returns> - public static HmacShaAssociation Create(string handle, byte[] secret, TimeSpan totalLifeLength) { - Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(handle)); - Contract.Requires<ArgumentNullException>(secret != null); - Contract.Ensures(Contract.Result<HmacShaAssociation>() != null); - - HmacSha shaType = hmacShaAssociationTypes.FirstOrDefault(sha => sha.SecretLength == secret.Length); - ErrorUtilities.VerifyProtocol(shaType != null, OpenIdStrings.NoAssociationTypeFoundByLength, secret.Length); - return new HmacShaAssociation(shaType, handle, secret, totalLifeLength); - } - - /// <summary> - /// Returns the length of the shared secret (in bytes). - /// </summary> - /// <param name="protocol">The protocol version being used that will be used to lookup the text in <paramref name="associationType"/></param> - /// <param name="associationType">The value of the protocol argument specifying the type of association. For example: "HMAC-SHA1".</param> - /// <returns>The length (in bytes) of the association secret.</returns> - /// <exception cref="ProtocolException">Thrown if no association can be found by the given name.</exception> - public static int GetSecretLength(Protocol protocol, string associationType) { - HmacSha match = hmacShaAssociationTypes.FirstOrDefault(shaType => String.Equals(shaType.GetAssociationType(protocol), associationType, StringComparison.Ordinal)); - ErrorUtilities.VerifyProtocol(match != null, OpenIdStrings.NoAssociationTypeFoundByName, associationType); - return match.SecretLength; - } - - /// <summary> - /// Creates a new association of a given type at an OpenID Provider. - /// </summary> - /// <param name="protocol">The protocol.</param> - /// <param name="associationType">Type of the association (i.e. HMAC-SHA1 or HMAC-SHA256)</param> - /// <param name="associationUse">A value indicating whether the new association will be used privately by the Provider for "dumb mode" authentication - /// or shared with the Relying Party for "smart mode" authentication.</param> - /// <param name="associationStore">The Provider's association store.</param> - /// <param name="securitySettings">The security settings of the Provider.</param> - /// <returns> - /// The newly created association. - /// </returns> - /// <remarks> - /// The new association is NOT automatically put into an association store. This must be done by the caller. - /// </remarks> - internal static HmacShaAssociation Create(Protocol protocol, string associationType, AssociationRelyingPartyType associationUse, IProviderAssociationStore associationStore, ProviderSecuritySettings securitySettings) { - Contract.Requires<ArgumentNullException>(protocol != null); - Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(associationType)); - Contract.Requires<ArgumentNullException>(associationStore != null); - Contract.Requires<ArgumentNullException>(securitySettings != null); - Contract.Ensures(Contract.Result<HmacShaAssociation>() != null); - - int secretLength = GetSecretLength(protocol, associationType); - - // Generate the secret that will be used for signing - byte[] secret = MessagingUtilities.GetCryptoRandomData(secretLength); - - TimeSpan lifetime; - if (associationUse == AssociationRelyingPartyType.Smart) { - if (!securitySettings.AssociationLifetimes.TryGetValue(associationType, out lifetime)) { - lifetime = DefaultMaximumLifetime; - } - } else { - lifetime = DumbSecretLifetime; - } - - string handle = associationStore.Serialize(secret, DateTime.UtcNow + lifetime, associationUse == AssociationRelyingPartyType.Dumb); - - Contract.Assert(protocol != null); // All the way up to the method call, the condition holds, yet we get a Requires failure next - Contract.Assert(secret != null); - Contract.Assert(!String.IsNullOrEmpty(associationType)); - var result = Create(protocol, associationType, handle, secret, lifetime); - return result; - } - - /// <summary> - /// Looks for the first association type in a preferred-order list that is - /// likely to be supported given a specific OpenID version and the security settings, - /// and perhaps a matching Diffie-Hellman session type. - /// </summary> - /// <param name="protocol">The OpenID version that dictates which associations are available.</param> - /// <param name="highSecurityIsBetter">A value indicating whether to consider higher strength security to be better. Use <c>true</c> for initial association requests from the Relying Party; use <c>false</c> from Providers when the Relying Party asks for an unrecognized association in order to pick a suggested alternative that is likely to be supported on both sides.</param> - /// <param name="securityRequirements">The set of requirements the selected association type must comply to.</param> - /// <param name="requireMatchingDHSessionType">Use <c>true</c> for HTTP associations, <c>false</c> for HTTPS associations.</param> - /// <param name="associationType">The resulting association type's well known protocol name. (i.e. HMAC-SHA256)</param> - /// <param name="sessionType">The resulting session type's well known protocol name, if a matching one is available. (i.e. DH-SHA256)</param> - /// <returns> - /// True if a qualifying association could be found; false otherwise. - /// </returns> - internal static bool TryFindBestAssociation(Protocol protocol, bool highSecurityIsBetter, SecuritySettings securityRequirements, bool requireMatchingDHSessionType, out string associationType, out string sessionType) { - Contract.Requires<ArgumentNullException>(protocol != null); - Contract.Requires<ArgumentNullException>(securityRequirements != null); - - associationType = null; - sessionType = null; - - // We use AsEnumerable() to avoid VerificationException (http://stackoverflow.com/questions/478422/why-does-simple-array-and-linq-generate-verificationexception-operation-could-de) - IEnumerable<HmacSha> preferredOrder = highSecurityIsBetter ? - hmacShaAssociationTypes.AsEnumerable() : hmacShaAssociationTypes.Reverse(); - - foreach (HmacSha sha in preferredOrder) { - int hashSizeInBits = sha.SecretLength * 8; - if (hashSizeInBits > securityRequirements.MaximumHashBitLength || - hashSizeInBits < securityRequirements.MinimumHashBitLength) { - continue; - } - sessionType = DiffieHellmanUtilities.GetNameForSize(protocol, hashSizeInBits); - if (requireMatchingDHSessionType && sessionType == null) { - continue; - } - associationType = sha.GetAssociationType(protocol); - if (associationType == null) { - continue; - } - - return true; - } - - return false; - } - - /// <summary> - /// Determines whether a named Diffie-Hellman session type and association type can be used together. - /// </summary> - /// <param name="protocol">The protocol carrying the names of the session and association types.</param> - /// <param name="associationType">The value of the openid.assoc_type parameter.</param> - /// <param name="sessionType">The value of the openid.session_type parameter.</param> - /// <returns> - /// <c>true</c> if the named association and session types are compatible; otherwise, <c>false</c>. - /// </returns> - internal static bool IsDHSessionCompatible(Protocol protocol, string associationType, string sessionType) { - Contract.Requires<ArgumentNullException>(protocol != null); - Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(associationType)); - Contract.Requires<ArgumentNullException>(sessionType != null); - - // All association types can work when no DH session is used at all. - if (string.Equals(sessionType, protocol.Args.SessionType.NoEncryption, StringComparison.Ordinal)) { - return true; - } - - // When there _is_ a DH session, it must match in hash length with the association type. - int associationSecretLengthInBytes = GetSecretLength(protocol, associationType); - int sessionHashLengthInBytes = DiffieHellmanUtilities.Lookup(protocol, sessionType).HashSize / 8; - return associationSecretLengthInBytes == sessionHashLengthInBytes; - } - - /// <summary> - /// Gets the string to pass as the assoc_type value in the OpenID protocol. - /// </summary> - /// <param name="protocol">The protocol version of the message that the assoc_type value will be included in.</param> - /// <returns> - /// The value that should be used for the openid.assoc_type parameter. - /// </returns> - [Pure] - internal override string GetAssociationType(Protocol protocol) { - return this.typeIdentity.GetAssociationType(protocol); - } - - /// <summary> - /// Returns the specific hash algorithm used for message signing. - /// </summary> - /// <returns> - /// The hash algorithm used for message signing. - /// </returns> - [Pure] - protected override HashAlgorithm CreateHasher() { - var result = this.typeIdentity.CreateHasher(SecretKey); - Contract.Assume(result != null); - return result; - } - - /// <summary> - /// Provides information about some HMAC-SHA hashing algorithm that OpenID supports. - /// </summary> - private class HmacSha { - /// <summary> - /// Gets or sets the function that takes a particular OpenID version and returns the value of the openid.assoc_type parameter in that protocol. - /// </summary> - internal Func<Protocol, string> GetAssociationType { get; set; } - - /// <summary> - /// Gets or sets a function that will create the <see cref="HashAlgorithm"/> using a given shared secret for the mac. - /// </summary> - internal Func<byte[], HashAlgorithm> CreateHasher { get; set; } - - /// <summary> - /// Gets or sets the base hash algorithm. - /// </summary> - internal HashAlgorithm BaseHashAlgorithm { get; set; } - - /// <summary> - /// Gets the size of the hash (in bytes). - /// </summary> - internal int SecretLength { get { return this.BaseHashAlgorithm.HashSize / 8; } } - } - } -}
\ No newline at end of file diff --git a/src/DotNetOpenAuth/OpenId/HostMetaDiscoveryService.cs b/src/DotNetOpenAuth/OpenId/HostMetaDiscoveryService.cs deleted file mode 100644 index 215ea24..0000000 --- a/src/DotNetOpenAuth/OpenId/HostMetaDiscoveryService.cs +++ /dev/null @@ -1,516 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="HostMetaDiscoveryService.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OpenId { - using System; - using System.Collections.Generic; - using System.Diagnostics.CodeAnalysis; - using System.Diagnostics.Contracts; - using System.Globalization; - using System.IO; - using System.Linq; - using System.Net; - using System.Security; - using System.Security.Cryptography; - using System.Security.Cryptography.X509Certificates; - using System.Security.Permissions; - using System.Text; - using System.Text.RegularExpressions; - using System.Xml; - using System.Xml.XPath; - using DotNetOpenAuth.Messaging; - using DotNetOpenAuth.OpenId.RelyingParty; - using DotNetOpenAuth.Xrds; - using DotNetOpenAuth.Yadis; - - /// <summary> - /// The discovery service to support host-meta based discovery, such as Google Apps for Domains. - /// </summary> - /// <remarks> - /// The spec for this discovery mechanism can be found at: - /// http://groups.google.com/group/google-federated-login-api/web/openid-discovery-for-hosted-domains - /// and the XMLDSig spec referenced in that spec can be found at: - /// http://wiki.oasis-open.org/xri/XrdOne/XmlDsigProfile - /// </remarks> - public class HostMetaDiscoveryService : IIdentifierDiscoveryService { - /// <summary> - /// The URI template for discovery host-meta on domains hosted by - /// Google Apps for Domains. - /// </summary> - private static readonly HostMetaProxy GoogleHostedHostMeta = new HostMetaProxy("https://www.google.com/accounts/o8/.well-known/host-meta?hd={0}", "hosted-id.google.com"); - - /// <summary> - /// Path to the well-known location of the host-meta document at a domain. - /// </summary> - private const string LocalHostMetaPath = "/.well-known/host-meta"; - - /// <summary> - /// The pattern within a host-meta file to look for to obtain the URI to the XRDS document. - /// </summary> - private static readonly Regex HostMetaLink = new Regex(@"^Link: <(?<location>.+?)>; rel=""describedby http://reltype.google.com/openid/xrd-op""; type=""application/xrds\+xml""$"); - - /// <summary> - /// Initializes a new instance of the <see cref="HostMetaDiscoveryService"/> class. - /// </summary> - public HostMetaDiscoveryService() { - this.TrustedHostMetaProxies = new List<HostMetaProxy>(); - } - - /// <summary> - /// Gets the set of URI templates to use to contact host-meta hosting proxies - /// for domain discovery. - /// </summary> - public IList<HostMetaProxy> TrustedHostMetaProxies { get; private set; } - - /// <summary> - /// Gets or sets a value indicating whether to trust Google to host domains' host-meta documents. - /// </summary> - /// <remarks> - /// This property is just a convenient mechanism for checking or changing the set of - /// trusted host-meta proxies in the <see cref="TrustedHostMetaProxies"/> property. - /// </remarks> - public bool UseGoogleHostedHostMeta { - get { - return this.TrustedHostMetaProxies.Contains(GoogleHostedHostMeta); - } - - set { - if (value != this.UseGoogleHostedHostMeta) { - if (value) { - this.TrustedHostMetaProxies.Add(GoogleHostedHostMeta); - } else { - this.TrustedHostMetaProxies.Remove(GoogleHostedHostMeta); - } - } - } - } - - #region IIdentifierDiscoveryService Members - - /// <summary> - /// Performs discovery on the specified identifier. - /// </summary> - /// <param name="identifier">The identifier to perform discovery on.</param> - /// <param name="requestHandler">The means to place outgoing HTTP requests.</param> - /// <param name="abortDiscoveryChain">if set to <c>true</c>, no further discovery services will be called for this identifier.</param> - /// <returns> - /// A sequence of service endpoints yielded by discovery. Must not be null, but may be empty. - /// </returns> - public IEnumerable<IdentifierDiscoveryResult> Discover(Identifier identifier, IDirectWebRequestHandler requestHandler, out bool abortDiscoveryChain) { - abortDiscoveryChain = false; - - // Google Apps are always URIs -- not XRIs. - var uriIdentifier = identifier as UriIdentifier; - if (uriIdentifier == null) { - return Enumerable.Empty<IdentifierDiscoveryResult>(); - } - - var results = new List<IdentifierDiscoveryResult>(); - string signingHost; - using (var response = GetXrdsResponse(uriIdentifier, requestHandler, out signingHost)) { - if (response != null) { - try { - var document = new XrdsDocument(XmlReader.Create(response.ResponseStream)); - ValidateXmlDSig(document, uriIdentifier, response, signingHost); - var xrds = GetXrdElements(document, uriIdentifier.Uri.Host); - - // Look for claimed identifier template URIs for an additional XRDS document. - results.AddRange(GetExternalServices(xrds, uriIdentifier, requestHandler)); - - // If we couldn't find any claimed identifiers, look for OP identifiers. - // Normally this would be the opposite (OP Identifiers take precedence over - // claimed identifiers, but for Google Apps, XRDS' always have OP Identifiers - // mixed in, which the OpenID spec mandate should eclipse Claimed Identifiers, - // which would break positive assertion checks). - if (results.Count == 0) { - results.AddRange(xrds.CreateServiceEndpoints(uriIdentifier, uriIdentifier)); - } - - abortDiscoveryChain = true; - } catch (XmlException ex) { - Logger.Yadis.ErrorFormat("Error while parsing XRDS document at {0} pointed to by host-meta: {1}", response.FinalUri, ex); - } - } - } - - return results; - } - - #endregion - - /// <summary> - /// Gets the XRD elements that have a given CanonicalID. - /// </summary> - /// <param name="document">The XRDS document.</param> - /// <param name="canonicalId">The CanonicalID to match on.</param> - /// <returns>A sequence of XRD elements.</returns> - private static IEnumerable<XrdElement> GetXrdElements(XrdsDocument document, string canonicalId) { - // filter to include only those XRD elements describing the host whose host-meta pointed us to this document. - return document.XrdElements.Where(xrd => string.Equals(xrd.CanonicalID, canonicalId, StringComparison.Ordinal)); - } - - /// <summary> - /// Gets the described-by services in XRD elements. - /// </summary> - /// <param name="xrds">The XRDs to search.</param> - /// <returns>A sequence of services.</returns> - private static IEnumerable<ServiceElement> GetDescribedByServices(IEnumerable<XrdElement> xrds) { - Contract.Requires<ArgumentNullException>(xrds != null); - Contract.Ensures(Contract.Result<IEnumerable<ServiceElement>>() != null); - - var describedBy = from xrd in xrds - from service in xrd.SearchForServiceTypeUris(p => "http://www.iana.org/assignments/relation/describedby") - select service; - return describedBy; - } - - /// <summary> - /// Gets the services for an identifier that are described by an external XRDS document. - /// </summary> - /// <param name="xrds">The XRD elements to search for described-by services.</param> - /// <param name="identifier">The identifier under discovery.</param> - /// <param name="requestHandler">The request handler.</param> - /// <returns>The discovered services.</returns> - private static IEnumerable<IdentifierDiscoveryResult> GetExternalServices(IEnumerable<XrdElement> xrds, UriIdentifier identifier, IDirectWebRequestHandler requestHandler) { - Contract.Requires<ArgumentNullException>(xrds != null); - Contract.Requires<ArgumentNullException>(identifier != null); - Contract.Requires<ArgumentNullException>(requestHandler != null); - Contract.Ensures(Contract.Result<IEnumerable<IdentifierDiscoveryResult>>() != null); - - var results = new List<IdentifierDiscoveryResult>(); - foreach (var serviceElement in GetDescribedByServices(xrds)) { - var templateNode = serviceElement.Node.SelectSingleNode("google:URITemplate", serviceElement.XmlNamespaceResolver); - var nextAuthorityNode = serviceElement.Node.SelectSingleNode("google:NextAuthority", serviceElement.XmlNamespaceResolver); - if (templateNode != null) { - Uri externalLocation = new Uri(templateNode.Value.Trim().Replace("{%uri}", Uri.EscapeDataString(identifier.Uri.AbsoluteUri))); - string nextAuthority = nextAuthorityNode != null ? nextAuthorityNode.Value.Trim() : identifier.Uri.Host; - try { - using (var externalXrdsResponse = GetXrdsResponse(identifier, requestHandler, externalLocation)) { - XrdsDocument externalXrds = new XrdsDocument(XmlReader.Create(externalXrdsResponse.ResponseStream)); - ValidateXmlDSig(externalXrds, identifier, externalXrdsResponse, nextAuthority); - results.AddRange(GetXrdElements(externalXrds, identifier).CreateServiceEndpoints(identifier, identifier)); - } - } catch (ProtocolException ex) { - Logger.Yadis.WarnFormat("HTTP GET error while retrieving described-by XRDS document {0}: {1}", externalLocation.AbsoluteUri, ex); - } catch (XmlException ex) { - Logger.Yadis.ErrorFormat("Error while parsing described-by XRDS document {0}: {1}", externalLocation.AbsoluteUri, ex); - } - } - } - - return results; - } - - /// <summary> - /// Validates the XML digital signature on an XRDS document. - /// </summary> - /// <param name="document">The XRDS document whose signature should be validated.</param> - /// <param name="identifier">The identifier under discovery.</param> - /// <param name="response">The response.</param> - /// <param name="signingHost">The host name on the certificate that should be used to verify the signature in the XRDS.</param> - /// <exception cref="ProtocolException">Thrown if the XRDS document has an invalid or a missing signature.</exception> - [SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "XmlDSig", Justification = "xml")] - private static void ValidateXmlDSig(XrdsDocument document, UriIdentifier identifier, IncomingWebResponse response, string signingHost) { - Contract.Requires<ArgumentNullException>(document != null); - Contract.Requires<ArgumentNullException>(identifier != null); - Contract.Requires<ArgumentNullException>(response != null); - - var signatureNode = document.Node.SelectSingleNode("/xrds:XRDS/ds:Signature", document.XmlNamespaceResolver); - ErrorUtilities.VerifyProtocol(signatureNode != null, OpenIdStrings.MissingElement, "Signature"); - var signedInfoNode = signatureNode.SelectSingleNode("ds:SignedInfo", document.XmlNamespaceResolver); - ErrorUtilities.VerifyProtocol(signedInfoNode != null, OpenIdStrings.MissingElement, "SignedInfo"); - ErrorUtilities.VerifyProtocol( - signedInfoNode.SelectSingleNode("ds:CanonicalizationMethod[@Algorithm='http://docs.oasis-open.org/xri/xrd/2009/01#canonicalize-raw-octets']", document.XmlNamespaceResolver) != null, - OpenIdStrings.UnsupportedCanonicalizationMethod); - ErrorUtilities.VerifyProtocol( - signedInfoNode.SelectSingleNode("ds:SignatureMethod[@Algorithm='http://www.w3.org/2000/09/xmldsig#rsa-sha1']", document.XmlNamespaceResolver) != null, - OpenIdStrings.UnsupportedSignatureMethod); - var certNodes = signatureNode.Select("ds:KeyInfo/ds:X509Data/ds:X509Certificate", document.XmlNamespaceResolver); - ErrorUtilities.VerifyProtocol(certNodes.Count > 0, OpenIdStrings.MissingElement, "X509Certificate"); - var certs = certNodes.Cast<XPathNavigator>().Select(n => new X509Certificate2(Convert.FromBase64String(n.Value.Trim()))).ToList(); - - // Verify that we trust the signer of the certificates. - // Start by trying to validate just the certificate used to sign the XRDS document, - // since we can do that with partial trust. - Logger.OpenId.Debug("Verifying that we trust the certificate used to sign the discovery document."); - if (!certs[0].Verify()) { - // We couldn't verify just the signing certificate, so try to verify the whole certificate chain. - try { - Logger.OpenId.Debug("Verifying the whole certificate chain."); - VerifyCertChain(certs); - Logger.OpenId.Debug("Certificate chain verified."); - } catch (SecurityException) { - Logger.Yadis.Warn("Signing certificate verification failed and we have insufficient code access security permissions to perform certificate chain validation."); - ErrorUtilities.ThrowProtocol(OpenIdStrings.X509CertificateNotTrusted); - } - } - - // Verify that the certificate is issued to the host on whom we are performing discovery. - string hostName = certs[0].GetNameInfo(X509NameType.DnsName, false); - ErrorUtilities.VerifyProtocol(string.Equals(hostName, signingHost, StringComparison.OrdinalIgnoreCase), OpenIdStrings.MisdirectedSigningCertificate, hostName, signingHost); - - // Verify the signature itself - byte[] signature = Convert.FromBase64String(response.Headers["Signature"]); - var provider = (RSACryptoServiceProvider)certs.First().PublicKey.Key; - byte[] data = new byte[response.ResponseStream.Length]; - response.ResponseStream.Seek(0, SeekOrigin.Begin); - response.ResponseStream.Read(data, 0, data.Length); - ErrorUtilities.VerifyProtocol(provider.VerifyData(data, "SHA1", signature), OpenIdStrings.InvalidDSig); - } - - /// <summary> - /// Verifies the cert chain. - /// </summary> - /// <param name="certs">The certs.</param> - /// <remarks> - /// This must be in a method of its own because there is a LinkDemand on the <see cref="X509Chain.Build"/> - /// method. By being in a method of its own, the caller of this method may catch a - /// <see cref="SecurityException"/> that is thrown if we're not running with full trust and execute - /// an alternative plan. - /// </remarks> - /// <exception cref="ProtocolException">Thrown if the certificate chain is invalid or unverifiable.</exception> - [SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", MessageId = "DotNetOpenAuth.Messaging.ErrorUtilities.ThrowProtocol(System.String,System.Object[])", Justification = "The localized portion is a string resource already."), SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands", Justification = "By design")] - private static void VerifyCertChain(List<X509Certificate2> certs) { - var chain = new X509Chain(); - foreach (var cert in certs) { - chain.Build(cert); - } - - if (chain.ChainStatus.Length > 0) { - ErrorUtilities.ThrowProtocol( - string.Format( - CultureInfo.CurrentCulture, - OpenIdStrings.X509CertificateNotTrusted + " {0}", - string.Join(", ", chain.ChainStatus.Select(status => status.StatusInformation).ToArray()))); - } - } - - /// <summary> - /// Gets the XRDS HTTP response for a given identifier. - /// </summary> - /// <param name="identifier">The identifier.</param> - /// <param name="requestHandler">The request handler.</param> - /// <param name="xrdsLocation">The location of the XRDS document to retrieve.</param> - /// <returns> - /// A HTTP response carrying an XRDS document. - /// </returns> - /// <exception cref="ProtocolException">Thrown if the XRDS document could not be obtained.</exception> - private static IncomingWebResponse GetXrdsResponse(UriIdentifier identifier, IDirectWebRequestHandler requestHandler, Uri xrdsLocation) { - Contract.Requires<ArgumentNullException>(identifier != null); - Contract.Requires<ArgumentNullException>(requestHandler != null); - Contract.Requires<ArgumentNullException>(xrdsLocation != null); - Contract.Ensures(Contract.Result<IncomingWebResponse>() != null); - - var request = (HttpWebRequest)WebRequest.Create(xrdsLocation); - request.CachePolicy = Yadis.IdentifierDiscoveryCachePolicy; - request.Accept = ContentTypes.Xrds; - var options = identifier.IsDiscoverySecureEndToEnd ? DirectWebRequestOptions.RequireSsl : DirectWebRequestOptions.None; - var response = requestHandler.GetResponse(request, options).GetSnapshot(Yadis.MaximumResultToScan); - if (!string.Equals(response.ContentType.MediaType, ContentTypes.Xrds, StringComparison.Ordinal)) { - Logger.Yadis.WarnFormat("Host-meta pointed to XRDS at {0}, but Content-Type at that URL was unexpected value '{1}'.", xrdsLocation, response.ContentType); - } - - return response; - } - - /// <summary> - /// Gets the XRDS HTTP response for a given identifier. - /// </summary> - /// <param name="identifier">The identifier.</param> - /// <param name="requestHandler">The request handler.</param> - /// <param name="signingHost">The host name on the certificate that should be used to verify the signature in the XRDS.</param> - /// <returns>A HTTP response carrying an XRDS document, or <c>null</c> if one could not be obtained.</returns> - /// <exception cref="ProtocolException">Thrown if the XRDS document could not be obtained.</exception> - private IncomingWebResponse GetXrdsResponse(UriIdentifier identifier, IDirectWebRequestHandler requestHandler, out string signingHost) { - Contract.Requires<ArgumentNullException>(identifier != null); - Contract.Requires<ArgumentNullException>(requestHandler != null); - Uri xrdsLocation = this.GetXrdsLocation(identifier, requestHandler, out signingHost); - if (xrdsLocation == null) { - return null; - } - - var response = GetXrdsResponse(identifier, requestHandler, xrdsLocation); - - return response; - } - - /// <summary> - /// Gets the location of the XRDS document that describes a given identifier. - /// </summary> - /// <param name="identifier">The identifier under discovery.</param> - /// <param name="requestHandler">The request handler.</param> - /// <param name="signingHost">The host name on the certificate that should be used to verify the signature in the XRDS.</param> - /// <returns>An absolute URI, or <c>null</c> if one could not be determined.</returns> - private Uri GetXrdsLocation(UriIdentifier identifier, IDirectWebRequestHandler requestHandler, out string signingHost) { - Contract.Requires<ArgumentNullException>(identifier != null); - Contract.Requires<ArgumentNullException>(requestHandler != null); - using (var hostMetaResponse = this.GetHostMeta(identifier, requestHandler, out signingHost)) { - if (hostMetaResponse == null) { - return null; - } - - using (var sr = hostMetaResponse.GetResponseReader()) { - string line = sr.ReadLine(); - Match m = HostMetaLink.Match(line); - if (m.Success) { - Uri location = new Uri(m.Groups["location"].Value); - Logger.Yadis.InfoFormat("Found link to XRDS at {0} in host-meta document {1}.", location, hostMetaResponse.FinalUri); - return location; - } - } - - Logger.Yadis.WarnFormat("Could not find link to XRDS in host-meta document: {0}", hostMetaResponse.FinalUri); - return null; - } - } - - /// <summary> - /// Gets the host-meta for a given identifier. - /// </summary> - /// <param name="identifier">The identifier.</param> - /// <param name="requestHandler">The request handler.</param> - /// <param name="signingHost">The host name on the certificate that should be used to verify the signature in the XRDS.</param> - /// <returns> - /// The host-meta response, or <c>null</c> if no host-meta document could be obtained. - /// </returns> - private IncomingWebResponse GetHostMeta(UriIdentifier identifier, IDirectWebRequestHandler requestHandler, out string signingHost) { - Contract.Requires<ArgumentNullException>(identifier != null); - Contract.Requires<ArgumentNullException>(requestHandler != null); - foreach (var hostMetaProxy in this.GetHostMetaLocations(identifier)) { - var hostMetaLocation = hostMetaProxy.GetProxy(identifier); - var request = (HttpWebRequest)WebRequest.Create(hostMetaLocation); - request.CachePolicy = Yadis.IdentifierDiscoveryCachePolicy; - var options = DirectWebRequestOptions.AcceptAllHttpResponses; - if (identifier.IsDiscoverySecureEndToEnd) { - options |= DirectWebRequestOptions.RequireSsl; - } - var response = requestHandler.GetResponse(request, options).GetSnapshot(Yadis.MaximumResultToScan); - try { - if (response.Status == HttpStatusCode.OK) { - Logger.Yadis.InfoFormat("Found host-meta for {0} at: {1}", identifier.Uri.Host, hostMetaLocation); - signingHost = hostMetaProxy.GetSigningHost(identifier); - return response; - } else { - Logger.Yadis.InfoFormat("Could not obtain host-meta for {0} from {1}", identifier.Uri.Host, hostMetaLocation); - response.Dispose(); - } - } catch { - response.Dispose(); - throw; - } - } - - signingHost = null; - return null; - } - - /// <summary> - /// Gets the URIs authorized to host host-meta documents on behalf of a given domain. - /// </summary> - /// <param name="identifier">The identifier.</param> - /// <returns>A sequence of URIs that MAY provide the host-meta for a given identifier.</returns> - private IEnumerable<HostMetaProxy> GetHostMetaLocations(UriIdentifier identifier) { - Contract.Requires<ArgumentNullException>(identifier != null); - - // First try the proxies, as they are considered more "secure" than the local - // host-meta for a domain since the domain may be defaced. - IEnumerable<HostMetaProxy> result = this.TrustedHostMetaProxies; - - // Finally, look for the local host-meta. - UriBuilder localHostMetaBuilder = new UriBuilder(); - localHostMetaBuilder.Scheme = identifier.IsDiscoverySecureEndToEnd || identifier.Uri.IsTransportSecure() ? Uri.UriSchemeHttps : Uri.UriSchemeHttp; - localHostMetaBuilder.Host = identifier.Uri.Host; - localHostMetaBuilder.Path = LocalHostMetaPath; - result = result.Concat(new[] { new HostMetaProxy(localHostMetaBuilder.Uri.AbsoluteUri, identifier.Uri.Host) }); - - return result; - } - - /// <summary> - /// A description of a web server that hosts host-meta documents. - /// </summary> - [SuppressMessage("Microsoft.Design", "CA1034:NestedTypesShouldNotBeVisible", Justification = "By design")] - public class HostMetaProxy { - /// <summary> - /// Initializes a new instance of the <see cref="HostMetaProxy"/> class. - /// </summary> - /// <param name="proxyFormat">The proxy formatting string.</param> - /// <param name="signingHostFormat">The signing host formatting string.</param> - public HostMetaProxy(string proxyFormat, string signingHostFormat) { - Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(proxyFormat)); - Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(signingHostFormat)); - this.ProxyFormat = proxyFormat; - this.SigningHostFormat = signingHostFormat; - } - - /// <summary> - /// Gets the URL of the host-meta proxy. - /// </summary> - /// <value>The absolute proxy URL, which may include {0} to be replaced with the host of the identifier to be discovered.</value> - public string ProxyFormat { get; private set; } - - /// <summary> - /// Gets the formatting string to determine the expected host name on the certificate - /// that is expected to be used to sign the XRDS document. - /// </summary> - /// <value> - /// Either a string literal, or a formatting string where these placeholders may exist: - /// {0} the host on the identifier discovery was originally performed on; - /// {1} the host on this proxy. - /// </value> - public string SigningHostFormat { get; private set; } - - /// <summary> - /// Gets the absolute proxy URI. - /// </summary> - /// <param name="identifier">The identifier being discovered.</param> - /// <returns>The an absolute URI.</returns> - public virtual Uri GetProxy(UriIdentifier identifier) { - Contract.Requires<ArgumentNullException>(identifier != null); - return new Uri(string.Format(CultureInfo.InvariantCulture, this.ProxyFormat, Uri.EscapeDataString(identifier.Uri.Host))); - } - - /// <summary> - /// Gets the signing host URI. - /// </summary> - /// <param name="identifier">The identifier being discovered.</param> - /// <returns>A host name.</returns> - public virtual string GetSigningHost(UriIdentifier identifier) { - Contract.Requires<ArgumentNullException>(identifier != null); - return string.Format(CultureInfo.InvariantCulture, this.SigningHostFormat, identifier.Uri.Host, this.GetProxy(identifier).Host); - } - - /// <summary> - /// Determines whether the specified <see cref="T:System.Object"/> is equal to the current <see cref="T:System.Object"/>. - /// </summary> - /// <param name="obj">The <see cref="T:System.Object"/> to compare with the current <see cref="T:System.Object"/>.</param> - /// <returns> - /// true if the specified <see cref="T:System.Object"/> is equal to the current <see cref="T:System.Object"/>; otherwise, false. - /// </returns> - /// <exception cref="T:System.NullReferenceException"> - /// The <paramref name="obj"/> parameter is null. - /// </exception> - public override bool Equals(object obj) { - var other = obj as HostMetaProxy; - if (other == null) { - return false; - } - - return this.ProxyFormat == other.ProxyFormat && this.SigningHostFormat == other.SigningHostFormat; - } - - /// <summary> - /// Serves as a hash function for a particular type. - /// </summary> - /// <returns> - /// A hash code for the current <see cref="T:System.Object"/>. - /// </returns> - public override int GetHashCode() { - return this.ProxyFormat.GetHashCode(); - } - } - } -} diff --git a/src/DotNetOpenAuth/OpenId/IIdentifierDiscoveryService.cs b/src/DotNetOpenAuth/OpenId/IIdentifierDiscoveryService.cs deleted file mode 100644 index fcea327..0000000 --- a/src/DotNetOpenAuth/OpenId/IIdentifierDiscoveryService.cs +++ /dev/null @@ -1,67 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="IIdentifierDiscoveryService.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OpenId { - using System; - using System.Collections.Generic; - using System.Diagnostics.CodeAnalysis; - using System.Diagnostics.Contracts; - using System.Linq; - using System.Text; - using DotNetOpenAuth.Messaging; - using DotNetOpenAuth.OpenId.RelyingParty; - - /// <summary> - /// A module that provides discovery services for OpenID identifiers. - /// </summary> - [ContractClass(typeof(IIdentifierDiscoveryServiceContract))] - public interface IIdentifierDiscoveryService { - /// <summary> - /// Performs discovery on the specified identifier. - /// </summary> - /// <param name="identifier">The identifier to perform discovery on.</param> - /// <param name="requestHandler">The means to place outgoing HTTP requests.</param> - /// <param name="abortDiscoveryChain">if set to <c>true</c>, no further discovery services will be called for this identifier.</param> - /// <returns> - /// A sequence of service endpoints yielded by discovery. Must not be null, but may be empty. - /// </returns> - [SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "2#", Justification = "By design")] - [Pure] - IEnumerable<IdentifierDiscoveryResult> Discover(Identifier identifier, IDirectWebRequestHandler requestHandler, out bool abortDiscoveryChain); - } - - /// <summary> - /// Code contract for the <see cref="IIdentifierDiscoveryService"/> interface. - /// </summary> - [ContractClassFor(typeof(IIdentifierDiscoveryService))] - internal abstract class IIdentifierDiscoveryServiceContract : IIdentifierDiscoveryService { - /// <summary> - /// Prevents a default instance of the <see cref="IIdentifierDiscoveryServiceContract"/> class from being created. - /// </summary> - private IIdentifierDiscoveryServiceContract() { - } - - #region IDiscoveryService Members - - /// <summary> - /// Performs discovery on the specified identifier. - /// </summary> - /// <param name="identifier">The identifier to perform discovery on.</param> - /// <param name="requestHandler">The means to place outgoing HTTP requests.</param> - /// <param name="abortDiscoveryChain">if set to <c>true</c>, no further discovery services will be called for this identifier.</param> - /// <returns> - /// A sequence of service endpoints yielded by discovery. Must not be null, but may be empty. - /// </returns> - IEnumerable<IdentifierDiscoveryResult> IIdentifierDiscoveryService.Discover(Identifier identifier, IDirectWebRequestHandler requestHandler, out bool abortDiscoveryChain) { - Contract.Requires<ArgumentNullException>(identifier != null); - Contract.Requires<ArgumentNullException>(requestHandler != null); - Contract.Ensures(Contract.Result<IEnumerable<IdentifierDiscoveryResult>>() != null); - throw new NotImplementedException(); - } - - #endregion - } -} diff --git a/src/DotNetOpenAuth/OpenId/Identifier.cs b/src/DotNetOpenAuth/OpenId/Identifier.cs deleted file mode 100644 index 305976a..0000000 --- a/src/DotNetOpenAuth/OpenId/Identifier.cs +++ /dev/null @@ -1,298 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="Identifier.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OpenId { - using System; - using System.Collections.Generic; - using System.Diagnostics; - using System.Diagnostics.CodeAnalysis; - using System.Diagnostics.Contracts; - using DotNetOpenAuth.Messaging; - using DotNetOpenAuth.OpenId.RelyingParty; - - /// <summary> - /// An Identifier is either a "http" or "https" URI, or an XRI. - /// </summary> - [Serializable] - [ContractVerification(true)] - [Pure] - [ContractClass(typeof(IdentifierContract))] - public abstract class Identifier { - /// <summary> - /// Initializes a new instance of the <see cref="Identifier"/> class. - /// </summary> - /// <param name="originalString">The original string before any normalization.</param> - /// <param name="isDiscoverySecureEndToEnd">Whether the derived class is prepared to guarantee end-to-end discovery - /// and initial redirect for authentication is performed using SSL.</param> - [SuppressMessage("Microsoft.Naming", "CA1720:IdentifiersShouldNotContainTypeNames", MessageId = "string", Justification = "Emphasis on string instead of the strong-typed Identifier.")] - protected Identifier(string originalString, bool isDiscoverySecureEndToEnd) { - this.OriginalString = originalString; - this.IsDiscoverySecureEndToEnd = isDiscoverySecureEndToEnd; - } - - /// <summary> - /// Gets the original string that was normalized to create this Identifier. - /// </summary> - internal string OriginalString { get; private set; } - - /// <summary> - /// Gets the Identifier in the form in which it should be serialized. - /// </summary> - /// <value> - /// For Identifiers that were originally deserialized, this is the exact same - /// string that was deserialized. For Identifiers instantiated in some other way, this is - /// the normalized form of the string used to instantiate the identifier. - /// </value> - internal virtual string SerializedString { - get { return this.IsDeserializedInstance ? this.OriginalString : this.ToString(); } - } - - /// <summary> - /// Gets or sets a value indicating whether <see cref="Identifier"/> instances are considered equal - /// based solely on their string reprsentations. - /// </summary> - /// <remarks> - /// This property serves as a test hook, so that MockIdentifier instances can be considered "equal" - /// to UriIdentifier instances. - /// </remarks> - protected internal static bool EqualityOnStrings { get; set; } - - /// <summary> - /// Gets a value indicating whether this Identifier will ensure SSL is - /// used throughout the discovery phase and initial redirect of authentication. - /// </summary> - /// <remarks> - /// If this is <c>false</c>, a value of <c>true</c> may be obtained by calling - /// <see cref="TryRequireSsl"/>. - /// </remarks> - protected internal bool IsDiscoverySecureEndToEnd { get; private set; } - - /// <summary> - /// Gets a value indicating whether this instance was initialized from - /// deserializing a message. - /// </summary> - /// <remarks> - /// This is interesting because when an Identifier comes from the network, - /// we can't normalize it and then expect signatures to still verify. - /// But if the Identifier is initialized locally, we can and should normalize it - /// before serializing it. - /// </remarks> - protected bool IsDeserializedInstance { get; private set; } - - /// <summary> - /// Converts the string representation of an Identifier to its strong type. - /// </summary> - /// <param name="identifier">The identifier.</param> - /// <returns>The particular Identifier instance to represent the value given.</returns> - [SuppressMessage("Microsoft.Design", "CA1057:StringUriOverloadsCallSystemUriOverloads", Justification = "Not all identifiers are URIs.")] - [SuppressMessage("Microsoft.Usage", "CA2225:OperatorOverloadsHaveNamedAlternates", Justification = "Our named alternate is Parse.")] - [DebuggerStepThrough] - public static implicit operator Identifier(string identifier) { - Contract.Requires<ArgumentException>(identifier == null || identifier.Length > 0); - Contract.Ensures((identifier == null) == (Contract.Result<Identifier>() == null)); - - if (identifier == null) { - return null; - } - return Parse(identifier); - } - - /// <summary> - /// Converts a given Uri to a strongly-typed Identifier. - /// </summary> - /// <param name="identifier">The identifier to convert.</param> - /// <returns>The result of the conversion.</returns> - [SuppressMessage("Microsoft.Usage", "CA2225:OperatorOverloadsHaveNamedAlternates", Justification = "We have a Parse function.")] - [DebuggerStepThrough] - public static implicit operator Identifier(Uri identifier) { - Contract.Ensures((identifier == null) == (Contract.Result<Identifier>() == null)); - if (identifier == null) { - return null; - } - - return new UriIdentifier(identifier); - } - - /// <summary> - /// Converts an Identifier to its string representation. - /// </summary> - /// <param name="identifier">The identifier to convert to a string.</param> - /// <returns>The result of the conversion.</returns> - [SuppressMessage("Microsoft.Usage", "CA2225:OperatorOverloadsHaveNamedAlternates", Justification = "We have a Parse function.")] - [DebuggerStepThrough] - public static implicit operator string(Identifier identifier) { - Contract.Ensures((identifier == null) == (Contract.Result<string>() == null)); - if (identifier == null) { - return null; - } - return identifier.ToString(); - } - - /// <summary> - /// Parses an identifier string and automatically determines - /// whether it is an XRI or URI. - /// </summary> - /// <param name="identifier">Either a URI or XRI identifier.</param> - /// <returns>An <see cref="Identifier"/> instance for the given value.</returns> - [SuppressMessage("Microsoft.Usage", "CA2234:PassSystemUriObjectsInsteadOfStrings", Justification = "Some of these identifiers are not properly formatted to be Uris at this stage.")] - public static Identifier Parse(string identifier) { - Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(identifier)); - Contract.Ensures(Contract.Result<Identifier>() != null); - - return Parse(identifier, false); - } - - /// <summary> - /// Parses an identifier string and automatically determines - /// whether it is an XRI or URI. - /// </summary> - /// <param name="identifier">Either a URI or XRI identifier.</param> - /// <param name="serializeExactValue">if set to <c>true</c> this Identifier will serialize exactly as given rather than in its normalized form.</param> - /// <returns> - /// An <see cref="Identifier"/> instance for the given value. - /// </returns> - [SuppressMessage("Microsoft.Usage", "CA2234:PassSystemUriObjectsInsteadOfStrings", Justification = "Some of these identifiers are not properly formatted to be Uris at this stage.")] - public static Identifier Parse(string identifier, bool serializeExactValue) { - Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(identifier)); - Contract.Ensures(Contract.Result<Identifier>() != null); - - Identifier id; - if (XriIdentifier.IsValidXri(identifier)) { - id = new XriIdentifier(identifier); - } else { - id = new UriIdentifier(identifier); - } - - id.IsDeserializedInstance = serializeExactValue; - return id; - } - - /// <summary> - /// Attempts to parse a string for an OpenId Identifier. - /// </summary> - /// <param name="value">The string to be parsed.</param> - /// <param name="result">The parsed Identifier form.</param> - /// <returns> - /// True if the operation was successful. False if the string was not a valid OpenId Identifier. - /// </returns> - public static bool TryParse(string value, out Identifier result) { - if (string.IsNullOrEmpty(value)) { - result = null; - return false; - } - - if (IsValid(value)) { - result = Parse(value); - return true; - } else { - result = null; - return false; - } - } - - /// <summary> - /// Checks the validity of a given string representation of some Identifier. - /// </summary> - /// <param name="identifier">The identifier.</param> - /// <returns> - /// <c>true</c> if the specified identifier is valid; otherwise, <c>false</c>. - /// </returns> - [SuppressMessage("Microsoft.Usage", "CA2234:PassSystemUriObjectsInsteadOfStrings", Justification = "Some of these identifiers are not properly formatted to be Uris at this stage.")] - public static bool IsValid(string identifier) { - Contract.Requires<ArgumentException>(!string.IsNullOrEmpty(identifier)); - return XriIdentifier.IsValidXri(identifier) || UriIdentifier.IsValidUri(identifier); - } - - /// <summary> - /// Tests equality between two <see cref="Identifier"/>s. - /// </summary> - /// <param name="id1">The first Identifier.</param> - /// <param name="id2">The second Identifier.</param> - /// <returns> - /// <c>true</c> if the two instances should be considered equal; <c>false</c> otherwise. - /// </returns> - public static bool operator ==(Identifier id1, Identifier id2) { - return id1.EqualsNullSafe(id2); - } - - /// <summary> - /// Tests inequality between two <see cref="Identifier"/>s. - /// </summary> - /// <param name="id1">The first Identifier.</param> - /// <param name="id2">The second Identifier.</param> - /// <returns> - /// <c>true</c> if the two instances should be considered unequal; <c>false</c> if they are equal. - /// </returns> - public static bool operator !=(Identifier id1, Identifier id2) { - return !id1.EqualsNullSafe(id2); - } - - /// <summary> - /// Tests equality between two <see cref="Identifier"/>s. - /// </summary> - /// <param name="obj">The <see cref="T:System.Object"/> to compare with the current <see cref="T:System.Object"/>.</param> - /// <returns> - /// true if the specified <see cref="T:System.Object"/> is equal to the current <see cref="T:System.Object"/>; otherwise, false. - /// </returns> - /// <exception cref="T:System.NullReferenceException"> - /// The <paramref name="obj"/> parameter is null. - /// </exception> - public override bool Equals(object obj) { - Debug.Fail("This should be overridden in every derived class."); - return base.Equals(obj); - } - - /// <summary> - /// Gets the hash code for an <see cref="Identifier"/> for storage in a hashtable. - /// </summary> - /// <returns> - /// A hash code for the current <see cref="T:System.Object"/>. - /// </returns> - public override int GetHashCode() { - Debug.Fail("This should be overridden in every derived class."); - return base.GetHashCode(); - } - - /// <summary> - /// Reparses the specified identifier in order to be assured that the concrete type that - /// implements the identifier is one of the well-known ones. - /// </summary> - /// <param name="identifier">The identifier.</param> - /// <returns>Either <see cref="XriIdentifier"/> or <see cref="UriIdentifier"/>.</returns> - internal static Identifier Reparse(Identifier identifier) { - Contract.Requires<ArgumentNullException>(identifier != null); - Contract.Ensures(Contract.Result<Identifier>() != null); - - return Parse(identifier, identifier.IsDeserializedInstance); - } - - /// <summary> - /// Returns an <see cref="Identifier"/> that has no URI fragment. - /// Quietly returns the original <see cref="Identifier"/> if it is not - /// a <see cref="UriIdentifier"/> or no fragment exists. - /// </summary> - /// <returns>A new <see cref="Identifier"/> instance if there was a - /// fragment to remove, otherwise this same instance..</returns> - [Pure] - internal abstract Identifier TrimFragment(); - - /// <summary> - /// Converts a given identifier to its secure equivalent. - /// UriIdentifiers originally created with an implied HTTP scheme change to HTTPS. - /// Discovery is made to require SSL for the entire resolution process. - /// </summary> - /// <param name="secureIdentifier"> - /// The newly created secure identifier. - /// If the conversion fails, <paramref name="secureIdentifier"/> retains - /// <i>this</i> identifiers identity, but will never discover any endpoints. - /// </param> - /// <returns> - /// True if the secure conversion was successful. - /// False if the Identifier was originally created with an explicit HTTP scheme. - /// </returns> - internal abstract bool TryRequireSsl(out Identifier secureIdentifier); - } -} diff --git a/src/DotNetOpenAuth/OpenId/IdentifierContract.cs b/src/DotNetOpenAuth/OpenId/IdentifierContract.cs deleted file mode 100644 index 4af18e1..0000000 --- a/src/DotNetOpenAuth/OpenId/IdentifierContract.cs +++ /dev/null @@ -1,57 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="IdentifierContract.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OpenId { - using System; - using System.Collections.Generic; - using System.Diagnostics.Contracts; - using DotNetOpenAuth.Messaging; - using DotNetOpenAuth.OpenId.RelyingParty; - - /// <summary> - /// Code Contract for the <see cref="Identifier"/> class. - /// </summary> - [ContractClassFor(typeof(Identifier))] - internal abstract class IdentifierContract : Identifier { - /// <summary> - /// Prevents a default instance of the IdentifierContract class from being created. - /// </summary> - private IdentifierContract() - : base(null, false) { - } - - /// <summary> - /// Returns an <see cref="Identifier"/> that has no URI fragment. - /// Quietly returns the original <see cref="Identifier"/> if it is not - /// a <see cref="UriIdentifier"/> or no fragment exists. - /// </summary> - /// <returns> - /// A new <see cref="Identifier"/> instance if there was a - /// fragment to remove, otherwise this same instance.. - /// </returns> - internal override Identifier TrimFragment() { - Contract.Ensures(Contract.Result<Identifier>() != null); - throw new NotImplementedException(); - } - - /// <summary> - /// Converts a given identifier to its secure equivalent. - /// UriIdentifiers originally created with an implied HTTP scheme change to HTTPS. - /// Discovery is made to require SSL for the entire resolution process. - /// </summary> - /// <param name="secureIdentifier">The newly created secure identifier. - /// If the conversion fails, <paramref name="secureIdentifier"/> retains - /// <i>this</i> identifiers identity, but will never discover any endpoints.</param> - /// <returns> - /// True if the secure conversion was successful. - /// False if the Identifier was originally created with an explicit HTTP scheme. - /// </returns> - internal override bool TryRequireSsl(out Identifier secureIdentifier) { - Contract.Ensures(Contract.ValueAtReturn(out secureIdentifier) != null); - throw new NotImplementedException(); - } - } -} diff --git a/src/DotNetOpenAuth/OpenId/IdentifierDiscoveryResult.cs b/src/DotNetOpenAuth/OpenId/IdentifierDiscoveryResult.cs deleted file mode 100644 index c851f24..0000000 --- a/src/DotNetOpenAuth/OpenId/IdentifierDiscoveryResult.cs +++ /dev/null @@ -1,497 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="IdentifierDiscoveryResult.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OpenId { - using System; - using System.Collections.Generic; - using System.Collections.ObjectModel; - using System.Diagnostics; - using System.Diagnostics.CodeAnalysis; - using System.Diagnostics.Contracts; - using System.Globalization; - using System.IO; - using System.Linq; - using System.Text; - using DotNetOpenAuth.Messaging; - using DotNetOpenAuth.OpenId.Messages; - using DotNetOpenAuth.OpenId.RelyingParty; - - /// <summary> - /// Represents a single OP endpoint from discovery on some OpenID Identifier. - /// </summary> - [DebuggerDisplay("ClaimedIdentifier: {ClaimedIdentifier}, ProviderEndpoint: {ProviderEndpoint}, OpenId: {Protocol.Version}")] - public sealed class IdentifierDiscoveryResult : IProviderEndpoint { - /// <summary> - /// Backing field for the <see cref="Protocol"/> property. - /// </summary> - private Protocol protocol; - - /// <summary> - /// Backing field for the <see cref="ClaimedIdentifier"/> property. - /// </summary> - private Identifier claimedIdentifier; - - /// <summary> - /// Backing field for the <see cref="FriendlyIdentifierForDisplay"/> property. - /// </summary> - private string friendlyIdentifierForDisplay; - - /// <summary> - /// Initializes a new instance of the <see cref="IdentifierDiscoveryResult"/> class. - /// </summary> - /// <param name="providerEndpoint">The provider endpoint.</param> - /// <param name="claimedIdentifier">The Claimed Identifier.</param> - /// <param name="userSuppliedIdentifier">The User-supplied Identifier.</param> - /// <param name="providerLocalIdentifier">The Provider Local Identifier.</param> - /// <param name="servicePriority">The service priority.</param> - /// <param name="uriPriority">The URI priority.</param> - private IdentifierDiscoveryResult(ProviderEndpointDescription providerEndpoint, Identifier claimedIdentifier, Identifier userSuppliedIdentifier, Identifier providerLocalIdentifier, int? servicePriority, int? uriPriority) { - Contract.Requires<ArgumentNullException>(providerEndpoint != null); - Contract.Requires<ArgumentNullException>(claimedIdentifier != null); - this.ProviderEndpoint = providerEndpoint.Uri; - this.Capabilities = new ReadOnlyCollection<string>(providerEndpoint.Capabilities); - this.Version = providerEndpoint.Version; - this.ClaimedIdentifier = claimedIdentifier; - this.ProviderLocalIdentifier = providerLocalIdentifier ?? claimedIdentifier; - this.UserSuppliedIdentifier = userSuppliedIdentifier; - this.ServicePriority = servicePriority; - this.ProviderEndpointPriority = uriPriority; - } - - /// <summary> - /// Gets the detected version of OpenID implemented by the Provider. - /// </summary> - public Version Version { get; private set; } - - /// <summary> - /// Gets the Identifier that was presented by the end user to the Relying Party, - /// or selected by the user at the OpenID Provider. - /// During the initiation phase of the protocol, an end user may enter - /// either their own Identifier or an OP Identifier. If an OP Identifier - /// is used, the OP may then assist the end user in selecting an Identifier - /// to share with the Relying Party. - /// </summary> - public Identifier UserSuppliedIdentifier { get; private set; } - - /// <summary> - /// Gets the Identifier that the end user claims to control. - /// </summary> - public Identifier ClaimedIdentifier { - get { - return this.claimedIdentifier; - } - - internal set { - // Take care to reparse the incoming identifier to make sure it's - // not a derived type that will override expected behavior. - // Elsewhere in this class, we count on the fact that this property - // is either UriIdentifier or XriIdentifier. MockIdentifier messes it up. - this.claimedIdentifier = value != null ? Identifier.Reparse(value) : null; - } - } - - /// <summary> - /// Gets an alternate Identifier for an end user that is local to a - /// particular OP and thus not necessarily under the end user's - /// control. - /// </summary> - public Identifier ProviderLocalIdentifier { get; private set; } - - /// <summary> - /// Gets a more user-friendly (but NON-secure!) string to display to the user as his identifier. - /// </summary> - /// <returns>A human-readable, abbreviated (but not secure) identifier the user MAY recognize as his own.</returns> - public string FriendlyIdentifierForDisplay { - get { - if (this.friendlyIdentifierForDisplay == null) { - XriIdentifier xri = this.ClaimedIdentifier as XriIdentifier; - UriIdentifier uri = this.ClaimedIdentifier as UriIdentifier; - if (xri != null) { - if (this.UserSuppliedIdentifier == null || String.Equals(this.UserSuppliedIdentifier, this.ClaimedIdentifier, StringComparison.OrdinalIgnoreCase)) { - this.friendlyIdentifierForDisplay = this.ClaimedIdentifier; - } else { - this.friendlyIdentifierForDisplay = this.UserSuppliedIdentifier; - } - } else if (uri != null) { - if (uri != this.Protocol.ClaimedIdentifierForOPIdentifier) { - string displayUri = uri.Uri.Host; - - // We typically want to display the path, because that will often have the username in it. - // As Google Apps for Domains and the like become more popular, a standard /openid path - // will often appear, which is not helpful to identifying the user so we'll avoid including - // that path if it's present. - if (!string.Equals(uri.Uri.AbsolutePath, "/openid", StringComparison.OrdinalIgnoreCase)) { - displayUri += uri.Uri.AbsolutePath.TrimEnd('/'); - } - - // Multi-byte unicode characters get encoded by the Uri class for transit. - // Since this is for display purposes, we want to reverse this and display a readable - // representation of these foreign characters. - this.friendlyIdentifierForDisplay = Uri.UnescapeDataString(displayUri); - } - } else { - ErrorUtilities.ThrowInternal("ServiceEndpoint.ClaimedIdentifier neither XRI nor URI."); - this.friendlyIdentifierForDisplay = this.ClaimedIdentifier; - } - } - - return this.friendlyIdentifierForDisplay; - } - } - - /// <summary> - /// Gets the provider endpoint. - /// </summary> - public Uri ProviderEndpoint { get; private set; } - - /// <summary> - /// Gets the @priority given in the XRDS document for this specific OP endpoint. - /// </summary> - public int? ProviderEndpointPriority { get; private set; } - - /// <summary> - /// Gets the @priority given in the XRDS document for this service - /// (which may consist of several endpoints). - /// </summary> - public int? ServicePriority { get; private set; } - - /// <summary> - /// Gets the collection of service type URIs found in the XRDS document describing this Provider. - /// </summary> - /// <value>Should never be null, but may be empty.</value> - public ReadOnlyCollection<string> Capabilities { get; private set; } - - #region IProviderEndpoint Members - - /// <summary> - /// Gets the URL that the OpenID Provider receives authentication requests at. - /// </summary> - /// <value>This value MUST be an absolute HTTP or HTTPS URL.</value> - Uri IProviderEndpoint.Uri { - get { return this.ProviderEndpoint; } - } - - #endregion - - /// <summary> - /// Gets an XRDS sorting routine that uses the XRDS Service/@Priority - /// attribute to determine order. - /// </summary> - /// <remarks> - /// Endpoints lacking any priority value are sorted to the end of the list. - /// </remarks> - internal static Comparison<IdentifierDiscoveryResult> EndpointOrder { - get { - // Sort first by service type (OpenID 2.0, 1.1, 1.0), - // then by Service/@priority, then by Service/Uri/@priority - return (se1, se2) => { - int result = GetEndpointPrecedenceOrderByServiceType(se1).CompareTo(GetEndpointPrecedenceOrderByServiceType(se2)); - if (result != 0) { - return result; - } - if (se1.ServicePriority.HasValue && se2.ServicePriority.HasValue) { - result = se1.ServicePriority.Value.CompareTo(se2.ServicePriority.Value); - if (result != 0) { - return result; - } - if (se1.ProviderEndpointPriority.HasValue && se2.ProviderEndpointPriority.HasValue) { - return se1.ProviderEndpointPriority.Value.CompareTo(se2.ProviderEndpointPriority.Value); - } else if (se1.ProviderEndpointPriority.HasValue) { - return -1; - } else if (se2.ProviderEndpointPriority.HasValue) { - return 1; - } else { - return 0; - } - } else { - if (se1.ServicePriority.HasValue) { - return -1; - } else if (se2.ServicePriority.HasValue) { - return 1; - } else { - // neither service defines a priority, so base ordering by uri priority. - if (se1.ProviderEndpointPriority.HasValue && se2.ProviderEndpointPriority.HasValue) { - return se1.ProviderEndpointPriority.Value.CompareTo(se2.ProviderEndpointPriority.Value); - } else if (se1.ProviderEndpointPriority.HasValue) { - return -1; - } else if (se2.ProviderEndpointPriority.HasValue) { - return 1; - } else { - return 0; - } - } - } - }; - } - } - - /// <summary> - /// Gets the protocol used by the OpenID Provider. - /// </summary> - internal Protocol Protocol { - get { - if (this.protocol == null) { - this.protocol = Protocol.Lookup(this.Version); - } - - return this.protocol; - } - } - - /// <summary> - /// Implements the operator ==. - /// </summary> - /// <param name="se1">The first service endpoint.</param> - /// <param name="se2">The second service endpoint.</param> - /// <returns>The result of the operator.</returns> - public static bool operator ==(IdentifierDiscoveryResult se1, IdentifierDiscoveryResult se2) { - return se1.EqualsNullSafe(se2); - } - - /// <summary> - /// Implements the operator !=. - /// </summary> - /// <param name="se1">The first service endpoint.</param> - /// <param name="se2">The second service endpoint.</param> - /// <returns>The result of the operator.</returns> - public static bool operator !=(IdentifierDiscoveryResult se1, IdentifierDiscoveryResult se2) { - return !(se1 == se2); - } - - /// <summary> - /// Determines whether the specified <see cref="T:System.Object"/> is equal to the current <see cref="T:System.Object"/>. - /// </summary> - /// <param name="obj">The <see cref="T:System.Object"/> to compare with the current <see cref="T:System.Object"/>.</param> - /// <returns> - /// true if the specified <see cref="T:System.Object"/> is equal to the current <see cref="T:System.Object"/>; otherwise, false. - /// </returns> - /// <exception cref="T:System.NullReferenceException"> - /// The <paramref name="obj"/> parameter is null. - /// </exception> - public override bool Equals(object obj) { - var other = obj as IdentifierDiscoveryResult; - if (other == null) { - return false; - } - - // We specifically do not check our ProviderSupportedServiceTypeUris array - // or the priority field - // as that is not persisted in our tokens, and it is not part of the - // important assertion validation that is part of the spec. - return - this.ClaimedIdentifier == other.ClaimedIdentifier && - this.ProviderEndpoint == other.ProviderEndpoint && - this.ProviderLocalIdentifier == other.ProviderLocalIdentifier && - this.Protocol.EqualsPractically(other.Protocol); - } - - /// <summary> - /// Serves as a hash function for a particular type. - /// </summary> - /// <returns> - /// A hash code for the current <see cref="T:System.Object"/>. - /// </returns> - public override int GetHashCode() { - return this.ClaimedIdentifier.GetHashCode(); - } - - /// <summary> - /// Returns a <see cref="T:System.String"/> that represents the current <see cref="T:System.Object"/>. - /// </summary> - /// <returns> - /// A <see cref="T:System.String"/> that represents the current <see cref="T:System.Object"/>. - /// </returns> - public override string ToString() { - StringBuilder builder = new StringBuilder(); - builder.AppendLine("ClaimedIdentifier: " + this.ClaimedIdentifier); - builder.AppendLine("ProviderLocalIdentifier: " + this.ProviderLocalIdentifier); - builder.AppendLine("ProviderEndpoint: " + this.ProviderEndpoint); - builder.AppendLine("OpenID version: " + this.Version); - builder.AppendLine("Service Type URIs:"); - foreach (string serviceTypeUri in this.Capabilities) { - builder.Append("\t"); - builder.AppendLine(serviceTypeUri); - } - builder.Length -= Environment.NewLine.Length; // trim last newline - return builder.ToString(); - } - - /// <summary> - /// Checks whether the OpenId Identifier claims support for a given extension. - /// </summary> - /// <typeparam name="T">The extension whose support is being queried.</typeparam> - /// <returns> - /// True if support for the extension is advertised. False otherwise. - /// </returns> - /// <remarks> - /// Note that a true or false return value is no guarantee of a Provider's - /// support for or lack of support for an extension. The return value is - /// determined by how the authenticating user filled out his/her XRDS document only. - /// The only way to be sure of support for a given extension is to include - /// the extension in the request and see if a response comes back for that extension. - /// </remarks> - [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "No parameter at all.")] - public bool IsExtensionSupported<T>() where T : IOpenIdMessageExtension, new() { - T extension = new T(); - return this.IsExtensionSupported(extension); - } - - /// <summary> - /// Checks whether the OpenId Identifier claims support for a given extension. - /// </summary> - /// <param name="extensionType">The extension whose support is being queried.</param> - /// <returns> - /// True if support for the extension is advertised. False otherwise. - /// </returns> - /// <remarks> - /// Note that a true or false return value is no guarantee of a Provider's - /// support for or lack of support for an extension. The return value is - /// determined by how the authenticating user filled out his/her XRDS document only. - /// The only way to be sure of support for a given extension is to include - /// the extension in the request and see if a response comes back for that extension. - /// </remarks> - public bool IsExtensionSupported(Type extensionType) { - var extension = (IOpenIdMessageExtension)Activator.CreateInstance(extensionType); - return this.IsExtensionSupported(extension); - } - - /// <summary> - /// Determines whether a given extension is supported by this endpoint. - /// </summary> - /// <param name="extension">An instance of the extension to check support for.</param> - /// <returns> - /// <c>true</c> if the extension is supported by this endpoint; otherwise, <c>false</c>. - /// </returns> - public bool IsExtensionSupported(IOpenIdMessageExtension extension) { - Contract.Requires<ArgumentNullException>(extension != null); - - // Consider the primary case. - if (this.IsTypeUriPresent(extension.TypeUri)) { - return true; - } - - // Consider the secondary cases. - if (extension.AdditionalSupportedTypeUris != null) { - if (extension.AdditionalSupportedTypeUris.Any(typeUri => this.IsTypeUriPresent(typeUri))) { - return true; - } - } - - return false; - } - - /// <summary> - /// Creates a <see cref="IdentifierDiscoveryResult"/> instance to represent some OP Identifier. - /// </summary> - /// <param name="providerIdentifier">The provider identifier (actually the user-supplied identifier).</param> - /// <param name="providerEndpoint">The provider endpoint.</param> - /// <param name="servicePriority">The service priority.</param> - /// <param name="uriPriority">The URI priority.</param> - /// <returns>The created <see cref="IdentifierDiscoveryResult"/> instance</returns> - internal static IdentifierDiscoveryResult CreateForProviderIdentifier(Identifier providerIdentifier, ProviderEndpointDescription providerEndpoint, int? servicePriority, int? uriPriority) { - Contract.Requires<ArgumentNullException>(providerEndpoint != null); - - Protocol protocol = Protocol.Lookup(providerEndpoint.Version); - - return new IdentifierDiscoveryResult( - providerEndpoint, - protocol.ClaimedIdentifierForOPIdentifier, - providerIdentifier, - protocol.ClaimedIdentifierForOPIdentifier, - servicePriority, - uriPriority); - } - - /// <summary> - /// Creates a <see cref="IdentifierDiscoveryResult"/> instance to represent some Claimed Identifier. - /// </summary> - /// <param name="claimedIdentifier">The claimed identifier.</param> - /// <param name="providerLocalIdentifier">The provider local identifier.</param> - /// <param name="providerEndpoint">The provider endpoint.</param> - /// <param name="servicePriority">The service priority.</param> - /// <param name="uriPriority">The URI priority.</param> - /// <returns>The created <see cref="IdentifierDiscoveryResult"/> instance</returns> - internal static IdentifierDiscoveryResult CreateForClaimedIdentifier(Identifier claimedIdentifier, Identifier providerLocalIdentifier, ProviderEndpointDescription providerEndpoint, int? servicePriority, int? uriPriority) { - return CreateForClaimedIdentifier(claimedIdentifier, null, providerLocalIdentifier, providerEndpoint, servicePriority, uriPriority); - } - - /// <summary> - /// Creates a <see cref="IdentifierDiscoveryResult"/> instance to represent some Claimed Identifier. - /// </summary> - /// <param name="claimedIdentifier">The claimed identifier.</param> - /// <param name="userSuppliedIdentifier">The user supplied identifier.</param> - /// <param name="providerLocalIdentifier">The provider local identifier.</param> - /// <param name="providerEndpoint">The provider endpoint.</param> - /// <param name="servicePriority">The service priority.</param> - /// <param name="uriPriority">The URI priority.</param> - /// <returns>The created <see cref="IdentifierDiscoveryResult"/> instance</returns> - internal static IdentifierDiscoveryResult CreateForClaimedIdentifier(Identifier claimedIdentifier, Identifier userSuppliedIdentifier, Identifier providerLocalIdentifier, ProviderEndpointDescription providerEndpoint, int? servicePriority, int? uriPriority) { - return new IdentifierDiscoveryResult(providerEndpoint, claimedIdentifier, userSuppliedIdentifier, providerLocalIdentifier, servicePriority, uriPriority); - } - - /// <summary> - /// Determines whether a given type URI is present on the specified provider endpoint. - /// </summary> - /// <param name="typeUri">The type URI.</param> - /// <returns> - /// <c>true</c> if the type URI is present on the specified provider endpoint; otherwise, <c>false</c>. - /// </returns> - internal bool IsTypeUriPresent(string typeUri) { - Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(typeUri)); - return this.Capabilities.Contains(typeUri); - } - - /// <summary> - /// Sets the Capabilities property (this method is a test hook.) - /// </summary> - /// <param name="value">The value.</param> - /// <remarks>The publicize.exe tool should work for the unit tests, but for some reason it fails on the build server.</remarks> - internal void SetCapabilitiesForTestHook(ReadOnlyCollection<string> value) { - this.Capabilities = value; - } - - /// <summary> - /// Gets the priority rating for a given type of endpoint, allowing a - /// priority sorting of endpoints. - /// </summary> - /// <param name="endpoint">The endpoint to prioritize.</param> - /// <returns>An arbitary integer, which may be used for sorting against other returned values from this method.</returns> - private static double GetEndpointPrecedenceOrderByServiceType(IdentifierDiscoveryResult endpoint) { - // The numbers returned from this method only need to compare against other numbers - // from this method, which makes them arbitrary but relational to only others here. - if (endpoint.Capabilities.Contains(Protocol.V20.OPIdentifierServiceTypeURI)) { - return 0; - } - if (endpoint.Capabilities.Contains(Protocol.V20.ClaimedIdentifierServiceTypeURI)) { - return 1; - } - if (endpoint.Capabilities.Contains(Protocol.V11.ClaimedIdentifierServiceTypeURI)) { - return 2; - } - if (endpoint.Capabilities.Contains(Protocol.V10.ClaimedIdentifierServiceTypeURI)) { - return 3; - } - return 10; - } - -#if CONTRACTS_FULL - /// <summary> - /// Verifies conditions that should be true for any valid state of this object. - /// </summary> - [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Called by code contracts.")] - [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called by code contracts.")] - [ContractInvariantMethod] - private void ObjectInvariant() { - Contract.Invariant(this.ProviderEndpoint != null); - Contract.Invariant(this.ClaimedIdentifier != null); - Contract.Invariant(this.ProviderLocalIdentifier != null); - Contract.Invariant(this.Capabilities != null); - Contract.Invariant(this.Version != null); - Contract.Invariant(this.Protocol != null); - } -#endif - } -} diff --git a/src/DotNetOpenAuth/OpenId/Interop/AuthenticationResponseShim.cs b/src/DotNetOpenAuth/OpenId/Interop/AuthenticationResponseShim.cs deleted file mode 100644 index c0354ac..0000000 --- a/src/DotNetOpenAuth/OpenId/Interop/AuthenticationResponseShim.cs +++ /dev/null @@ -1,120 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="AuthenticationResponseShim.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OpenId.Interop { - using System; - using System.Diagnostics.CodeAnalysis; - using System.Diagnostics.Contracts; - using System.Runtime.InteropServices; - using System.Web; - using DotNetOpenAuth.Messaging; - using DotNetOpenAuth.OpenId.Extensions.SimpleRegistration; - using DotNetOpenAuth.OpenId.RelyingParty; - - /// <summary> - /// The COM type used to provide details of an authentication result to a relying party COM client. - /// </summary> - [SuppressMessage("Microsoft.Interoperability", "CA1409:ComVisibleTypesShouldBeCreatable", Justification = "It's only creatable on the inside. It must be ComVisible for ASP to see it.")] - [ComVisible(true), Obsolete("This class acts as a COM Server and should not be called directly from .NET code.")] - public sealed class AuthenticationResponseShim { - /// <summary> - /// The response read in by the Relying Party. - /// </summary> - private readonly IAuthenticationResponse response; - - /// <summary> - /// Initializes a new instance of the <see cref="AuthenticationResponseShim"/> class. - /// </summary> - /// <param name="response">The response.</param> - internal AuthenticationResponseShim(IAuthenticationResponse response) { - Contract.Requires<ArgumentNullException>(response != null); - - this.response = response; - var claimsResponse = this.response.GetExtension<ClaimsResponse>(); - if (claimsResponse != null) { - this.ClaimsResponse = new ClaimsResponseShim(claimsResponse); - } - } - - /// <summary> - /// Gets an Identifier that the end user claims to own. For use with user database storage and lookup. - /// May be null for some failed authentications (i.e. failed directed identity authentications). - /// </summary> - /// <remarks> - /// <para> - /// This is the secure identifier that should be used for database storage and lookup. - /// It is not always friendly (i.e. =Arnott becomes =!9B72.7DD1.50A9.5CCD), but it protects - /// user identities against spoofing and other attacks. - /// </para> - /// <para> - /// For user-friendly identifiers to display, use the - /// <see cref="FriendlyIdentifierForDisplay"/> property. - /// </para> - /// </remarks> - public string ClaimedIdentifier { - get { return this.response.ClaimedIdentifier; } - } - - /// <summary> - /// Gets a user-friendly OpenID Identifier for display purposes ONLY. - /// </summary> - /// <remarks> - /// <para> - /// This <i>should</i> be put through <see cref="HttpUtility.HtmlEncode(string)"/> before - /// sending to a browser to secure against javascript injection attacks. - /// </para> - /// <para> - /// This property retains some aspects of the user-supplied identifier that get lost - /// in the <see cref="ClaimedIdentifier"/>. For example, XRIs used as user-supplied - /// identifiers (i.e. =Arnott) become unfriendly unique strings (i.e. =!9B72.7DD1.50A9.5CCD). - /// For display purposes, such as text on a web page that says "You're logged in as ...", - /// this property serves to provide the =Arnott string, or whatever else is the most friendly - /// string close to what the user originally typed in. - /// </para> - /// <para> - /// If the user-supplied identifier is a URI, this property will be the URI after all - /// redirects, and with the protocol and fragment trimmed off. - /// If the user-supplied identifier is an XRI, this property will be the original XRI. - /// If the user-supplied identifier is an OpenID Provider identifier (i.e. yahoo.com), - /// this property will be the Claimed Identifier, with the protocol stripped if it is a URI. - /// </para> - /// <para> - /// It is <b>very</b> important that this property <i>never</i> be used for database storage - /// or lookup to avoid identity spoofing and other security risks. For database storage - /// and lookup please use the <see cref="ClaimedIdentifier"/> property. - /// </para> - /// </remarks> - public string FriendlyIdentifierForDisplay { - get { return this.response.FriendlyIdentifierForDisplay; } - } - - /// <summary> - /// Gets the provider endpoint that sent the assertion. - /// </summary> - public string ProviderEndpoint { - get { return this.response.Provider != null ? this.response.Provider.Uri.AbsoluteUri : null; } - } - - /// <summary> - /// Gets a value indicating whether the authentication attempt succeeded. - /// </summary> - public bool Successful { - get { return this.response.Status == AuthenticationStatus.Authenticated; } - } - - /// <summary> - /// Gets the Simple Registration response. - /// </summary> - public ClaimsResponseShim ClaimsResponse { get; private set; } - - /// <summary> - /// Gets details regarding a failed authentication attempt, if available. - /// </summary> - public string ExceptionMessage { - get { return this.response.Exception != null ? this.response.Exception.Message : null; } - } - } -} diff --git a/src/DotNetOpenAuth/OpenId/Interop/ClaimsResponseShim.cs b/src/DotNetOpenAuth/OpenId/Interop/ClaimsResponseShim.cs deleted file mode 100644 index 689777b..0000000 --- a/src/DotNetOpenAuth/OpenId/Interop/ClaimsResponseShim.cs +++ /dev/null @@ -1,108 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="ClaimsResponseShim.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OpenId.Interop { - using System; - using System.Diagnostics.CodeAnalysis; - using System.Diagnostics.Contracts; - using System.Runtime.InteropServices; - using DotNetOpenAuth.Messaging; - using DotNetOpenAuth.OpenId.Extensions.SimpleRegistration; - - /// <summary> - /// A struct storing Simple Registration field values describing an - /// authenticating user. - /// </summary> - [SuppressMessage("Microsoft.Interoperability", "CA1409:ComVisibleTypesShouldBeCreatable", Justification = "It's only creatable on the inside. It must be ComVisible for ASP to see it.")] - [ComVisible(true), Obsolete("This class acts as a COM Server and should not be called directly from .NET code.")] - [ContractVerification(true)] - public sealed class ClaimsResponseShim { - /// <summary> - /// The Simple Registration claims response message that this shim wraps. - /// </summary> - private readonly ClaimsResponse response; - - /// <summary> - /// Initializes a new instance of the <see cref="ClaimsResponseShim"/> class. - /// </summary> - /// <param name="response">The Simple Registration response to wrap.</param> - internal ClaimsResponseShim(ClaimsResponse response) - { - Contract.Requires<ArgumentNullException>(response != null); - - this.response = response; - } - - /// <summary> - /// Gets the nickname the user goes by. - /// </summary> - public string Nickname { - get { return this.response.Nickname; } - } - - /// <summary> - /// Gets the user's email address. - /// </summary> - public string Email { - get { return this.response.Email; } - } - - /// <summary> - /// Gets the full name of a user as a single string. - /// </summary> - public string FullName { - get { return this.response.FullName; } - } - - /// <summary> - /// Gets the raw birth date string given by the extension. - /// </summary> - /// <value>A string in the format yyyy-MM-dd.</value> - public string BirthDate { - get { return this.response.BirthDateRaw; } - } - - /// <summary> - /// Gets the gender of the user. - /// </summary> - public string Gender { - get { - if (this.response.Gender.HasValue) { - return this.response.Gender.Value == Extensions.SimpleRegistration.Gender.Male ? Constants.Genders.Male : Constants.Genders.Female; - } - return null; - } - } - - /// <summary> - /// Gets the zip code / postal code of the user. - /// </summary> - public string PostalCode { - get { return this.response.PostalCode; } - } - - /// <summary> - /// Gets the country of the user. - /// </summary> - public string Country { - get { return this.response.Country; } - } - - /// <summary> - /// Gets the primary/preferred language of the user. - /// </summary> - public string Language { - get { return this.response.Language; } - } - - /// <summary> - /// Gets the user's timezone. - /// </summary> - public string TimeZone { - get { return this.response.TimeZone; } - } - } -}
\ No newline at end of file diff --git a/src/DotNetOpenAuth/OpenId/Interop/OpenIdRelyingPartyShim.cs b/src/DotNetOpenAuth/OpenId/Interop/OpenIdRelyingPartyShim.cs deleted file mode 100644 index fc0f32e..0000000 --- a/src/DotNetOpenAuth/OpenId/Interop/OpenIdRelyingPartyShim.cs +++ /dev/null @@ -1,190 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="OpenIdRelyingPartyShim.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OpenId.Interop { - using System; - using System.Diagnostics.CodeAnalysis; - using System.IO; - using System.Runtime.InteropServices; - using System.Text; - using System.Web; - using DotNetOpenAuth.Messaging; - using DotNetOpenAuth.OpenId.Extensions.SimpleRegistration; - using DotNetOpenAuth.OpenId.RelyingParty; - - /// <summary> - /// The COM interface describing the DotNetOpenAuth functionality available to - /// COM client OpenID relying parties. - /// </summary> - [Guid("56BD3DB0-EE0D-4191-ADFC-1F3705CD2636")] - [InterfaceType(ComInterfaceType.InterfaceIsDual)] - public interface IOpenIdRelyingParty { - /// <summary> - /// Creates an authentication request to verify that a user controls - /// some given Identifier. - /// </summary> - /// <param name="userSuppliedIdentifier"> - /// The Identifier supplied by the user. This may be a URL, an XRI or i-name. - /// </param> - /// <param name="realm"> - /// The shorest URL that describes this relying party web site's address. - /// For example, if your login page is found at https://www.example.com/login.aspx, - /// your realm would typically be https://www.example.com/. - /// </param> - /// <param name="returnToUrl"> - /// The URL of the login page, or the page prepared to receive authentication - /// responses from the OpenID Provider. - /// </param> - /// <returns> - /// An authentication request object that describes the HTTP response to - /// send to the user agent to initiate the authentication. - /// </returns> - /// <exception cref="ProtocolException">Thrown if no OpenID endpoint could be found.</exception> - string CreateRequest(string userSuppliedIdentifier, string realm, string returnToUrl); - - /// <summary> - /// Creates an authentication request to verify that a user controls - /// some given Identifier. - /// </summary> - /// <param name="userSuppliedIdentifier">The Identifier supplied by the user. This may be a URL, an XRI or i-name.</param> - /// <param name="realm">The shorest URL that describes this relying party web site's address. - /// For example, if your login page is found at https://www.example.com/login.aspx, - /// your realm would typically be https://www.example.com/.</param> - /// <param name="returnToUrl">The URL of the login page, or the page prepared to receive authentication - /// responses from the OpenID Provider.</param> - /// <param name="optionalSreg">A comma-delimited list of simple registration fields to request as optional.</param> - /// <param name="requiredSreg">A comma-delimited list of simple registration fields to request as required.</param> - /// <returns> - /// An authentication request object that describes the HTTP response to - /// send to the user agent to initiate the authentication. - /// </returns> - /// <exception cref="ProtocolException">Thrown if no OpenID endpoint could be found.</exception> - [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Sreg", Justification = "Accepted acronym")] - string CreateRequestWithSimpleRegistration(string userSuppliedIdentifier, string realm, string returnToUrl, string optionalSreg, string requiredSreg); - - /// <summary> - /// Gets the result of a user agent's visit to his OpenId provider in an - /// authentication attempt. Null if no response is available. - /// </summary> - /// <param name="url">The incoming request URL .</param> - /// <param name="form">The form data that may have been included in the case of a POST request.</param> - /// <returns>The Provider's response to a previous authentication request, or null if no response is present.</returns> -#pragma warning disable 0618 // we're using the COM type properly - AuthenticationResponseShim ProcessAuthentication(string url, string form); -#pragma warning restore 0618 - } - - /// <summary> - /// Implementation of <see cref="IOpenIdRelyingParty"/>, providing a subset of the - /// functionality available to .NET clients. - /// </summary> - [Guid("8F97A798-B4C5-4da5-9727-EE7DD96A8CD9")] - [ProgId("DotNetOpenAuth.OpenId.RelyingParty.OpenIdRelyingParty")] - [ComVisible(true), Obsolete("This class acts as a COM Server and should not be called directly from .NET code.", true)] - [ClassInterface(ClassInterfaceType.None)] - public sealed class OpenIdRelyingPartyShim : IOpenIdRelyingParty { - /// <summary> - /// The OpenIdRelyingParty instance to use for requests. - /// </summary> - private static OpenIdRelyingParty relyingParty; - - /// <summary> - /// Initializes static members of the <see cref="OpenIdRelyingPartyShim"/> class. - /// </summary> - static OpenIdRelyingPartyShim() { - relyingParty = new OpenIdRelyingParty(null); - relyingParty.Behaviors.Add(new Behaviors.AXFetchAsSregTransform()); - } - - /// <summary> - /// Initializes a new instance of the <see cref="OpenIdRelyingPartyShim"/> class. - /// </summary> - public OpenIdRelyingPartyShim() { - Reporting.RecordFeatureUse(this); - } - - /// <summary> - /// Creates an authentication request to verify that a user controls - /// some given Identifier. - /// </summary> - /// <param name="userSuppliedIdentifier"> - /// The Identifier supplied by the user. This may be a URL, an XRI or i-name. - /// </param> - /// <param name="realm"> - /// The shorest URL that describes this relying party web site's address. - /// For example, if your login page is found at https://www.example.com/login.aspx, - /// your realm would typically be https://www.example.com/. - /// </param> - /// <param name="returnToUrl"> - /// The URL of the login page, or the page prepared to receive authentication - /// responses from the OpenID Provider. - /// </param> - /// <returns> - /// An authentication request object that describes the HTTP response to - /// send to the user agent to initiate the authentication. - /// </returns> - /// <exception cref="ProtocolException">Thrown if no OpenID endpoint could be found.</exception> - [SuppressMessage("Microsoft.Usage", "CA2234:PassSystemUriObjectsInsteadOfStrings", Justification = "COM requires primitive types")] - public string CreateRequest(string userSuppliedIdentifier, string realm, string returnToUrl) { - var request = relyingParty.CreateRequest(userSuppliedIdentifier, realm, new Uri(returnToUrl)); - return request.RedirectingResponse.GetDirectUriRequest(relyingParty.Channel).AbsoluteUri; - } - - /// <summary> - /// Creates an authentication request to verify that a user controls - /// some given Identifier. - /// </summary> - /// <param name="userSuppliedIdentifier">The Identifier supplied by the user. This may be a URL, an XRI or i-name.</param> - /// <param name="realm">The shorest URL that describes this relying party web site's address. - /// For example, if your login page is found at https://www.example.com/login.aspx, - /// your realm would typically be https://www.example.com/.</param> - /// <param name="returnToUrl">The URL of the login page, or the page prepared to receive authentication - /// responses from the OpenID Provider.</param> - /// <param name="optionalSreg">A comma-delimited list of simple registration fields to request as optional.</param> - /// <param name="requiredSreg">A comma-delimited list of simple registration fields to request as required.</param> - /// <returns> - /// An authentication request object that describes the HTTP response to - /// send to the user agent to initiate the authentication. - /// </returns> - /// <exception cref="ProtocolException">Thrown if no OpenID endpoint could be found.</exception> - [SuppressMessage("Microsoft.Usage", "CA2234:PassSystemUriObjectsInsteadOfStrings", Justification = "COM requires primitive types")] - public string CreateRequestWithSimpleRegistration(string userSuppliedIdentifier, string realm, string returnToUrl, string optionalSreg, string requiredSreg) { - var request = relyingParty.CreateRequest(userSuppliedIdentifier, realm, new Uri(returnToUrl)); - - ClaimsRequest sreg = new ClaimsRequest(); - if (!string.IsNullOrEmpty(optionalSreg)) { - sreg.SetProfileRequestFromList(optionalSreg.Split(','), DemandLevel.Request); - } - if (!string.IsNullOrEmpty(requiredSreg)) { - sreg.SetProfileRequestFromList(requiredSreg.Split(','), DemandLevel.Require); - } - request.AddExtension(sreg); - return request.RedirectingResponse.GetDirectUriRequest(relyingParty.Channel).AbsoluteUri; - } - - /// <summary> - /// Gets the result of a user agent's visit to his OpenId provider in an - /// authentication attempt. Null if no response is available. - /// </summary> - /// <param name="url">The incoming request URL.</param> - /// <param name="form">The form data that may have been included in the case of a POST request.</param> - /// <returns>The Provider's response to a previous authentication request, or null if no response is present.</returns> - public AuthenticationResponseShim ProcessAuthentication(string url, string form) { - HttpRequestInfo requestInfo = new HttpRequestInfo { UrlBeforeRewriting = new Uri(url) }; - if (!string.IsNullOrEmpty(form)) { - requestInfo.HttpMethod = "POST"; - requestInfo.InputStream = new MemoryStream(Encoding.Unicode.GetBytes(form)); - } - - var response = relyingParty.GetResponse(requestInfo); - if (response != null) { - return new AuthenticationResponseShim(response); - } - - return null; - } - } -} diff --git a/src/DotNetOpenAuth/OpenId/Messages/AssociateDiffieHellmanRequest.cs b/src/DotNetOpenAuth/OpenId/Messages/AssociateDiffieHellmanRequest.cs deleted file mode 100644 index 01e23e4..0000000 --- a/src/DotNetOpenAuth/OpenId/Messages/AssociateDiffieHellmanRequest.cs +++ /dev/null @@ -1,119 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="AssociateDiffieHellmanRequest.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OpenId.Messages { - using System; - using System.Collections.Generic; - using System.Globalization; - using System.Linq; - using System.Text; - using DotNetOpenAuth.Messaging; - using DotNetOpenAuth.Messaging.Reflection; - using Org.Mentalis.Security.Cryptography; - - /// <summary> - /// An OpenID direct request from Relying Party to Provider to initiate an association that uses Diffie-Hellman encryption. - /// </summary> - internal class AssociateDiffieHellmanRequest : AssociateRequest { - /// <summary> - /// The (only) value we use for the X variable in the Diffie-Hellman algorithm. - /// </summary> - internal static readonly int DefaultX = 1024; - - /// <summary> - /// The default gen value for the Diffie-Hellman algorithm. - /// </summary> - internal static readonly byte[] DefaultGen = { 2 }; - - /// <summary> - /// The default modulus value for the Diffie-Hellman algorithm. - /// </summary> - internal static readonly byte[] DefaultMod = { - 0, 220, 249, 58, 11, 136, 57, 114, 236, 14, 25, 152, 154, 197, 162, - 206, 49, 14, 29, 55, 113, 126, 141, 149, 113, 187, 118, 35, 115, 24, - 102, 230, 30, 247, 90, 46, 39, 137, 139, 5, 127, 152, 145, 194, 226, - 122, 99, 156, 63, 41, 182, 8, 20, 88, 28, 211, 178, 202, 57, 134, 210, - 104, 55, 5, 87, 125, 69, 194, 231, 229, 45, 200, 28, 122, 23, 24, 118, - 229, 206, 167, 75, 20, 72, 191, 223, 175, 24, 130, 142, 253, 37, 25, - 241, 78, 69, 227, 130, 102, 52, 175, 25, 73, 229, 181, 53, 204, 130, - 154, 72, 59, 138, 118, 34, 62, 93, 73, 10, 37, 127, 5, 189, 255, 22, - 242, 251, 34, 197, 131, 171 }; - - /// <summary> - /// Initializes a new instance of the <see cref="AssociateDiffieHellmanRequest"/> class. - /// </summary> - /// <param name="version">The OpenID version this message must comply with.</param> - /// <param name="providerEndpoint">The OpenID Provider endpoint.</param> - internal AssociateDiffieHellmanRequest(Version version, Uri providerEndpoint) - : base(version, providerEndpoint) { - this.DiffieHellmanModulus = DefaultMod; - this.DiffieHellmanGen = DefaultGen; - } - - /// <summary> - /// Gets or sets the openid.dh_modulus value. - /// </summary> - /// <value>May be null if the default value given in the OpenID spec is to be used.</value> - [MessagePart("openid.dh_modulus", IsRequired = false, AllowEmpty = false)] - internal byte[] DiffieHellmanModulus { get; set; } - - /// <summary> - /// Gets or sets the openid.dh_gen value. - /// </summary> - /// <value>May be null if the default value given in the OpenID spec is to be used.</value> - [MessagePart("openid.dh_gen", IsRequired = false, AllowEmpty = false)] - internal byte[] DiffieHellmanGen { get; set; } - - /// <summary> - /// Gets or sets the openid.dh_consumer_public value. - /// </summary> - /// <remarks> - /// This property is initialized with a call to <see cref="InitializeRequest"/>. - /// </remarks> - [MessagePart("openid.dh_consumer_public", IsRequired = true, AllowEmpty = false)] - internal byte[] DiffieHellmanConsumerPublic { get; set; } - - /// <summary> - /// Gets the Diffie-Hellman algorithm. - /// </summary> - /// <remarks> - /// This property is initialized with a call to <see cref="InitializeRequest"/>. - /// </remarks> - internal DiffieHellman Algorithm { get; private set; } - - /// <summary> - /// Called by the Relying Party to initialize the Diffie-Hellman algorithm and consumer public key properties. - /// </summary> - internal void InitializeRequest() { - if (this.DiffieHellmanModulus == null || this.DiffieHellmanGen == null) { - throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, OpenIdStrings.DiffieHellmanRequiredPropertiesNotSet, string.Join(", ", new string[] { "DiffieHellmanModulus", "DiffieHellmanGen" }))); - } - - this.Algorithm = new DiffieHellmanManaged(this.DiffieHellmanModulus ?? DefaultMod, this.DiffieHellmanGen ?? DefaultGen, DefaultX); - byte[] consumerPublicKeyExchange = this.Algorithm.CreateKeyExchange(); - this.DiffieHellmanConsumerPublic = DiffieHellmanUtilities.EnsurePositive(consumerPublicKeyExchange); - } - - /// <summary> - /// Creates a Provider's response to an incoming association request. - /// </summary> - /// <returns> - /// The appropriate association response message. - /// </returns> - /// <remarks> - /// <para>If an association can be successfully created, the - /// <see cref="AssociateSuccessfulResponse.CreateAssociation"/> method must not be - /// called by this method.</para> - /// <para>Successful association response messages will derive from <see cref="AssociateSuccessfulResponse"/>. - /// Failed association response messages will derive from <see cref="AssociateUnsuccessfulResponse"/>.</para> - /// </remarks> - protected override IProtocolMessage CreateResponseCore() { - var response = new AssociateDiffieHellmanResponse(this.Version, this); - response.AssociationType = this.AssociationType; - return response; - } - } -} diff --git a/src/DotNetOpenAuth/OpenId/Messages/AssociateDiffieHellmanResponse.cs b/src/DotNetOpenAuth/OpenId/Messages/AssociateDiffieHellmanResponse.cs deleted file mode 100644 index 5237826..0000000 --- a/src/DotNetOpenAuth/OpenId/Messages/AssociateDiffieHellmanResponse.cs +++ /dev/null @@ -1,103 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="AssociateDiffieHellmanResponse.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OpenId.Messages { - using System; - using System.Diagnostics.Contracts; - using System.Security.Cryptography; - using DotNetOpenAuth.Messaging; - using DotNetOpenAuth.Messaging.Reflection; - using DotNetOpenAuth.OpenId.Provider; - using Org.Mentalis.Security.Cryptography; - - /// <summary> - /// The successful Diffie-Hellman association response message. - /// </summary> - /// <remarks> - /// Association response messages are described in OpenID 2.0 section 8.2. This type covers section 8.2.3. - /// </remarks> - internal class AssociateDiffieHellmanResponse : AssociateSuccessfulResponse { - /// <summary> - /// Initializes a new instance of the <see cref="AssociateDiffieHellmanResponse"/> class. - /// </summary> - /// <param name="responseVersion">The OpenID version of the response message.</param> - /// <param name="originatingRequest">The originating request.</param> - internal AssociateDiffieHellmanResponse(Version responseVersion, AssociateDiffieHellmanRequest originatingRequest) - : base(responseVersion, originatingRequest) { - } - - /// <summary> - /// Gets or sets the Provider's Diffie-Hellman public key. - /// </summary> - /// <value>btwoc(g ^ xb mod p)</value> - [MessagePart("dh_server_public", IsRequired = true, AllowEmpty = false)] - internal byte[] DiffieHellmanServerPublic { get; set; } - - /// <summary> - /// Gets or sets the MAC key (shared secret), encrypted with the secret Diffie-Hellman value. - /// </summary> - /// <value>H(btwoc(g ^ (xa * xb) mod p)) XOR MAC key. H is either "SHA1" or "SHA256" depending on the session type. </value> - [MessagePart("enc_mac_key", IsRequired = true, AllowEmpty = false)] - internal byte[] EncodedMacKey { get; set; } - - /// <summary> - /// Creates the association at relying party side after the association response has been received. - /// </summary> - /// <param name="request">The original association request that was already sent and responded to.</param> - /// <returns>The newly created association.</returns> - /// <remarks> - /// The resulting association is <i>not</i> added to the association store and must be done by the caller. - /// </remarks> - protected override Association CreateAssociationAtRelyingParty(AssociateRequest request) { - var diffieHellmanRequest = request as AssociateDiffieHellmanRequest; - ErrorUtilities.VerifyArgument(diffieHellmanRequest != null, OpenIdStrings.DiffieHellmanAssociationRequired); - - HashAlgorithm hasher = DiffieHellmanUtilities.Lookup(Protocol, this.SessionType); - byte[] associationSecret = DiffieHellmanUtilities.SHAHashXorSecret(hasher, diffieHellmanRequest.Algorithm, this.DiffieHellmanServerPublic, this.EncodedMacKey); - - Association association = HmacShaAssociation.Create(Protocol, this.AssociationType, this.AssociationHandle, associationSecret, TimeSpan.FromSeconds(this.ExpiresIn)); - return association; - } - - /// <summary> - /// Creates the association at the provider side after the association request has been received. - /// </summary> - /// <param name="request">The association request.</param> - /// <param name="associationStore">The OpenID Provider's association store or handle encoder.</param> - /// <param name="securitySettings">The security settings of the Provider.</param> - /// <returns> - /// The newly created association. - /// </returns> - /// <remarks> - /// The response message is updated to include the details of the created association by this method, - /// but the resulting association is <i>not</i> added to the association store and must be done by the caller. - /// </remarks> - protected override Association CreateAssociationAtProvider(AssociateRequest request, IProviderAssociationStore associationStore, ProviderSecuritySettings securitySettings) { - var diffieHellmanRequest = request as AssociateDiffieHellmanRequest; - ErrorUtilities.VerifyInternal(diffieHellmanRequest != null, "Expected a DH request type."); - - this.SessionType = this.SessionType ?? request.SessionType; - - // Go ahead and create the association first, complete with its secret that we're about to share. - Association association = HmacShaAssociation.Create(this.Protocol, this.AssociationType, AssociationRelyingPartyType.Smart, associationStore, securitySettings); - - // We now need to securely communicate the secret to the relying party using Diffie-Hellman. - // We do this by performing a DH algorithm on the secret and setting a couple of properties - // that will be transmitted to the Relying Party. The RP will perform an inverse operation - // using its part of a DH secret in order to decrypt the shared secret we just invented - // above when we created the association. - using (DiffieHellman dh = new DiffieHellmanManaged( - diffieHellmanRequest.DiffieHellmanModulus ?? AssociateDiffieHellmanRequest.DefaultMod, - diffieHellmanRequest.DiffieHellmanGen ?? AssociateDiffieHellmanRequest.DefaultGen, - AssociateDiffieHellmanRequest.DefaultX)) { - HashAlgorithm hasher = DiffieHellmanUtilities.Lookup(this.Protocol, this.SessionType); - this.DiffieHellmanServerPublic = DiffieHellmanUtilities.EnsurePositive(dh.CreateKeyExchange()); - this.EncodedMacKey = DiffieHellmanUtilities.SHAHashXorSecret(hasher, dh, diffieHellmanRequest.DiffieHellmanConsumerPublic, association.SecretKey); - } - return association; - } - } -} diff --git a/src/DotNetOpenAuth/OpenId/Messages/AssociateRequest.cs b/src/DotNetOpenAuth/OpenId/Messages/AssociateRequest.cs deleted file mode 100644 index 2a0dc7a..0000000 --- a/src/DotNetOpenAuth/OpenId/Messages/AssociateRequest.cs +++ /dev/null @@ -1,214 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="AssociateRequest.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OpenId.Messages { - using System; - using System.Collections.Generic; - using System.Diagnostics; - using System.Diagnostics.Contracts; - using System.Linq; - using System.Text; - using DotNetOpenAuth.Messaging; - using DotNetOpenAuth.OpenId.Provider; - using DotNetOpenAuth.OpenId.RelyingParty; - - /// <summary> - /// An OpenID direct request from Relying Party to Provider to initiate an association. - /// </summary> - [DebuggerDisplay("OpenID {Version} {Mode} {AssociationType} {SessionType}")] - internal abstract class AssociateRequest : RequestBase { - /// <summary> - /// Initializes a new instance of the <see cref="AssociateRequest"/> class. - /// </summary> - /// <param name="version">The OpenID version this message must comply with.</param> - /// <param name="providerEndpoint">The OpenID Provider endpoint.</param> - protected AssociateRequest(Version version, Uri providerEndpoint) - : base(version, providerEndpoint, GetProtocolConstant(version, p => p.Args.Mode.associate), MessageTransport.Direct) { - } - - /// <summary> - /// Gets or sets the preferred association type. The association type defines the algorithm to be used to sign subsequent messages. - /// </summary> - /// <value>Value: A valid association type from Section 8.3.</value> - [MessagePart("openid.assoc_type", IsRequired = true, AllowEmpty = false)] - internal string AssociationType { get; set; } - - /// <summary> - /// Gets or sets the preferred association session type. This defines the method used to encrypt the association's MAC key in transit. - /// </summary> - /// <value>Value: A valid association session type from Section 8.4 (Association Session Types). </value> - /// <remarks>Note: Unless using transport layer encryption, "no-encryption" MUST NOT be used. </remarks> - [MessagePart("openid.session_type", IsRequired = false, AllowEmpty = true)] - [MessagePart("openid.session_type", IsRequired = true, AllowEmpty = false, MinVersion = "2.0")] - internal string SessionType { get; set; } - - /// <summary> - /// Checks the message state for conformity to the protocol specification - /// and throws an exception if the message is invalid. - /// </summary> - /// <remarks> - /// <para>Some messages have required fields, or combinations of fields that must relate to each other - /// in specialized ways. After deserializing a message, this method checks the state of the - /// message to see if it conforms to the protocol.</para> - /// <para>Note that this property should <i>not</i> check signatures or perform any state checks - /// outside this scope of this particular message.</para> - /// </remarks> - /// <exception cref="ProtocolException">Thrown if the message is invalid.</exception> - public override void EnsureValidMessage() { - base.EnsureValidMessage(); - - ErrorUtilities.VerifyProtocol( - !string.Equals(this.SessionType, Protocol.Args.SessionType.NoEncryption, StringComparison.Ordinal) || this.Recipient.IsTransportSecure(), - OpenIdStrings.NoEncryptionSessionRequiresHttps, - this); - } - - /// <summary> - /// Creates an association request message that is appropriate for a given Provider. - /// </summary> - /// <param name="securityRequirements">The set of requirements the selected association type must comply to.</param> - /// <param name="provider">The provider to create an association with.</param> - /// <returns> - /// The message to send to the Provider to request an association. - /// Null if no association could be created that meet the security requirements - /// and the provider OpenID version. - /// </returns> - internal static AssociateRequest Create(SecuritySettings securityRequirements, IProviderEndpoint provider) { - Contract.Requires<ArgumentNullException>(securityRequirements != null); - Contract.Requires<ArgumentNullException>(provider != null); - - // Apply our knowledge of the endpoint's transport, OpenID version, and - // security requirements to decide the best association. - bool unencryptedAllowed = provider.Uri.IsTransportSecure(); - bool useDiffieHellman = !unencryptedAllowed; - string associationType, sessionType; - if (!HmacShaAssociation.TryFindBestAssociation(Protocol.Lookup(provider.Version), true, securityRequirements, useDiffieHellman, out associationType, out sessionType)) { - // There are no associations that meet all requirements. - Logger.OpenId.Warn("Security requirements and protocol combination knock out all possible association types. Dumb mode forced."); - return null; - } - - return Create(securityRequirements, provider, associationType, sessionType); - } - - /// <summary> - /// Creates an association request message that is appropriate for a given Provider. - /// </summary> - /// <param name="securityRequirements">The set of requirements the selected association type must comply to.</param> - /// <param name="provider">The provider to create an association with.</param> - /// <param name="associationType">Type of the association.</param> - /// <param name="sessionType">Type of the session.</param> - /// <returns> - /// The message to send to the Provider to request an association. - /// Null if no association could be created that meet the security requirements - /// and the provider OpenID version. - /// </returns> - internal static AssociateRequest Create(SecuritySettings securityRequirements, IProviderEndpoint provider, string associationType, string sessionType) { - Contract.Requires<ArgumentNullException>(securityRequirements != null); - Contract.Requires<ArgumentNullException>(provider != null); - Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(associationType)); - Contract.Requires<ArgumentNullException>(sessionType != null); - - bool unencryptedAllowed = provider.Uri.IsTransportSecure(); - if (unencryptedAllowed) { - var associateRequest = new AssociateUnencryptedRequest(provider.Version, provider.Uri); - associateRequest.AssociationType = associationType; - return associateRequest; - } else { - var associateRequest = new AssociateDiffieHellmanRequest(provider.Version, provider.Uri); - associateRequest.AssociationType = associationType; - associateRequest.SessionType = sessionType; - associateRequest.InitializeRequest(); - return associateRequest; - } - } - - /// <summary> - /// Creates a Provider's response to an incoming association request. - /// </summary> - /// <param name="associationStore">The association store.</param> - /// <param name="securitySettings">The security settings on the Provider.</param> - /// <returns> - /// The appropriate association response that is ready to be sent back to the Relying Party. - /// </returns> - /// <remarks> - /// <para>If an association is created, it will be automatically be added to the provided - /// association store.</para> - /// <para>Successful association response messages will derive from <see cref="AssociateSuccessfulResponse"/>. - /// Failed association response messages will derive from <see cref="AssociateUnsuccessfulResponse"/>.</para> - /// </remarks> - internal IProtocolMessage CreateResponse(IProviderAssociationStore associationStore, ProviderSecuritySettings securitySettings) { - Contract.Requires<ArgumentNullException>(associationStore != null); - Contract.Requires<ArgumentNullException>(securitySettings != null); - - IProtocolMessage response; - if (securitySettings.IsAssociationInPermittedRange(Protocol, this.AssociationType) && - HmacShaAssociation.IsDHSessionCompatible(Protocol, this.AssociationType, this.SessionType)) { - response = this.CreateResponseCore(); - - // Create and store the association if this is a successful response. - var successResponse = response as AssociateSuccessfulResponse; - if (successResponse != null) { - successResponse.CreateAssociation(this, associationStore, securitySettings); - } - } else { - response = this.CreateUnsuccessfulResponse(securitySettings); - } - - return response; - } - - /// <summary> - /// Creates a Provider's response to an incoming association request. - /// </summary> - /// <returns> - /// The appropriate association response message. - /// </returns> - /// <remarks> - /// <para>If an association can be successfully created, the - /// <see cref="AssociateSuccessfulResponse.CreateAssociation"/> method must not be - /// called by this method.</para> - /// <para>Successful association response messages will derive from <see cref="AssociateSuccessfulResponse"/>. - /// Failed association response messages will derive from <see cref="AssociateUnsuccessfulResponse"/>.</para> - /// </remarks> - protected abstract IProtocolMessage CreateResponseCore(); - - /// <summary> - /// Creates a response that notifies the Relying Party that the requested - /// association type is not supported by this Provider, and offers - /// an alternative association type, if possible. - /// </summary> - /// <param name="securitySettings">The security settings that apply to this Provider.</param> - /// <returns>The response to send to the Relying Party.</returns> - private AssociateUnsuccessfulResponse CreateUnsuccessfulResponse(ProviderSecuritySettings securitySettings) { - Contract.Requires<ArgumentNullException>(securitySettings != null); - - var unsuccessfulResponse = new AssociateUnsuccessfulResponse(this.Version, this); - - // The strategy here is to suggest that the RP try again with the lowest - // permissible security settings, giving the RP the best chance of being - // able to match with a compatible request. - bool unencryptedAllowed = this.Recipient.IsTransportSecure(); - bool useDiffieHellman = !unencryptedAllowed; - string associationType, sessionType; - if (HmacShaAssociation.TryFindBestAssociation(Protocol, false, securitySettings, useDiffieHellman, out associationType, out sessionType)) { - ErrorUtilities.VerifyInternal(this.AssociationType != associationType, "The RP asked for an association that should have been allowed, but the OP is trying to suggest the same one as an alternative!"); - unsuccessfulResponse.AssociationType = associationType; - unsuccessfulResponse.SessionType = sessionType; - Logger.OpenId.InfoFormat( - "Association requested of type '{0}' and session '{1}', which the Provider does not support. Sending back suggested alternative of '{0}' with session '{1}'.", - this.AssociationType, - this.SessionType, - unsuccessfulResponse.AssociationType, - unsuccessfulResponse.SessionType); - } else { - Logger.OpenId.InfoFormat("Association requested of type '{0}' and session '{1}', which the Provider does not support. No alternative association type qualified for suggesting back to the Relying Party.", this.AssociationType, this.SessionType); - } - - return unsuccessfulResponse; - } - } -} diff --git a/src/DotNetOpenAuth/OpenId/Messages/AssociateSuccessfulResponse.cs b/src/DotNetOpenAuth/OpenId/Messages/AssociateSuccessfulResponse.cs deleted file mode 100644 index 42d8816..0000000 --- a/src/DotNetOpenAuth/OpenId/Messages/AssociateSuccessfulResponse.cs +++ /dev/null @@ -1,159 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="AssociateSuccessfulResponse.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OpenId.Messages { - using System; - using System.Collections.Generic; - using System.Diagnostics; - using System.Diagnostics.Contracts; - using System.Linq; - using System.Text; - using DotNetOpenAuth.Messaging; - using DotNetOpenAuth.OpenId.Provider; - - /// <summary> - /// The base class that all successful association response messages derive from. - /// </summary> - /// <remarks> - /// Association response messages are described in OpenID 2.0 section 8.2. This type covers section 8.2.1. - /// </remarks> - [DebuggerDisplay("OpenID {Version} associate response {AssociationHandle} {AssociationType} {SessionType}")] - [ContractClass(typeof(AssociateSuccessfulResponseContract))] - internal abstract class AssociateSuccessfulResponse : DirectResponseBase { - /// <summary> - /// A flag indicating whether an association has already been created. - /// </summary> - private bool associationCreated; - - /// <summary> - /// Initializes a new instance of the <see cref="AssociateSuccessfulResponse"/> class. - /// </summary> - /// <param name="responseVersion">The OpenID version of the response message.</param> - /// <param name="originatingRequest">The originating request.</param> - internal AssociateSuccessfulResponse(Version responseVersion, AssociateRequest originatingRequest) - : base(responseVersion, originatingRequest) { - } - - /// <summary> - /// Gets or sets the association handle is used as a key to refer to this association in subsequent messages. - /// </summary> - /// <value>A string 255 characters or less in length. It MUST consist only of ASCII characters in the range 33-126 inclusive (printable non-whitespace characters). </value> - [MessagePart("assoc_handle", IsRequired = true, AllowEmpty = false)] - internal string AssociationHandle { get; set; } - - /// <summary> - /// Gets or sets the preferred association type. The association type defines the algorithm to be used to sign subsequent messages. - /// </summary> - /// <value>Value: A valid association type from Section 8.3.</value> - [MessagePart("assoc_type", IsRequired = true, AllowEmpty = false)] - internal string AssociationType { get; set; } - - /// <summary> - /// Gets or sets the value of the "openid.session_type" parameter from the request. - /// If the OP is unwilling or unable to support this association type, it MUST return an - /// unsuccessful response (Unsuccessful Response Parameters). - /// </summary> - /// <value>Value: A valid association session type from Section 8.4 (Association Session Types). </value> - /// <remarks>Note: Unless using transport layer encryption, "no-encryption" MUST NOT be used. </remarks> - [MessagePart("session_type", IsRequired = false, AllowEmpty = true)] - [MessagePart("session_type", IsRequired = true, AllowEmpty = false, MinVersion = "2.0")] - internal string SessionType { get; set; } - - /// <summary> - /// Gets or sets the lifetime, in seconds, of this association. The Relying Party MUST NOT use the association after this time has passed. - /// </summary> - /// <value>An integer, represented in base 10 ASCII. </value> - [MessagePart("expires_in", IsRequired = true)] - internal long ExpiresIn { get; set; } - - /// <summary> - /// Checks the message state for conformity to the protocol specification - /// and throws an exception if the message is invalid. - /// </summary> - /// <remarks> - /// <para>Some messages have required fields, or combinations of fields that must relate to each other - /// in specialized ways. After deserializing a message, this method checks the state of the - /// message to see if it conforms to the protocol.</para> - /// <para>Note that this property should <i>not</i> check signatures or perform any state checks - /// outside this scope of this particular message.</para> - /// </remarks> - /// <exception cref="ProtocolException">Thrown if the message is invalid.</exception> - public override void EnsureValidMessage() { - base.EnsureValidMessage(); - - if (this.Version.Major < 2) { - ErrorUtilities.VerifyProtocol( - string.IsNullOrEmpty(this.SessionType) || string.Equals(this.SessionType, this.Protocol.Args.SessionType.DH_SHA1, StringComparison.Ordinal), - MessagingStrings.UnexpectedMessagePartValueForConstant, - GetType().Name, - Protocol.openid.session_type, - this.Protocol.Args.SessionType.DH_SHA1, - this.SessionType); - } - } - - /// <summary> - /// Called to create the Association based on a request previously given by the Relying Party. - /// </summary> - /// <param name="request">The prior request for an association.</param> - /// <param name="associationStore">The Provider's association store.</param> - /// <param name="securitySettings">The security settings for the Provider. Should be <c>null</c> for Relying Parties.</param> - /// <returns> - /// The created association. - /// </returns> - /// <remarks> - /// The response message is updated to include the details of the created association by this method. - /// This method is called by both the Provider and the Relying Party, but actually performs - /// quite different operations in either scenario. - /// </remarks> - internal Association CreateAssociation(AssociateRequest request, IProviderAssociationStore associationStore, ProviderSecuritySettings securitySettings) { - Contract.Requires<ArgumentNullException>(request != null); - ErrorUtilities.VerifyInternal(!this.associationCreated, "The association has already been created."); - - Association association; - - // If this message is outgoing, then we need to initialize some common - // properties based on the created association. - if (this.Incoming) { - association = this.CreateAssociationAtRelyingParty(request); - } else { - ErrorUtilities.VerifyArgumentNotNull(securitySettings, "securitySettings"); - association = this.CreateAssociationAtProvider(request, associationStore, securitySettings); - this.ExpiresIn = association.SecondsTillExpiration; - this.AssociationHandle = association.Handle; - } - - this.associationCreated = true; - - return association; - } - - /// <summary> - /// Called to create the Association based on a request previously given by the Relying Party. - /// </summary> - /// <param name="request">The prior request for an association.</param> - /// <param name="associationStore">The Provider's association store.</param> - /// <param name="securitySettings">The security settings of the Provider.</param> - /// <returns> - /// The created association. - /// </returns> - /// <remarks> - /// <para>The caller will update this message's <see cref="ExpiresIn"/> and <see cref="AssociationHandle"/> - /// properties based on the <see cref="Association"/> returned by this method, but any other - /// association type specific properties must be set by this method.</para> - /// <para>The response message is updated to include the details of the created association by this method, - /// but the resulting association is <i>not</i> added to the association store and must be done by the caller.</para> - /// </remarks> - protected abstract Association CreateAssociationAtProvider(AssociateRequest request, IProviderAssociationStore associationStore, ProviderSecuritySettings securitySettings); - - /// <summary> - /// Called to create the Association based on a request previously given by the Relying Party. - /// </summary> - /// <param name="request">The prior request for an association.</param> - /// <returns>The created association.</returns> - protected abstract Association CreateAssociationAtRelyingParty(AssociateRequest request); - } -} diff --git a/src/DotNetOpenAuth/OpenId/Messages/AssociateSuccessfulResponseContract.cs b/src/DotNetOpenAuth/OpenId/Messages/AssociateSuccessfulResponseContract.cs deleted file mode 100644 index d474608..0000000 --- a/src/DotNetOpenAuth/OpenId/Messages/AssociateSuccessfulResponseContract.cs +++ /dev/null @@ -1,30 +0,0 @@ -// <auto-generated /> - -namespace DotNetOpenAuth.OpenId.Messages { - using System; - using System.Collections.Generic; - using System.Diagnostics; - using System.Diagnostics.Contracts; - using System.Linq; - using System.Text; - using DotNetOpenAuth.Messaging; - using DotNetOpenAuth.OpenId.Provider; - - [ContractClassFor(typeof(AssociateSuccessfulResponse))] - internal abstract class AssociateSuccessfulResponseContract : AssociateSuccessfulResponse { - protected AssociateSuccessfulResponseContract() : base(null, null) { - } - - protected override Association CreateAssociationAtProvider(AssociateRequest request, IProviderAssociationStore associationStore, ProviderSecuritySettings securitySettings) { - Contract.Requires<ArgumentNullException>(request != null); - Contract.Requires<ArgumentNullException>(associationStore != null); - Contract.Requires<ArgumentNullException>(securitySettings != null); - throw new NotImplementedException(); - } - - protected override Association CreateAssociationAtRelyingParty(AssociateRequest request) { - Contract.Requires<ArgumentNullException>(request != null); - throw new NotImplementedException(); - } - } -} diff --git a/src/DotNetOpenAuth/OpenId/Messages/AssociateUnencryptedRequest.cs b/src/DotNetOpenAuth/OpenId/Messages/AssociateUnencryptedRequest.cs deleted file mode 100644 index ef302a5..0000000 --- a/src/DotNetOpenAuth/OpenId/Messages/AssociateUnencryptedRequest.cs +++ /dev/null @@ -1,69 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="AssociateUnencryptedRequest.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OpenId.Messages { - using System; - using DotNetOpenAuth.Messaging; - using DotNetOpenAuth.Messaging.Reflection; - - /// <summary> - /// Represents an association request that is sent using HTTPS and otherwise communicates the shared secret in plain text. - /// </summary> - internal class AssociateUnencryptedRequest : AssociateRequest { - /// <summary> - /// Initializes a new instance of the <see cref="AssociateUnencryptedRequest"/> class. - /// </summary> - /// <param name="version">The OpenID version this message must comply with.</param> - /// <param name="providerEndpoint">The OpenID Provider endpoint.</param> - internal AssociateUnencryptedRequest(Version version, Uri providerEndpoint) - : base(version, providerEndpoint) { - SessionType = Protocol.Args.SessionType.NoEncryption; - } - - /// <summary> - /// Checks the message state for conformity to the protocol specification - /// and throws an exception if the message is invalid. - /// </summary> - /// <remarks> - /// <para>Some messages have required fields, or combinations of fields that must relate to each other - /// in specialized ways. After deserializing a message, this method checks the state of the - /// message to see if it conforms to the protocol.</para> - /// <para>Note that this property should <i>not</i> check signatures or perform any state checks - /// outside this scope of this particular message.</para> - /// </remarks> - /// <exception cref="ProtocolException">Thrown if the message is invalid.</exception> - public override void EnsureValidMessage() { - base.EnsureValidMessage(); - - ErrorUtilities.VerifyProtocol( - string.Equals(this.SessionType, Protocol.Args.SessionType.NoEncryption, StringComparison.Ordinal), - MessagingStrings.UnexpectedMessagePartValueForConstant, - GetType().Name, - Protocol.openid.session_type, - Protocol.Args.SessionType.NoEncryption, - SessionType); - } - - /// <summary> - /// Creates a Provider's response to an incoming association request. - /// </summary> - /// <returns> - /// The appropriate association response message. - /// </returns> - /// <remarks> - /// <para>If an association can be successfully created, the - /// <see cref="AssociateSuccessfulResponse.CreateAssociation"/> method must not be - /// called by this method.</para> - /// <para>Successful association response messages will derive from <see cref="AssociateSuccessfulResponse"/>. - /// Failed association response messages will derive from <see cref="AssociateUnsuccessfulResponse"/>.</para> - /// </remarks> - protected override IProtocolMessage CreateResponseCore() { - var response = new AssociateUnencryptedResponse(this.Version, this); - response.AssociationType = this.AssociationType; - return response; - } - } -} diff --git a/src/DotNetOpenAuth/OpenId/Messages/AssociateUnencryptedResponse.cs b/src/DotNetOpenAuth/OpenId/Messages/AssociateUnencryptedResponse.cs deleted file mode 100644 index 7e2194a..0000000 --- a/src/DotNetOpenAuth/OpenId/Messages/AssociateUnencryptedResponse.cs +++ /dev/null @@ -1,70 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="AssociateUnencryptedResponse.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OpenId.Messages { - using System; - using DotNetOpenAuth.Messaging; - using DotNetOpenAuth.Messaging.Reflection; - using DotNetOpenAuth.OpenId.Provider; - - /// <summary> - /// The successful unencrypted association response message. - /// </summary> - /// <remarks> - /// Association response messages are described in OpenID 2.0 section 8.2. This type covers section 8.2.2. - /// </remarks> - internal class AssociateUnencryptedResponse : AssociateSuccessfulResponse { - /// <summary> - /// Initializes a new instance of the <see cref="AssociateUnencryptedResponse"/> class. - /// </summary> - /// <param name="responseVersion">The OpenID version of the response message.</param> - /// <param name="originatingRequest">The originating request.</param> - internal AssociateUnencryptedResponse(Version responseVersion, AssociateUnencryptedRequest originatingRequest) - : base(responseVersion, originatingRequest) { - SessionType = Protocol.Args.SessionType.NoEncryption; - } - - /// <summary> - /// Gets or sets the MAC key (shared secret) for this association, Base 64 (Josefsson, S., “The Base16, Base32, and Base64 Data Encodings,” .) [RFC3548] encoded. - /// </summary> - [MessagePart("mac_key", IsRequired = true, AllowEmpty = false)] - internal byte[] MacKey { get; set; } - - /// <summary> - /// Called to create the Association based on a request previously given by the Relying Party. - /// </summary> - /// <param name="request">The prior request for an association.</param> - /// <param name="associationStore">The Provider's association store.</param> - /// <param name="securitySettings">The security settings of the Provider.</param> - /// <returns> - /// The created association. - /// </returns> - /// <remarks> - /// <para>The caller will update this message's - /// <see cref="AssociateSuccessfulResponse.ExpiresIn"/> and - /// <see cref="AssociateSuccessfulResponse.AssociationHandle"/> - /// properties based on the <see cref="Association"/> returned by this method, but any other - /// association type specific properties must be set by this method.</para> - /// <para>The response message is updated to include the details of the created association by this method, - /// but the resulting association is <i>not</i> added to the association store and must be done by the caller.</para> - /// </remarks> - protected override Association CreateAssociationAtProvider(AssociateRequest request, IProviderAssociationStore associationStore, ProviderSecuritySettings securitySettings) { - Association association = HmacShaAssociation.Create(Protocol, this.AssociationType, AssociationRelyingPartyType.Smart, associationStore, securitySettings); - this.MacKey = association.SecretKey; - return association; - } - - /// <summary> - /// Called to create the Association based on a request previously given by the Relying Party. - /// </summary> - /// <param name="request">The prior request for an association.</param> - /// <returns>The created association.</returns> - protected override Association CreateAssociationAtRelyingParty(AssociateRequest request) { - Association association = HmacShaAssociation.Create(Protocol, this.AssociationType, this.AssociationHandle, this.MacKey, TimeSpan.FromSeconds(this.ExpiresIn)); - return association; - } - } -} diff --git a/src/DotNetOpenAuth/OpenId/Messages/CheckAuthenticationRequest.cs b/src/DotNetOpenAuth/OpenId/Messages/CheckAuthenticationRequest.cs deleted file mode 100644 index db69d3d..0000000 --- a/src/DotNetOpenAuth/OpenId/Messages/CheckAuthenticationRequest.cs +++ /dev/null @@ -1,79 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="CheckAuthenticationRequest.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OpenId.Messages { - using System; - using System.Collections.Generic; - using System.Diagnostics.Contracts; - using System.Linq; - using System.Text; - using DotNetOpenAuth.Messaging; - using DotNetOpenAuth.Messaging.Reflection; - using DotNetOpenAuth.OpenId.ChannelElements; - - /// <summary> - /// A message a Relying Party sends to a Provider to confirm the validity - /// of a positive assertion that was signed by a Provider-only secret. - /// </summary> - /// <remarks> - /// The significant payload of this message depends entirely upon the - /// assertion message, and therefore is all in the - /// <see cref="DotNetOpenAuth.Messaging.IMessage.ExtraData"/> property bag. - /// </remarks> - internal class CheckAuthenticationRequest : RequestBase { - /// <summary> - /// Initializes a new instance of the <see cref="CheckAuthenticationRequest"/> class. - /// </summary> - /// <param name="version">The OpenID version this message must comply with.</param> - /// <param name="providerEndpoint">The OpenID Provider endpoint.</param> - internal CheckAuthenticationRequest(Version version, Uri providerEndpoint) - : base(version, providerEndpoint, GetProtocolConstant(version, p => p.Args.Mode.check_authentication), MessageTransport.Direct) { - } - - /// <summary> - /// Initializes a new instance of the <see cref="CheckAuthenticationRequest"/> class - /// based on the contents of some signed message whose signature must be verified. - /// </summary> - /// <param name="message">The message whose signature should be verified.</param> - /// <param name="channel">The channel. This is used only within the constructor and is not stored in a field.</param> - internal CheckAuthenticationRequest(IndirectSignedResponse message, Channel channel) - : base(message.Version, message.ProviderEndpoint, GetProtocolConstant(message.Version, p => p.Args.Mode.check_authentication), MessageTransport.Direct) { - Contract.Requires<ArgumentNullException>(channel != null); - - // Copy all message parts from the id_res message into this one, - // except for the openid.mode parameter. - MessageDictionary checkPayload = channel.MessageDescriptions.GetAccessor(message, true); - MessageDictionary thisPayload = channel.MessageDescriptions.GetAccessor(this); - foreach (var pair in checkPayload) { - if (!string.Equals(pair.Key, this.Protocol.openid.mode)) { - thisPayload[pair.Key] = pair.Value; - } - } - } - - /// <summary> - /// Gets or sets a value indicating whether the signature being verified by this request - /// is in fact valid. - /// </summary> - /// <value><c>true</c> if the signature is valid; otherwise, <c>false</c>.</value> - /// <remarks> - /// This property is automatically set as the message is received by the channel's - /// signing binding element. - /// </remarks> - internal bool IsValid { get; set; } - - /// <summary> - /// Gets or sets the ReturnTo that existed in the original signed message. - /// </summary> - /// <remarks> - /// This exists strictly for convenience in recreating the <see cref="IndirectSignedResponse"/> - /// message. - /// </remarks> - [MessagePart("openid.return_to", IsRequired = true, AllowEmpty = false, Encoder = typeof(OriginalStringUriEncoder))] - [MessagePart("openid.return_to", IsRequired = false, AllowEmpty = false, MinVersion = "2.0", Encoder = typeof(OriginalStringUriEncoder))] - internal Uri ReturnTo { get; set; } - } -} diff --git a/src/DotNetOpenAuth/OpenId/Messages/CheckAuthenticationResponse.cs b/src/DotNetOpenAuth/OpenId/Messages/CheckAuthenticationResponse.cs deleted file mode 100644 index f4d5243..0000000 --- a/src/DotNetOpenAuth/OpenId/Messages/CheckAuthenticationResponse.cs +++ /dev/null @@ -1,79 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="CheckAuthenticationResponse.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OpenId.Messages { - using System; - using System.Collections.Generic; - using System.Diagnostics.Contracts; - using System.Linq; - using System.Text; - using DotNetOpenAuth.Messaging; - using DotNetOpenAuth.OpenId.ChannelElements; - using DotNetOpenAuth.OpenId.Provider; - - /// <summary> - /// The message sent from the Provider to the Relying Party to confirm/deny - /// the validity of an assertion that was signed by a private Provider secret. - /// </summary> - internal class CheckAuthenticationResponse : DirectResponseBase { - /// <summary> - /// Initializes a new instance of the <see cref="CheckAuthenticationResponse"/> class - /// for use by the Relying Party. - /// </summary> - /// <param name="responseVersion">The OpenID version of the response message.</param> - /// <param name="request">The request that this message is responding to.</param> - internal CheckAuthenticationResponse(Version responseVersion, CheckAuthenticationRequest request) - : base(responseVersion, request) { - } - - /// <summary> - /// Initializes a new instance of the <see cref="CheckAuthenticationResponse"/> class - /// for use by the Provider. - /// </summary> - /// <param name="request">The request that this message is responding to.</param> - /// <param name="provider">The OpenID Provider that is preparing to send this response.</param> - internal CheckAuthenticationResponse(CheckAuthenticationRequest request, OpenIdProvider provider) - : base(request.Version, request) { - Contract.Requires<ArgumentNullException>(provider != null); - - // The channel's binding elements have already set the request's IsValid property - // appropriately. We just copy it into the response message. - this.IsValid = request.IsValid; - - // Confirm the RP should invalidate the association handle only if the association - // is not valid (any longer). OpenID 2.0 section 11.4.2.2. - IndirectSignedResponse signedResponse = new IndirectSignedResponse(request, provider.Channel); - string invalidateHandle = ((ITamperResistantOpenIdMessage)signedResponse).InvalidateHandle; - if (!string.IsNullOrEmpty(invalidateHandle) && !provider.AssociationStore.IsValid(signedResponse, false, invalidateHandle)) { - this.InvalidateHandle = invalidateHandle; - } - } - - /// <summary> - /// Gets or sets a value indicating whether the signature of the verification request is valid. - /// </summary> - [MessagePart("is_valid", IsRequired = true)] - internal bool IsValid { get; set; } - - /// <summary> - /// Gets or sets the handle the relying party should invalidate if <see cref="IsValid"/> is true. - /// </summary> - /// <value>The "invalidate_handle" value sent in the verification request, if the OP confirms it is invalid.</value> - /// <remarks> - /// <para>If present in a verification response with "is_valid" set to "true", - /// the Relying Party SHOULD remove the corresponding association from - /// its store and SHOULD NOT send further authentication requests with - /// this handle.</para> - /// <para>This two-step process for invalidating associations is necessary - /// to prevent an attacker from invalidating an association at will by - /// adding "invalidate_handle" parameters to an authentication response.</para> - /// <para>For OpenID 1.1, we allow this to be present but empty to put up with poor implementations such as Blogger.</para> - /// </remarks> - [MessagePart("invalidate_handle", IsRequired = false, AllowEmpty = true, MaxVersion = "1.1")] - [MessagePart("invalidate_handle", IsRequired = false, AllowEmpty = false, MinVersion = "2.0")] - internal string InvalidateHandle { get; set; } - } -} diff --git a/src/DotNetOpenAuth/OpenId/Messages/CheckIdRequest.cs b/src/DotNetOpenAuth/OpenId/Messages/CheckIdRequest.cs deleted file mode 100644 index 09c36a5..0000000 --- a/src/DotNetOpenAuth/OpenId/Messages/CheckIdRequest.cs +++ /dev/null @@ -1,94 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="CheckIdRequest.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OpenId.Messages { - using System; - using System.Collections.Generic; - using System.Diagnostics; - using System.Linq; - using System.Text; - using DotNetOpenAuth.Messaging; - using DotNetOpenAuth.OpenId.RelyingParty; - - /// <summary> - /// An authentication request from a Relying Party to a Provider. - /// </summary> - /// <remarks> - /// This message type satisfies OpenID 2.0 section 9.1. - /// </remarks> - [DebuggerDisplay("OpenID {Version} {Mode} {ClaimedIdentifier}")] - [Serializable] - internal class CheckIdRequest : SignedResponseRequest { - /// <summary> - /// Initializes a new instance of the <see cref="CheckIdRequest"/> class. - /// </summary> - /// <param name="version">The OpenID version to use.</param> - /// <param name="providerEndpoint">The Provider endpoint that receives this message.</param> - /// <param name="mode"> - /// <see cref="AuthenticationRequestMode.Immediate"/> for asynchronous javascript clients; - /// <see cref="AuthenticationRequestMode.Setup"/> to allow the Provider to interact with the user in order to complete authentication. - /// </param> - internal CheckIdRequest(Version version, Uri providerEndpoint, AuthenticationRequestMode mode) : - base(version, providerEndpoint, mode) { - } - - /// <summary> - /// Gets or sets the Claimed Identifier. - /// </summary> - /// <remarks> - /// <para>"openid.claimed_id" and "openid.identity" SHALL be either both present or both absent. - /// If neither value is present, the assertion is not about an identifier, - /// and will contain other information in its payload, using extensions (Extensions). </para> - /// <para>It is RECOMMENDED that OPs accept XRI identifiers with or without the "xri://" prefix, as specified in the Normalization (Normalization) section. </para> - /// </remarks> - [MessagePart("openid.claimed_id", IsRequired = true, AllowEmpty = false, MinVersion = "2.0")] - internal Identifier ClaimedIdentifier { get; set; } - - /// <summary> - /// Gets or sets the OP Local Identifier. - /// </summary> - /// <value>The OP-Local Identifier. </value> - /// <remarks> - /// <para>If a different OP-Local Identifier is not specified, the claimed - /// identifier MUST be used as the value for openid.identity.</para> - /// <para>Note: If this is set to the special value - /// "http://specs.openid.net/auth/2.0/identifier_select" then the OP SHOULD - /// choose an Identifier that belongs to the end user. This parameter MAY - /// be omitted if the request is not about an identifier (for instance if - /// an extension is in use that makes the request meaningful without it; - /// see openid.claimed_id above). </para> - /// </remarks> - [MessagePart("openid.identity", IsRequired = true, AllowEmpty = false)] - internal Identifier LocalIdentifier { get; set; } - - /// <summary> - /// Checks the message state for conformity to the protocol specification - /// and throws an exception if the message is invalid. - /// </summary> - /// <remarks> - /// <para>Some messages have required fields, or combinations of fields that must relate to each other - /// in specialized ways. After deserializing a message, this method checks the state of the - /// message to see if it conforms to the protocol.</para> - /// <para>Note that this property should <i>not</i> check signatures or perform any state checks - /// outside this scope of this particular message.</para> - /// </remarks> - /// <exception cref="ProtocolException">Thrown if the message is invalid.</exception> - public override void EnsureValidMessage() { - base.EnsureValidMessage(); - - if (this.Protocol.ClaimedIdentifierForOPIdentifier != null) { - // Ensure that the claimed_id and identity parameters are either both the - // special identifier_select value or both NOT that value. - ErrorUtilities.VerifyProtocol( - (this.LocalIdentifier == this.Protocol.ClaimedIdentifierForOPIdentifier) == (this.ClaimedIdentifier == this.Protocol.ClaimedIdentifierForOPIdentifier), - OpenIdStrings.MatchingArgumentsExpected, - Protocol.openid.claimed_id, - Protocol.openid.identity, - Protocol.ClaimedIdentifierForOPIdentifier); - } - } - } -} diff --git a/src/DotNetOpenAuth/OpenId/Messages/DirectResponseBase.cs b/src/DotNetOpenAuth/OpenId/Messages/DirectResponseBase.cs deleted file mode 100644 index e7619bc..0000000 --- a/src/DotNetOpenAuth/OpenId/Messages/DirectResponseBase.cs +++ /dev/null @@ -1,157 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="DirectResponseBase.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OpenId.Messages { - using System; - using System.Collections.Generic; - using System.Diagnostics; - using System.Diagnostics.CodeAnalysis; - using System.Diagnostics.Contracts; - using DotNetOpenAuth.Messaging; - - /// <summary> - /// A common base class for OpenID direct message responses. - /// </summary> - [DebuggerDisplay("OpenID {Version} response")] - internal class DirectResponseBase : IDirectResponseProtocolMessage { - /// <summary> - /// The openid.ns parameter in the message. - /// </summary> - /// <value>"http://specs.openid.net/auth/2.0" </value> - /// <remarks> - /// OpenID 2.0 Section 5.1.2: - /// This particular value MUST be present for the response to be a valid OpenID 2.0 response. - /// Future versions of the specification may define different values in order to allow message - /// recipients to properly interpret the request. - /// </remarks> - [SuppressMessage("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields", Justification = "Read by reflection.")] - [MessagePart("ns", IsRequired = true, AllowEmpty = false, MinVersion = "2.0")] -#pragma warning disable 0414 // read by reflection - private readonly string OpenIdNamespace = Protocol.OpenId2Namespace; -#pragma warning restore 0414 - - /// <summary> - /// Backing store for the <see cref="OriginatingRequest"/> properties. - /// </summary> - private IDirectedProtocolMessage originatingRequest; - - /// <summary> - /// Backing store for the <see cref="Incoming"/> properties. - /// </summary> - private bool incoming; - - /// <summary> - /// The dictionary of parameters that are not part of the OpenID specification. - /// </summary> - private Dictionary<string, string> extraData = new Dictionary<string, string>(); - - /// <summary> - /// Initializes a new instance of the <see cref="DirectResponseBase"/> class. - /// </summary> - /// <param name="responseVersion">The OpenID version of the response message.</param> - /// <param name="originatingRequest">The originating request. May be null in case the request is unrecognizable and this is an error response.</param> - protected DirectResponseBase(Version responseVersion, IDirectedProtocolMessage originatingRequest) { - Contract.Requires<ArgumentNullException>(responseVersion != null); - - this.Version = responseVersion; - this.originatingRequest = originatingRequest; - } - - #region IProtocolMessage Properties - - /// <summary> - /// Gets the version of the protocol this message is prepared to implement. - /// </summary> - /// <value>Version 2.0</value> - public Version Version { get; private set; } - - /// <summary> - /// Gets the level of protection this message requires. - /// </summary> - /// <value><see cref="MessageProtections.None"/></value> - public MessageProtections RequiredProtection { - get { return MessageProtections.None; } - } - - /// <summary> - /// Gets a value indicating whether this is a direct or indirect message. - /// </summary> - /// <value><see cref="MessageTransport.Direct"/></value> - public MessageTransport Transport { - get { return MessageTransport.Direct; } - } - - /// <summary> - /// Gets the extra, non-OAuth parameters included in the message. - /// </summary> - public IDictionary<string, string> ExtraData { - get { return this.extraData; } - } - - #endregion - - #region IDirectResponseProtocolMessage Members - - /// <summary> - /// Gets the originating request message that caused this response to be formed. - /// </summary> - /// <remarks> - /// This property may be null if the request message was undecipherable. - /// </remarks> - IDirectedProtocolMessage IDirectResponseProtocolMessage.OriginatingRequest { - get { return this.originatingRequest; } - } - - #endregion - - /// <summary> - /// Gets a value indicating whether this message was deserialized as an incoming message. - /// </summary> - protected internal bool Incoming { - get { return this.incoming; } - } - - /// <summary> - /// Gets the protocol used by this message. - /// </summary> - protected Protocol Protocol { - get { return Protocol.Lookup(this.Version); } - } - - /// <summary> - /// Gets the originating request message that caused this response to be formed. - /// </summary> - protected IDirectedProtocolMessage OriginatingRequest { - get { return this.originatingRequest; } - } - - #region IProtocolMessage methods - - /// <summary> - /// Checks the message state for conformity to the protocol specification - /// and throws an exception if the message is invalid. - /// </summary> - /// <remarks> - /// <para>Some messages have required fields, or combinations of fields that must relate to each other - /// in specialized ways. After deserializing a message, this method checks the state of the - /// message to see if it conforms to the protocol.</para> - /// <para>Note that this property should <i>not</i> check signatures or perform any state checks - /// outside this scope of this particular message.</para> - /// </remarks> - /// <exception cref="ProtocolException">Thrown if the message is invalid.</exception> - public virtual void EnsureValidMessage() { - } - - #endregion - - /// <summary> - /// Sets a flag indicating that this message is received (as opposed to sent). - /// </summary> - internal void SetAsIncoming() { - this.incoming = true; - } - } -} diff --git a/src/DotNetOpenAuth/OpenId/Messages/IndirectResponseBase.cs b/src/DotNetOpenAuth/OpenId/Messages/IndirectResponseBase.cs deleted file mode 100644 index fce6028..0000000 --- a/src/DotNetOpenAuth/OpenId/Messages/IndirectResponseBase.cs +++ /dev/null @@ -1,113 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="IndirectResponseBase.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OpenId.Messages { - using System; - using System.Collections.Generic; - using System.Diagnostics.Contracts; - using System.Linq; - using System.Text; - using DotNetOpenAuth.Messaging; - - /// <summary> - /// A common base class from which indirect response messages should derive. - /// </summary> - [Serializable] - internal class IndirectResponseBase : RequestBase, IProtocolMessageWithExtensions { - /// <summary> - /// Backing store for the <see cref="Extensions"/> property. - /// </summary> - private IList<IExtensionMessage> extensions = new List<IExtensionMessage>(); - - /// <summary> - /// Initializes a new instance of the <see cref="IndirectResponseBase"/> class. - /// </summary> - /// <param name="request">The request that caused this response message to be constructed.</param> - /// <param name="mode">The value of the openid.mode parameter.</param> - protected IndirectResponseBase(SignedResponseRequest request, string mode) - : base(GetVersion(request), GetReturnTo(request), mode, MessageTransport.Indirect) { - Contract.Requires<ArgumentNullException>(request != null); - - this.OriginatingRequest = request; - } - - /// <summary> - /// Initializes a new instance of the <see cref="IndirectResponseBase"/> class - /// for unsolicited assertion scenarios. - /// </summary> - /// <param name="version">The OpenID version supported at the Relying Party.</param> - /// <param name="relyingPartyReturnTo"> - /// The URI at which the Relying Party receives OpenID indirect messages. - /// </param> - /// <param name="mode">The value to use for the openid.mode parameter.</param> - protected IndirectResponseBase(Version version, Uri relyingPartyReturnTo, string mode) - : base(version, relyingPartyReturnTo, mode, MessageTransport.Indirect) { - } - - #region IProtocolMessageWithExtensions Members - - /// <summary> - /// Gets the list of extensions that are included with this message. - /// </summary> - /// <value></value> - /// <remarks> - /// Implementations of this interface should ensure that this property never returns null. - /// </remarks> - public IList<IExtensionMessage> Extensions { - get { return this.extensions; } - } - - #endregion - - /// <summary> - /// Gets the signed extensions on this message. - /// </summary> - internal IEnumerable<IOpenIdMessageExtension> SignedExtensions { - get { return this.extensions.OfType<IOpenIdMessageExtension>().Where(ext => ext.IsSignedByRemoteParty); } - } - - /// <summary> - /// Gets the unsigned extensions on this message. - /// </summary> - internal IEnumerable<IOpenIdMessageExtension> UnsignedExtensions { - get { return this.extensions.OfType<IOpenIdMessageExtension>().Where(ext => !ext.IsSignedByRemoteParty); } - } - - /// <summary> - /// Gets the originating request message, if applicable. - /// </summary> - protected SignedResponseRequest OriginatingRequest { get; private set; } - - /// <summary> - /// Gets the <see cref="IMessage.Version"/> property of a message. - /// </summary> - /// <param name="message">The message to fetch the protocol version from.</param> - /// <returns>The value of the <see cref="IMessage.Version"/> property.</returns> - /// <remarks> - /// This method can be used by a constructor to throw an <see cref="ArgumentNullException"/> - /// instead of a <see cref="NullReferenceException"/>. - /// </remarks> - internal static Version GetVersion(IProtocolMessage message) { - Contract.Requires<ArgumentNullException>(message != null); - return message.Version; - } - - /// <summary> - /// Gets the <see cref="SignedResponseRequest.ReturnTo"/> property of a message. - /// </summary> - /// <param name="message">The message to fetch the ReturnTo from.</param> - /// <returns>The value of the <see cref="SignedResponseRequest.ReturnTo"/> property.</returns> - /// <remarks> - /// This method can be used by a constructor to throw an <see cref="ArgumentNullException"/> - /// instead of a <see cref="NullReferenceException"/>. - /// </remarks> - private static Uri GetReturnTo(SignedResponseRequest message) { - Contract.Requires<ArgumentNullException>(message != null); - ErrorUtilities.VerifyProtocol(message.ReturnTo != null, OpenIdStrings.ReturnToRequiredForResponse); - return message.ReturnTo; - } - } -} diff --git a/src/DotNetOpenAuth/OpenId/Messages/IndirectSignedResponse.cs b/src/DotNetOpenAuth/OpenId/Messages/IndirectSignedResponse.cs deleted file mode 100644 index baeae16..0000000 --- a/src/DotNetOpenAuth/OpenId/Messages/IndirectSignedResponse.cs +++ /dev/null @@ -1,409 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="IndirectSignedResponse.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OpenId.Messages { - using System; - using System.Collections.Generic; - using System.Collections.Specialized; - using System.Diagnostics; - using System.Diagnostics.CodeAnalysis; - using System.Diagnostics.Contracts; - using System.Globalization; - using System.Linq; - using System.Net.Security; - using System.Web; - using DotNetOpenAuth.Messaging; - using DotNetOpenAuth.Messaging.Bindings; - using DotNetOpenAuth.Messaging.Reflection; - using DotNetOpenAuth.OpenId.ChannelElements; - - /// <summary> - /// An indirect message from a Provider to a Relying Party where at least part of the - /// payload is signed so the Relying Party can verify it has not been tampered with. - /// </summary> - [DebuggerDisplay("OpenID {Version} {Mode} (no id assertion)")] - [Serializable] - internal class IndirectSignedResponse : IndirectResponseBase, ITamperResistantOpenIdMessage { - /// <summary> - /// The allowed date/time formats for the response_nonce parameter. - /// </summary> - /// <remarks> - /// This array of formats is not yet a complete list. - /// </remarks> - private static readonly string[] PermissibleDateTimeFormats = { "yyyy-MM-ddTHH:mm:ssZ" }; - - /// <summary> - /// Backing field for the <see cref="IExpiringProtocolMessage.UtcCreationDate"/> property. - /// </summary> - /// <remarks> - /// The field initializer being DateTime.UtcNow allows for OpenID 1.x messages - /// to pass through the StandardExpirationBindingElement. - /// </remarks> - private DateTime creationDateUtc = DateTime.UtcNow; - - /// <summary> - /// Backing store for the <see cref="ReturnToParameters"/> property. - /// </summary> - private IDictionary<string, string> returnToParameters; - - /// <summary> - /// Initializes a new instance of the <see cref="IndirectSignedResponse"/> class. - /// </summary> - /// <param name="request"> - /// The authentication request that caused this assertion to be generated. - /// </param> - internal IndirectSignedResponse(SignedResponseRequest request) - : base(request, Protocol.Lookup(GetVersion(request)).Args.Mode.id_res) { - Contract.Requires<ArgumentNullException>(request != null); - - this.ReturnTo = request.ReturnTo; - this.ProviderEndpoint = request.Recipient.StripQueryArgumentsWithPrefix(Protocol.openid.Prefix); - ((ITamperResistantOpenIdMessage)this).AssociationHandle = request.AssociationHandle; - } - - /// <summary> - /// Initializes a new instance of the <see cref="IndirectSignedResponse"/> class - /// in order to perform signature verification at the Provider. - /// </summary> - /// <param name="previouslySignedMessage">The previously signed message.</param> - /// <param name="channel">The channel. This is used only within the constructor and is not stored in a field.</param> - internal IndirectSignedResponse(CheckAuthenticationRequest previouslySignedMessage, Channel channel) - : base(GetVersion(previouslySignedMessage), previouslySignedMessage.ReturnTo, Protocol.Lookup(GetVersion(previouslySignedMessage)).Args.Mode.id_res) { - Contract.Requires<ArgumentNullException>(channel != null); - - // Copy all message parts from the check_authentication message into this one, - // except for the openid.mode parameter. - MessageDictionary checkPayload = channel.MessageDescriptions.GetAccessor(previouslySignedMessage); - MessageDictionary thisPayload = channel.MessageDescriptions.GetAccessor(this); - foreach (var pair in checkPayload) { - if (!string.Equals(pair.Key, this.Protocol.openid.mode)) { - thisPayload[pair.Key] = pair.Value; - } - } - } - - /// <summary> - /// Initializes a new instance of the <see cref="IndirectSignedResponse"/> class - /// for unsolicited assertions. - /// </summary> - /// <param name="version">The OpenID version to use.</param> - /// <param name="relyingPartyReturnTo">The return_to URL of the Relying Party. - /// This value will commonly be from <see cref="SignedResponseRequest.ReturnTo"/>, - /// but for unsolicited assertions may come from the Provider performing RP discovery - /// to find the appropriate return_to URL to use.</param> - internal IndirectSignedResponse(Version version, Uri relyingPartyReturnTo) - : base(version, relyingPartyReturnTo, Protocol.Lookup(version).Args.Mode.id_res) { - this.ReturnTo = relyingPartyReturnTo; - } - - /// <summary> - /// Gets the level of protection this message requires. - /// </summary> - /// <value> - /// <see cref="MessageProtections.All"/> for OpenID 2.0 messages. - /// <see cref="MessageProtections.TamperProtection"/> for OpenID 1.x messages. - /// </value> - /// <remarks> - /// Although the required protection is reduced for OpenID 1.x, - /// this library will provide Relying Party hosts with all protections - /// by adding its own specially-crafted nonce to the authentication request - /// messages except for stateless RPs in OpenID 1.x messages. - /// </remarks> - public override MessageProtections RequiredProtection { - // We actually manage to provide All protections regardless of OpenID version - // on both the Provider and Relying Party side, except for stateless RPs for OpenID 1.x. - get { return this.Version.Major < 2 ? MessageProtections.TamperProtection : MessageProtections.All; } - } - - /// <summary> - /// Gets or sets the message signature. - /// </summary> - /// <value>Base 64 encoded signature calculated as specified in Section 6 (Generating Signatures).</value> - [MessagePart("openid.sig", IsRequired = true, AllowEmpty = false)] - string ITamperResistantProtocolMessage.Signature { get; set; } - - /// <summary> - /// Gets or sets the signed parameter order. - /// </summary> - /// <value>Comma-separated list of signed fields.</value> - /// <example>"op_endpoint,identity,claimed_id,return_to,assoc_handle,response_nonce"</example> - /// <remarks> - /// This entry consists of the fields without the "openid." prefix that the signature covers. - /// This list MUST contain at least "op_endpoint", "return_to" "response_nonce" and "assoc_handle", - /// and if present in the response, "claimed_id" and "identity". - /// Additional keys MAY be signed as part of the message. See Generating Signatures. - /// </remarks> - [MessagePart("openid.signed", IsRequired = true, AllowEmpty = false)] - string ITamperResistantOpenIdMessage.SignedParameterOrder { get; set; } - - /// <summary> - /// Gets or sets the association handle used to sign the message. - /// </summary> - /// <value>The handle for the association that was used to sign this assertion. </value> - [MessagePart("openid.assoc_handle", IsRequired = true, AllowEmpty = false, RequiredProtection = ProtectionLevel.Sign, MinVersion = "2.0")] - [MessagePart("openid.assoc_handle", IsRequired = true, AllowEmpty = false, RequiredProtection = ProtectionLevel.None, MaxVersion = "1.1")] - string ITamperResistantOpenIdMessage.AssociationHandle { get; set; } - - /// <summary> - /// Gets or sets the nonce that will protect the message from replay attacks. - /// </summary> - string IReplayProtectedProtocolMessage.Nonce { get; set; } - - /// <summary> - /// Gets the context within which the nonce must be unique. - /// </summary> - string IReplayProtectedProtocolMessage.NonceContext { - get { - if (this.ProviderEndpoint != null) { - return this.ProviderEndpoint.AbsoluteUri; - } else { - // This is the Provider, on an OpenID 1.x check_authentication message. - // We don't need any special nonce context because the Provider - // generated and consumed the nonce. - return string.Empty; - } - } - } - - /// <summary> - /// Gets or sets the UTC date/time the message was originally sent onto the network. - /// </summary> - /// <remarks> - /// The property setter should ensure a UTC date/time, - /// and throw an exception if this is not possible. - /// </remarks> - /// <exception cref="ArgumentException"> - /// Thrown when a DateTime that cannot be converted to UTC is set. - /// </exception> - DateTime IExpiringProtocolMessage.UtcCreationDate { - get { return this.creationDateUtc; } - set { this.creationDateUtc = value.ToUniversalTimeSafe(); } - } - - /// <summary> - /// Gets or sets the association handle that the Provider wants the Relying Party to not use any more. - /// </summary> - /// <value>If the Relying Party sent an invalid association handle with the request, it SHOULD be included here.</value> - /// <remarks> - /// For OpenID 1.1, we allow this to be present but empty to put up with poor implementations such as Blogger. - /// </remarks> - [MessagePart("openid.invalidate_handle", IsRequired = false, AllowEmpty = true, MaxVersion = "1.1")] - [MessagePart("openid.invalidate_handle", IsRequired = false, AllowEmpty = false, MinVersion = "2.0")] - string ITamperResistantOpenIdMessage.InvalidateHandle { get; set; } - - /// <summary> - /// Gets or sets the Provider Endpoint URI. - /// </summary> - [MessagePart("openid.op_endpoint", IsRequired = true, AllowEmpty = false, RequiredProtection = ProtectionLevel.Sign, MinVersion = "2.0")] - internal Uri ProviderEndpoint { get; set; } - - /// <summary> - /// Gets or sets the return_to parameter as the relying party provided - /// it in <see cref="SignedResponseRequest.ReturnTo"/>. - /// </summary> - /// <value>Verbatim copy of the return_to URL parameter sent in the - /// request, before the Provider modified it. </value> - [MessagePart("openid.return_to", IsRequired = true, AllowEmpty = false, RequiredProtection = ProtectionLevel.Sign, Encoder = typeof(OriginalStringUriEncoder))] - internal Uri ReturnTo { get; set; } - - /// <summary> - /// Gets or sets a value indicating whether the <see cref="ReturnTo"/> - /// URI's query string is unaltered between when the Relying Party - /// sent the original request and when the response was received. - /// </summary> - /// <remarks> - /// This property is not persisted in the transmitted message, and - /// has no effect on the Provider-side of the communication. - /// </remarks> - internal bool ReturnToParametersSignatureValidated { get; set; } - - /// <summary> - /// Gets or sets the nonce that will protect the message from replay attacks. - /// </summary> - /// <value> - /// <para>A string 255 characters or less in length, that MUST be unique to - /// this particular successful authentication response. The nonce MUST start - /// with the current time on the server, and MAY contain additional ASCII - /// characters in the range 33-126 inclusive (printable non-whitespace characters), - /// as necessary to make each response unique. The date and time MUST be - /// formatted as specified in section 5.6 of [RFC3339] - /// (Klyne, G. and C. Newman, “Date and Time on the Internet: Timestamps,” .), - /// with the following restrictions:</para> - /// <list type="bullet"> - /// <item>All times must be in the UTC timezone, indicated with a "Z".</item> - /// <item>No fractional seconds are allowed</item> - /// </list> - /// </value> - /// <example>2005-05-15T17:11:51ZUNIQUE</example> - internal string ResponseNonceTestHook { - get { return this.ResponseNonce; } - set { this.ResponseNonce = value; } - } - - /// <summary> - /// Gets or sets the nonce that will protect the message from replay attacks. - /// </summary> - /// <value> - /// <para>A string 255 characters or less in length, that MUST be unique to - /// this particular successful authentication response. The nonce MUST start - /// with the current time on the server, and MAY contain additional ASCII - /// characters in the range 33-126 inclusive (printable non-whitespace characters), - /// as necessary to make each response unique. The date and time MUST be - /// formatted as specified in section 5.6 of [RFC3339] - /// (Klyne, G. and C. Newman, “Date and Time on the Internet: Timestamps,” .), - /// with the following restrictions:</para> - /// <list type="bullet"> - /// <item>All times must be in the UTC timezone, indicated with a "Z".</item> - /// <item>No fractional seconds are allowed</item> - /// </list> - /// </value> - /// <example>2005-05-15T17:11:51ZUNIQUE</example> - [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called by messaging framework via reflection.")] - [MessagePart("openid.response_nonce", IsRequired = true, AllowEmpty = false, RequiredProtection = ProtectionLevel.Sign, MinVersion = "2.0")] - [MessagePart("openid.response_nonce", IsRequired = false, AllowEmpty = false, RequiredProtection = ProtectionLevel.None, MaxVersion = "1.1")] - private string ResponseNonce { - get { - string uniqueFragment = ((IReplayProtectedProtocolMessage)this).Nonce; - return this.creationDateUtc.ToString(PermissibleDateTimeFormats[0], CultureInfo.InvariantCulture) + uniqueFragment; - } - - set { - if (value == null) { - ((IReplayProtectedProtocolMessage)this).Nonce = null; - } else { - int indexOfZ = value.IndexOf("Z", StringComparison.Ordinal); - ErrorUtilities.VerifyProtocol(indexOfZ >= 0, MessagingStrings.UnexpectedMessagePartValue, Protocol.openid.response_nonce, value); - this.creationDateUtc = DateTime.Parse(value.Substring(0, indexOfZ + 1), CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal); - ((IReplayProtectedProtocolMessage)this).Nonce = value.Substring(indexOfZ + 1); - } - } - } - - /// <summary> - /// Gets the querystring key=value pairs in the return_to URL. - /// </summary> - private IDictionary<string, string> ReturnToParameters { - get { - if (this.returnToParameters == null) { - this.returnToParameters = HttpUtility.ParseQueryString(this.ReturnTo.Query).ToDictionary(); - } - - return this.returnToParameters; - } - } - - /// <summary> - /// Checks the message state for conformity to the protocol specification - /// and throws an exception if the message is invalid. - /// </summary> - /// <remarks> - /// <para>Some messages have required fields, or combinations of fields that must relate to each other - /// in specialized ways. After deserializing a message, this method checks the state of the - /// message to see if it conforms to the protocol.</para> - /// <para>Note that this property should <i>not</i> check signatures or perform any state checks - /// outside this scope of this particular message.</para> - /// </remarks> - /// <exception cref="ProtocolException">Thrown if the message is invalid.</exception> - public override void EnsureValidMessage() { - base.EnsureValidMessage(); - - this.VerifyReturnToMatchesRecipient(); - } - - /// <summary> - /// Gets the value of a named parameter in the return_to URL without signature protection. - /// </summary> - /// <param name="key">The full name of the parameter whose value is being sought.</param> - /// <returns>The value of the parameter if it is present and unaltered from when - /// the Relying Party signed it; <c>null</c> otherwise.</returns> - /// <remarks> - /// This method will always return null on the Provider-side, since Providers - /// cannot verify the private signature made by the relying party. - /// </remarks> - internal string GetReturnToArgument(string key) { - Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(key)); - ErrorUtilities.VerifyInternal(this.ReturnTo != null, "ReturnTo was expected to be required but is null."); - - string value; - this.ReturnToParameters.TryGetValue(key, out value); - return value; - } - - /// <summary> - /// Gets the names of the callback parameters added to the original authentication request - /// without signature protection. - /// </summary> - /// <returns>A sequence of the callback parameter names.</returns> - internal IEnumerable<string> GetReturnToParameterNames() { - return this.ReturnToParameters.Keys; - } - - /// <summary> - /// Gets a dictionary of all the message part names and values - /// that are included in the message signature. - /// </summary> - /// <param name="channel">The channel.</param> - /// <returns> - /// A dictionary of the signed message parts. - /// </returns> - internal IDictionary<string, string> GetSignedMessageParts(Channel channel) { - Contract.Requires<ArgumentNullException>(channel != null); - - ITamperResistantOpenIdMessage signedSelf = this; - if (signedSelf.SignedParameterOrder == null) { - return EmptyDictionary<string, string>.Instance; - } - - MessageDictionary messageDictionary = channel.MessageDescriptions.GetAccessor(this); - string[] signedPartNamesWithoutPrefix = signedSelf.SignedParameterOrder.Split(','); - Dictionary<string, string> signedParts = new Dictionary<string, string>(signedPartNamesWithoutPrefix.Length); - - var signedPartNames = signedPartNamesWithoutPrefix.Select(part => Protocol.openid.Prefix + part); - foreach (string partName in signedPartNames) { - signedParts[partName] = messageDictionary[partName]; - } - - return signedParts; - } - - /// <summary> - /// Determines whether one querystring contains every key=value pair that - /// another querystring contains. - /// </summary> - /// <param name="superset">The querystring that should contain at least all the key=value pairs of the other.</param> - /// <param name="subset">The querystring containing the set of key=value pairs to test for in the other.</param> - /// <returns> - /// <c>true</c> if <paramref name="superset"/> contains all the query parameters that <paramref name="subset"/> does; <c>false</c> otherwise. - /// </returns> - private static bool IsQuerySubsetOf(string superset, string subset) { - NameValueCollection subsetArgs = HttpUtility.ParseQueryString(subset); - NameValueCollection supersetArgs = HttpUtility.ParseQueryString(superset); - return subsetArgs.Keys.Cast<string>().All(key => string.Equals(subsetArgs[key], supersetArgs[key], StringComparison.Ordinal)); - } - - /// <summary> - /// Verifies that the openid.return_to field matches the URL of the actual HTTP request. - /// </summary> - /// <remarks> - /// From OpenId Authentication 2.0 section 11.1: - /// To verify that the "openid.return_to" URL matches the URL that is processing this assertion: - /// * The URL scheme, authority, and path MUST be the same between the two URLs. - /// * Any query parameters that are present in the "openid.return_to" URL MUST - /// also be present with the same values in the URL of the HTTP request the RP received. - /// </remarks> - private void VerifyReturnToMatchesRecipient() { - ErrorUtilities.VerifyProtocol( - string.Equals(this.Recipient.Scheme, this.ReturnTo.Scheme, StringComparison.OrdinalIgnoreCase) && - string.Equals(this.Recipient.Authority, this.ReturnTo.Authority, StringComparison.OrdinalIgnoreCase) && - string.Equals(this.Recipient.AbsolutePath, this.ReturnTo.AbsolutePath, StringComparison.Ordinal) && - IsQuerySubsetOf(this.Recipient.Query, this.ReturnTo.Query), - OpenIdStrings.ReturnToParamDoesNotMatchRequestUrl, - Protocol.openid.return_to, - this.ReturnTo, - this.Recipient); - } - } -} diff --git a/src/DotNetOpenAuth/OpenId/Messages/NegativeAssertionResponse.cs b/src/DotNetOpenAuth/OpenId/Messages/NegativeAssertionResponse.cs deleted file mode 100644 index 52ff884..0000000 --- a/src/DotNetOpenAuth/OpenId/Messages/NegativeAssertionResponse.cs +++ /dev/null @@ -1,142 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="NegativeAssertionResponse.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OpenId.Messages { - using System; - using System.Collections.Generic; - using System.Diagnostics.Contracts; - using System.Linq; - using System.Text; - using DotNetOpenAuth.Messaging; - using DotNetOpenAuth.OpenId.RelyingParty; - - /// <summary> - /// The message OpenID Providers send back to Relying Parties to refuse - /// to assert the identity of a user. - /// </summary> - [Serializable] - internal class NegativeAssertionResponse : IndirectResponseBase { - /// <summary> - /// Initializes a new instance of the <see cref="NegativeAssertionResponse"/> class. - /// </summary> - /// <param name="request">The request that the relying party sent.</param> - internal NegativeAssertionResponse(CheckIdRequest request) - : this(request, null) { - } - - /// <summary> - /// Initializes a new instance of the <see cref="NegativeAssertionResponse"/> class. - /// </summary> - /// <param name="request">The request that the relying party sent.</param> - /// <param name="channel">The channel to use to simulate construction of the user_setup_url, if applicable. May be null, but the user_setup_url will not be constructed.</param> - internal NegativeAssertionResponse(SignedResponseRequest request, Channel channel) - : base(request, GetMode(request)) { - // If appropriate, and when we're provided with a channel to do it, - // go ahead and construct the user_setup_url - if (this.Version.Major < 2 && request.Immediate && channel != null) { - // All requests are CheckIdRequests in OpenID 1.x, so this cast should be safe. - this.UserSetupUrl = ConstructUserSetupUrl((CheckIdRequest)request, channel); - } - } - - /// <summary> - /// Initializes a new instance of the <see cref="NegativeAssertionResponse"/> class. - /// </summary> - /// <param name="version">The version.</param> - /// <param name="relyingPartyReturnTo">The relying party return to.</param> - /// <param name="mode">The value of the openid.mode parameter.</param> - internal NegativeAssertionResponse(Version version, Uri relyingPartyReturnTo, string mode) - : base(version, relyingPartyReturnTo, mode) { - } - - /// <summary> - /// Gets or sets the URL the relying party can use to upgrade their authentication - /// request from an immediate to a setup message. - /// </summary> - /// <value>URL to redirect User-Agent to so the End User can do whatever's necessary to fulfill the assertion.</value> - /// <remarks> - /// This part is only included in OpenID 1.x responses. - /// </remarks> - [MessagePart("openid.user_setup_url", AllowEmpty = false, IsRequired = false, MaxVersion = "1.1")] - internal Uri UserSetupUrl { get; set; } - - /// <summary> - /// Gets a value indicating whether this <see cref="NegativeAssertionResponse"/> - /// is in response to an authentication request made in immediate mode. - /// </summary> - /// <value><c>true</c> if the request was in immediate mode; otherwise, <c>false</c>.</value> - internal bool Immediate { - get { - if (this.OriginatingRequest != null) { - return this.OriginatingRequest.Immediate; - } else { - if (String.Equals(this.Mode, Protocol.Args.Mode.setup_needed, StringComparison.Ordinal)) { - return true; - } else if (String.Equals(this.Mode, Protocol.Args.Mode.cancel, StringComparison.Ordinal)) { - return false; - } else { - throw ErrorUtilities.ThrowProtocol(MessagingStrings.UnexpectedMessagePartValue, Protocol.openid.mode, this.Mode); - } - } - } - } - - /// <summary> - /// Checks the message state for conformity to the protocol specification - /// and throws an exception if the message is invalid. - /// </summary> - /// <remarks> - /// <para>Some messages have required fields, or combinations of fields that must relate to each other - /// in specialized ways. After deserializing a message, this method checks the state of the - /// message to see if it conforms to the protocol.</para> - /// <para>Note that this property should <i>not</i> check signatures or perform any state checks - /// outside this scope of this particular message.</para> - /// </remarks> - /// <exception cref="ProtocolException">Thrown if the message is invalid.</exception> - public override void EnsureValidMessage() { - base.EnsureValidMessage(); - - // Since there are a couple of negative assertion modes, ensure that the mode given is one of the allowed ones. - ErrorUtilities.VerifyProtocol(String.Equals(this.Mode, Protocol.Args.Mode.setup_needed, StringComparison.Ordinal) || String.Equals(this.Mode, Protocol.Args.Mode.cancel, StringComparison.Ordinal), MessagingStrings.UnexpectedMessagePartValue, Protocol.openid.mode, this.Mode); - - if (this.Immediate && Protocol.Version.Major < 2) { - ErrorUtilities.VerifyProtocol(this.UserSetupUrl != null, OpenIdStrings.UserSetupUrlRequiredInImmediateNegativeResponse); - } - } - - /// <summary> - /// Constructs the value for the user_setup_url parameter to be sent back - /// in negative assertions in response to OpenID 1.x RP's checkid_immediate requests. - /// </summary> - /// <param name="immediateRequest">The immediate request.</param> - /// <param name="channel">The channel to use to simulate construction of the message.</param> - /// <returns>The value to use for the user_setup_url parameter.</returns> - private static Uri ConstructUserSetupUrl(CheckIdRequest immediateRequest, Channel channel) { - Contract.Requires<ArgumentNullException>(immediateRequest != null); - Contract.Requires<ArgumentNullException>(channel != null); - ErrorUtilities.VerifyInternal(immediateRequest.Immediate, "Only immediate requests should be sent here."); - - var setupRequest = new CheckIdRequest(immediateRequest.Version, immediateRequest.Recipient, AuthenticationRequestMode.Setup); - setupRequest.LocalIdentifier = immediateRequest.LocalIdentifier; - setupRequest.ReturnTo = immediateRequest.ReturnTo; - setupRequest.Realm = immediateRequest.Realm; - setupRequest.AssociationHandle = immediateRequest.AssociationHandle; - return channel.PrepareResponse(setupRequest).GetDirectUriRequest(channel); - } - - /// <summary> - /// Gets the value for the openid.mode that is appropriate for this response. - /// </summary> - /// <param name="request">The request that we're responding to.</param> - /// <returns>The value of the openid.mode parameter to use.</returns> - private static string GetMode(SignedResponseRequest request) { - Contract.Requires<ArgumentNullException>(request != null); - - Protocol protocol = Protocol.Lookup(request.Version); - return request.Immediate ? protocol.Args.Mode.setup_needed : protocol.Args.Mode.cancel; - } - } -} diff --git a/src/DotNetOpenAuth/OpenId/Messages/RequestBase.cs b/src/DotNetOpenAuth/OpenId/Messages/RequestBase.cs deleted file mode 100644 index 8e4cb9d..0000000 --- a/src/DotNetOpenAuth/OpenId/Messages/RequestBase.cs +++ /dev/null @@ -1,185 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="RequestBase.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OpenId.Messages { - using System; - using System.Collections.Generic; - using System.Diagnostics; - using System.Diagnostics.CodeAnalysis; - using System.Diagnostics.Contracts; - using DotNetOpenAuth.Messaging; - - /// <summary> - /// A common base class for OpenID request messages and indirect responses (since they are ultimately requests). - /// </summary> - [DebuggerDisplay("OpenID {Version} {Mode}")] - [Serializable] - internal class RequestBase : IDirectedProtocolMessage { - /// <summary> - /// The openid.ns parameter in the message. - /// </summary> - /// <value>"http://specs.openid.net/auth/2.0" </value> - /// <remarks> - /// This particular value MUST be present for the request to be a valid OpenID Authentication 2.0 request. Future versions of the specification may define different values in order to allow message recipients to properly interpret the request. - /// </remarks> - [SuppressMessage("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields", Justification = "Read by reflection.")] - [MessagePart("openid.ns", IsRequired = true, AllowEmpty = false, MinVersion = "2.0")] -#pragma warning disable 0414 // read by reflection - private readonly string OpenIdNamespace = Protocol.OpenId2Namespace; -#pragma warning restore 0414 - - /// <summary> - /// Backing store for the <see cref="ExtraData"/> property. - /// </summary> - private readonly Dictionary<string, string> extraData = new Dictionary<string, string>(); - - /// <summary> - /// Backing store for the <see cref="Incoming"/> property. - /// </summary> - private bool incoming; - - /// <summary> - /// Initializes a new instance of the <see cref="RequestBase"/> class. - /// </summary> - /// <param name="version">The OpenID version this message must comply with.</param> - /// <param name="providerEndpoint">The OpenID Provider endpoint.</param> - /// <param name="mode">The value for the openid.mode parameter.</param> - /// <param name="transport">A value indicating whether the message will be transmitted directly or indirectly.</param> - protected RequestBase(Version version, Uri providerEndpoint, string mode, MessageTransport transport) { - Contract.Requires<ArgumentNullException>(providerEndpoint != null); - Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(mode)); - - this.Recipient = providerEndpoint; - this.Mode = mode; - this.Transport = transport; - this.Version = version; - } - - /// <summary> - /// Gets the value of the openid.mode parameter. - /// </summary> - [MessagePart("openid.mode", IsRequired = true, AllowEmpty = false)] - public string Mode { get; private set; } - - #region IDirectedProtocolMessage Members - - /// <summary> - /// Gets the preferred method of transport for the message. - /// </summary> - /// <value> - /// For direct messages this is the OpenID mandated POST. - /// For indirect messages both GET and POST are allowed. - /// </value> - HttpDeliveryMethods IDirectedProtocolMessage.HttpMethods { - get { - // OpenID 2.0 section 5.1.1 - HttpDeliveryMethods methods = HttpDeliveryMethods.PostRequest; - if (this.Transport == MessageTransport.Indirect) { - methods |= HttpDeliveryMethods.GetRequest; - } - return methods; - } - } - - /// <summary> - /// Gets the recipient of the message. - /// </summary> - /// <value>The OP endpoint, or the RP return_to.</value> - public Uri Recipient { - get; - private set; - } - - #endregion - - #region IProtocolMessage Properties - - /// <summary> - /// Gets the version of the protocol this message is prepared to implement. - /// </summary> - /// <value>Version 2.0</value> - public Version Version { get; private set; } - - /// <summary> - /// Gets the level of protection this message requires. - /// </summary> - /// <value><see cref="MessageProtections.None"/></value> - public virtual MessageProtections RequiredProtection { - get { return MessageProtections.None; } - } - - /// <summary> - /// Gets a value indicating whether this is a direct or indirect message. - /// </summary> - /// <value><see cref="MessageTransport.Direct"/></value> - public MessageTransport Transport { get; private set; } - - /// <summary> - /// Gets the extra parameters included in the message. - /// </summary> - /// <value>An empty dictionary.</value> - public IDictionary<string, string> ExtraData { - get { return this.extraData; } - } - - #endregion - - /// <summary> - /// Gets a value indicating whether this message was deserialized as an incoming message. - /// </summary> - protected internal bool Incoming { - get { return this.incoming; } - } - - /// <summary> - /// Gets the protocol used by this message. - /// </summary> - protected Protocol Protocol { - get { return Protocol.Lookup(this.Version); } - } - - #region IProtocolMessage Methods - - /// <summary> - /// Checks the message state for conformity to the protocol specification - /// and throws an exception if the message is invalid. - /// </summary> - /// <remarks> - /// <para>Some messages have required fields, or combinations of fields that must relate to each other - /// in specialized ways. After deserializing a message, this method checks the state of the - /// message to see if it conforms to the protocol.</para> - /// <para>Note that this property should <i>not</i> check signatures or perform any state checks - /// outside this scope of this particular message.</para> - /// </remarks> - /// <exception cref="ProtocolException">Thrown if the message is invalid.</exception> - public virtual void EnsureValidMessage() { - } - - #endregion - - /// <summary> - /// Sets a flag indicating that this message is received (as opposed to sent). - /// </summary> - internal void SetAsIncoming() { - this.incoming = true; - } - - /// <summary> - /// Gets some string from a given version of the OpenID protocol. - /// </summary> - /// <param name="protocolVersion">The protocol version to use for lookup.</param> - /// <param name="mode">A function that can retrieve the desired protocol constant.</param> - /// <returns>The value of the constant.</returns> - /// <remarks> - /// This method can be used by a constructor to throw an <see cref="ArgumentNullException"/> - /// instead of a <see cref="NullReferenceException"/>. - /// </remarks> - protected static string GetProtocolConstant(Version protocolVersion, Func<Protocol, string> mode) { - Contract.Requires<ArgumentNullException>(protocolVersion != null); - return mode(Protocol.Lookup(protocolVersion)); - } - } -} diff --git a/src/DotNetOpenAuth/OpenId/Messages/SignedResponseRequest.cs b/src/DotNetOpenAuth/OpenId/Messages/SignedResponseRequest.cs deleted file mode 100644 index 7eb5407..0000000 --- a/src/DotNetOpenAuth/OpenId/Messages/SignedResponseRequest.cs +++ /dev/null @@ -1,185 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="SignedResponseRequest.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OpenId.Messages { - using System; - using System.Collections.Generic; - using System.Diagnostics; - using System.Diagnostics.Contracts; - using System.Linq; - using System.Text; - using DotNetOpenAuth.Messaging; - using DotNetOpenAuth.OpenId.RelyingParty; - - /// <summary> - /// An indirect request from a Relying Party to a Provider where the response - /// is expected to be signed. - /// </summary> - [Serializable] - internal class SignedResponseRequest : RequestBase, IProtocolMessageWithExtensions { - /// <summary> - /// Backing store for the <see cref="Extensions"/> property. - /// </summary> - private IList<IExtensionMessage> extensions = new List<IExtensionMessage>(); - - /// <summary> - /// Initializes a new instance of the <see cref="SignedResponseRequest"/> class. - /// </summary> - /// <param name="version">The OpenID version to use.</param> - /// <param name="providerEndpoint">The Provider endpoint that receives this message.</param> - /// <param name="mode"> - /// <see cref="AuthenticationRequestMode.Immediate"/> for asynchronous javascript clients; - /// <see cref="AuthenticationRequestMode.Setup"/> to allow the Provider to interact with the user in order to complete authentication. - /// </param> - internal SignedResponseRequest(Version version, Uri providerEndpoint, AuthenticationRequestMode mode) : - base(version, providerEndpoint, GetMode(version, mode), DotNetOpenAuth.Messaging.MessageTransport.Indirect) { - } - - #region IProtocolMessageWithExtensions Members - - /// <summary> - /// Gets the list of extensions that are included with this message. - /// </summary> - /// <value></value> - /// <remarks> - /// Implementations of this interface should ensure that this property never returns null. - /// </remarks> - public IList<IExtensionMessage> Extensions { - get { return this.extensions; } - } - - #endregion - - /// <summary> - /// Gets a value indicating whether the Provider is allowed to interact with the user - /// as part of authentication. - /// </summary> - /// <value><c>true</c> if using OpenID immediate mode; otherwise, <c>false</c>.</value> - internal bool Immediate { - get { return String.Equals(this.Mode, Protocol.Args.Mode.checkid_immediate, StringComparison.Ordinal); } - } - - /// <summary> - /// Gets or sets the handle of the association the RP would like the Provider - /// to use for signing a positive assertion in the response message. - /// </summary> - /// <value>A handle for an association between the Relying Party and the OP - /// that SHOULD be used to sign the response. </value> - /// <remarks> - /// If no association handle is sent, the transaction will take place in Stateless Mode - /// (Verifying Directly with the OpenID Provider). - /// </remarks> - [MessagePart("openid.assoc_handle", IsRequired = false, AllowEmpty = false)] - internal string AssociationHandle { get; set; } - - /// <summary> - /// Gets or sets the URL the Provider should redirect the user agent to following - /// the authentication attempt. - /// </summary> - /// <value>URL to which the OP SHOULD return the User-Agent with the response - /// indicating the status of the request.</value> - /// <remarks> - /// <para>If this value is not sent in the request it signifies that the Relying Party - /// does not wish for the end user to be returned. </para> - /// <para>The return_to URL MAY be used as a mechanism for the Relying Party to attach - /// context about the authentication request to the authentication response. - /// This document does not define a mechanism by which the RP can ensure that query - /// parameters are not modified by outside parties; such a mechanism can be defined - /// by the RP itself. </para> - /// </remarks> - [MessagePart("openid.return_to", IsRequired = true, AllowEmpty = false)] - [MessagePart("openid.return_to", IsRequired = false, AllowEmpty = false, MinVersion = "2.0")] - internal Uri ReturnTo { get; set; } - - /// <summary> - /// Gets or sets the Relying Party discovery URL the Provider may use to verify the - /// source of the authentication request. - /// </summary> - /// <value> - /// URL pattern the OP SHOULD ask the end user to trust. See Section 9.2 (Realms). - /// This value MUST be sent if openid.return_to is omitted. - /// Default: The <see cref="ReturnTo"/> URL. - /// </value> - [MessagePart("openid.trust_root", IsRequired = false, AllowEmpty = false)] - [MessagePart("openid.realm", IsRequired = false, AllowEmpty = false, MinVersion = "2.0")] - internal Realm Realm { get; set; } - - /// <summary> - /// Gets or sets a value indicating whether the return_to value should be signed. - /// </summary> - internal bool SignReturnTo { get; set; } - - /// <summary> - /// Checks the message state for conformity to the protocol specification - /// and throws an exception if the message is invalid. - /// </summary> - /// <remarks> - /// <para>Some messages have required fields, or combinations of fields that must relate to each other - /// in specialized ways. After deserializing a message, this method checks the state of the - /// message to see if it conforms to the protocol.</para> - /// <para>Note that this property should <i>not</i> check signatures or perform any state checks - /// outside this scope of this particular message.</para> - /// </remarks> - /// <exception cref="ProtocolException">Thrown if the message is invalid.</exception> - public override void EnsureValidMessage() { - base.EnsureValidMessage(); - - if (this.Realm == null) { - // Set the default Realm per the spec if it is not explicitly given. - this.Realm = this.ReturnTo; - } else if (this.ReturnTo != null) { - // Verify that the realm and return_to agree. - ErrorUtilities.VerifyProtocol(this.Realm.Contains(this.ReturnTo), OpenIdStrings.ReturnToNotUnderRealm, this.ReturnTo, this.Realm); - } - } - - /// <summary> - /// Adds parameters to the return_to querystring. - /// </summary> - /// <param name="keysValues">The keys=value pairs to add to the return_to query string.</param> - /// <remarks> - /// This method is useful if the Relying Party wants to recall some value - /// when and if a positive assertion comes back from the Provider. - /// </remarks> - internal void AddReturnToArguments(IEnumerable<KeyValuePair<string, string>> keysValues) { - Contract.Requires<ArgumentNullException>(keysValues != null); - ErrorUtilities.VerifyOperation(this.ReturnTo != null, OpenIdStrings.ReturnToRequiredForOperation); - UriBuilder returnToBuilder = new UriBuilder(this.ReturnTo); - returnToBuilder.AppendAndReplaceQueryArgs(keysValues); - this.ReturnTo = returnToBuilder.Uri; - } - - /// <summary> - /// Adds a parameter to the return_to querystring. - /// </summary> - /// <param name="key">The name of the parameter.</param> - /// <param name="value">The value of the argument.</param> - /// <remarks> - /// This method is useful if the Relying Party wants to recall some value - /// when and if a positive assertion comes back from the Provider. - /// </remarks> - internal void AddReturnToArguments(string key, string value) { - var pair = new KeyValuePair<string, string>(key, value); - this.AddReturnToArguments(new[] { pair }); - } - - /// <summary> - /// Gets the value of the openid.mode parameter based on the protocol version and immediate flag. - /// </summary> - /// <param name="version">The OpenID version to use.</param> - /// <param name="mode"> - /// <see cref="AuthenticationRequestMode.Immediate"/> for asynchronous javascript clients; - /// <see cref="AuthenticationRequestMode.Setup"/> to allow the Provider to interact with the user in order to complete authentication. - /// </param> - /// <returns>checkid_immediate or checkid_setup</returns> - private static string GetMode(Version version, AuthenticationRequestMode mode) { - Contract.Requires<ArgumentNullException>(version != null); - - Protocol protocol = Protocol.Lookup(version); - return mode == AuthenticationRequestMode.Immediate ? protocol.Args.Mode.checkid_immediate : protocol.Args.Mode.checkid_setup; - } - } -} diff --git a/src/DotNetOpenAuth/OpenId/NoDiscoveryIdentifier.cs b/src/DotNetOpenAuth/OpenId/NoDiscoveryIdentifier.cs deleted file mode 100644 index 1a6e7e9..0000000 --- a/src/DotNetOpenAuth/OpenId/NoDiscoveryIdentifier.cs +++ /dev/null @@ -1,101 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="NoDiscoveryIdentifier.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OpenId { - using System; - using System.Collections.Generic; - using System.Diagnostics.Contracts; - using System.Linq; - using DotNetOpenAuth.Messaging; - using DotNetOpenAuth.OpenId.RelyingParty; - - /// <summary> - /// Wraps an existing Identifier and prevents it from performing discovery. - /// </summary> - [ContractVerification(true)] - [Pure] - internal class NoDiscoveryIdentifier : Identifier { - /// <summary> - /// The wrapped identifier. - /// </summary> - private readonly Identifier wrappedIdentifier; - - /// <summary> - /// Initializes a new instance of the <see cref="NoDiscoveryIdentifier"/> class. - /// </summary> - /// <param name="wrappedIdentifier">The ordinary Identifier whose discovery is being masked.</param> - /// <param name="claimSsl">Whether this Identifier should claim to be SSL-secure, although no discovery will never generate service endpoints anyway.</param> - internal NoDiscoveryIdentifier(Identifier wrappedIdentifier, bool claimSsl) - : base(wrappedIdentifier.OriginalString, claimSsl) { - Contract.Requires<ArgumentNullException>(wrappedIdentifier != null); - - this.wrappedIdentifier = wrappedIdentifier; - } - - /// <summary> - /// Returns a <see cref="T:System.String"/> that represents the current <see cref="T:System.Object"/>. - /// </summary> - /// <returns> - /// A <see cref="T:System.String"/> that represents the current <see cref="T:System.Object"/>. - /// </returns> - public override string ToString() { - return this.wrappedIdentifier.ToString(); - } - - /// <summary> - /// Tests equality between two <see cref="Identifier"/>s. - /// </summary> - /// <param name="obj">The <see cref="T:System.Object"/> to compare with the current <see cref="T:System.Object"/>.</param> - /// <returns> - /// true if the specified <see cref="T:System.Object"/> is equal to the current <see cref="T:System.Object"/>; otherwise, false. - /// </returns> - /// <exception cref="T:System.NullReferenceException"> - /// The <paramref name="obj"/> parameter is null. - /// </exception> - public override bool Equals(object obj) { - return this.wrappedIdentifier.Equals(obj); - } - - /// <summary> - /// Gets the hash code for an <see cref="Identifier"/> for storage in a hashtable. - /// </summary> - /// <returns> - /// A hash code for the current <see cref="T:System.Object"/>. - /// </returns> - public override int GetHashCode() { - return this.wrappedIdentifier.GetHashCode(); - } - - /// <summary> - /// Returns an <see cref="Identifier"/> that has no URI fragment. - /// Quietly returns the original <see cref="Identifier"/> if it is not - /// a <see cref="UriIdentifier"/> or no fragment exists. - /// </summary> - /// <returns> - /// A new <see cref="Identifier"/> instance if there was a - /// fragment to remove, otherwise this same instance.. - /// </returns> - internal override Identifier TrimFragment() { - return new NoDiscoveryIdentifier(this.wrappedIdentifier.TrimFragment(), IsDiscoverySecureEndToEnd); - } - - /// <summary> - /// Converts a given identifier to its secure equivalent. - /// UriIdentifiers originally created with an implied HTTP scheme change to HTTPS. - /// Discovery is made to require SSL for the entire resolution process. - /// </summary> - /// <param name="secureIdentifier">The newly created secure identifier. - /// If the conversion fails, <paramref name="secureIdentifier"/> retains - /// <i>this</i> identifiers identity, but will never discover any endpoints.</param> - /// <returns> - /// True if the secure conversion was successful. - /// False if the Identifier was originally created with an explicit HTTP scheme. - /// </returns> - internal override bool TryRequireSsl(out Identifier secureIdentifier) { - return this.wrappedIdentifier.TryRequireSsl(out secureIdentifier); - } - } -} diff --git a/src/DotNetOpenAuth/OpenId/OpenIdUtilities.cs b/src/DotNetOpenAuth/OpenId/OpenIdUtilities.cs deleted file mode 100644 index 68babd9..0000000 --- a/src/DotNetOpenAuth/OpenId/OpenIdUtilities.cs +++ /dev/null @@ -1,195 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="OpenIdUtilities.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OpenId { - using System; - using System.Collections.Generic; - using System.Diagnostics.CodeAnalysis; - using System.Diagnostics.Contracts; - using System.Globalization; - using System.Linq; - using System.Text; - using System.Text.RegularExpressions; - using System.Web.UI; - using DotNetOpenAuth.Messaging; - using DotNetOpenAuth.OpenId.ChannelElements; - using DotNetOpenAuth.OpenId.Extensions; - using DotNetOpenAuth.OpenId.Messages; - using DotNetOpenAuth.OpenId.Provider; - using DotNetOpenAuth.OpenId.RelyingParty; - - /// <summary> - /// A set of utilities especially useful to OpenID. - /// </summary> - public static class OpenIdUtilities { - /// <summary> - /// The prefix to designate this library's proprietary parameters added to the protocol. - /// </summary> - internal const string CustomParameterPrefix = "dnoa."; - - /// <summary> - /// Creates a random association handle. - /// </summary> - /// <returns>The association handle.</returns> - public static string GenerateRandomAssociationHandle() { - Contract.Ensures(!String.IsNullOrEmpty(Contract.Result<string>())); - - // Generate the handle. It must be unique, and preferably unpredictable, - // so we use a time element and a random data element to generate it. - string uniq = MessagingUtilities.GetCryptoRandomDataAsBase64(4); - return string.Format(CultureInfo.InvariantCulture, "{{{0}}}{{{1}}}", DateTime.UtcNow.Ticks, uniq); - } - - /// <summary> - /// Gets the OpenID protocol instance for the version in a message. - /// </summary> - /// <param name="message">The message.</param> - /// <returns>The OpenID protocol instance.</returns> - internal static Protocol GetProtocol(this IProtocolMessage message) { - Contract.Requires<ArgumentNullException>(message != null); - return Protocol.Lookup(message.Version); - } - - /// <summary> - /// Changes the position of some element in a list. - /// </summary> - /// <typeparam name="T">The type of elements stored in the list.</typeparam> - /// <param name="list">The list to be modified.</param> - /// <param name="position">The new position for the given element.</param> - /// <param name="value">The element to move within the list.</param> - /// <exception cref="InternalErrorException">Thrown if the element does not already exist in the list.</exception> - internal static void MoveTo<T>(this IList<T> list, int position, T value) { - ErrorUtilities.VerifyInternal(list.Remove(value), "Unable to find element in list."); - list.Insert(position, value); - } - - /// <summary> - /// Corrects any URI decoding the Provider may have inappropriately done - /// to our return_to URL, resulting in an otherwise corrupted base64 encoded value. - /// </summary> - /// <param name="value">The base64 encoded value. May be null.</param> - /// <returns> - /// The value; corrected if corruption had occurred. - /// </returns> - /// <remarks> - /// AOL may have incorrectly URI-decoded the token for us in the return_to, - /// resulting in a token URI-decoded twice by the time we see it, and no - /// longer being a valid base64 string. - /// It turns out that the only symbols from base64 that is also encoded - /// in URI encoding rules are the + and / characters. - /// AOL decodes the %2b sequence to the + character - /// and the %2f sequence to the / character (it shouldn't decode at all). - /// When we do our own URI decoding, the + character becomes a space (corrupting base64) - /// but the / character remains a /, so no further corruption happens to this character. - /// So to correct this we just need to change any spaces we find in the token - /// back to + characters. - /// </remarks> - internal static string FixDoublyUriDecodedBase64String(string value) { - if (value == null) { - return null; - } - - if (value.Contains(" ")) { - Logger.OpenId.Error("Deserializing a corrupted token. The OpenID Provider may have inappropriately decoded the return_to URL before sending it back to us."); - value = value.Replace(' ', '+'); // Undo any extra decoding the Provider did - } - - return value; - } - - /// <summary> - /// Rounds the given <see cref="DateTime"/> downward to the whole second. - /// </summary> - /// <param name="dateTime">The DateTime object to adjust.</param> - /// <returns>The new <see cref="DateTime"/> value.</returns> - internal static DateTime CutToSecond(DateTime dateTime) { - return new DateTime(dateTime.Ticks - (dateTime.Ticks % TimeSpan.TicksPerSecond), dateTime.Kind); - } - - /// <summary> - /// Gets the fully qualified Realm URL, given a Realm that may be relative to a particular page. - /// </summary> - /// <param name="page">The hosting page that has the realm value to resolve.</param> - /// <param name="realm">The realm, which may begin with "*." or "~/".</param> - /// <param name="requestContext">The request context.</param> - /// <returns>The fully-qualified realm.</returns> - [SuppressMessage("Microsoft.Usage", "CA1806:DoNotIgnoreMethodResults", MessageId = "DotNetOpenAuth.OpenId.Realm", Justification = "Using ctor for validation.")] - internal static UriBuilder GetResolvedRealm(Page page, string realm, HttpRequestInfo requestContext) { - Contract.Requires<ArgumentNullException>(page != null); - Contract.Requires<ArgumentNullException>(requestContext != null); - - // Allow for *. realm notation, as well as ASP.NET ~/ shortcuts. - - // We have to temporarily remove the *. notation if it's there so that - // the rest of our URL manipulation will succeed. - bool foundWildcard = false; - - // Note: we don't just use string.Replace because poorly written URLs - // could potentially have multiple :// sequences in them. - MatchEvaluator matchDelegate = delegate(Match m) { - foundWildcard = true; - return m.Groups[1].Value; - }; - string realmNoWildcard = Regex.Replace(realm, @"^(\w+://)\*\.", matchDelegate); - - UriBuilder fullyQualifiedRealm = new UriBuilder( - new Uri(requestContext.UrlBeforeRewriting, page.ResolveUrl(realmNoWildcard))); - - if (foundWildcard) { - fullyQualifiedRealm.Host = "*." + fullyQualifiedRealm.Host; - } - - // Is it valid? - new Realm(fullyQualifiedRealm); // throws if not valid - - return fullyQualifiedRealm; - } - - /// <summary> - /// Gets the extension factories from the extension aggregator on an OpenID channel. - /// </summary> - /// <param name="channel">The channel.</param> - /// <returns>The list of factories that will be used to generate extension instances.</returns> - /// <remarks> - /// This is an extension method on <see cref="Channel"/> rather than an instance - /// method on <see cref="OpenIdChannel"/> because the <see cref="OpenIdRelyingParty"/> - /// and <see cref="OpenIdProvider"/> classes don't strong-type to <see cref="OpenIdChannel"/> - /// to allow flexibility in the specific type of channel the user (or tests) - /// can plug in. - /// </remarks> - internal static IList<IOpenIdExtensionFactory> GetExtensionFactories(this Channel channel) { - Contract.Requires<ArgumentNullException>(channel != null); - - var extensionsBindingElement = channel.BindingElements.OfType<ExtensionsBindingElement>().SingleOrDefault(); - ErrorUtilities.VerifyOperation(extensionsBindingElement != null, OpenIdStrings.UnsupportedChannelConfiguration); - IOpenIdExtensionFactory factory = extensionsBindingElement.ExtensionFactory; - var aggregator = factory as OpenIdExtensionFactoryAggregator; - ErrorUtilities.VerifyOperation(aggregator != null, OpenIdStrings.UnsupportedChannelConfiguration); - return aggregator.Factories; - } - - /// <summary> - /// Determines whether the association with the specified handle is (still) valid. - /// </summary> - /// <param name="associationStore">The association store.</param> - /// <param name="containingMessage">The OpenID message that referenced this association handle.</param> - /// <param name="isPrivateAssociation">A value indicating whether a private association is expected.</param> - /// <param name="handle">The association handle.</param> - /// <returns> - /// <c>true</c> if the specified containing message is valid; otherwise, <c>false</c>. - /// </returns> - internal static bool IsValid(this IProviderAssociationStore associationStore, IProtocolMessage containingMessage, bool isPrivateAssociation, string handle) { - Contract.Requires<ArgumentNullException>(associationStore != null); - Contract.Requires<ArgumentNullException>(containingMessage != null); - Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(handle)); - try { - return associationStore.Deserialize(containingMessage, isPrivateAssociation, handle) != null; - } catch (ProtocolException) { - return false; - } - } - } -} diff --git a/src/DotNetOpenAuth/OpenId/OpenIdXrdsHelper.cs b/src/DotNetOpenAuth/OpenId/OpenIdXrdsHelper.cs deleted file mode 100644 index 00468ed..0000000 --- a/src/DotNetOpenAuth/OpenId/OpenIdXrdsHelper.cs +++ /dev/null @@ -1,212 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="OpenIdXrdsHelper.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OpenId { - using System; - using System.Collections.Generic; - using System.Diagnostics.Contracts; - using System.Linq; - using DotNetOpenAuth.Messaging; - using DotNetOpenAuth.OpenId.RelyingParty; - using DotNetOpenAuth.Xrds; - - /// <summary> - /// Adds OpenID-specific extension methods to the XrdsDocument class. - /// </summary> - internal static class OpenIdXrdsHelper { - /// <summary> - /// Finds the Relying Party return_to receiving endpoints. - /// </summary> - /// <param name="xrds">The XrdsDocument instance to use in this process.</param> - /// <returns>A sequence of Relying Party descriptors for the return_to endpoints.</returns> - /// <remarks> - /// This is useful for Providers to send unsolicited assertions to Relying Parties, - /// or for Provider's to perform RP discovery/verification as part of authentication. - /// </remarks> - internal static IEnumerable<RelyingPartyEndpointDescription> FindRelyingPartyReceivingEndpoints(this XrdsDocument xrds) { - Contract.Requires<ArgumentNullException>(xrds != null); - Contract.Ensures(Contract.Result<IEnumerable<RelyingPartyEndpointDescription>>() != null); - - return from service in xrds.FindReturnToServices() - from uri in service.UriElements - select new RelyingPartyEndpointDescription(uri.Uri, service.TypeElementUris); - } - - /// <summary> - /// Finds the icons the relying party wants an OP to display as part of authentication, - /// per the UI extension spec. - /// </summary> - /// <param name="xrds">The XrdsDocument to search.</param> - /// <returns>A sequence of the icon URLs in preferred order.</returns> - internal static IEnumerable<Uri> FindRelyingPartyIcons(this XrdsDocument xrds) { - Contract.Requires<ArgumentNullException>(xrds != null); - Contract.Ensures(Contract.Result<IEnumerable<Uri>>() != null); - - return from xrd in xrds.XrdElements - from service in xrd.OpenIdRelyingPartyIcons - from uri in service.UriElements - select uri.Uri; - } - - /// <summary> - /// Creates the service endpoints described in this document, useful for requesting - /// authentication of one of the OpenID Providers that result from it. - /// </summary> - /// <param name="xrds">The XrdsDocument instance to use in this process.</param> - /// <param name="claimedIdentifier">The claimed identifier that was used to discover this XRDS document.</param> - /// <param name="userSuppliedIdentifier">The user supplied identifier.</param> - /// <returns> - /// A sequence of OpenID Providers that can assert ownership of the <paramref name="claimedIdentifier"/>. - /// </returns> - internal static IEnumerable<IdentifierDiscoveryResult> CreateServiceEndpoints(this IEnumerable<XrdElement> xrds, UriIdentifier claimedIdentifier, UriIdentifier userSuppliedIdentifier) { - Contract.Requires<ArgumentNullException>(xrds != null); - Contract.Requires<ArgumentNullException>(claimedIdentifier != null); - Contract.Requires<ArgumentNullException>(userSuppliedIdentifier != null); - Contract.Ensures(Contract.Result<IEnumerable<IdentifierDiscoveryResult>>() != null); - - var endpoints = new List<IdentifierDiscoveryResult>(); - endpoints.AddRange(xrds.GenerateOPIdentifierServiceEndpoints(userSuppliedIdentifier)); - endpoints.AddRange(xrds.GenerateClaimedIdentifierServiceEndpoints(claimedIdentifier, userSuppliedIdentifier)); - - Logger.Yadis.DebugFormat("Total services discovered in XRDS: {0}", endpoints.Count); - Logger.Yadis.Debug(endpoints.ToStringDeferred(true)); - return endpoints; - } - - /// <summary> - /// Creates the service endpoints described in this document, useful for requesting - /// authentication of one of the OpenID Providers that result from it. - /// </summary> - /// <param name="xrds">The XrdsDocument instance to use in this process.</param> - /// <param name="userSuppliedIdentifier">The user-supplied i-name that was used to discover this XRDS document.</param> - /// <returns>A sequence of OpenID Providers that can assert ownership of the canonical ID given in this document.</returns> - internal static IEnumerable<IdentifierDiscoveryResult> CreateServiceEndpoints(this IEnumerable<XrdElement> xrds, XriIdentifier userSuppliedIdentifier) { - Contract.Requires<ArgumentNullException>(xrds != null); - Contract.Requires<ArgumentNullException>(userSuppliedIdentifier != null); - Contract.Ensures(Contract.Result<IEnumerable<IdentifierDiscoveryResult>>() != null); - - var endpoints = new List<IdentifierDiscoveryResult>(); - endpoints.AddRange(xrds.GenerateOPIdentifierServiceEndpoints(userSuppliedIdentifier)); - endpoints.AddRange(xrds.GenerateClaimedIdentifierServiceEndpoints(userSuppliedIdentifier)); - Logger.Yadis.DebugFormat("Total services discovered in XRDS: {0}", endpoints.Count); - Logger.Yadis.Debug(endpoints.ToStringDeferred(true)); - return endpoints; - } - - /// <summary> - /// Generates OpenID Providers that can authenticate using directed identity. - /// </summary> - /// <param name="xrds">The XrdsDocument instance to use in this process.</param> - /// <param name="opIdentifier">The OP Identifier entered (and resolved) by the user. Essentially the user-supplied identifier.</param> - /// <returns>A sequence of the providers that can offer directed identity services.</returns> - private static IEnumerable<IdentifierDiscoveryResult> GenerateOPIdentifierServiceEndpoints(this IEnumerable<XrdElement> xrds, Identifier opIdentifier) { - Contract.Requires<ArgumentNullException>(xrds != null); - Contract.Requires<ArgumentNullException>(opIdentifier != null); - Contract.Ensures(Contract.Result<IEnumerable<IdentifierDiscoveryResult>>() != null); - return from service in xrds.FindOPIdentifierServices() - from uri in service.UriElements - let protocol = Protocol.FindBestVersion(p => p.OPIdentifierServiceTypeURI, service.TypeElementUris) - let providerDescription = new ProviderEndpointDescription(uri.Uri, service.TypeElementUris) - select IdentifierDiscoveryResult.CreateForProviderIdentifier(opIdentifier, providerDescription, service.Priority, uri.Priority); - } - - /// <summary> - /// Generates the OpenID Providers that are capable of asserting ownership - /// of a particular URI claimed identifier. - /// </summary> - /// <param name="xrds">The XrdsDocument instance to use in this process.</param> - /// <param name="claimedIdentifier">The claimed identifier.</param> - /// <param name="userSuppliedIdentifier">The user supplied identifier.</param> - /// <returns> - /// A sequence of the providers that can assert ownership of the given identifier. - /// </returns> - private static IEnumerable<IdentifierDiscoveryResult> GenerateClaimedIdentifierServiceEndpoints(this IEnumerable<XrdElement> xrds, UriIdentifier claimedIdentifier, UriIdentifier userSuppliedIdentifier) { - Contract.Requires<ArgumentNullException>(xrds != null); - Contract.Requires<ArgumentNullException>(claimedIdentifier != null); - Contract.Ensures(Contract.Result<IEnumerable<IdentifierDiscoveryResult>>() != null); - - return from service in xrds.FindClaimedIdentifierServices() - from uri in service.UriElements - where uri.Uri != null - let providerEndpoint = new ProviderEndpointDescription(uri.Uri, service.TypeElementUris) - select IdentifierDiscoveryResult.CreateForClaimedIdentifier(claimedIdentifier, userSuppliedIdentifier, service.ProviderLocalIdentifier, providerEndpoint, service.Priority, uri.Priority); - } - - /// <summary> - /// Generates the OpenID Providers that are capable of asserting ownership - /// of a particular XRI claimed identifier. - /// </summary> - /// <param name="xrds">The XrdsDocument instance to use in this process.</param> - /// <param name="userSuppliedIdentifier">The i-name supplied by the user.</param> - /// <returns>A sequence of the providers that can assert ownership of the given identifier.</returns> - private static IEnumerable<IdentifierDiscoveryResult> GenerateClaimedIdentifierServiceEndpoints(this IEnumerable<XrdElement> xrds, XriIdentifier userSuppliedIdentifier) { - // Cannot use code contracts because this method uses yield return. - ////Contract.Requires<ArgumentNullException>(xrds != null); - ////Contract.Ensures(Contract.Result<IEnumerable<IdentifierDiscoveryResult>>() != null); - ErrorUtilities.VerifyArgumentNotNull(xrds, "xrds"); - - foreach (var service in xrds.FindClaimedIdentifierServices()) { - foreach (var uri in service.UriElements) { - // spec section 7.3.2.3 on Claimed Id -> CanonicalID substitution - if (service.Xrd.CanonicalID == null) { - Logger.Yadis.WarnFormat(XrdsStrings.MissingCanonicalIDElement, userSuppliedIdentifier); - break; // skip on to next service - } - ErrorUtilities.VerifyProtocol(service.Xrd.IsCanonicalIdVerified, XrdsStrings.CIDVerificationFailed, userSuppliedIdentifier); - - // In the case of XRI names, the ClaimedId is actually the CanonicalID. - var claimedIdentifier = new XriIdentifier(service.Xrd.CanonicalID); - var providerEndpoint = new ProviderEndpointDescription(uri.Uri, service.TypeElementUris); - yield return IdentifierDiscoveryResult.CreateForClaimedIdentifier(claimedIdentifier, userSuppliedIdentifier, service.ProviderLocalIdentifier, providerEndpoint, service.Priority, uri.Priority); - } - } - } - - /// <summary> - /// Enumerates the XRDS service elements that describe OpenID Providers offering directed identity assertions. - /// </summary> - /// <param name="xrds">The XrdsDocument instance to use in this process.</param> - /// <returns>A sequence of service elements.</returns> - private static IEnumerable<ServiceElement> FindOPIdentifierServices(this IEnumerable<XrdElement> xrds) { - Contract.Requires<ArgumentNullException>(xrds != null); - Contract.Ensures(Contract.Result<IEnumerable<ServiceElement>>() != null); - - return from xrd in xrds - from service in xrd.OpenIdProviderIdentifierServices - select service; - } - - /// <summary> - /// Returns the OpenID-compatible services described by a given XRDS document, - /// in priority order. - /// </summary> - /// <param name="xrds">The XrdsDocument instance to use in this process.</param> - /// <returns>A sequence of the services offered.</returns> - private static IEnumerable<ServiceElement> FindClaimedIdentifierServices(this IEnumerable<XrdElement> xrds) { - Contract.Requires<ArgumentNullException>(xrds != null); - Contract.Ensures(Contract.Result<IEnumerable<ServiceElement>>() != null); - - return from xrd in xrds - from service in xrd.OpenIdClaimedIdentifierServices - select service; - } - - /// <summary> - /// Enumerates the XRDS service elements that describe OpenID Relying Party return_to URLs - /// that can receive authentication assertions. - /// </summary> - /// <param name="xrds">The XrdsDocument instance to use in this process.</param> - /// <returns>A sequence of service elements.</returns> - private static IEnumerable<ServiceElement> FindReturnToServices(this XrdsDocument xrds) { - Contract.Requires<ArgumentNullException>(xrds != null); - Contract.Ensures(Contract.Result<IEnumerable<ServiceElement>>() != null); - - return from xrd in xrds.XrdElements - from service in xrd.OpenIdRelyingPartyReturnToServices - select service; - } - } -} diff --git a/src/DotNetOpenAuth/OpenId/Protocol.cs b/src/DotNetOpenAuth/OpenId/Protocol.cs deleted file mode 100644 index 5aacfd2..0000000 --- a/src/DotNetOpenAuth/OpenId/Protocol.cs +++ /dev/null @@ -1,472 +0,0 @@ -// <auto-generated/> // disable StyleCop on this file -//----------------------------------------------------------------------- -// <copyright file="Protocol.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OpenId { - using System; - using System.Collections.Generic; - using DotNetOpenAuth.Messaging; - using System.Globalization; - using System.Diagnostics.CodeAnalysis; - using System.Diagnostics.Contracts; - using System.Diagnostics; - - /// <summary> - /// An enumeration of the OpenID protocol versions supported by this library. - /// </summary> - public enum ProtocolVersion { - /// <summary> - /// OpenID Authentication 1.0 - /// </summary> - V10, - /// <summary> - /// OpenID Authentication 1.1 - /// </summary> - V11, - /// <summary> - /// OpenID Authentication 2.0 - /// </summary> - V20, - } - - /// <summary> - /// Tracks the several versions of OpenID this library supports and the unique - /// constants to each version used in the protocol. - /// </summary> - [DebuggerDisplay("OpenID {Version}")] - internal sealed class Protocol { - /// <summary> - /// The value of the openid.ns parameter in the OpenID 2.0 specification. - /// </summary> - internal const string OpenId2Namespace = "http://specs.openid.net/auth/2.0"; - - /// <summary> - /// Scans a list for matches with some element of the OpenID protocol, - /// searching from newest to oldest protocol for the first and best match. - /// </summary> - /// <typeparam name="T">The type of element retrieved from the <see cref="Protocol"/> instance.</typeparam> - /// <param name="elementOf">Takes a <see cref="Protocol"/> instance and returns an element of it.</param> - /// <param name="list">The list to scan for matches.</param> - /// <returns>The protocol with the element that matches some item in the list.</returns> - internal static Protocol FindBestVersion<T>(Func<Protocol, T> elementOf, IEnumerable<T> list) { - foreach (var protocol in Protocol.AllVersions) { - foreach (var item in list) { - if (item != null && item.Equals(elementOf(protocol))) - return protocol; - } - } - return null; - } - - Protocol(QueryParameters queryBits) { - openidnp = queryBits; - openid = new QueryParameters(queryBits); - } - - // Well-known, supported versions of the OpenID spec. - public static readonly Protocol V10 = new Protocol(new QueryParameters()) { - Version = new Version(1, 0), - XmlNamespace = "http://openid.net/xmlns/1.0", - QueryDeclaredNamespaceVersion = null, - ClaimedIdentifierServiceTypeURI = "http://openid.net/signon/1.0", - OPIdentifierServiceTypeURI = null, // not supported - ClaimedIdentifierForOPIdentifier = null, // not supported - RPReturnToTypeURI = null, // not supported - HtmlDiscoveryProviderKey = "openid.server", - HtmlDiscoveryLocalIdKey = "openid.delegate", - }; - public static readonly Protocol V11 = new Protocol(new QueryParameters()) { - Version = new Version(1, 1), - XmlNamespace = "http://openid.net/xmlns/1.0", - QueryDeclaredNamespaceVersion = null, - ClaimedIdentifierServiceTypeURI = "http://openid.net/signon/1.1", - OPIdentifierServiceTypeURI = null, // not supported - ClaimedIdentifierForOPIdentifier = null, // not supported - RPReturnToTypeURI = null, // not supported - HtmlDiscoveryProviderKey = "openid.server", - HtmlDiscoveryLocalIdKey = "openid.delegate", - }; - public static readonly Protocol V20 = new Protocol(new QueryParameters() { - Realm = "realm", - op_endpoint = "op_endpoint", - response_nonce = "response_nonce", - error_code = "error_code", - user_setup_url = null, - }) { - Version = new Version(2, 0), - XmlNamespace = null, // no longer applicable - QueryDeclaredNamespaceVersion = Protocol.OpenId2Namespace, - ClaimedIdentifierServiceTypeURI = "http://specs.openid.net/auth/2.0/signon", - OPIdentifierServiceTypeURI = "http://specs.openid.net/auth/2.0/server", - ClaimedIdentifierForOPIdentifier = "http://specs.openid.net/auth/2.0/identifier_select", - RPReturnToTypeURI = "http://specs.openid.net/auth/2.0/return_to", - HtmlDiscoveryProviderKey = "openid2.provider", - HtmlDiscoveryLocalIdKey = "openid2.local_id", - Args = new QueryArguments() { - SessionType = new QueryArguments.SessionTypes() { - NoEncryption = "no-encryption", - DH_SHA256 = "DH-SHA256", - DH_SHA384 = "DH-SHA384", - DH_SHA512 = "DH-SHA512", - }, - SignatureAlgorithm = new QueryArguments.SignatureAlgorithms() { - HMAC_SHA256 = "HMAC-SHA256", - HMAC_SHA384 = "HMAC-SHA384", - HMAC_SHA512 = "HMAC-SHA512", - }, - Mode = new QueryArguments.Modes() { - setup_needed = "setup_needed", - }, - }, - }; - - /// <summary> - /// A list of all supported OpenID versions, in order starting from newest version. - /// </summary> - public readonly static List<Protocol> AllVersions = new List<Protocol>() { V20, V11, V10 }; - - /// <summary> - /// A list of all supported OpenID versions, in order starting from newest version. - /// V1.1 and V1.0 are considered the same and only V1.1 is in the list. - /// </summary> - public readonly static List<Protocol> AllPracticalVersions = new List<Protocol>() { V20, V11 }; - - /// <summary> - /// The default (or most recent) supported version of the OpenID protocol. - /// </summary> - public readonly static Protocol Default = AllVersions[0]; - public static Protocol Lookup(Version version) { - foreach (Protocol protocol in AllVersions) { - if (protocol.Version == version) return protocol; - } - throw new ArgumentOutOfRangeException("version"); - } - public static Protocol Lookup(ProtocolVersion version) { - switch (version) { - case ProtocolVersion.V10: return Protocol.V10; - case ProtocolVersion.V11: return Protocol.V11; - case ProtocolVersion.V20: return Protocol.V20; - default: throw new ArgumentOutOfRangeException("version"); - } - } - /// <summary> - /// Attempts to detect the right OpenID protocol version based on the contents - /// of an incoming OpenID <i>indirect</i> message or <i>direct request</i>. - /// </summary> - internal static Protocol Detect(IDictionary<string, string> query) { - Contract.Requires<ArgumentNullException>(query != null); - return query.ContainsKey(V20.openid.ns) ? V20 : V11; - } - /// <summary> - /// Attempts to detect the right OpenID protocol version based on the contents - /// of an incoming OpenID <i>direct</i> response message. - /// </summary> - internal static Protocol DetectFromDirectResponse(IDictionary<string, string> query) { - Contract.Requires<ArgumentNullException>(query != null); - return query.ContainsKey(V20.openidnp.ns) ? V20 : V11; - } - /// <summary> - /// Attemps to detect the highest OpenID protocol version supported given a set - /// of XRDS Service Type URIs included for some service. - /// </summary> - internal static Protocol Detect(IEnumerable<string> serviceTypeURIs) { - Contract.Requires<ArgumentNullException>(serviceTypeURIs != null); - return FindBestVersion(p => p.OPIdentifierServiceTypeURI, serviceTypeURIs) ?? - FindBestVersion(p => p.ClaimedIdentifierServiceTypeURI, serviceTypeURIs) ?? - FindBestVersion(p => p.RPReturnToTypeURI, serviceTypeURIs); - } - - /// <summary> - /// The OpenID version that this <see cref="Protocol"/> instance describes. - /// </summary> - public Version Version; - /// <summary> - /// Returns the <see cref="ProtocolVersion"/> enum value for the <see cref="Protocol"/> instance. - /// </summary> - public ProtocolVersion ProtocolVersion { - get { - switch (Version.Major) { - case 1: return ProtocolVersion.V11; - case 2: return ProtocolVersion.V20; - default: throw new ArgumentException(null); // this should never happen - } - } - } - /// <summary> - /// The namespace of OpenId 1.x elements in XRDS documents. - /// </summary> - public string XmlNamespace; - /// <summary> - /// The value of the openid.ns parameter that appears on the query string - /// whenever data is passed between relying party and provider for OpenID 2.0 - /// and later. - /// </summary> - public string QueryDeclaredNamespaceVersion; - /// <summary> - /// The XRD/Service/Type value discovered in an XRDS document when - /// "discovering" on a Claimed Identifier (http://andrewarnott.yahoo.com) - /// </summary> - public string ClaimedIdentifierServiceTypeURI; - /// <summary> - /// The XRD/Service/Type value discovered in an XRDS document when - /// "discovering" on an OP Identifier rather than a Claimed Identifier. - /// (http://yahoo.com) - /// </summary> - public string OPIdentifierServiceTypeURI; - /// <summary> - /// The XRD/Service/Type value discovered in an XRDS document when - /// "discovering" on a Realm URL and looking for the endpoint URL - /// that can receive authentication assertions. - /// </summary> - public string RPReturnToTypeURI; - /// <summary> - /// Used as the Claimed Identifier and the OP Local Identifier when - /// the User Supplied Identifier is an OP Identifier. - /// </summary> - public string ClaimedIdentifierForOPIdentifier; - /// <summary> - /// The value of the 'rel' attribute in an HTML document's LINK tag - /// when the same LINK tag's HREF attribute value contains the URL to an - /// OP Endpoint URL. - /// </summary> - public string HtmlDiscoveryProviderKey; - /// <summary> - /// The value of the 'rel' attribute in an HTML document's LINK tag - /// when the same LINK tag's HREF attribute value contains the URL to use - /// as the OP Local Identifier. - /// </summary> - public string HtmlDiscoveryLocalIdKey; - /// <summary> - /// Parts of the protocol that define parameter names that appear in the - /// query string. Each parameter name is prefixed with 'openid.'. - /// </summary> - public readonly QueryParameters openid; - /// <summary> - /// Parts of the protocol that define parameter names that appear in the - /// query string. Each parameter name is NOT prefixed with 'openid.'. - /// </summary> - public readonly QueryParameters openidnp; - /// <summary> - /// The various 'constants' that appear as parameter arguments (values). - /// </summary> - public QueryArguments Args = new QueryArguments(); - - internal sealed class QueryParameters { - /// <summary> - /// The value "openid." - /// </summary> - public readonly string Prefix = "openid."; - [SuppressMessage("Microsoft.Performance", "CA1805:DoNotInitializeUnnecessarily")] - public QueryParameters() { } - [SuppressMessage("Microsoft.Performance", "CA1805:DoNotInitializeUnnecessarily")] - public QueryParameters(QueryParameters addPrefixTo) { - ns = addPrefix(addPrefixTo.ns); - return_to = addPrefix(addPrefixTo.return_to); - Realm = addPrefix(addPrefixTo.Realm); - mode = addPrefix(addPrefixTo.mode); - error = addPrefix(addPrefixTo.error); - error_code = addPrefix(addPrefixTo.error_code); - identity = addPrefix(addPrefixTo.identity); - op_endpoint = addPrefix(addPrefixTo.op_endpoint); - response_nonce = addPrefix(addPrefixTo.response_nonce); - claimed_id = addPrefix(addPrefixTo.claimed_id); - expires_in = addPrefix(addPrefixTo.expires_in); - assoc_type = addPrefix(addPrefixTo.assoc_type); - assoc_handle = addPrefix(addPrefixTo.assoc_handle); - session_type = addPrefix(addPrefixTo.session_type); - is_valid = addPrefix(addPrefixTo.is_valid); - sig = addPrefix(addPrefixTo.sig); - signed = addPrefix(addPrefixTo.signed); - user_setup_url = addPrefix(addPrefixTo.user_setup_url); - invalidate_handle = addPrefix(addPrefixTo.invalidate_handle); - dh_modulus = addPrefix(addPrefixTo.dh_modulus); - dh_gen = addPrefix(addPrefixTo.dh_gen); - dh_consumer_public = addPrefix(addPrefixTo.dh_consumer_public); - dh_server_public = addPrefix(addPrefixTo.dh_server_public); - enc_mac_key = addPrefix(addPrefixTo.enc_mac_key); - mac_key = addPrefix(addPrefixTo.mac_key); - } - string addPrefix(string original) { - return (original != null) ? Prefix + original : null; - } - // These fields default to 1.x specifications, and are overridden - // as necessary by later versions in the Protocol class initializers. - // Null values in any version suggests that that feature is absent from that version. - public string ns = "ns"; - public string return_to = "return_to"; - public string Realm = "trust_root"; - public string mode = "mode"; - public string error = "error"; - public string error_code = null; - public string identity = "identity"; - public string op_endpoint = null; - public string response_nonce = null; - public string claimed_id = "claimed_id"; - public string expires_in = "expires_in"; - public string assoc_type = "assoc_type"; - public string assoc_handle = "assoc_handle"; - public string session_type = "session_type"; - public string is_valid = "is_valid"; - public string sig = "sig"; - public string signed = "signed"; - public string user_setup_url = "user_setup_url"; - public string invalidate_handle = "invalidate_handle"; - public string dh_modulus = "dh_modulus"; - public string dh_gen = "dh_gen"; - public string dh_consumer_public = "dh_consumer_public"; - public string dh_server_public = "dh_server_public"; - public string enc_mac_key = "enc_mac_key"; - public string mac_key = "mac_key"; - -#if CONTRACTS_FULL - /// <summary> - /// Verifies conditions that should be true for any valid state of this object. - /// </summary> - [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Called by code contracts.")] - [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called by code contracts.")] - [ContractInvariantMethod] - private void ObjectInvariant() { - Contract.Invariant(!string.IsNullOrEmpty(this.Prefix)); - } -#endif - } - - internal sealed class QueryArguments { - public ErrorCodes ErrorCode = new ErrorCodes(); - public SessionTypes SessionType = new SessionTypes(); - public SignatureAlgorithms SignatureAlgorithm = new SignatureAlgorithms(); - public Modes Mode = new Modes(); - public IsValidValues IsValid = new IsValidValues(); - - internal sealed class ErrorCodes { - public string UnsupportedType = "unsupported-type"; - } - internal sealed class SessionTypes { - /// <summary> - /// A preference order list of all supported session types. - /// </summary> - public string[] All { get { return new[] { DH_SHA512, DH_SHA384, DH_SHA256, DH_SHA1, NoEncryption }; } } - public string[] AllDiffieHellman { get { return new[] { DH_SHA512, DH_SHA384, DH_SHA256, DH_SHA1 }; } } - public string DH_SHA1 = "DH-SHA1"; - public string DH_SHA256; - public string DH_SHA384; - public string DH_SHA512; - public string NoEncryption = string.Empty; - public string Best { - get { - foreach (string algorithmName in All) { - if (algorithmName != null) { - return algorithmName; - } - } - throw new ProtocolException(); // really bad... we have no signing algorithms at all - } - } - } - internal sealed class SignatureAlgorithms { - /// <summary> - /// A preference order list of signature algorithms we support. - /// </summary> - public string[] All { get { return new[] { HMAC_SHA512, HMAC_SHA384, HMAC_SHA256, HMAC_SHA1 }; } } - public string HMAC_SHA1 = "HMAC-SHA1"; - public string HMAC_SHA256; - public string HMAC_SHA384; - public string HMAC_SHA512; - public string Best { - get { - foreach (string algorithmName in All) { - if (algorithmName != null) { - return algorithmName; - } - } - throw new ProtocolException(); // really bad... we have no signing algorithms at all - } - } - } - internal sealed class Modes { - public string cancel = "cancel"; - public string error = "error"; - public string id_res = "id_res"; - public string checkid_immediate = "checkid_immediate"; - public string checkid_setup = "checkid_setup"; - public string check_authentication = "check_authentication"; - public string associate = "associate"; - public string setup_needed = "id_res"; // V2 overrides this - } - internal sealed class IsValidValues { - public string True = "true"; - public string False = "false"; - } - } - - /// <summary> - /// The maximum time a user can be allowed to take to complete authentication. - /// </summary> - /// <remarks> - /// This is used to calculate the length of time that nonces are stored. - /// This is internal until we can decide whether to leave this static, or make - /// it an instance member, or put it inside the IConsumerApplicationStore interface. - /// </remarks> - internal static TimeSpan MaximumUserAgentAuthenticationTime = TimeSpan.FromMinutes(5); - /// <summary> - /// The maximum permissible difference in clocks between relying party and - /// provider web servers, discounting time zone differences. - /// </summary> - /// <remarks> - /// This is used when storing/validating nonces from the provider. - /// If it is conceivable that a server's clock could be up to five minutes - /// off from true UTC time, then the maximum time skew should be set to - /// ten minutes to allow one server to be five minutes ahead and the remote - /// server to be five minutes behind and still be able to communicate. - /// </remarks> - internal static TimeSpan MaximumAllowableTimeSkew = TimeSpan.FromMinutes(10); - - /// <summary> - /// Checks whether a given Protocol version practically equals this one - /// for purposes of verifying a match for assertion verification. - /// </summary> - /// <param name="other">The other version to check against this one.</param> - /// <returns><c>true</c> if this and the given Protocol versions are essentially the same.</returns> - /// <remarks> - /// OpenID v1.0 never had a spec, and 1.0 and 1.1 are indistinguishable because of that. - /// Therefore for assertion verification, 1.0 and 1.1 are considered equivalent. - /// </remarks> - public bool EqualsPractically(Protocol other) { - if (other == null) { - return false; - } - - // Exact version match is definitely equality. - if (this.Version == other.Version) { - return true; - } - - // If both protocol versions are 1.x, it doesn't matter if one - // is 1.0 and the other is 1.1 for assertion verification purposes. - if (this.Version.Major == 1 && other.Version.Major == 1) { - return true; - } - - // Different version. - return false; - } - - public override bool Equals(object obj) { - Protocol other = obj as Protocol; - if (other == null) { - return false; - } - - return this.Version == other.Version; - } - public override int GetHashCode() { - return Version.GetHashCode(); - } - public override string ToString() { - return string.Format(CultureInfo.CurrentCulture, "OpenID Authentication {0}.{1}", Version.Major, Version.Minor); - } - } -} diff --git a/src/DotNetOpenAuth/OpenId/Provider/AnonymousRequest.cs b/src/DotNetOpenAuth/OpenId/Provider/AnonymousRequest.cs deleted file mode 100644 index 974d729..0000000 --- a/src/DotNetOpenAuth/OpenId/Provider/AnonymousRequest.cs +++ /dev/null @@ -1,92 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="AnonymousRequest.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OpenId.Provider { - using System; - using System.Diagnostics.CodeAnalysis; - using System.Diagnostics.Contracts; - using DotNetOpenAuth.Messaging; - using DotNetOpenAuth.OpenId.Messages; - - /// <summary> - /// Provides access to a host Provider to read an incoming extension-only checkid request message, - /// and supply extension responses or a cancellation message to the RP. - /// </summary> - [Serializable] - internal class AnonymousRequest : HostProcessedRequest, IAnonymousRequest { - /// <summary> - /// The extension-response message to send, if the host site chooses to send it. - /// </summary> - private readonly IndirectSignedResponse positiveResponse; - - /// <summary> - /// Initializes a new instance of the <see cref="AnonymousRequest"/> class. - /// </summary> - /// <param name="provider">The provider that received the request.</param> - /// <param name="request">The incoming authentication request message.</param> - [SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", MessageId = "System.Diagnostics.Contracts.__ContractsRuntime.Requires<System.ArgumentException>(System.Boolean,System.String,System.String)", Justification = "Code contracts"), SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "AuthenticationRequest", Justification = "Type name"), SuppressMessage("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily", Justification = "Code contracts require it.")] - internal AnonymousRequest(OpenIdProvider provider, SignedResponseRequest request) - : base(provider, request) { - Contract.Requires<ArgumentNullException>(provider != null); - Contract.Requires<ArgumentException>(!(request is CheckIdRequest)); - - this.positiveResponse = new IndirectSignedResponse(request); - } - - #region HostProcessedRequest members - - /// <summary> - /// Gets or sets the provider endpoint. - /// </summary> - /// <value> - /// The default value is the URL that the request came in on from the relying party. - /// </value> - public override Uri ProviderEndpoint { - get { return this.positiveResponse.ProviderEndpoint; } - set { this.positiveResponse.ProviderEndpoint = value; } - } - - #endregion - - #region IAnonymousRequest Members - - /// <summary> - /// Gets or sets a value indicating whether the user approved sending any data to the relying party. - /// </summary> - /// <value><c>true</c> if approved; otherwise, <c>false</c>.</value> - public bool? IsApproved { get; set; } - - #endregion - - #region Request members - - /// <summary> - /// Gets a value indicating whether the response is ready to be sent to the user agent. - /// </summary> - /// <remarks> - /// This property returns false if there are properties that must be set on this - /// request instance before the response can be sent. - /// </remarks> - public override bool IsResponseReady { - get { return this.IsApproved.HasValue; } - } - - /// <summary> - /// Gets the response message, once <see cref="IsResponseReady"/> is <c>true</c>. - /// </summary> - protected override IProtocolMessage ResponseMessage { - get { - if (this.IsApproved.HasValue) { - return this.IsApproved.Value ? (IProtocolMessage)this.positiveResponse : this.NegativeResponse; - } else { - return null; - } - } - } - - #endregion - } -} diff --git a/src/DotNetOpenAuth/OpenId/Provider/AnonymousRequestEventArgs.cs b/src/DotNetOpenAuth/OpenId/Provider/AnonymousRequestEventArgs.cs deleted file mode 100644 index 9ffaa55..0000000 --- a/src/DotNetOpenAuth/OpenId/Provider/AnonymousRequestEventArgs.cs +++ /dev/null @@ -1,31 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="AnonymousRequestEventArgs.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OpenId.Provider { - using System; - using System.Diagnostics.Contracts; - using DotNetOpenAuth.Messaging; - - /// <summary> - /// The event arguments that include details of the incoming request. - /// </summary> - public class AnonymousRequestEventArgs : EventArgs { - /// <summary> - /// Initializes a new instance of the <see cref="AnonymousRequestEventArgs"/> class. - /// </summary> - /// <param name="request">The incoming OpenID request.</param> - internal AnonymousRequestEventArgs(IAnonymousRequest request) { - Contract.Requires<ArgumentNullException>(request != null); - - this.Request = request; - } - - /// <summary> - /// Gets the incoming OpenID request. - /// </summary> - public IAnonymousRequest Request { get; private set; } - } -} diff --git a/src/DotNetOpenAuth/OpenId/Provider/AssociationDataBag.cs b/src/DotNetOpenAuth/OpenId/Provider/AssociationDataBag.cs deleted file mode 100644 index a8ac41e..0000000 --- a/src/DotNetOpenAuth/OpenId/Provider/AssociationDataBag.cs +++ /dev/null @@ -1,95 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="AssociationDataBag.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OpenId.Provider { - using System; - using System.Collections.Generic; - using System.Diagnostics.Contracts; - using System.IO; - using System.Linq; - using System.Text; - using DotNetOpenAuth.Messaging; - using DotNetOpenAuth.Messaging.Bindings; - - /// <summary> - /// A signed and encrypted serialization of an association. - /// </summary> - internal class AssociationDataBag : DataBag, IStreamSerializingDataBag { - /// <summary> - /// Initializes a new instance of the <see cref="AssociationDataBag"/> class. - /// </summary> - public AssociationDataBag() { - } - - /// <summary> - /// Gets or sets the association secret. - /// </summary> - [MessagePart(IsRequired = true)] - internal byte[] Secret { get; set; } - - /// <summary> - /// Gets or sets the UTC time that this association expires. - /// </summary> - [MessagePart(IsRequired = true)] - internal DateTime ExpiresUtc { get; set; } - - /// <summary> - /// Gets or sets a value indicating whether this instance is for "dumb" mode RPs. - /// </summary> - /// <value> - /// <c>true</c> if this instance is private association; otherwise, <c>false</c>. - /// </value> - [MessagePart(IsRequired = true)] - internal bool IsPrivateAssociation { - get { return this.AssociationType == AssociationRelyingPartyType.Dumb; } - set { this.AssociationType = value ? AssociationRelyingPartyType.Dumb : AssociationRelyingPartyType.Smart; } - } - - /// <summary> - /// Gets or sets the type of the association (shared or private, a.k.a. smart or dumb). - /// </summary> - internal AssociationRelyingPartyType AssociationType { get; set; } - - /// <summary> - /// Serializes the instance to the specified stream. - /// </summary> - /// <param name="stream">The stream.</param> - public void Serialize(Stream stream) { - var writer = new BinaryWriter(stream); - writer.Write(this.IsPrivateAssociation); - writer.WriteBuffer(this.Secret); - writer.Write((int)(this.ExpiresUtc - TimestampEncoder.Epoch).TotalSeconds); - writer.Flush(); - } - - /// <summary> - /// Initializes the fields on this instance from the specified stream. - /// </summary> - /// <param name="stream">The stream.</param> - public void Deserialize(Stream stream) { - var reader = new BinaryReader(stream); - this.IsPrivateAssociation = reader.ReadBoolean(); - this.Secret = reader.ReadBuffer(); - this.ExpiresUtc = TimestampEncoder.Epoch + TimeSpan.FromSeconds(reader.ReadInt32()); - } - - /// <summary> - /// Creates the formatter used for serialization of this type. - /// </summary> - /// <param name="cryptoKeyStore">The crypto key store used when signing or encrypting.</param> - /// <param name="bucket">The bucket in which symmetric keys are stored for signing/encrypting data.</param> - /// <param name="minimumAge">The minimum age.</param> - /// <returns> - /// A formatter for serialization. - /// </returns> - internal static IDataBagFormatter<AssociationDataBag> CreateFormatter(ICryptoKeyStore cryptoKeyStore, string bucket, TimeSpan? minimumAge = null) { - Contract.Requires<ArgumentNullException>(cryptoKeyStore != null); - Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(bucket)); - Contract.Ensures(Contract.Result<IDataBagFormatter<AssociationDataBag>>() != null); - return new BinaryDataBagFormatter<AssociationDataBag>(cryptoKeyStore, bucket, signed: true, encrypted: true, minimumAge: minimumAge); - } - } -} diff --git a/src/DotNetOpenAuth/OpenId/Provider/AuthenticationRequest.cs b/src/DotNetOpenAuth/OpenId/Provider/AuthenticationRequest.cs deleted file mode 100644 index d22f858..0000000 --- a/src/DotNetOpenAuth/OpenId/Provider/AuthenticationRequest.cs +++ /dev/null @@ -1,227 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="AuthenticationRequest.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OpenId.Provider { - using System; - using System.Diagnostics.Contracts; - using DotNetOpenAuth.Messaging; - using DotNetOpenAuth.OpenId.Messages; - - /// <summary> - /// Implements the <see cref="IAuthenticationRequest"/> interface - /// so that OpenID Provider sites can easily respond to authentication - /// requests. - /// </summary> - [Serializable] - internal class AuthenticationRequest : HostProcessedRequest, IAuthenticationRequest { - /// <summary> - /// The positive assertion to send, if the host site chooses to send it. - /// </summary> - private readonly PositiveAssertionResponse positiveResponse; - - /// <summary> - /// Initializes a new instance of the <see cref="AuthenticationRequest"/> class. - /// </summary> - /// <param name="provider">The provider that received the request.</param> - /// <param name="request">The incoming authentication request message.</param> - internal AuthenticationRequest(OpenIdProvider provider, CheckIdRequest request) - : base(provider, request) { - Contract.Requires<ArgumentNullException>(provider != null); - - this.positiveResponse = new PositiveAssertionResponse(request); - - if (this.ClaimedIdentifier == Protocol.ClaimedIdentifierForOPIdentifier && - Protocol.ClaimedIdentifierForOPIdentifier != null) { - // Force the hosting OP to deal with identifier_select by nulling out the two identifiers. - this.IsDirectedIdentity = true; - this.positiveResponse.ClaimedIdentifier = null; - this.positiveResponse.LocalIdentifier = null; - } - - // URL delegation is only detectable from 2.0 RPs, since openid.claimed_id isn't included from 1.0 RPs. - // If the openid.claimed_id is present, and if it's different than the openid.identity argument, then - // the RP has discovered a claimed identifier that has delegated authentication to this Provider. - this.IsDelegatedIdentifier = this.ClaimedIdentifier != null && this.ClaimedIdentifier != this.LocalIdentifier; - - Reporting.RecordEventOccurrence("AuthenticationRequest.IsDelegatedIdentifier", this.IsDelegatedIdentifier.ToString()); - } - - #region HostProcessedRequest members - - /// <summary> - /// Gets or sets the provider endpoint. - /// </summary> - /// <value> - /// The default value is the URL that the request came in on from the relying party. - /// </value> - public override Uri ProviderEndpoint { - get { return this.positiveResponse.ProviderEndpoint; } - set { this.positiveResponse.ProviderEndpoint = value; } - } - - #endregion - - /// <summary> - /// Gets a value indicating whether the response is ready to be created and sent. - /// </summary> - public override bool IsResponseReady { - get { - // The null checks on the identifiers is to make sure that an identifier_select - // has been resolved to actual identifiers. - return this.IsAuthenticated.HasValue && - (!this.IsAuthenticated.Value || !this.IsDirectedIdentity || (this.LocalIdentifier != null && this.ClaimedIdentifier != null)); - } - } - - #region IAuthenticationRequest Properties - - /// <summary> - /// Gets a value indicating whether the Provider should help the user - /// select a Claimed Identifier to send back to the relying party. - /// </summary> - public bool IsDirectedIdentity { get; private set; } - - /// <summary> - /// Gets a value indicating whether the requesting Relying Party is using a delegated URL. - /// </summary> - /// <remarks> - /// When delegated identifiers are used, the <see cref="ClaimedIdentifier"/> should not - /// be changed at the Provider during authentication. - /// Delegation is only detectable on requests originating from OpenID 2.0 relying parties. - /// A relying party implementing only OpenID 1.x may use delegation and this property will - /// return false anyway. - /// </remarks> - public bool IsDelegatedIdentifier { get; private set; } - - /// <summary> - /// Gets or sets the Local Identifier to this OpenID Provider of the user attempting - /// to authenticate. Check <see cref="IsDirectedIdentity"/> to see if - /// this value is valid. - /// </summary> - /// <remarks> - /// This may or may not be the same as the Claimed Identifier that the user agent - /// originally supplied to the relying party. The Claimed Identifier - /// endpoint may be delegating authentication to this provider using - /// this provider's local id, which is what this property contains. - /// Use this identifier when looking up this user in the provider's user account - /// list. - /// </remarks> - public Identifier LocalIdentifier { - get { - return this.positiveResponse.LocalIdentifier; - } - - set { - // Keep LocalIdentifier and ClaimedIdentifier in sync for directed identity. - if (this.IsDirectedIdentity) { - if (this.ClaimedIdentifier != null && this.ClaimedIdentifier != value) { - throw new InvalidOperationException(OpenIdStrings.IdentifierSelectRequiresMatchingIdentifiers); - } - - this.positiveResponse.ClaimedIdentifier = value; - } - - this.positiveResponse.LocalIdentifier = value; - } - } - - /// <summary> - /// Gets or sets the identifier that the user agent is claiming at the relying party site. - /// Check <see cref="IsDirectedIdentity"/> to see if this value is valid. - /// </summary> - /// <remarks> - /// <para>This property can only be set if <see cref="IsDelegatedIdentifier"/> is - /// false, to prevent breaking URL delegation.</para> - /// <para>This will not be the same as this provider's local identifier for the user - /// if the user has set up his/her own identity page that points to this - /// provider for authentication.</para> - /// <para>The provider may use this identifier for displaying to the user when - /// asking for the user's permission to authenticate to the relying party.</para> - /// </remarks> - /// <exception cref="InvalidOperationException">Thrown from the setter - /// if <see cref="IsDelegatedIdentifier"/> is true.</exception> - public Identifier ClaimedIdentifier { - get { - return this.positiveResponse.ClaimedIdentifier; - } - - set { - // Keep LocalIdentifier and ClaimedIdentifier in sync for directed identity. - if (this.IsDirectedIdentity) { - this.positiveResponse.LocalIdentifier = value; - } - - this.positiveResponse.ClaimedIdentifier = value; - } - } - - /// <summary> - /// Gets or sets a value indicating whether the provider has determined that the - /// <see cref="ClaimedIdentifier"/> belongs to the currently logged in user - /// and wishes to share this information with the consumer. - /// </summary> - public bool? IsAuthenticated { get; set; } - - #endregion - - /// <summary> - /// Gets the original request message. - /// </summary> - protected new CheckIdRequest RequestMessage { - get { return (CheckIdRequest)base.RequestMessage; } - } - - /// <summary> - /// Gets the response message, once <see cref="IsResponseReady"/> is <c>true</c>. - /// </summary> - protected override IProtocolMessage ResponseMessage { - get { - if (this.IsAuthenticated.HasValue) { - return this.IsAuthenticated.Value ? (IProtocolMessage)this.positiveResponse : this.NegativeResponse; - } else { - return null; - } - } - } - - #region IAuthenticationRequest Methods - - /// <summary> - /// Adds an optional fragment (#fragment) portion to the ClaimedIdentifier. - /// Useful for identifier recycling. - /// </summary> - /// <param name="fragment">Should not include the # prefix character as that will be added internally. - /// May be null or the empty string to clear a previously set fragment.</param> - /// <remarks> - /// <para>Unlike the <see cref="ClaimedIdentifier"/> property, which can only be set if - /// using directed identity, this method can be called on any URI claimed identifier.</para> - /// <para>Because XRI claimed identifiers (the canonical IDs) are never recycled, - /// this method should<i>not</i> be called for XRIs.</para> - /// </remarks> - /// <exception cref="InvalidOperationException"> - /// Thrown when this method is called on an XRI, or on a directed identity - /// request before the <see cref="ClaimedIdentifier"/> property is set. - /// </exception> - public void SetClaimedIdentifierFragment(string fragment) { - UriBuilder builder = new UriBuilder(this.ClaimedIdentifier); - builder.Fragment = fragment; - this.positiveResponse.ClaimedIdentifier = builder.Uri; - } - - /// <summary> - /// Sets the Claimed and Local identifiers even after they have been initially set. - /// </summary> - /// <param name="identifier">The value to set to the <see cref="ClaimedIdentifier"/> and <see cref="LocalIdentifier"/> properties.</param> - internal void ResetClaimedAndLocalIdentifiers(Identifier identifier) { - Contract.Requires<ArgumentNullException>(identifier != null); - - this.positiveResponse.ClaimedIdentifier = identifier; - this.positiveResponse.LocalIdentifier = identifier; - } - - #endregion - } -} diff --git a/src/DotNetOpenAuth/OpenId/Provider/AutoResponsiveRequest.cs b/src/DotNetOpenAuth/OpenId/Provider/AutoResponsiveRequest.cs deleted file mode 100644 index 41e082b..0000000 --- a/src/DotNetOpenAuth/OpenId/Provider/AutoResponsiveRequest.cs +++ /dev/null @@ -1,78 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="AutoResponsiveRequest.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OpenId.Provider { - using System; - using System.Collections.Generic; - using System.Diagnostics.Contracts; - using System.Linq; - using System.Text; - using DotNetOpenAuth.Messaging; - using DotNetOpenAuth.OpenId.Messages; - - /// <summary> - /// Handles messages coming into an OpenID Provider for which the entire - /// response message can be automatically determined without help from - /// the hosting web site. - /// </summary> - internal class AutoResponsiveRequest : Request { - /// <summary> - /// The response message to send. - /// </summary> - private readonly IProtocolMessage response; - - /// <summary> - /// Initializes a new instance of the <see cref="AutoResponsiveRequest"/> class. - /// </summary> - /// <param name="request">The request message.</param> - /// <param name="response">The response that is ready for transmittal.</param> - /// <param name="securitySettings">The security settings.</param> - internal AutoResponsiveRequest(IDirectedProtocolMessage request, IProtocolMessage response, ProviderSecuritySettings securitySettings) - : base(request, securitySettings) { - Contract.Requires<ArgumentNullException>(response != null); - - this.response = response; - } - - /// <summary> - /// Initializes a new instance of the <see cref="AutoResponsiveRequest"/> class - /// for a response to an unrecognizable request. - /// </summary> - /// <param name="response">The response that is ready for transmittal.</param> - /// <param name="securitySettings">The security settings.</param> - internal AutoResponsiveRequest(IProtocolMessage response, ProviderSecuritySettings securitySettings) - : base(IndirectResponseBase.GetVersion(response), securitySettings) { - Contract.Requires<ArgumentNullException>(response != null); - - this.response = response; - } - - /// <summary> - /// Gets a value indicating whether the response is ready to be sent to the user agent. - /// </summary> - /// <remarks> - /// This property returns false if there are properties that must be set on this - /// request instance before the response can be sent. - /// </remarks> - public override bool IsResponseReady { - get { return true; } - } - - /// <summary> - /// Gets the response message, once <see cref="IsResponseReady"/> is <c>true</c>. - /// </summary> - internal IProtocolMessage ResponseMessageTestHook { - get { return this.ResponseMessage; } - } - - /// <summary> - /// Gets the response message, once <see cref="IsResponseReady"/> is <c>true</c>. - /// </summary> - protected override IProtocolMessage ResponseMessage { - get { return this.response; } - } - } -} diff --git a/src/DotNetOpenAuth/OpenId/Provider/HostProcessedRequest.cs b/src/DotNetOpenAuth/OpenId/Provider/HostProcessedRequest.cs deleted file mode 100644 index ec0c58a..0000000 --- a/src/DotNetOpenAuth/OpenId/Provider/HostProcessedRequest.cs +++ /dev/null @@ -1,178 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="HostProcessedRequest.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OpenId.Provider { - using System; - using System.Collections.Generic; - using System.Diagnostics.Contracts; - using System.Linq; - using System.Net; - using System.Text; - using DotNetOpenAuth.Messaging; - using DotNetOpenAuth.OpenId.Messages; - - /// <summary> - /// A base class from which identity and non-identity RP requests can derive. - /// </summary> - [Serializable] - internal abstract class HostProcessedRequest : Request, IHostProcessedRequest { - /// <summary> - /// The negative assertion to send, if the host site chooses to send it. - /// </summary> - private readonly NegativeAssertionResponse negativeResponse; - - /// <summary> - /// A cache of the result from discovery of the Realm URL. - /// </summary> - private RelyingPartyDiscoveryResult? realmDiscoveryResult; - - /// <summary> - /// Initializes a new instance of the <see cref="HostProcessedRequest"/> class. - /// </summary> - /// <param name="provider">The provider that received the request.</param> - /// <param name="request">The incoming request message.</param> - protected HostProcessedRequest(OpenIdProvider provider, SignedResponseRequest request) - : base(request, provider.SecuritySettings) { - Contract.Requires<ArgumentNullException>(provider != null); - - this.negativeResponse = new NegativeAssertionResponse(request, provider.Channel); - Reporting.RecordEventOccurrence(this, request.Realm); - } - - #region IHostProcessedRequest Properties - - /// <summary> - /// Gets the version of OpenID being used by the relying party that sent the request. - /// </summary> - public ProtocolVersion RelyingPartyVersion { - get { return Protocol.Lookup(this.RequestMessage.Version).ProtocolVersion; } - } - - /// <summary> - /// Gets a value indicating whether the consumer demands an immediate response. - /// If false, the consumer is willing to wait for the identity provider - /// to authenticate the user. - /// </summary> - public bool Immediate { - get { return this.RequestMessage.Immediate; } - } - - /// <summary> - /// Gets the URL the consumer site claims to use as its 'base' address. - /// </summary> - public Realm Realm { - get { return this.RequestMessage.Realm; } - } - - /// <summary> - /// Gets or sets the provider endpoint. - /// </summary> - /// <value> - /// The default value is the URL that the request came in on from the relying party. - /// </value> - public abstract Uri ProviderEndpoint { get; set; } - - #endregion - - /// <summary> - /// Gets a value indicating whether realm discovery been performed. - /// </summary> - internal bool HasRealmDiscoveryBeenPerformed { - get { return this.realmDiscoveryResult.HasValue; } - } - - /// <summary> - /// Gets the negative response. - /// </summary> - protected NegativeAssertionResponse NegativeResponse { - get { return this.negativeResponse; } - } - - /// <summary> - /// Gets the original request message. - /// </summary> - /// <value>This may be null in the case of an unrecognizable message.</value> - protected new SignedResponseRequest RequestMessage { - get { return (SignedResponseRequest)base.RequestMessage; } - } - - #region IHostProcessedRequest Methods - - /// <summary> - /// Gets a value indicating whether verification of the return URL claimed by the Relying Party - /// succeeded. - /// </summary> - /// <param name="provider">The OpenIdProvider that is performing the RP discovery.</param> - /// <returns>Result of realm discovery.</returns> - /// <remarks> - /// Return URL verification is only attempted if this property is queried. - /// The result of the verification is cached per request so calling this - /// property getter multiple times in one request is not a performance hit. - /// See OpenID Authentication 2.0 spec section 9.2.1. - /// </remarks> - public RelyingPartyDiscoveryResult IsReturnUrlDiscoverable(OpenIdProvider provider) { - if (!this.realmDiscoveryResult.HasValue) { - this.realmDiscoveryResult = this.IsReturnUrlDiscoverableCore(provider); - } - - return this.realmDiscoveryResult.Value; - } - - /// <summary> - /// Gets a value indicating whether verification of the return URL claimed by the Relying Party - /// succeeded. - /// </summary> - /// <param name="provider">The OpenIdProvider that is performing the RP discovery.</param> - /// <returns>Result of realm discovery.</returns> - private RelyingPartyDiscoveryResult IsReturnUrlDiscoverableCore(OpenIdProvider provider) { - Contract.Requires<ArgumentNullException>(provider != null); - - ErrorUtilities.VerifyInternal(this.Realm != null, "Realm should have been read or derived by now."); - - try { - if (this.SecuritySettings.RequireSsl && this.Realm.Scheme != Uri.UriSchemeHttps) { - Logger.OpenId.WarnFormat("RP discovery failed because RequireSsl is true and RP discovery would begin at insecure URL {0}.", this.Realm); - return RelyingPartyDiscoveryResult.NoServiceDocument; - } - - var returnToEndpoints = this.Realm.DiscoverReturnToEndpoints(provider.Channel.WebRequestHandler, false); - if (returnToEndpoints == null) { - return RelyingPartyDiscoveryResult.NoServiceDocument; - } - - foreach (var returnUrl in returnToEndpoints) { - Realm discoveredReturnToUrl = returnUrl.ReturnToEndpoint; - - // The spec requires that the return_to URLs given in an RPs XRDS doc - // do not contain wildcards. - if (discoveredReturnToUrl.DomainWildcard) { - Logger.Yadis.WarnFormat("Realm {0} contained return_to URL {1} which contains a wildcard, which is not allowed.", Realm, discoveredReturnToUrl); - continue; - } - - // Use the same rules as return_to/realm matching to check whether this - // URL fits the return_to URL we were given. - if (discoveredReturnToUrl.Contains(this.RequestMessage.ReturnTo)) { - // no need to keep looking after we find a match - return RelyingPartyDiscoveryResult.Success; - } - } - } catch (ProtocolException ex) { - // Don't do anything else. We quietly fail at return_to verification and return false. - Logger.Yadis.InfoFormat("Relying party discovery at URL {0} failed. {1}", Realm, ex); - return RelyingPartyDiscoveryResult.NoServiceDocument; - } catch (WebException ex) { - // Don't do anything else. We quietly fail at return_to verification and return false. - Logger.Yadis.InfoFormat("Relying party discovery at URL {0} failed. {1}", Realm, ex); - return RelyingPartyDiscoveryResult.NoServiceDocument; - } - - return RelyingPartyDiscoveryResult.NoMatchingReturnTo; - } - - #endregion - } -} diff --git a/src/DotNetOpenAuth/OpenId/Provider/IAuthenticationRequest.cs b/src/DotNetOpenAuth/OpenId/Provider/IAuthenticationRequest.cs deleted file mode 100644 index f59d436..0000000 --- a/src/DotNetOpenAuth/OpenId/Provider/IAuthenticationRequest.cs +++ /dev/null @@ -1,367 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="IAuthenticationRequest.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OpenId.Provider { - using System; - using System.Collections.Generic; - using System.Diagnostics.Contracts; - using System.Text; - using DotNetOpenAuth.Messaging; - - /// <summary> - /// Instances of this interface represent incoming authentication requests. - /// This interface provides the details of the request and allows setting - /// the response. - /// </summary> - [ContractClass(typeof(IAuthenticationRequestContract))] - public interface IAuthenticationRequest : IHostProcessedRequest { - /// <summary> - /// Gets a value indicating whether the Provider should help the user - /// select a Claimed Identifier to send back to the relying party. - /// </summary> - bool IsDirectedIdentity { get; } - - /// <summary> - /// Gets a value indicating whether the requesting Relying Party is using a delegated URL. - /// </summary> - /// <remarks> - /// When delegated identifiers are used, the <see cref="ClaimedIdentifier"/> should not - /// be changed at the Provider during authentication. - /// Delegation is only detectable on requests originating from OpenID 2.0 relying parties. - /// A relying party implementing only OpenID 1.x may use delegation and this property will - /// return false anyway. - /// </remarks> - bool IsDelegatedIdentifier { get; } - - /// <summary> - /// Gets or sets the Local Identifier to this OpenID Provider of the user attempting - /// to authenticate. Check <see cref="IsDirectedIdentity"/> to see if - /// this value is valid. - /// </summary> - /// <remarks> - /// This may or may not be the same as the Claimed Identifier that the user agent - /// originally supplied to the relying party. The Claimed Identifier - /// endpoint may be delegating authentication to this provider using - /// this provider's local id, which is what this property contains. - /// Use this identifier when looking up this user in the provider's user account - /// list. - /// </remarks> - Identifier LocalIdentifier { get; set; } - - /// <summary> - /// Gets or sets the identifier that the user agent is claiming at the relying party site. - /// Check <see cref="IsDirectedIdentity"/> to see if this value is valid. - /// </summary> - /// <remarks> - /// <para>This property can only be set if <see cref="IsDelegatedIdentifier"/> is - /// false, to prevent breaking URL delegation.</para> - /// <para>This will not be the same as this provider's local identifier for the user - /// if the user has set up his/her own identity page that points to this - /// provider for authentication.</para> - /// <para>The provider may use this identifier for displaying to the user when - /// asking for the user's permission to authenticate to the relying party.</para> - /// </remarks> - /// <exception cref="InvalidOperationException">Thrown from the setter - /// if <see cref="IsDelegatedIdentifier"/> is true.</exception> - Identifier ClaimedIdentifier { get; set; } - - /// <summary> - /// Gets or sets a value indicating whether the provider has determined that the - /// <see cref="ClaimedIdentifier"/> belongs to the currently logged in user - /// and wishes to share this information with the consumer. - /// </summary> - bool? IsAuthenticated { get; set; } - - /// <summary> - /// Adds an optional fragment (#fragment) portion to the ClaimedIdentifier. - /// Useful for identifier recycling. - /// </summary> - /// <param name="fragment"> - /// Should not include the # prefix character as that will be added internally. - /// May be null or the empty string to clear a previously set fragment. - /// </param> - /// <remarks> - /// <para>Unlike the <see cref="ClaimedIdentifier"/> property, which can only be set if - /// using directed identity, this method can be called on any URI claimed identifier.</para> - /// <para>Because XRI claimed identifiers (the canonical IDs) are never recycled, - /// this method should<i>not</i> be called for XRIs.</para> - /// </remarks> - /// <exception cref="InvalidOperationException"> - /// Thrown when this method is called on an XRI, or on a directed identity - /// request before the <see cref="ClaimedIdentifier"/> property is set. - /// </exception> - void SetClaimedIdentifierFragment(string fragment); - } - - /// <summary> - /// Code contract class for the <see cref="IAuthenticationRequest"/> type. - /// </summary> - [ContractClassFor(typeof(IAuthenticationRequest))] - internal abstract class IAuthenticationRequestContract : IAuthenticationRequest { - /// <summary> - /// Initializes a new instance of the <see cref="IAuthenticationRequestContract"/> class. - /// </summary> - protected IAuthenticationRequestContract() { - } - - #region IAuthenticationRequest Properties - - /// <summary> - /// Gets a value indicating whether the Provider should help the user - /// select a Claimed Identifier to send back to the relying party. - /// </summary> - bool IAuthenticationRequest.IsDirectedIdentity { - get { throw new NotImplementedException(); } - } - - /// <summary> - /// Gets a value indicating whether the requesting Relying Party is using a delegated URL. - /// </summary> - /// <remarks> - /// When delegated identifiers are used, the <see cref="IAuthenticationRequest.ClaimedIdentifier"/> should not - /// be changed at the Provider during authentication. - /// Delegation is only detectable on requests originating from OpenID 2.0 relying parties. - /// A relying party implementing only OpenID 1.x may use delegation and this property will - /// return false anyway. - /// </remarks> - bool IAuthenticationRequest.IsDelegatedIdentifier { - get { throw new NotImplementedException(); } - } - - /// <summary> - /// Gets or sets the Local Identifier to this OpenID Provider of the user attempting - /// to authenticate. Check <see cref="IAuthenticationRequest.IsDirectedIdentity"/> to see if - /// this value is valid. - /// </summary> - /// <remarks> - /// This may or may not be the same as the Claimed Identifier that the user agent - /// originally supplied to the relying party. The Claimed Identifier - /// endpoint may be delegating authentication to this provider using - /// this provider's local id, which is what this property contains. - /// Use this identifier when looking up this user in the provider's user account - /// list. - /// </remarks> - Identifier IAuthenticationRequest.LocalIdentifier { - get { - throw new NotImplementedException(); - } - - set { - throw new NotImplementedException(); - } - } - - /// <summary> - /// Gets or sets the identifier that the user agent is claiming at the relying party site. - /// Check <see cref="IAuthenticationRequest.IsDirectedIdentity"/> to see if this value is valid. - /// </summary> - /// <remarks> - /// <para>This property can only be set if <see cref="IAuthenticationRequest.IsDelegatedIdentifier"/> is - /// false, to prevent breaking URL delegation.</para> - /// <para>This will not be the same as this provider's local identifier for the user - /// if the user has set up his/her own identity page that points to this - /// provider for authentication.</para> - /// <para>The provider may use this identifier for displaying to the user when - /// asking for the user's permission to authenticate to the relying party.</para> - /// </remarks> - /// <exception cref="InvalidOperationException">Thrown from the setter - /// if <see cref="IAuthenticationRequest.IsDelegatedIdentifier"/> is true.</exception> - Identifier IAuthenticationRequest.ClaimedIdentifier { - get { - throw new NotImplementedException(); - } - - set { - IAuthenticationRequest req = this; - Contract.Requires<InvalidOperationException>(!req.IsDelegatedIdentifier, OpenIdStrings.ClaimedIdentifierCannotBeSetOnDelegatedAuthentication); - Contract.Requires<InvalidOperationException>(!req.IsDirectedIdentity || !(req.LocalIdentifier != null && req.LocalIdentifier != value), OpenIdStrings.IdentifierSelectRequiresMatchingIdentifiers); - } - } - - /// <summary> - /// Gets or sets a value indicating whether the provider has determined that the - /// <see cref="IAuthenticationRequest.ClaimedIdentifier"/> belongs to the currently logged in user - /// and wishes to share this information with the consumer. - /// </summary> - bool? IAuthenticationRequest.IsAuthenticated { - get { - throw new NotImplementedException(); - } - - set { - throw new NotImplementedException(); - } - } - - #endregion - - #region IHostProcessedRequest Properties - - /// <summary> - /// Gets the version of OpenID being used by the relying party that sent the request. - /// </summary> - ProtocolVersion IHostProcessedRequest.RelyingPartyVersion { - get { throw new NotImplementedException(); } - } - - /// <summary> - /// Gets the URL the consumer site claims to use as its 'base' address. - /// </summary> - Realm IHostProcessedRequest.Realm { - get { throw new NotImplementedException(); } - } - - /// <summary> - /// Gets a value indicating whether the consumer demands an immediate response. - /// If false, the consumer is willing to wait for the identity provider - /// to authenticate the user. - /// </summary> - bool IHostProcessedRequest.Immediate { - get { throw new NotImplementedException(); } - } - - /// <summary> - /// Gets or sets the provider endpoint claimed in the positive assertion. - /// </summary> - /// <value> - /// The default value is the URL that the request came in on from the relying party. - /// This value MUST match the value for the OP Endpoint in the discovery results for the - /// claimed identifier being asserted in a positive response. - /// </value> - Uri IHostProcessedRequest.ProviderEndpoint { - get { - throw new NotImplementedException(); - } - - set { - throw new NotImplementedException(); - } - } - - #endregion - - #region IRequest Properties - - /// <summary> - /// Gets a value indicating whether the response is ready to be sent to the user agent. - /// </summary> - /// <remarks> - /// This property returns false if there are properties that must be set on this - /// request instance before the response can be sent. - /// </remarks> - bool IRequest.IsResponseReady { - get { throw new NotImplementedException(); } - } - - /// <summary> - /// Gets or sets the security settings that apply to this request. - /// </summary> - /// <value> - /// Defaults to the <see cref="OpenIdProvider.SecuritySettings"/> on the <see cref="OpenIdProvider"/>. - /// </value> - ProviderSecuritySettings IRequest.SecuritySettings { - get { - throw new NotImplementedException(); - } - - set { - throw new NotImplementedException(); - } - } - - #endregion - - #region IAuthenticationRequest Methods - - /// <summary> - /// Adds an optional fragment (#fragment) portion to the ClaimedIdentifier. - /// Useful for identifier recycling. - /// </summary> - /// <param name="fragment">Should not include the # prefix character as that will be added internally. - /// May be null or the empty string to clear a previously set fragment.</param> - /// <remarks> - /// <para>Unlike the <see cref="IAuthenticationRequest.ClaimedIdentifier"/> property, which can only be set if - /// using directed identity, this method can be called on any URI claimed identifier.</para> - /// <para>Because XRI claimed identifiers (the canonical IDs) are never recycled, - /// this method should<i>not</i> be called for XRIs.</para> - /// </remarks> - /// <exception cref="InvalidOperationException"> - /// Thrown when this method is called on an XRI, or on a directed identity - /// request before the <see cref="IAuthenticationRequest.ClaimedIdentifier"/> property is set. - /// </exception> - void IAuthenticationRequest.SetClaimedIdentifierFragment(string fragment) { - Contract.Requires<InvalidOperationException>(!(((IAuthenticationRequest)this).IsDirectedIdentity && ((IAuthenticationRequest)this).ClaimedIdentifier == null), OpenIdStrings.ClaimedIdentifierMustBeSetFirst); - Contract.Requires<InvalidOperationException>(!(((IAuthenticationRequest)this).ClaimedIdentifier is XriIdentifier), OpenIdStrings.FragmentNotAllowedOnXRIs); - - throw new NotImplementedException(); - } - - #endregion - - #region IHostProcessedRequest Methods - - /// <summary> - /// Attempts to perform relying party discovery of the return URL claimed by the Relying Party. - /// </summary> - /// <param name="provider">The OpenIdProvider that is performing the RP discovery.</param> - /// <returns> - /// The details of how successful the relying party discovery was. - /// </returns> - /// <remarks> - /// <para>Return URL verification is only attempted if this method is called.</para> - /// <para>See OpenID Authentication 2.0 spec section 9.2.1.</para> - /// </remarks> - RelyingPartyDiscoveryResult IHostProcessedRequest.IsReturnUrlDiscoverable(OpenIdProvider provider) { - throw new NotImplementedException(); - } - - #endregion - - #region IRequest Methods - - /// <summary> - /// Adds an extension to the response to send to the relying party. - /// </summary> - /// <param name="extension">The extension to add to the response message.</param> - void IRequest.AddResponseExtension(DotNetOpenAuth.OpenId.Messages.IOpenIdMessageExtension extension) { - throw new NotImplementedException(); - } - - /// <summary> - /// Removes any response extensions previously added using <see cref="IRequest.AddResponseExtension"/>. - /// </summary> - /// <remarks> - /// This should be called before sending a negative response back to the relying party - /// if extensions were already added, since negative responses cannot carry extensions. - /// </remarks> - void IRequest.ClearResponseExtensions() { - } - - /// <summary> - /// Gets an extension sent from the relying party. - /// </summary> - /// <typeparam name="T">The type of the extension.</typeparam> - /// <returns> - /// An instance of the extension initialized with values passed in with the request. - /// </returns> - T IRequest.GetExtension<T>() { - throw new NotImplementedException(); - } - - /// <summary> - /// Gets an extension sent from the relying party. - /// </summary> - /// <param name="extensionType">The type of the extension.</param> - /// <returns> - /// An instance of the extension initialized with values passed in with the request. - /// </returns> - DotNetOpenAuth.OpenId.Messages.IOpenIdMessageExtension IRequest.GetExtension(Type extensionType) { - throw new NotImplementedException(); - } - - #endregion - } -} diff --git a/src/DotNetOpenAuth/OpenId/Provider/IDirectedIdentityIdentifierProvider.cs b/src/DotNetOpenAuth/OpenId/Provider/IDirectedIdentityIdentifierProvider.cs deleted file mode 100644 index 985bb54..0000000 --- a/src/DotNetOpenAuth/OpenId/Provider/IDirectedIdentityIdentifierProvider.cs +++ /dev/null @@ -1,83 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="IDirectedIdentityIdentifierProvider.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OpenId.Provider { - using System; - using System.Diagnostics.Contracts; - - /// <summary> - /// An interface to provide custom identifiers for users logging into specific relying parties. - /// </summary> - /// <remarks> - /// This interface would allow, for example, the Provider to offer PPIDs to their users, - /// allowing the users to log into RPs without leaving any clue as to their true identity, - /// and preventing multiple RPs from colluding to track user activity across realms. - /// </remarks> - [ContractClass(typeof(IDirectedIdentityIdentifierProviderContract))] - public interface IDirectedIdentityIdentifierProvider { - /// <summary> - /// Gets the Identifier to use for the Claimed Identifier and Local Identifier of - /// an outgoing positive assertion. - /// </summary> - /// <param name="localIdentifier">The OP local identifier for the authenticating user.</param> - /// <param name="relyingPartyRealm">The realm of the relying party receiving the assertion.</param> - /// <returns> - /// A valid, discoverable OpenID Identifier that should be used as the value for the - /// openid.claimed_id and openid.local_id parameters. Must not be null. - /// </returns> - Uri GetIdentifier(Identifier localIdentifier, Realm relyingPartyRealm); - - /// <summary> - /// Determines whether a given identifier is the primary (non-PPID) local identifier for some user. - /// </summary> - /// <param name="identifier">The identifier in question.</param> - /// <returns> - /// <c>true</c> if the given identifier is the valid, unique identifier for some uesr (and NOT a PPID); otherwise, <c>false</c>. - /// </returns> - [Pure] - bool IsUserLocalIdentifier(Identifier identifier); - } - - /// <summary> - /// Contract class for the <see cref="IDirectedIdentityIdentifierProvider"/> type. - /// </summary> - [ContractClassFor(typeof(IDirectedIdentityIdentifierProvider))] - internal abstract class IDirectedIdentityIdentifierProviderContract : IDirectedIdentityIdentifierProvider { - #region IDirectedIdentityIdentifierProvider Members - - /// <summary> - /// Gets the Identifier to use for the Claimed Identifier and Local Identifier of - /// an outgoing positive assertion. - /// </summary> - /// <param name="localIdentifier">The OP local identifier for the authenticating user.</param> - /// <param name="relyingPartyRealm">The realm of the relying party receiving the assertion.</param> - /// <returns> - /// A valid, discoverable OpenID Identifier that should be used as the value for the - /// openid.claimed_id and openid.local_id parameters. Must not be null. - /// </returns> - Uri IDirectedIdentityIdentifierProvider.GetIdentifier(Identifier localIdentifier, Realm relyingPartyRealm) { - Contract.Requires<ArgumentNullException>(localIdentifier != null); - Contract.Requires<ArgumentNullException>(relyingPartyRealm != null); - Contract.Requires<ArgumentException>(((IDirectedIdentityIdentifierProvider)this).IsUserLocalIdentifier(localIdentifier), OpenIdStrings.ArgumentIsPpidIdentifier); - throw new NotImplementedException(); - } - - /// <summary> - /// Determines whether a given identifier is the primary (non-PPID) local identifier for some user. - /// </summary> - /// <param name="identifier">The identifier in question.</param> - /// <returns> - /// <c>true</c> if the given identifier is the valid, unique identifier for some uesr (and NOT a PPID); otherwise, <c>false</c>. - /// </returns> - bool IDirectedIdentityIdentifierProvider.IsUserLocalIdentifier(Identifier identifier) { - Contract.Requires<ArgumentNullException>(identifier != null); - - throw new NotImplementedException(); - } - - #endregion - } -} diff --git a/src/DotNetOpenAuth/OpenId/Provider/IHostProcessedRequest.cs b/src/DotNetOpenAuth/OpenId/Provider/IHostProcessedRequest.cs deleted file mode 100644 index 1c38d4b..0000000 --- a/src/DotNetOpenAuth/OpenId/Provider/IHostProcessedRequest.cs +++ /dev/null @@ -1,202 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="IHostProcessedRequest.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OpenId.Provider { - using System; - using System.Diagnostics.Contracts; - using DotNetOpenAuth.Messaging; - using DotNetOpenAuth.OpenId.Messages; - - /// <summary> - /// Interface exposing incoming messages to the OpenID Provider that - /// require interaction with the host site. - /// </summary> - [ContractClass(typeof(IHostProcessedRequestContract))] - public interface IHostProcessedRequest : IRequest { - /// <summary> - /// Gets the version of OpenID being used by the relying party that sent the request. - /// </summary> - ProtocolVersion RelyingPartyVersion { get; } - - /// <summary> - /// Gets the URL the consumer site claims to use as its 'base' address. - /// </summary> - Realm Realm { get; } - - /// <summary> - /// Gets a value indicating whether the consumer demands an immediate response. - /// If false, the consumer is willing to wait for the identity provider - /// to authenticate the user. - /// </summary> - bool Immediate { get; } - - /// <summary> - /// Gets or sets the provider endpoint claimed in the positive assertion. - /// </summary> - /// <value> - /// The default value is the URL that the request came in on from the relying party. - /// This value MUST match the value for the OP Endpoint in the discovery results for the - /// claimed identifier being asserted in a positive response. - /// </value> - Uri ProviderEndpoint { get; set; } - - /// <summary> - /// Attempts to perform relying party discovery of the return URL claimed by the Relying Party. - /// </summary> - /// <param name="provider">The OpenIdProvider that is performing the RP discovery.</param> - /// <returns> - /// The details of how successful the relying party discovery was. - /// </returns> - /// <remarks> - /// <para>Return URL verification is only attempted if this method is called.</para> - /// <para>See OpenID Authentication 2.0 spec section 9.2.1.</para> - /// </remarks> - RelyingPartyDiscoveryResult IsReturnUrlDiscoverable(OpenIdProvider provider); - } - - /// <summary> - /// Code contract for the <see cref="IHostProcessedRequest"/> type. - /// </summary> - [ContractClassFor(typeof(IHostProcessedRequest))] - internal abstract class IHostProcessedRequestContract : IHostProcessedRequest { - /// <summary> - /// Initializes a new instance of the <see cref="IHostProcessedRequestContract"/> class. - /// </summary> - protected IHostProcessedRequestContract() { - } - - #region IHostProcessedRequest Properties - - /// <summary> - /// Gets the version of OpenID being used by the relying party that sent the request. - /// </summary> - ProtocolVersion IHostProcessedRequest.RelyingPartyVersion { - get { throw new System.NotImplementedException(); } - } - - /// <summary> - /// Gets the URL the consumer site claims to use as its 'base' address. - /// </summary> - Realm IHostProcessedRequest.Realm { - get { throw new System.NotImplementedException(); } - } - - /// <summary> - /// Gets a value indicating whether the consumer demands an immediate response. - /// If false, the consumer is willing to wait for the identity provider - /// to authenticate the user. - /// </summary> - bool IHostProcessedRequest.Immediate { - get { throw new System.NotImplementedException(); } - } - - /// <summary> - /// Gets or sets the provider endpoint. - /// </summary> - /// <value> - /// The default value is the URL that the request came in on from the relying party. - /// </value> - Uri IHostProcessedRequest.ProviderEndpoint { - get { - Contract.Ensures(Contract.Result<Uri>() != null); - throw new NotImplementedException(); - } - - set { - Contract.Requires(value != null); - throw new NotImplementedException(); - } - } - - #endregion - - #region IRequest Members - - /// <summary> - /// Gets or sets the security settings that apply to this request. - /// </summary> - /// <value> - /// Defaults to the <see cref="OpenIdProvider.SecuritySettings"/> on the <see cref="OpenIdProvider"/>. - /// </value> - ProviderSecuritySettings IRequest.SecuritySettings { - get { throw new NotImplementedException(); } - set { throw new NotImplementedException(); } - } - - /// <summary> - /// Gets a value indicating whether the response is ready to be sent to the user agent. - /// </summary> - /// <remarks> - /// This property returns false if there are properties that must be set on this - /// request instance before the response can be sent. - /// </remarks> - bool IRequest.IsResponseReady { - get { throw new System.NotImplementedException(); } - } - - /// <summary> - /// Adds an extension to the response to send to the relying party. - /// </summary> - /// <param name="extension">The extension to add to the response message.</param> - void IRequest.AddResponseExtension(DotNetOpenAuth.OpenId.Messages.IOpenIdMessageExtension extension) { - throw new System.NotImplementedException(); - } - - /// <summary> - /// Removes any response extensions previously added using <see cref="IRequest.AddResponseExtension"/>. - /// </summary> - /// <remarks> - /// This should be called before sending a negative response back to the relying party - /// if extensions were already added, since negative responses cannot carry extensions. - /// </remarks> - void IRequest.ClearResponseExtensions() { - } - - /// <summary> - /// Gets an extension sent from the relying party. - /// </summary> - /// <typeparam name="T">The type of the extension.</typeparam> - /// <returns> - /// An instance of the extension initialized with values passed in with the request. - /// </returns> - T IRequest.GetExtension<T>() { - throw new System.NotImplementedException(); - } - - /// <summary> - /// Gets an extension sent from the relying party. - /// </summary> - /// <param name="extensionType">The type of the extension.</param> - /// <returns> - /// An instance of the extension initialized with values passed in with the request. - /// </returns> - DotNetOpenAuth.OpenId.Messages.IOpenIdMessageExtension IRequest.GetExtension(System.Type extensionType) { - throw new System.NotImplementedException(); - } - - #endregion - - #region IHostProcessedRequest Methods - - /// <summary> - /// Attempts to perform relying party discovery of the return URL claimed by the Relying Party. - /// </summary> - /// <param name="provider">The OpenIdProvider that is performing the RP discovery.</param> - /// <returns> - /// The details of how successful the relying party discovery was. - /// </returns> - /// <remarks> - /// <para>Return URL verification is only attempted if this method is called.</para> - /// <para>See OpenID Authentication 2.0 spec section 9.2.1.</para> - /// </remarks> - RelyingPartyDiscoveryResult IHostProcessedRequest.IsReturnUrlDiscoverable(OpenIdProvider provider) { - Contract.Requires<ArgumentNullException>(provider != null); - throw new System.NotImplementedException(); - } - - #endregion - } -} diff --git a/src/DotNetOpenAuth/OpenId/Provider/IProviderAssociationStore.cs b/src/DotNetOpenAuth/OpenId/Provider/IProviderAssociationStore.cs deleted file mode 100644 index 5a554c1..0000000 --- a/src/DotNetOpenAuth/OpenId/Provider/IProviderAssociationStore.cs +++ /dev/null @@ -1,90 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="IProviderAssociationStore.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OpenId.Provider { - using System; - using System.Collections.Generic; - using System.Diagnostics.Contracts; - using System.Linq; - using System.Text; - using DotNetOpenAuth.Messaging; - - /// <summary> - /// Provides association serialization and deserialization. - /// </summary> - /// <remarks> - /// Implementations may choose to store the association details in memory or a database table and simply return a - /// short, randomly generated string that is the key to that data. Alternatively, an implementation may - /// sign and encrypt the association details and then encode the results as a base64 string and return that value - /// as the association handle, thereby avoiding any association persistence at the OpenID Provider. - /// When taking the latter approach however, it is of course imperative that the association be encrypted - /// to avoid disclosing the secret to anyone who sees the association handle, which itself isn't considered to - /// be confidential. - /// </remarks> - [ContractClass(typeof(IProviderAssociationStoreContract))] - internal interface IProviderAssociationStore { - /// <summary> - /// Stores an association and returns a handle for it. - /// </summary> - /// <param name="secret">The association secret.</param> - /// <param name="expiresUtc">The UTC time that the association should expire.</param> - /// <param name="privateAssociation">A value indicating whether this is a private association.</param> - /// <returns> - /// The association handle that represents this association. - /// </returns> - string Serialize(byte[] secret, DateTime expiresUtc, bool privateAssociation); - - /// <summary> - /// Retrieves an association given an association handle. - /// </summary> - /// <param name="containingMessage">The OpenID message that referenced this association handle.</param> - /// <param name="privateAssociation">A value indicating whether a private association is expected.</param> - /// <param name="handle">The association handle.</param> - /// <returns> - /// An association instance, or <c>null</c> if the association has expired or the signature is incorrect (which may be because the OP's symmetric key has changed). - /// </returns> - /// <exception cref="ProtocolException">Thrown if the association is not of the expected type.</exception> - Association Deserialize(IProtocolMessage containingMessage, bool privateAssociation, string handle); - } - - /// <summary> - /// Code contract for the <see cref="IProviderAssociationStore"/> interface. - /// </summary> - [ContractClassFor(typeof(IProviderAssociationStore))] - internal abstract class IProviderAssociationStoreContract : IProviderAssociationStore { - /// <summary> - /// Stores an association and returns a handle for it. - /// </summary> - /// <param name="secret">The association secret.</param> - /// <param name="expiresUtc">The expires UTC.</param> - /// <param name="privateAssociation">A value indicating whether this is a private association.</param> - /// <returns> - /// The association handle that represents this association. - /// </returns> - string IProviderAssociationStore.Serialize(byte[] secret, DateTime expiresUtc, bool privateAssociation) { - Contract.Requires<ArgumentNullException>(secret != null); - Contract.Requires<ArgumentException>(expiresUtc.Kind == DateTimeKind.Utc); - Contract.Ensures(!String.IsNullOrEmpty(Contract.Result<string>())); - throw new NotImplementedException(); - } - - /// <summary> - /// Retrieves an association given an association handle. - /// </summary> - /// <param name="containingMessage">The OpenID message that referenced this association handle.</param> - /// <param name="privateAssociation">A value indicating whether a private association is expected.</param> - /// <param name="handle">The association handle.</param> - /// <returns> - /// An association instance, or <c>null</c> if the association has expired or the signature is incorrect (which may be because the OP's symmetric key has changed). - /// </returns> - /// <exception cref="ProtocolException">Thrown if the association is not of the expected type.</exception> - Association IProviderAssociationStore.Deserialize(IProtocolMessage containingMessage, bool privateAssociation, string handle) { - Contract.Requires<ArgumentNullException>(containingMessage != null); - Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(handle)); - throw new NotImplementedException(); - } - } -} diff --git a/src/DotNetOpenAuth/OpenId/Provider/IProviderBehavior.cs b/src/DotNetOpenAuth/OpenId/Provider/IProviderBehavior.cs deleted file mode 100644 index 01b4ac8..0000000 --- a/src/DotNetOpenAuth/OpenId/Provider/IProviderBehavior.cs +++ /dev/null @@ -1,114 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="IProviderBehavior.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OpenId.Provider { - using System; - using System.Diagnostics.Contracts; - using DotNetOpenAuth.OpenId.ChannelElements; - - /// <summary> - /// Applies a custom security policy to certain OpenID security settings and behaviors. - /// </summary> - [ContractClass(typeof(IProviderBehaviorContract))] - public interface IProviderBehavior { - /// <summary> - /// Applies a well known set of security requirements to a default set of security settings. - /// </summary> - /// <param name="securitySettings">The security settings to enhance with the requirements of this profile.</param> - /// <remarks> - /// Care should be taken to never decrease security when applying a profile. - /// Profiles should only enhance security requirements to avoid being - /// incompatible with each other. - /// </remarks> - void ApplySecuritySettings(ProviderSecuritySettings securitySettings); - - /// <summary> - /// Called when a request is received by the Provider. - /// </summary> - /// <param name="request">The incoming request.</param> - /// <returns> - /// <c>true</c> if this behavior owns this request and wants to stop other behaviors - /// from handling it; <c>false</c> to allow other behaviors to process this request. - /// </returns> - /// <remarks> - /// Implementations may set a new value to <see cref="IRequest.SecuritySettings"/> but - /// should not change the properties on the instance of <see cref="ProviderSecuritySettings"/> - /// itself as that instance may be shared across many requests. - /// </remarks> - bool OnIncomingRequest(IRequest request); - - /// <summary> - /// Called when the Provider is preparing to send a response to an authentication request. - /// </summary> - /// <param name="request">The request that is configured to generate the outgoing response.</param> - /// <returns> - /// <c>true</c> if this behavior owns this request and wants to stop other behaviors - /// from handling it; <c>false</c> to allow other behaviors to process this request. - /// </returns> - bool OnOutgoingResponse(IAuthenticationRequest request); - } - - /// <summary> - /// Code contract for the <see cref="IProviderBehavior"/> type. - /// </summary> - [ContractClassFor(typeof(IProviderBehavior))] - internal abstract class IProviderBehaviorContract : IProviderBehavior { - /// <summary> - /// Initializes a new instance of the <see cref="IProviderBehaviorContract"/> class. - /// </summary> - protected IProviderBehaviorContract() { - } - - #region IProviderBehavior Members - - /// <summary> - /// Applies a well known set of security requirements to a default set of security settings. - /// </summary> - /// <param name="securitySettings">The security settings to enhance with the requirements of this profile.</param> - /// <remarks> - /// Care should be taken to never decrease security when applying a profile. - /// Profiles should only enhance security requirements to avoid being - /// incompatible with each other. - /// </remarks> - void IProviderBehavior.ApplySecuritySettings(ProviderSecuritySettings securitySettings) { - Contract.Requires<ArgumentNullException>(securitySettings != null); - throw new System.NotImplementedException(); - } - - /// <summary> - /// Called when a request is received by the Provider. - /// </summary> - /// <param name="request">The incoming request.</param> - /// <returns> - /// <c>true</c> if this behavior owns this request and wants to stop other behaviors - /// from handling it; <c>false</c> to allow other behaviors to process this request. - /// </returns> - /// <remarks> - /// Implementations may set a new value to <see cref="IRequest.SecuritySettings"/> but - /// should not change the properties on the instance of <see cref="ProviderSecuritySettings"/> - /// itself as that instance may be shared across many requests. - /// </remarks> - bool IProviderBehavior.OnIncomingRequest(IRequest request) { - Contract.Requires<ArgumentNullException>(request != null); - throw new System.NotImplementedException(); - } - - /// <summary> - /// Called when the Provider is preparing to send a response to an authentication request. - /// </summary> - /// <param name="request">The request that is configured to generate the outgoing response.</param> - /// <returns> - /// <c>true</c> if this behavior owns this request and wants to stop other behaviors - /// from handling it; <c>false</c> to allow other behaviors to process this request. - /// </returns> - bool IProviderBehavior.OnOutgoingResponse(IAuthenticationRequest request) { - Contract.Requires<ArgumentNullException>(request != null); - throw new System.NotImplementedException(); - } - - #endregion - } -} diff --git a/src/DotNetOpenAuth/OpenId/Provider/IRequest.cs b/src/DotNetOpenAuth/OpenId/Provider/IRequest.cs deleted file mode 100644 index c231fa3..0000000 --- a/src/DotNetOpenAuth/OpenId/Provider/IRequest.cs +++ /dev/null @@ -1,151 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="IRequest.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OpenId.Provider { - using System; - using System.Collections.Generic; - using System.Diagnostics.CodeAnalysis; - using System.Diagnostics.Contracts; - using System.Text; - using DotNetOpenAuth.Messaging; - using DotNetOpenAuth.OpenId.Messages; - - /// <summary> - /// Represents an incoming OpenId authentication request. - /// </summary> - /// <remarks> - /// Requests may be infrastructural to OpenID and allow auto-responses, or they may - /// be authentication requests where the Provider site has to make decisions based - /// on its own user database and policies. - /// </remarks> - [ContractClass(typeof(IRequestContract))] - public interface IRequest { - /// <summary> - /// Gets a value indicating whether the response is ready to be sent to the user agent. - /// </summary> - /// <remarks> - /// This property returns false if there are properties that must be set on this - /// request instance before the response can be sent. - /// </remarks> - bool IsResponseReady { get; } - - /// <summary> - /// Gets or sets the security settings that apply to this request. - /// </summary> - /// <value>Defaults to the <see cref="OpenIdProvider.SecuritySettings"/> on the <see cref="OpenIdProvider"/>.</value> - ProviderSecuritySettings SecuritySettings { get; set; } - - /// <summary> - /// Adds an extension to the response to send to the relying party. - /// </summary> - /// <param name="extension">The extension to add to the response message.</param> - void AddResponseExtension(IOpenIdMessageExtension extension); - - /// <summary> - /// Removes any response extensions previously added using <see cref="IRequest.AddResponseExtension"/>. - /// </summary> - /// <remarks> - /// This should be called before sending a negative response back to the relying party - /// if extensions were already added, since negative responses cannot carry extensions. - /// </remarks> - void ClearResponseExtensions(); - - /// <summary> - /// Gets an extension sent from the relying party. - /// </summary> - /// <typeparam name="T">The type of the extension.</typeparam> - /// <returns>An instance of the extension initialized with values passed in with the request.</returns> - [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "No parameter to make of type T.")] - T GetExtension<T>() where T : IOpenIdMessageExtension, new(); - - /// <summary> - /// Gets an extension sent from the relying party. - /// </summary> - /// <param name="extensionType">The type of the extension.</param> - /// <returns>An instance of the extension initialized with values passed in with the request.</returns> - IOpenIdMessageExtension GetExtension(Type extensionType); - } - - /// <summary> - /// Code contract for the <see cref="IRequest"/> interface. - /// </summary> - [ContractClassFor(typeof(IRequest))] - internal abstract class IRequestContract : IRequest { - /// <summary> - /// Prevents a default instance of the <see cref="IRequestContract"/> class from being created. - /// </summary> - private IRequestContract() { - } - - #region IRequest Members - - /// <summary> - /// Gets or sets the security settings that apply to this request. - /// </summary> - /// <value> - /// Defaults to the <see cref="OpenIdProvider.SecuritySettings"/> on the <see cref="OpenIdProvider"/>. - /// </value> - ProviderSecuritySettings IRequest.SecuritySettings { - get { throw new NotImplementedException(); } - set { throw new NotImplementedException(); } - } - - /// <summary> - /// Gets a value indicating whether the response is ready to be sent to the user agent. - /// </summary> - /// <remarks> - /// This property returns false if there are properties that must be set on this - /// request instance before the response can be sent. - /// </remarks> - bool IRequest.IsResponseReady { - get { throw new NotImplementedException(); } - } - - /// <summary> - /// Adds an extension to the response to send to the relying party. - /// </summary> - /// <param name="extension">The extension to add to the response message.</param> - void IRequest.AddResponseExtension(IOpenIdMessageExtension extension) { - Contract.Requires<ArgumentNullException>(extension != null); - throw new NotImplementedException(); - } - - /// <summary> - /// Removes any response extensions previously added using <see cref="IRequest.AddResponseExtension"/>. - /// </summary> - /// <remarks> - /// This should be called before sending a negative response back to the relying party - /// if extensions were already added, since negative responses cannot carry extensions. - /// </remarks> - void IRequest.ClearResponseExtensions() { - } - - /// <summary> - /// Gets an extension sent from the relying party. - /// </summary> - /// <typeparam name="T">The type of the extension.</typeparam> - /// <returns> - /// An instance of the extension initialized with values passed in with the request. - /// </returns> - T IRequest.GetExtension<T>() { - throw new NotImplementedException(); - } - - /// <summary> - /// Gets an extension sent from the relying party. - /// </summary> - /// <param name="extensionType">The type of the extension.</param> - /// <returns> - /// An instance of the extension initialized with values passed in with the request. - /// </returns> - IOpenIdMessageExtension IRequest.GetExtension(Type extensionType) { - Contract.Requires<ArgumentNullException>(extensionType != null); - throw new NotImplementedException(); - } - - #endregion - } -} diff --git a/src/DotNetOpenAuth/OpenId/Provider/OpenIdProvider.cs b/src/DotNetOpenAuth/OpenId/Provider/OpenIdProvider.cs deleted file mode 100644 index dbab28b..0000000 --- a/src/DotNetOpenAuth/OpenId/Provider/OpenIdProvider.cs +++ /dev/null @@ -1,662 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="OpenIdProvider.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OpenId.Provider { - using System; - using System.Collections.Generic; - using System.Collections.ObjectModel; - using System.Collections.Specialized; - using System.ComponentModel; - using System.Diagnostics.CodeAnalysis; - using System.Diagnostics.Contracts; - using System.Linq; - using System.Threading; - using System.Web; - using DotNetOpenAuth.Configuration; - using DotNetOpenAuth.Messaging; - using DotNetOpenAuth.Messaging.Bindings; - using DotNetOpenAuth.OpenId.ChannelElements; - using DotNetOpenAuth.OpenId.Messages; - using RP = DotNetOpenAuth.OpenId.RelyingParty; - - /// <summary> - /// Offers services for a web page that is acting as an OpenID identity server. - /// </summary> - [SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Justification = "By design")] - [ContractVerification(true)] - public sealed class OpenIdProvider : IDisposable { - /// <summary> - /// The name of the key to use in the HttpApplication cache to store the - /// instance of <see cref="StandardProviderApplicationStore"/> to use. - /// </summary> - private const string ApplicationStoreKey = "DotNetOpenAuth.OpenId.Provider.OpenIdProvider.ApplicationStore"; - - /// <summary> - /// Backing store for the <see cref="Behaviors"/> property. - /// </summary> - private readonly ObservableCollection<IProviderBehavior> behaviors = new ObservableCollection<IProviderBehavior>(); - - /// <summary> - /// Backing field for the <see cref="SecuritySettings"/> property. - /// </summary> - private ProviderSecuritySettings securitySettings; - - /// <summary> - /// The relying party used to perform discovery on identifiers being sent in - /// unsolicited positive assertions. - /// </summary> - private RP.OpenIdRelyingParty relyingParty; - - /// <summary> - /// Initializes a new instance of the <see cref="OpenIdProvider"/> class. - /// </summary> - public OpenIdProvider() - : this(DotNetOpenAuthSection.Configuration.OpenId.Provider.ApplicationStore.CreateInstance(HttpApplicationStore)) { - Contract.Ensures(this.SecuritySettings != null); - Contract.Ensures(this.Channel != null); - } - - /// <summary> - /// Initializes a new instance of the <see cref="OpenIdProvider"/> class. - /// </summary> - /// <param name="applicationStore">The application store to use. Cannot be null.</param> - public OpenIdProvider(IOpenIdApplicationStore applicationStore) - : this((INonceStore)applicationStore, (ICryptoKeyStore)applicationStore) { - Contract.Requires<ArgumentNullException>(applicationStore != null); - Contract.Ensures(this.SecuritySettings != null); - Contract.Ensures(this.Channel != null); - } - - /// <summary> - /// Initializes a new instance of the <see cref="OpenIdProvider"/> class. - /// </summary> - /// <param name="nonceStore">The nonce store to use. Cannot be null.</param> - /// <param name="cryptoKeyStore">The crypto key store. Cannot be null.</param> - private OpenIdProvider(INonceStore nonceStore, ICryptoKeyStore cryptoKeyStore) { - Contract.Requires<ArgumentNullException>(nonceStore != null); - Contract.Requires<ArgumentNullException>(cryptoKeyStore != null); - Contract.Ensures(this.SecuritySettings != null); - Contract.Ensures(this.Channel != null); - - this.SecuritySettings = DotNetOpenAuthSection.Configuration.OpenId.Provider.SecuritySettings.CreateSecuritySettings(); - this.behaviors.CollectionChanged += this.OnBehaviorsChanged; - foreach (var behavior in DotNetOpenAuthSection.Configuration.OpenId.Provider.Behaviors.CreateInstances(false)) { - this.behaviors.Add(behavior); - } - - this.AssociationStore = new SwitchingAssociationStore(cryptoKeyStore, this.SecuritySettings); - this.Channel = new OpenIdChannel(this.AssociationStore, nonceStore, this.SecuritySettings); - this.CryptoKeyStore = cryptoKeyStore; - - Reporting.RecordFeatureAndDependencyUse(this, nonceStore); - } - - /// <summary> - /// Gets the standard state storage mechanism that uses ASP.NET's - /// HttpApplication state dictionary to store associations and nonces. - /// </summary> - [EditorBrowsable(EditorBrowsableState.Advanced)] - public static IOpenIdApplicationStore HttpApplicationStore { - get { - Contract.Requires<InvalidOperationException>(HttpContext.Current != null && HttpContext.Current.Request != null, MessagingStrings.HttpContextRequired); - Contract.Ensures(Contract.Result<IOpenIdApplicationStore>() != null); - HttpContext context = HttpContext.Current; - var store = (IOpenIdApplicationStore)context.Application[ApplicationStoreKey]; - if (store == null) { - context.Application.Lock(); - try { - if ((store = (IOpenIdApplicationStore)context.Application[ApplicationStoreKey]) == null) { - context.Application[ApplicationStoreKey] = store = new StandardProviderApplicationStore(); - } - } finally { - context.Application.UnLock(); - } - } - - return store; - } - } - - /// <summary> - /// Gets the channel to use for sending/receiving messages. - /// </summary> - public Channel Channel { get; internal set; } - - /// <summary> - /// Gets the security settings used by this Provider. - /// </summary> - public ProviderSecuritySettings SecuritySettings { - get { - Contract.Ensures(Contract.Result<ProviderSecuritySettings>() != null); - Contract.Assume(this.securitySettings != null); - return this.securitySettings; - } - - internal set { - Contract.Requires<ArgumentNullException>(value != null); - this.securitySettings = value; - } - } - - /// <summary> - /// Gets the extension factories. - /// </summary> - public IList<IOpenIdExtensionFactory> ExtensionFactories { - get { return this.Channel.GetExtensionFactories(); } - } - - /// <summary> - /// Gets or sets the mechanism a host site can use to receive - /// notifications of errors when communicating with remote parties. - /// </summary> - public IErrorReporting ErrorReporting { get; set; } - - /// <summary> - /// Gets a list of custom behaviors to apply to OpenID actions. - /// </summary> - /// <remarks> - /// Adding behaviors can impact the security settings of the <see cref="OpenIdProvider"/> - /// in ways that subsequently removing the behaviors will not reverse. - /// </remarks> - public ICollection<IProviderBehavior> Behaviors { - get { return this.behaviors; } - } - - /// <summary> - /// Gets the crypto key store. - /// </summary> - public ICryptoKeyStore CryptoKeyStore { get; private set; } - - /// <summary> - /// Gets the association store. - /// </summary> - internal IProviderAssociationStore AssociationStore { get; private set; } - - /// <summary> - /// Gets the channel. - /// </summary> - internal OpenIdChannel OpenIdChannel { - get { return (OpenIdChannel)this.Channel; } - } - - /// <summary> - /// Gets the list of services that can perform discovery on identifiers given to this relying party. - /// </summary> - internal IList<IIdentifierDiscoveryService> DiscoveryServices { - get { return this.RelyingParty.DiscoveryServices; } - } - - /// <summary> - /// Gets the web request handler to use for discovery and the part of - /// authentication where direct messages are sent to an untrusted remote party. - /// </summary> - internal IDirectWebRequestHandler WebRequestHandler { - get { return this.Channel.WebRequestHandler; } - } - - /// <summary> - /// Gets the relying party used for discovery of identifiers sent in unsolicited assertions. - /// </summary> - private RP.OpenIdRelyingParty RelyingParty { - get { - if (this.relyingParty == null) { - lock (this) { - if (this.relyingParty == null) { - // we just need an RP that's capable of discovery, so stateless mode is fine. - this.relyingParty = new RP.OpenIdRelyingParty(null); - } - } - } - - this.relyingParty.Channel.WebRequestHandler = this.WebRequestHandler; - return this.relyingParty; - } - } - - /// <summary> - /// Gets the incoming OpenID request if there is one, or null if none was detected. - /// </summary> - /// <returns>The request that the hosting Provider should possibly process and then transmit the response for.</returns> - /// <remarks> - /// <para>Requests may be infrastructural to OpenID and allow auto-responses, or they may - /// be authentication requests where the Provider site has to make decisions based - /// on its own user database and policies.</para> - /// <para>Requires an <see cref="HttpContext.Current">HttpContext.Current</see> context.</para> - /// </remarks> - /// <exception cref="InvalidOperationException">Thrown if <see cref="HttpContext.Current">HttpContext.Current</see> == <c>null</c>.</exception> - /// <exception cref="ProtocolException">Thrown if the incoming message is recognized but deviates from the protocol specification irrecoverably.</exception> - public IRequest GetRequest() { - return this.GetRequest(this.Channel.GetRequestFromContext()); - } - - /// <summary> - /// Gets the incoming OpenID request if there is one, or null if none was detected. - /// </summary> - /// <param name="httpRequestInfo">The incoming HTTP request to extract the message from.</param> - /// <returns> - /// The request that the hosting Provider should process and then transmit the response for. - /// Null if no valid OpenID request was detected in the given HTTP request. - /// </returns> - /// <remarks> - /// Requests may be infrastructural to OpenID and allow auto-responses, or they may - /// be authentication requests where the Provider site has to make decisions based - /// on its own user database and policies. - /// </remarks> - /// <exception cref="ProtocolException">Thrown if the incoming message is recognized - /// but deviates from the protocol specification irrecoverably.</exception> - public IRequest GetRequest(HttpRequestInfo httpRequestInfo) { - Contract.Requires<ArgumentNullException>(httpRequestInfo != null); - IDirectedProtocolMessage incomingMessage = null; - - try { - incomingMessage = this.Channel.ReadFromRequest(httpRequestInfo); - if (incomingMessage == null) { - // If the incoming request does not resemble an OpenID message at all, - // it's probably a user who just navigated to this URL, and we should - // just return null so the host can display a message to the user. - if (httpRequestInfo.HttpMethod == "GET" && !httpRequestInfo.UrlBeforeRewriting.QueryStringContainPrefixedParameters(Protocol.Default.openid.Prefix)) { - return null; - } - - ErrorUtilities.ThrowProtocol(MessagingStrings.UnexpectedMessageReceivedOfMany); - } - - IRequest result = null; - - var checkIdMessage = incomingMessage as CheckIdRequest; - if (checkIdMessage != null) { - result = new AuthenticationRequest(this, checkIdMessage); - } - - if (result == null) { - var extensionOnlyRequest = incomingMessage as SignedResponseRequest; - if (extensionOnlyRequest != null) { - result = new AnonymousRequest(this, extensionOnlyRequest); - } - } - - if (result == null) { - var checkAuthMessage = incomingMessage as CheckAuthenticationRequest; - if (checkAuthMessage != null) { - result = new AutoResponsiveRequest(incomingMessage, new CheckAuthenticationResponse(checkAuthMessage, this), this.SecuritySettings); - } - } - - if (result == null) { - var associateMessage = incomingMessage as AssociateRequest; - if (associateMessage != null) { - result = new AutoResponsiveRequest(incomingMessage, associateMessage.CreateResponse(this.AssociationStore, this.SecuritySettings), this.SecuritySettings); - } - } - - if (result != null) { - foreach (var behavior in this.Behaviors) { - if (behavior.OnIncomingRequest(result)) { - // This behavior matched this request. - break; - } - } - - return result; - } - - throw ErrorUtilities.ThrowProtocol(MessagingStrings.UnexpectedMessageReceivedOfMany); - } catch (ProtocolException ex) { - IRequest errorResponse = this.GetErrorResponse(ex, httpRequestInfo, incomingMessage); - if (errorResponse == null) { - throw; - } - - return errorResponse; - } - } - - /// <summary> - /// Sends the response to a received request. - /// </summary> - /// <param name="request">The incoming OpenID request whose response is to be sent.</param> - /// <exception cref="ThreadAbortException">Thrown by ASP.NET in order to prevent additional data from the page being sent to the client and corrupting the response.</exception> - /// <remarks> - /// <para>Requires an HttpContext.Current context. If one is not available, the caller should use - /// <see cref="PrepareResponse"/> instead and manually send the <see cref="OutgoingWebResponse"/> - /// to the client.</para> - /// </remarks> - /// <exception cref="InvalidOperationException">Thrown if <see cref="IRequest.IsResponseReady"/> is <c>false</c>.</exception> - [SuppressMessage("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily", Justification = "Code Contract requires that we cast early.")] - [EditorBrowsable(EditorBrowsableState.Never)] - public void SendResponse(IRequest request) { - Contract.Requires<InvalidOperationException>(HttpContext.Current != null, MessagingStrings.CurrentHttpContextRequired); - Contract.Requires<ArgumentNullException>(request != null); - Contract.Requires<ArgumentException>(request.IsResponseReady); - - this.ApplyBehaviorsToResponse(request); - Request requestInternal = (Request)request; - this.Channel.Send(requestInternal.Response); - } - - /// <summary> - /// Sends the response to a received request. - /// </summary> - /// <param name="request">The incoming OpenID request whose response is to be sent.</param> - /// <remarks> - /// <para>Requires an HttpContext.Current context. If one is not available, the caller should use - /// <see cref="PrepareResponse"/> instead and manually send the <see cref="OutgoingWebResponse"/> - /// to the client.</para> - /// </remarks> - /// <exception cref="InvalidOperationException">Thrown if <see cref="IRequest.IsResponseReady"/> is <c>false</c>.</exception> - [SuppressMessage("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily", Justification = "Code Contract requires that we cast early.")] - public void Respond(IRequest request) { - Contract.Requires<InvalidOperationException>(HttpContext.Current != null, MessagingStrings.CurrentHttpContextRequired); - Contract.Requires<ArgumentNullException>(request != null); - Contract.Requires<ArgumentException>(request.IsResponseReady); - - this.ApplyBehaviorsToResponse(request); - Request requestInternal = (Request)request; - this.Channel.Respond(requestInternal.Response); - } - - /// <summary> - /// Gets the response to a received request. - /// </summary> - /// <param name="request">The request.</param> - /// <returns>The response that should be sent to the client.</returns> - /// <exception cref="InvalidOperationException">Thrown if <see cref="IRequest.IsResponseReady"/> is <c>false</c>.</exception> - [SuppressMessage("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily", Justification = "Code Contract requires that we cast early.")] - public OutgoingWebResponse PrepareResponse(IRequest request) { - Contract.Requires<ArgumentNullException>(request != null); - Contract.Requires<ArgumentException>(request.IsResponseReady); - - this.ApplyBehaviorsToResponse(request); - Request requestInternal = (Request)request; - return this.Channel.PrepareResponse(requestInternal.Response); - } - - /// <summary> - /// Sends an identity assertion on behalf of one of this Provider's - /// members in order to redirect the user agent to a relying party - /// web site and log him/her in immediately in one uninterrupted step. - /// </summary> - /// <param name="providerEndpoint">The absolute URL on the Provider site that receives OpenID messages.</param> - /// <param name="relyingPartyRealm">The URL of the Relying Party web site. - /// This will typically be the home page, but may be a longer URL if - /// that Relying Party considers the scope of its realm to be more specific. - /// The URL provided here must allow discovery of the Relying Party's - /// XRDS document that advertises its OpenID RP endpoint.</param> - /// <param name="claimedIdentifier">The Identifier you are asserting your member controls.</param> - /// <param name="localIdentifier">The Identifier you know your user by internally. This will typically - /// be the same as <paramref name="claimedIdentifier"/>.</param> - /// <param name="extensions">The extensions.</param> - public void SendUnsolicitedAssertion(Uri providerEndpoint, Realm relyingPartyRealm, Identifier claimedIdentifier, Identifier localIdentifier, params IExtensionMessage[] extensions) { - Contract.Requires<InvalidOperationException>(HttpContext.Current != null, MessagingStrings.HttpContextRequired); - Contract.Requires<ArgumentNullException>(providerEndpoint != null); - Contract.Requires<ArgumentException>(providerEndpoint.IsAbsoluteUri); - Contract.Requires<ArgumentNullException>(relyingPartyRealm != null); - Contract.Requires<ArgumentNullException>(claimedIdentifier != null); - Contract.Requires<ArgumentNullException>(localIdentifier != null); - - this.PrepareUnsolicitedAssertion(providerEndpoint, relyingPartyRealm, claimedIdentifier, localIdentifier, extensions).Send(); - } - - /// <summary> - /// Prepares an identity assertion on behalf of one of this Provider's - /// members in order to redirect the user agent to a relying party - /// web site and log him/her in immediately in one uninterrupted step. - /// </summary> - /// <param name="providerEndpoint">The absolute URL on the Provider site that receives OpenID messages.</param> - /// <param name="relyingPartyRealm">The URL of the Relying Party web site. - /// This will typically be the home page, but may be a longer URL if - /// that Relying Party considers the scope of its realm to be more specific. - /// The URL provided here must allow discovery of the Relying Party's - /// XRDS document that advertises its OpenID RP endpoint.</param> - /// <param name="claimedIdentifier">The Identifier you are asserting your member controls.</param> - /// <param name="localIdentifier">The Identifier you know your user by internally. This will typically - /// be the same as <paramref name="claimedIdentifier"/>.</param> - /// <param name="extensions">The extensions.</param> - /// <returns> - /// A <see cref="OutgoingWebResponse"/> object describing the HTTP response to send - /// the user agent to allow the redirect with assertion to happen. - /// </returns> - public OutgoingWebResponse PrepareUnsolicitedAssertion(Uri providerEndpoint, Realm relyingPartyRealm, Identifier claimedIdentifier, Identifier localIdentifier, params IExtensionMessage[] extensions) { - Contract.Requires<ArgumentNullException>(providerEndpoint != null); - Contract.Requires<ArgumentException>(providerEndpoint.IsAbsoluteUri); - Contract.Requires<ArgumentNullException>(relyingPartyRealm != null); - Contract.Requires<ArgumentNullException>(claimedIdentifier != null); - Contract.Requires<ArgumentNullException>(localIdentifier != null); - Contract.Requires<InvalidOperationException>(this.Channel.WebRequestHandler != null); - - // Although the RP should do their due diligence to make sure that this OP - // is authorized to send an assertion for the given claimed identifier, - // do due diligence by performing our own discovery on the claimed identifier - // and make sure that it is tied to this OP and OP local identifier. - if (this.SecuritySettings.UnsolicitedAssertionVerification != ProviderSecuritySettings.UnsolicitedAssertionVerificationLevel.NeverVerify) { - var serviceEndpoint = IdentifierDiscoveryResult.CreateForClaimedIdentifier(claimedIdentifier, localIdentifier, new ProviderEndpointDescription(providerEndpoint, Protocol.Default.Version), null, null); - var discoveredEndpoints = this.RelyingParty.Discover(claimedIdentifier); - if (!discoveredEndpoints.Contains(serviceEndpoint)) { - Logger.OpenId.WarnFormat( - "Failed to send unsolicited assertion for {0} because its discovered services did not include this endpoint: {1}{2}{1}Discovered endpoints: {1}{3}", - claimedIdentifier, - Environment.NewLine, - serviceEndpoint, - discoveredEndpoints.ToStringDeferred(true)); - - // Only FAIL if the setting is set for it. - if (this.securitySettings.UnsolicitedAssertionVerification == ProviderSecuritySettings.UnsolicitedAssertionVerificationLevel.RequireSuccess) { - ErrorUtilities.ThrowProtocol(OpenIdStrings.UnsolicitedAssertionForUnrelatedClaimedIdentifier, claimedIdentifier); - } - } - } - - Logger.OpenId.InfoFormat("Preparing unsolicited assertion for {0}", claimedIdentifier); - RelyingPartyEndpointDescription returnToEndpoint = null; - var returnToEndpoints = relyingPartyRealm.DiscoverReturnToEndpoints(this.WebRequestHandler, true); - if (returnToEndpoints != null) { - returnToEndpoint = returnToEndpoints.FirstOrDefault(); - } - ErrorUtilities.VerifyProtocol(returnToEndpoint != null, OpenIdStrings.NoRelyingPartyEndpointDiscovered, relyingPartyRealm); - - var positiveAssertion = new PositiveAssertionResponse(returnToEndpoint) { - ProviderEndpoint = providerEndpoint, - ClaimedIdentifier = claimedIdentifier, - LocalIdentifier = localIdentifier, - }; - - if (extensions != null) { - foreach (IExtensionMessage extension in extensions) { - positiveAssertion.Extensions.Add(extension); - } - } - - Reporting.RecordEventOccurrence(this, "PrepareUnsolicitedAssertion"); - return this.Channel.PrepareResponse(positiveAssertion); - } - - #region IDisposable Members - - /// <summary> - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. - /// </summary> - public void Dispose() { - this.Dispose(true); - GC.SuppressFinalize(this); - } - - /// <summary> - /// Releases unmanaged and - optionally - managed resources - /// </summary> - /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param> - private void Dispose(bool disposing) { - if (disposing) { - // Tear off the instance member as a local variable for thread safety. - IDisposable channel = this.Channel as IDisposable; - if (channel != null) { - channel.Dispose(); - } - - if (this.relyingParty != null) { - this.relyingParty.Dispose(); - } - } - } - - #endregion - - /// <summary> - /// Applies all behaviors to the response message. - /// </summary> - /// <param name="request">The request.</param> - private void ApplyBehaviorsToResponse(IRequest request) { - var authRequest = request as IAuthenticationRequest; - if (authRequest != null) { - foreach (var behavior in this.Behaviors) { - if (behavior.OnOutgoingResponse(authRequest)) { - // This behavior matched this request. - break; - } - } - } - } - - /// <summary> - /// Prepares the return value for the GetRequest method in the event of an exception. - /// </summary> - /// <param name="ex">The exception that forms the basis of the error response. Must not be null.</param> - /// <param name="httpRequestInfo">The incoming HTTP request. Must not be null.</param> - /// <param name="incomingMessage">The incoming message. May be null in the case that it was malformed.</param> - /// <returns> - /// Either the <see cref="IRequest"/> to return to the host site or null to indicate no response could be reasonably created and that the caller should rethrow the exception. - /// </returns> - private IRequest GetErrorResponse(ProtocolException ex, HttpRequestInfo httpRequestInfo, IDirectedProtocolMessage incomingMessage) { - Contract.Requires<ArgumentNullException>(ex != null); - Contract.Requires<ArgumentNullException>(httpRequestInfo != null); - - Logger.OpenId.Error("An exception was generated while processing an incoming OpenID request.", ex); - IErrorMessage errorMessage; - - // We must create the appropriate error message type (direct vs. indirect) - // based on what we see in the request. - string returnTo = httpRequestInfo.QueryString[Protocol.Default.openid.return_to]; - if (returnTo != null) { - // An indirect request message from the RP - // We need to return an indirect response error message so the RP can consume it. - // Consistent with OpenID 2.0 section 5.2.3. - var indirectRequest = incomingMessage as SignedResponseRequest; - if (indirectRequest != null) { - errorMessage = new IndirectErrorResponse(indirectRequest); - } else { - errorMessage = new IndirectErrorResponse(Protocol.Default.Version, new Uri(returnTo)); - } - } else if (httpRequestInfo.HttpMethod == "POST") { - // A direct request message from the RP - // We need to return a direct response error message so the RP can consume it. - // Consistent with OpenID 2.0 section 5.1.2.2. - errorMessage = new DirectErrorResponse(Protocol.Default.Version, incomingMessage); - } else { - // This may be an indirect request from an RP that was so badly - // formed that we cannot even return an error to the RP. - // The best we can do is display an error to the user. - // Returning null cues the caller to "throw;" - return null; - } - - errorMessage.ErrorMessage = ex.ToStringDescriptive(); - - // Allow host to log this error and issue a ticket #. - // We tear off the field to a local var for thread safety. - IErrorReporting hostErrorHandler = this.ErrorReporting; - if (hostErrorHandler != null) { - errorMessage.Contact = hostErrorHandler.Contact; - errorMessage.Reference = hostErrorHandler.LogError(ex); - } - - if (incomingMessage != null) { - return new AutoResponsiveRequest(incomingMessage, errorMessage, this.SecuritySettings); - } else { - return new AutoResponsiveRequest(errorMessage, this.SecuritySettings); - } - } - - /// <summary> - /// Called by derived classes when behaviors are added or removed. - /// </summary> - /// <param name="sender">The collection being modified.</param> - /// <param name="e">The <see cref="System.Collections.Specialized.NotifyCollectionChangedEventArgs"/> instance containing the event data.</param> - private void OnBehaviorsChanged(object sender, NotifyCollectionChangedEventArgs e) { - foreach (IProviderBehavior profile in e.NewItems) { - profile.ApplySecuritySettings(this.SecuritySettings); - Reporting.RecordFeatureUse(profile); - } - } - - /// <summary> - /// Provides a single OP association store instance that can handle switching between - /// association handle encoding modes. - /// </summary> - private class SwitchingAssociationStore : IProviderAssociationStore { - /// <summary> - /// The security settings of the Provider. - /// </summary> - private readonly ProviderSecuritySettings securitySettings; - - /// <summary> - /// The association store that records association secrets in the association handles themselves. - /// </summary> - private IProviderAssociationStore associationHandleEncoder; - - /// <summary> - /// The association store that records association secrets in a secret store. - /// </summary> - private IProviderAssociationStore associationSecretStorage; - - /// <summary> - /// Initializes a new instance of the <see cref="SwitchingAssociationStore"/> class. - /// </summary> - /// <param name="cryptoKeyStore">The crypto key store.</param> - /// <param name="securitySettings">The security settings.</param> - internal SwitchingAssociationStore(ICryptoKeyStore cryptoKeyStore, ProviderSecuritySettings securitySettings) { - Contract.Requires<ArgumentNullException>(cryptoKeyStore != null); - Contract.Requires<ArgumentNullException>(securitySettings != null); - this.securitySettings = securitySettings; - - this.associationHandleEncoder = new ProviderAssociationHandleEncoder(cryptoKeyStore); - this.associationSecretStorage = new ProviderAssociationKeyStorage(cryptoKeyStore); - } - - /// <summary> - /// Gets the association store that applies given the Provider's current security settings. - /// </summary> - internal IProviderAssociationStore AssociationStore { - get { return this.securitySettings.EncodeAssociationSecretsInHandles ? this.associationHandleEncoder : this.associationSecretStorage; } - } - - /// <summary> - /// Stores an association and returns a handle for it. - /// </summary> - /// <param name="secret">The association secret.</param> - /// <param name="expiresUtc">The UTC time that the association should expire.</param> - /// <param name="privateAssociation">A value indicating whether this is a private association.</param> - /// <returns> - /// The association handle that represents this association. - /// </returns> - public string Serialize(byte[] secret, DateTime expiresUtc, bool privateAssociation) { - return this.AssociationStore.Serialize(secret, expiresUtc, privateAssociation); - } - - /// <summary> - /// Retrieves an association given an association handle. - /// </summary> - /// <param name="containingMessage">The OpenID message that referenced this association handle.</param> - /// <param name="isPrivateAssociation">A value indicating whether a private association is expected.</param> - /// <param name="handle">The association handle.</param> - /// <returns> - /// An association instance, or <c>null</c> if the association has expired or the signature is incorrect (which may be because the OP's symmetric key has changed). - /// </returns> - /// <exception cref="ProtocolException">Thrown if the association is not of the expected type.</exception> - public Association Deserialize(IProtocolMessage containingMessage, bool isPrivateAssociation, string handle) { - return this.AssociationStore.Deserialize(containingMessage, isPrivateAssociation, handle); - } - } - } -} diff --git a/src/DotNetOpenAuth/OpenId/Provider/PrivatePersonalIdentifierProviderBase.cs b/src/DotNetOpenAuth/OpenId/Provider/PrivatePersonalIdentifierProviderBase.cs deleted file mode 100644 index 46e172c..0000000 --- a/src/DotNetOpenAuth/OpenId/Provider/PrivatePersonalIdentifierProviderBase.cs +++ /dev/null @@ -1,224 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="PrivatePersonalIdentifierProviderBase.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OpenId.Provider { - using System; - using System.Collections.Generic; - using System.Diagnostics.CodeAnalysis; - using System.Diagnostics.Contracts; - using System.Globalization; - using System.Linq; - using System.Security.Cryptography; - using System.Text; - using DotNetOpenAuth.Messaging; - - /// <summary> - /// Provides standard PPID Identifiers to users to protect their identity from individual relying parties - /// and from colluding groups of relying parties. - /// </summary> - public abstract class PrivatePersonalIdentifierProviderBase : IDirectedIdentityIdentifierProvider { - /// <summary> - /// The type of hash function to use for the <see cref="Hasher"/> property. - /// </summary> - private const string HashAlgorithmName = "SHA256"; - - /// <summary> - /// The length of the salt to generate for first time PPID-users. - /// </summary> - private int newSaltLength = 20; - - /// <summary> - /// Initializes a new instance of the <see cref="PrivatePersonalIdentifierProviderBase"/> class. - /// </summary> - /// <param name="baseIdentifier">The base URI on which to append the anonymous part.</param> - protected PrivatePersonalIdentifierProviderBase(Uri baseIdentifier) { - Contract.Requires<ArgumentNullException>(baseIdentifier != null); - - this.Hasher = HashAlgorithm.Create(HashAlgorithmName); - this.Encoder = Encoding.UTF8; - this.BaseIdentifier = baseIdentifier; - this.PairwiseUnique = AudienceScope.Realm; - } - - /// <summary> - /// A granularity description for who wide of an audience sees the same generated PPID. - /// </summary> - [SuppressMessage("Microsoft.Design", "CA1034:NestedTypesShouldNotBeVisible", Justification = "Breaking change")] - public enum AudienceScope { - /// <summary> - /// A unique Identifier is generated for every realm. This is the highest security setting. - /// </summary> - Realm, - - /// <summary> - /// Only the host name in the realm is used in calculating the PPID, - /// allowing for some level of sharing of the PPID Identifiers between RPs - /// that are able to share the same realm host value. - /// </summary> - RealmHost, - - /// <summary> - /// Although the user's Identifier is still opaque to the RP so they cannot determine - /// who the user is at the OP, the same Identifier is used at all RPs so collusion - /// between the RPs is possible. - /// </summary> - Global, - } - - /// <summary> - /// Gets the base URI on which to append the anonymous part. - /// </summary> - public Uri BaseIdentifier { get; private set; } - - /// <summary> - /// Gets or sets a value indicating whether each Realm will get its own private identifier - /// for the authenticating uesr. - /// </summary> - /// <value>The default value is <see cref="AudienceScope.Realm"/>.</value> - [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Pairwise", Justification = "Meaningful word")] - public AudienceScope PairwiseUnique { get; set; } - - /// <summary> - /// Gets the hash function to use to perform the one-way transform of a personal identifier - /// to an "anonymous" looking one. - /// </summary> - protected HashAlgorithm Hasher { get; private set; } - - /// <summary> - /// Gets the encoder to use for transforming the personal identifier into bytes for hashing. - /// </summary> - protected Encoding Encoder { get; private set; } - - /// <summary> - /// Gets or sets the new length of the salt. - /// </summary> - /// <value>The new length of the salt.</value> - protected int NewSaltLength { - get { - return this.newSaltLength; - } - - set { - Contract.Requires<ArgumentOutOfRangeException>(value > 0); - this.newSaltLength = value; - } - } - - #region IDirectedIdentityIdentifierProvider Members - - /// <summary> - /// Gets the Identifier to use for the Claimed Identifier and Local Identifier of - /// an outgoing positive assertion. - /// </summary> - /// <param name="localIdentifier">The OP local identifier for the authenticating user.</param> - /// <param name="relyingPartyRealm">The realm of the relying party receiving the assertion.</param> - /// <returns> - /// A valid, discoverable OpenID Identifier that should be used as the value for the - /// openid.claimed_id and openid.local_id parameters. Must not be null. - /// </returns> - public Uri GetIdentifier(Identifier localIdentifier, Realm relyingPartyRealm) { - byte[] salt = this.GetHashSaltForLocalIdentifier(localIdentifier); - string valueToHash = localIdentifier + "#"; - switch (this.PairwiseUnique) { - case AudienceScope.Realm: - valueToHash += relyingPartyRealm; - break; - case AudienceScope.RealmHost: - valueToHash += relyingPartyRealm.Host; - break; - case AudienceScope.Global: - break; - default: - throw new InvalidOperationException( - string.Format( - CultureInfo.CurrentCulture, - OpenIdStrings.UnexpectedEnumPropertyValue, - "PairwiseUnique", - this.PairwiseUnique)); - } - - byte[] valueAsBytes = this.Encoder.GetBytes(valueToHash); - byte[] bytesToHash = new byte[valueAsBytes.Length + salt.Length]; - valueAsBytes.CopyTo(bytesToHash, 0); - salt.CopyTo(bytesToHash, valueAsBytes.Length); - byte[] hash = this.Hasher.ComputeHash(bytesToHash); - string base64Hash = Convert.ToBase64String(hash); - Uri anonymousIdentifier = this.AppendIdentifiers(base64Hash); - return anonymousIdentifier; - } - - /// <summary> - /// Determines whether a given identifier is the primary (non-PPID) local identifier for some user. - /// </summary> - /// <param name="identifier">The identifier in question.</param> - /// <returns> - /// <c>true</c> if the given identifier is the valid, unique identifier for some uesr (and NOT a PPID); otherwise, <c>false</c>. - /// </returns> - public virtual bool IsUserLocalIdentifier(Identifier identifier) - { - return !identifier.ToString().StartsWith(this.BaseIdentifier.AbsoluteUri, StringComparison.Ordinal); - } - - #endregion - - /// <summary> - /// Creates a new salt to assign to a user. - /// </summary> - /// <returns>A non-null buffer of length <see cref="NewSaltLength"/> filled with a random salt.</returns> - protected virtual byte[] CreateSalt() { - // We COULD use a crypto random function, but for a salt it seems overkill. - return MessagingUtilities.GetNonCryptoRandomData(this.NewSaltLength); - } - - /// <summary> - /// Creates a new PPID Identifier by appending a pseudonymous identifier suffix to - /// the <see cref="BaseIdentifier"/>. - /// </summary> - /// <param name="uriHash">The unique part of the Identifier to append to the common first part.</param> - /// <returns>The full PPID Identifier.</returns> - [SuppressMessage("Microsoft.Usage", "CA2234:PassSystemUriObjectsInsteadOfStrings", Justification = "NOT equivalent overload. The recommended one breaks on relative URIs.")] - protected virtual Uri AppendIdentifiers(string uriHash) { - Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(uriHash)); - - if (string.IsNullOrEmpty(this.BaseIdentifier.Query)) { - // The uriHash will appear on the path itself. - string pathEncoded = Uri.EscapeUriString(uriHash.Replace('/', '_')); - return new Uri(this.BaseIdentifier, pathEncoded); - } else { - // The uriHash will appear on the query string. - string dataEncoded = Uri.EscapeDataString(uriHash); - return new Uri(this.BaseIdentifier + dataEncoded); - } - } - - /// <summary> - /// Gets the salt to use for generating an anonymous identifier for a given OP local identifier. - /// </summary> - /// <param name="localIdentifier">The OP local identifier.</param> - /// <returns>The salt to use in the hash.</returns> - /// <remarks> - /// It is important that this method always return the same value for a given - /// <paramref name="localIdentifier"/>. - /// New salts can be generated for local identifiers without previously assigned salt - /// values by calling <see cref="CreateSalt"/> or by a custom method. - /// </remarks> - protected abstract byte[] GetHashSaltForLocalIdentifier(Identifier localIdentifier); - -#if CONTRACTS_FULL - /// <summary> - /// Verifies conditions that should be true for any valid state of this object. - /// </summary> - [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called by code contracts.")] - [ContractInvariantMethod] - private void ObjectInvariant() { - Contract.Invariant(this.Hasher != null); - Contract.Invariant(this.Encoder != null); - Contract.Invariant(this.BaseIdentifier != null); - Contract.Invariant(this.NewSaltLength > 0); - } -#endif - } -} diff --git a/src/DotNetOpenAuth/OpenId/Provider/ProviderAssociationHandleEncoder.cs b/src/DotNetOpenAuth/OpenId/Provider/ProviderAssociationHandleEncoder.cs deleted file mode 100644 index 5de560c..0000000 --- a/src/DotNetOpenAuth/OpenId/Provider/ProviderAssociationHandleEncoder.cs +++ /dev/null @@ -1,84 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="ProviderAssociationHandleEncoder.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OpenId.Provider { - using System; - using System.Diagnostics.Contracts; - using System.Threading; - using DotNetOpenAuth.Configuration; - using DotNetOpenAuth.Messaging; - using DotNetOpenAuth.Messaging.Bindings; - - /// <summary> - /// Provides association storage in the association handle itself, but embedding signed and encrypted association - /// details in the handle. - /// </summary> - public class ProviderAssociationHandleEncoder : IProviderAssociationStore { - /// <summary> - /// The name of the bucket in which to store keys that encrypt association data into association handles. - /// </summary> - internal const string AssociationHandleEncodingSecretBucket = "https://localhost/dnoa/association_handles"; - - /// <summary> - /// The crypto key store used to persist encryption keys. - /// </summary> - private readonly ICryptoKeyStore cryptoKeyStore; - - /// <summary> - /// Initializes a new instance of the <see cref="ProviderAssociationHandleEncoder"/> class. - /// </summary> - /// <param name="cryptoKeyStore">The crypto key store.</param> - public ProviderAssociationHandleEncoder(ICryptoKeyStore cryptoKeyStore) { - Contract.Requires<ArgumentNullException>(cryptoKeyStore != null); - this.cryptoKeyStore = cryptoKeyStore; - } - - /// <summary> - /// Encodes the specified association data bag. - /// </summary> - /// <param name="secret">The symmetric secret.</param> - /// <param name="expiresUtc">The UTC time that the association should expire.</param> - /// <param name="privateAssociation">A value indicating whether this is a private association.</param> - /// <returns> - /// The association handle that represents this association. - /// </returns> - public string Serialize(byte[] secret, DateTime expiresUtc, bool privateAssociation) { - var associationDataBag = new AssociationDataBag { - Secret = secret, - IsPrivateAssociation = privateAssociation, - ExpiresUtc = expiresUtc, - }; - - var formatter = AssociationDataBag.CreateFormatter(this.cryptoKeyStore, AssociationHandleEncodingSecretBucket, expiresUtc - DateTime.UtcNow); - return formatter.Serialize(associationDataBag); - } - - /// <summary> - /// Retrieves an association given an association handle. - /// </summary> - /// <param name="containingMessage">The OpenID message that referenced this association handle.</param> - /// <param name="privateAssociation">A value indicating whether a private association is expected.</param> - /// <param name="handle">The association handle.</param> - /// <returns> - /// An association instance, or <c>null</c> if the association has expired or the signature is incorrect (which may be because the OP's symmetric key has changed). - /// </returns> - /// <exception cref="ProtocolException">Thrown if the association is not of the expected type.</exception> - public Association Deserialize(IProtocolMessage containingMessage, bool privateAssociation, string handle) { - var formatter = AssociationDataBag.CreateFormatter(this.cryptoKeyStore, AssociationHandleEncodingSecretBucket); - AssociationDataBag bag; - try { - bag = formatter.Deserialize(containingMessage, handle); - } catch (ProtocolException ex) { - Logger.OpenId.Error("Rejecting an association because deserialization of the encoded handle failed.", ex); - return null; - } - - ErrorUtilities.VerifyProtocol(bag.IsPrivateAssociation == privateAssociation, "Unexpected association type."); - Association assoc = Association.Deserialize(handle, bag.ExpiresUtc, bag.Secret); - return assoc.IsExpired ? null : assoc; - } - } -} diff --git a/src/DotNetOpenAuth/OpenId/Provider/ProviderAssociationKeyStorage.cs b/src/DotNetOpenAuth/OpenId/Provider/ProviderAssociationKeyStorage.cs deleted file mode 100644 index 9801aac..0000000 --- a/src/DotNetOpenAuth/OpenId/Provider/ProviderAssociationKeyStorage.cs +++ /dev/null @@ -1,79 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="ProviderAssociationKeyStorage.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OpenId.Provider { - using System; - using System.Diagnostics.Contracts; - using DotNetOpenAuth.Messaging; - using DotNetOpenAuth.Messaging.Bindings; - - /// <summary> - /// An association storage mechanism that stores the association secrets in a private store, - /// and returns randomly generated association handles to refer to these secrets. - /// </summary> - internal class ProviderAssociationKeyStorage : IProviderAssociationStore { - /// <summary> - /// The bucket to use when recording shared associations. - /// </summary> - internal const string SharedAssociationBucket = "https://localhost/dnoa/shared_associations"; - - /// <summary> - /// The bucket to use when recording private associations. - /// </summary> - internal const string PrivateAssociationBucket = "https://localhost/dnoa/private_associations"; - - /// <summary> - /// The backing crypto key store. - /// </summary> - private readonly ICryptoKeyStore cryptoKeyStore; - - /// <summary> - /// Initializes a new instance of the <see cref="ProviderAssociationKeyStorage"/> class. - /// </summary> - /// <param name="cryptoKeyStore">The store where association secrets will be recorded.</param> - internal ProviderAssociationKeyStorage(ICryptoKeyStore cryptoKeyStore) { - Contract.Requires<ArgumentNullException>(cryptoKeyStore != null); - this.cryptoKeyStore = cryptoKeyStore; - } - - /// <summary> - /// Stores an association and returns a handle for it. - /// </summary> - /// <param name="secret">The association secret.</param> - /// <param name="expiresUtc">The UTC time that the association should expire.</param> - /// <param name="privateAssociation">A value indicating whether this is a private association.</param> - /// <returns> - /// The association handle that represents this association. - /// </returns> - public string Serialize(byte[] secret, DateTime expiresUtc, bool privateAssociation) { - string handle; - this.cryptoKeyStore.StoreKey( - privateAssociation ? PrivateAssociationBucket : SharedAssociationBucket, - handle = OpenIdUtilities.GenerateRandomAssociationHandle(), - new CryptoKey(secret, expiresUtc)); - return handle; - } - - /// <summary> - /// Retrieves an association given an association handle. - /// </summary> - /// <param name="containingMessage">The OpenID message that referenced this association handle.</param> - /// <param name="isPrivateAssociation">A value indicating whether a private association is expected.</param> - /// <param name="handle">The association handle.</param> - /// <returns> - /// An association instance, or <c>null</c> if the association has expired or the signature is incorrect (which may be because the OP's symmetric key has changed). - /// </returns> - /// <exception cref="ProtocolException">Thrown if the association is not of the expected type.</exception> - public Association Deserialize(IProtocolMessage containingMessage, bool isPrivateAssociation, string handle) { - var key = this.cryptoKeyStore.GetKey(isPrivateAssociation ? PrivateAssociationBucket : SharedAssociationBucket, handle); - if (key != null) { - return Association.Deserialize(handle, key.ExpiresUtc, key.Key); - } - - return null; - } - } -} diff --git a/src/DotNetOpenAuth/OpenId/Provider/ProviderEndpoint.cs b/src/DotNetOpenAuth/OpenId/Provider/ProviderEndpoint.cs deleted file mode 100644 index 4a90843..0000000 --- a/src/DotNetOpenAuth/OpenId/Provider/ProviderEndpoint.cs +++ /dev/null @@ -1,267 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="ProviderEndpoint.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OpenId.Provider { - using System; - using System.Collections.Generic; - using System.ComponentModel; - using System.Diagnostics.Contracts; - using System.Text; - using System.Web; - using System.Web.UI; - using System.Web.UI.WebControls; - using DotNetOpenAuth.Configuration; - using DotNetOpenAuth.Messaging; - using DotNetOpenAuth.OpenId.Messages; - - /// <summary> - /// An OpenID Provider control that automatically responds to certain - /// automated OpenID messages, and routes authentication requests to - /// custom code via an event handler. - /// </summary> - [DefaultEvent("AuthenticationChallenge")] - [ToolboxData("<{0}:ProviderEndpoint runat='server' />")] - public class ProviderEndpoint : Control { - /// <summary> - /// The key used to store the pending authentication request in the ASP.NET session. - /// </summary> - private const string PendingRequestKey = "pendingRequest"; - - /// <summary> - /// The default value for the <see cref="Enabled"/> property. - /// </summary> - private const bool EnabledDefault = true; - - /// <summary> - /// The view state key in which to store the value of the <see cref="Enabled"/> property. - /// </summary> - private const string EnabledViewStateKey = "Enabled"; - - /// <summary> - /// Backing field for the <see cref="Provider"/> property. - /// </summary> - private static OpenIdProvider provider; - - /// <summary> - /// The lock that must be obtained when initializing the provider field. - /// </summary> - private static object providerInitializerLock = new object(); - - /// <summary> - /// Fired when an incoming OpenID request is an authentication challenge - /// that must be responded to by the Provider web site according to its - /// own user database and policies. - /// </summary> - public event EventHandler<AuthenticationChallengeEventArgs> AuthenticationChallenge; - - /// <summary> - /// Fired when an incoming OpenID message carries extension requests - /// but is not regarding any OpenID identifier. - /// </summary> - public event EventHandler<AnonymousRequestEventArgs> AnonymousRequest; - - /// <summary> - /// Gets or sets the <see cref="OpenIdProvider"/> instance to use for all instances of this control. - /// </summary> - /// <value>The default value is an <see cref="OpenIdProvider"/> instance initialized according to the web.config file.</value> - public static OpenIdProvider Provider { - get { - Contract.Ensures(Contract.Result<OpenIdProvider>() != null); - if (provider == null) { - lock (providerInitializerLock) { - if (provider == null) { - provider = CreateProvider(); - } - } - } - - return provider; - } - - set { - Contract.Requires<ArgumentNullException>(value != null); - provider = value; - } - } - - /// <summary> - /// Gets or sets an incoming OpenID authentication request that has not yet been responded to. - /// </summary> - /// <remarks> - /// This request is stored in the ASP.NET Session state, so it will survive across - /// redirects, postbacks, and transfers. This allows you to authenticate the user - /// yourself, and confirm his/her desire to authenticate to the relying party site - /// before responding to the relying party's authentication request. - /// </remarks> - public static IAuthenticationRequest PendingAuthenticationRequest { - get { - Contract.Requires<InvalidOperationException>(HttpContext.Current != null, MessagingStrings.HttpContextRequired); - Contract.Requires<InvalidOperationException>(HttpContext.Current.Session != null, MessagingStrings.SessionRequired); - Contract.Ensures(Contract.Result<IAuthenticationRequest>() == null || PendingRequest != null); - return HttpContext.Current.Session[PendingRequestKey] as IAuthenticationRequest; - } - - set { - Contract.Requires<InvalidOperationException>(HttpContext.Current != null, MessagingStrings.HttpContextRequired); - Contract.Requires<InvalidOperationException>(HttpContext.Current.Session != null, MessagingStrings.SessionRequired); - HttpContext.Current.Session[PendingRequestKey] = value; - } - } - - /// <summary> - /// Gets or sets an incoming OpenID anonymous request that has not yet been responded to. - /// </summary> - /// <remarks> - /// This request is stored in the ASP.NET Session state, so it will survive across - /// redirects, postbacks, and transfers. This allows you to authenticate the user - /// yourself, and confirm his/her desire to provide data to the relying party site - /// before responding to the relying party's request. - /// </remarks> - public static IAnonymousRequest PendingAnonymousRequest { - get { - Contract.Requires<InvalidOperationException>(HttpContext.Current != null, MessagingStrings.HttpContextRequired); - Contract.Requires<InvalidOperationException>(HttpContext.Current.Session != null, MessagingStrings.SessionRequired); - Contract.Ensures(Contract.Result<IAnonymousRequest>() == null || PendingRequest != null); - return HttpContext.Current.Session[PendingRequestKey] as IAnonymousRequest; - } - - set { - Contract.Requires<InvalidOperationException>(HttpContext.Current != null, MessagingStrings.HttpContextRequired); - Contract.Requires<InvalidOperationException>(HttpContext.Current.Session != null, MessagingStrings.SessionRequired); - HttpContext.Current.Session[PendingRequestKey] = value; - } - } - - /// <summary> - /// Gets or sets an incoming OpenID request that has not yet been responded to. - /// </summary> - /// <remarks> - /// This request is stored in the ASP.NET Session state, so it will survive across - /// redirects, postbacks, and transfers. This allows you to authenticate the user - /// yourself, and confirm his/her desire to provide data to the relying party site - /// before responding to the relying party's request. - /// </remarks> - public static IHostProcessedRequest PendingRequest { - get { - Contract.Requires<InvalidOperationException>(HttpContext.Current != null, MessagingStrings.HttpContextRequired); - Contract.Requires<InvalidOperationException>(HttpContext.Current.Session != null, MessagingStrings.SessionRequired); - return HttpContext.Current.Session[PendingRequestKey] as IHostProcessedRequest; - } - - set { - Contract.Requires<InvalidOperationException>(HttpContext.Current != null, MessagingStrings.HttpContextRequired); - Contract.Requires<InvalidOperationException>(HttpContext.Current.Session != null, MessagingStrings.SessionRequired); - HttpContext.Current.Session[PendingRequestKey] = value; - } - } - - /// <summary> - /// Gets or sets a value indicating whether or not this control should - /// be listening for and responding to incoming OpenID requests. - /// </summary> - [Category("Behavior"), DefaultValue(EnabledDefault)] - public bool Enabled { - get { - return ViewState[EnabledViewStateKey] == null ? - EnabledDefault : (bool)ViewState[EnabledViewStateKey]; - } - - set { - ViewState[EnabledViewStateKey] = value; - } - } - - /// <summary> - /// Sends the response for the <see cref="PendingAuthenticationRequest"/> and clears the property. - /// </summary> - public static void SendResponse() { - var pendingRequest = PendingRequest; - PendingRequest = null; - Provider.SendResponse(pendingRequest); - } - - /// <summary> - /// Checks for incoming OpenID requests, responds to ones it can - /// respond to without policy checks, and fires events for custom - /// handling of the ones it cannot decide on automatically. - /// </summary> - /// <param name="e">The <see cref="T:System.EventArgs"/> object that contains the event data.</param> - protected override void OnLoad(EventArgs e) { - base.OnLoad(e); - - // There is the unusual scenario that this control is hosted by - // an ASP.NET web page that has other UI on it to that the user - // might see, including controls that cause a postback to occur. - // We definitely want to ignore postbacks, since any openid messages - // they contain will be old. - if (this.Enabled && !this.Page.IsPostBack) { - // Use the explicitly given state store on this control if there is one. - // Then try the configuration file specified one. Finally, use the default - // in-memory one that's built into OpenIdProvider. - // determine what incoming message was received - IRequest request = Provider.GetRequest(); - if (request != null) { - PendingRequest = null; - - // process the incoming message appropriately and send the response - IAuthenticationRequest idrequest; - IAnonymousRequest anonRequest; - if ((idrequest = request as IAuthenticationRequest) != null) { - PendingAuthenticationRequest = idrequest; - this.OnAuthenticationChallenge(idrequest); - } else if ((anonRequest = request as IAnonymousRequest) != null) { - PendingAnonymousRequest = anonRequest; - if (!this.OnAnonymousRequest(anonRequest)) { - // This is a feature not supported by the OP, so - // go ahead and set disapproved so we can send a response. - Logger.OpenId.Warn("An incoming anonymous OpenID request message was detected, but the ProviderEndpoint.AnonymousRequest event is not handled, so returning cancellation message to relying party."); - anonRequest.IsApproved = false; - } - } - if (request.IsResponseReady) { - PendingAuthenticationRequest = null; - Provider.SendResponse(request); - } - } - } - } - - /// <summary> - /// Fires the <see cref="AuthenticationChallenge"/> event. - /// </summary> - /// <param name="request">The request to include in the event args.</param> - protected virtual void OnAuthenticationChallenge(IAuthenticationRequest request) { - var authenticationChallenge = this.AuthenticationChallenge; - if (authenticationChallenge != null) { - authenticationChallenge(this, new AuthenticationChallengeEventArgs(request)); - } - } - - /// <summary> - /// Fires the <see cref="AnonymousRequest"/> event. - /// </summary> - /// <param name="request">The request to include in the event args.</param> - /// <returns><c>true</c> if there were any anonymous request handlers.</returns> - protected virtual bool OnAnonymousRequest(IAnonymousRequest request) { - var anonymousRequest = this.AnonymousRequest; - if (anonymousRequest != null) { - anonymousRequest(this, new AnonymousRequestEventArgs(request)); - return true; - } else { - return false; - } - } - - /// <summary> - /// Creates the default OpenIdProvider to use. - /// </summary> - /// <returns>The new instance of OpenIdProvider.</returns> - private static OpenIdProvider CreateProvider() { - Contract.Ensures(Contract.Result<OpenIdProvider>() != null); - return new OpenIdProvider(DotNetOpenAuthSection.Configuration.OpenId.Provider.ApplicationStore.CreateInstance(OpenIdProvider.HttpApplicationStore)); - } - } -} diff --git a/src/DotNetOpenAuth/OpenId/Provider/Request.cs b/src/DotNetOpenAuth/OpenId/Provider/Request.cs deleted file mode 100644 index 4e54ef9..0000000 --- a/src/DotNetOpenAuth/OpenId/Provider/Request.cs +++ /dev/null @@ -1,210 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="Request.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OpenId.Provider { - using System; - using System.Collections.Generic; - using System.Diagnostics.Contracts; - using System.Linq; - using System.Text; - using DotNetOpenAuth.Messaging; - using DotNetOpenAuth.OpenId.Messages; - - /// <summary> - /// Implements the <see cref="IRequest"/> interface for all incoming - /// request messages to an OpenID Provider. - /// </summary> - [Serializable] - [ContractClass(typeof(RequestContract))] - [ContractVerification(true)] - internal abstract class Request : IRequest { - /// <summary> - /// The incoming request message. - /// </summary> - private readonly IDirectedProtocolMessage request; - - /// <summary> - /// The incoming request message cast to its extensible form. - /// Or null if the message does not support extensions. - /// </summary> - private readonly IProtocolMessageWithExtensions extensibleMessage; - - /// <summary> - /// The version of the OpenID protocol to use. - /// </summary> - private readonly Version protocolVersion; - - /// <summary> - /// Backing store for the <see cref="Protocol"/> property. - /// </summary> - [NonSerialized] - private Protocol protocol; - - /// <summary> - /// The list of extensions to add to the response message. - /// </summary> - private List<IOpenIdMessageExtension> responseExtensions = new List<IOpenIdMessageExtension>(); - - /// <summary> - /// Initializes a new instance of the <see cref="Request"/> class. - /// </summary> - /// <param name="request">The incoming request message.</param> - /// <param name="securitySettings">The security settings from the channel.</param> - protected Request(IDirectedProtocolMessage request, ProviderSecuritySettings securitySettings) { - Contract.Requires<ArgumentNullException>(request != null); - Contract.Requires<ArgumentNullException>(securitySettings != null); - - this.request = request; - this.SecuritySettings = securitySettings; - this.protocolVersion = this.request.Version; - this.extensibleMessage = request as IProtocolMessageWithExtensions; - } - - /// <summary> - /// Initializes a new instance of the <see cref="Request"/> class. - /// </summary> - /// <param name="version">The version.</param> - /// <param name="securitySettings">The security settings.</param> - protected Request(Version version, ProviderSecuritySettings securitySettings) { - Contract.Requires<ArgumentNullException>(version != null); - Contract.Requires<ArgumentNullException>(securitySettings != null); - - this.protocolVersion = version; - this.SecuritySettings = securitySettings; - } - - #region IRequest Properties - - /// <summary> - /// Gets a value indicating whether the response is ready to be sent to the user agent. - /// </summary> - /// <remarks> - /// This property returns false if there are properties that must be set on this - /// request instance before the response can be sent. - /// </remarks> - public abstract bool IsResponseReady { get; } - - /// <summary> - /// Gets or sets the security settings that apply to this request. - /// </summary> - /// <value>Defaults to the <see cref="OpenIdProvider.SecuritySettings"/> on the <see cref="OpenIdProvider"/>.</value> - public ProviderSecuritySettings SecuritySettings { get; set; } - - /// <summary> - /// Gets the response to send to the user agent. - /// </summary> - /// <exception cref="InvalidOperationException">Thrown if <see cref="IsResponseReady"/> is <c>false</c>.</exception> - internal IProtocolMessage Response { - get { - Contract.Requires<InvalidOperationException>(this.IsResponseReady, OpenIdStrings.ResponseNotReady); - Contract.Ensures(Contract.Result<IProtocolMessage>() != null); - - if (this.responseExtensions.Count > 0) { - var extensibleResponse = this.ResponseMessage as IProtocolMessageWithExtensions; - ErrorUtilities.VerifyOperation(extensibleResponse != null, MessagingStrings.MessageNotExtensible, this.ResponseMessage.GetType().Name); - foreach (var extension in this.responseExtensions) { - // It's possible that a prior call to this property - // has already added some/all of the extensions to the message. - // We don't have to worry about deleting old ones because - // this class provides no facility for removing extensions - // that are previously added. - if (!extensibleResponse.Extensions.Contains(extension)) { - extensibleResponse.Extensions.Add(extension); - } - } - } - - return this.ResponseMessage; - } - } - - #endregion - - /// <summary> - /// Gets the original request message. - /// </summary> - /// <value>This may be null in the case of an unrecognizable message.</value> - protected internal IDirectedProtocolMessage RequestMessage { - get { return this.request; } - } - - /// <summary> - /// Gets the response message, once <see cref="IsResponseReady"/> is <c>true</c>. - /// </summary> - protected abstract IProtocolMessage ResponseMessage { get; } - - /// <summary> - /// Gets the protocol version used in the request. - /// </summary> - protected Protocol Protocol { - get { - if (this.protocol == null) { - this.protocol = Protocol.Lookup(this.protocolVersion); - } - - return this.protocol; - } - } - - #region IRequest Methods - - /// <summary> - /// Adds an extension to the response to send to the relying party. - /// </summary> - /// <param name="extension">The extension to add to the response message.</param> - public void AddResponseExtension(IOpenIdMessageExtension extension) { - // Because the derived AuthenticationRequest class can swap out - // one response message for another (auth vs. no-auth), and because - // some response messages support extensions while others don't, - // we just add the extensions to a collection here and add them - // to the response on the way out. - this.responseExtensions.Add(extension); - } - - /// <summary> - /// Removes any response extensions previously added using <see cref="AddResponseExtension"/>. - /// </summary> - /// <remarks> - /// This should be called before sending a negative response back to the relying party - /// if extensions were already added, since negative responses cannot carry extensions. - /// </remarks> - public void ClearResponseExtensions() { - this.responseExtensions.Clear(); - } - - /// <summary> - /// Gets an extension sent from the relying party. - /// </summary> - /// <typeparam name="T">The type of the extension.</typeparam> - /// <returns> - /// An instance of the extension initialized with values passed in with the request. - /// </returns> - public T GetExtension<T>() where T : IOpenIdMessageExtension, new() { - if (this.extensibleMessage != null) { - return this.extensibleMessage.Extensions.OfType<T>().SingleOrDefault(); - } else { - return default(T); - } - } - - /// <summary> - /// Gets an extension sent from the relying party. - /// </summary> - /// <param name="extensionType">The type of the extension.</param> - /// <returns> - /// An instance of the extension initialized with values passed in with the request. - /// </returns> - public IOpenIdMessageExtension GetExtension(Type extensionType) { - if (this.extensibleMessage != null) { - return this.extensibleMessage.Extensions.OfType<IOpenIdMessageExtension>().Where(ext => extensionType.IsInstanceOfType(ext)).SingleOrDefault(); - } else { - return null; - } - } - - #endregion - } -} diff --git a/src/DotNetOpenAuth/OpenId/Provider/RequestContract.cs b/src/DotNetOpenAuth/OpenId/Provider/RequestContract.cs deleted file mode 100644 index dee140e..0000000 --- a/src/DotNetOpenAuth/OpenId/Provider/RequestContract.cs +++ /dev/null @@ -1,48 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="RequestContract.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OpenId.Provider { - using System; - using System.Collections.Generic; - using System.Diagnostics.Contracts; - using System.Linq; - using System.Text; - using DotNetOpenAuth.Messaging; - - /// <summary> - /// Code contract for the <see cref="Request"/> class. - /// </summary> - [ContractClassFor(typeof(Request))] - internal abstract class RequestContract : Request { - /// <summary> - /// Prevents a default instance of the <see cref="RequestContract"/> class from being created. - /// </summary> - private RequestContract() : base((Version)null, null) { - } - - /// <summary> - /// Gets a value indicating whether the response is ready to be sent to the user agent. - /// </summary> - /// <remarks> - /// This property returns false if there are properties that must be set on this - /// request instance before the response can be sent. - /// </remarks> - public override bool IsResponseReady { - get { throw new NotImplementedException(); } - } - - /// <summary> - /// Gets the response message, once <see cref="IsResponseReady"/> is <c>true</c>. - /// </summary> - protected override IProtocolMessage ResponseMessage { - get { - Contract.Requires<InvalidOperationException>(this.IsResponseReady); - Contract.Ensures(Contract.Result<IProtocolMessage>() != null); - throw new NotImplementedException(); - } - } - } -} diff --git a/src/DotNetOpenAuth/OpenId/Provider/StandardProviderApplicationStore.cs b/src/DotNetOpenAuth/OpenId/Provider/StandardProviderApplicationStore.cs deleted file mode 100644 index c13c4bc..0000000 --- a/src/DotNetOpenAuth/OpenId/Provider/StandardProviderApplicationStore.cs +++ /dev/null @@ -1,117 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="StandardProviderApplicationStore.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OpenId.Provider { - using System; - using System.Collections.Generic; - using DotNetOpenAuth.Configuration; - using DotNetOpenAuth.Messaging.Bindings; - - /// <summary> - /// An in-memory store for Providers, suitable for single server, single process - /// ASP.NET web sites. - /// </summary> - /// <remarks> - /// This class provides only a basic implementation that is likely to work - /// out of the box on most single-server web sites. It is highly recommended - /// that high traffic web sites consider using a database to store the information - /// used by an OpenID Provider and write a custom implementation of the - /// <see cref="IOpenIdApplicationStore"/> interface to use instead of this - /// class. - /// </remarks> - public class StandardProviderApplicationStore : IOpenIdApplicationStore { - /// <summary> - /// The nonce store to use. - /// </summary> - private readonly INonceStore nonceStore; - - /// <summary> - /// The crypto key store where symmetric keys are persisted. - /// </summary> - private readonly ICryptoKeyStore cryptoKeyStore; - - /// <summary> - /// Initializes a new instance of the <see cref="StandardProviderApplicationStore"/> class. - /// </summary> - public StandardProviderApplicationStore() { - this.nonceStore = new NonceMemoryStore(DotNetOpenAuthSection.Configuration.OpenId.MaxAuthenticationTime); - this.cryptoKeyStore = new MemoryCryptoKeyStore(); - } - - #region INonceStore Members - - /// <summary> - /// Stores a given nonce and timestamp. - /// </summary> - /// <param name="context">The context, or namespace, within which the <paramref name="nonce"/> must be unique.</param> - /// <param name="nonce">A series of random characters.</param> - /// <param name="timestampUtc">The timestamp that together with the nonce string make it unique. - /// The timestamp may also be used by the data store to clear out old nonces.</param> - /// <returns> - /// True if the nonce+timestamp (combination) was not previously in the database. - /// False if the nonce was stored previously with the same timestamp. - /// </returns> - /// <remarks> - /// The nonce must be stored for no less than the maximum time window a message may - /// be processed within before being discarded as an expired message. - /// If the binding element is applicable to your channel, this expiration window - /// is retrieved or set using the - /// <see cref="StandardExpirationBindingElement.MaximumMessageAge"/> property. - /// </remarks> - public bool StoreNonce(string context, string nonce, DateTime timestampUtc) { - return this.nonceStore.StoreNonce(context, nonce, timestampUtc); - } - - #endregion - - #region ICryptoKeyStore - - /// <summary> - /// Gets the key in a given bucket and handle. - /// </summary> - /// <param name="bucket">The bucket name. Case sensitive.</param> - /// <param name="handle">The key handle. Case sensitive.</param> - /// <returns> - /// The cryptographic key, or <c>null</c> if no matching key was found. - /// </returns> - public CryptoKey GetKey(string bucket, string handle) { - return this.cryptoKeyStore.GetKey(bucket, handle); - } - - /// <summary> - /// Gets a sequence of existing keys within a given bucket. - /// </summary> - /// <param name="bucket">The bucket name. Case sensitive.</param> - /// <returns> - /// A sequence of handles and keys, ordered by descending <see cref="CryptoKey.ExpiresUtc"/>. - /// </returns> - public IEnumerable<KeyValuePair<string, CryptoKey>> GetKeys(string bucket) { - return this.cryptoKeyStore.GetKeys(bucket); - } - - /// <summary> - /// Stores a cryptographic key. - /// </summary> - /// <param name="bucket">The name of the bucket to store the key in. Case sensitive.</param> - /// <param name="handle">The handle to the key, unique within the bucket. Case sensitive.</param> - /// <param name="key">The key to store.</param> - /// <exception cref="CryptoKeyCollisionException">Thrown in the event of a conflict with an existing key in the same bucket and with the same handle.</exception> - public void StoreKey(string bucket, string handle, CryptoKey key) { - this.cryptoKeyStore.StoreKey(bucket, handle, key); - } - - /// <summary> - /// Removes the key. - /// </summary> - /// <param name="bucket">The bucket name. Case sensitive.</param> - /// <param name="handle">The key handle. Case sensitive.</param> - public void RemoveKey(string bucket, string handle) { - this.cryptoKeyStore.RemoveKey(bucket, handle); - } - - #endregion - } -} diff --git a/src/DotNetOpenAuth/OpenId/ProviderEndpointDescription.cs b/src/DotNetOpenAuth/OpenId/ProviderEndpointDescription.cs deleted file mode 100644 index 6514ffd..0000000 --- a/src/DotNetOpenAuth/OpenId/ProviderEndpointDescription.cs +++ /dev/null @@ -1,134 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="ProviderEndpointDescription.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OpenId { - using System; - using System.Collections.Generic; - using System.Collections.ObjectModel; - using System.Diagnostics.CodeAnalysis; - using System.Diagnostics.Contracts; - using System.Linq; - using DotNetOpenAuth.Messaging; - using DotNetOpenAuth.OpenId.Messages; - using DotNetOpenAuth.OpenId.RelyingParty; - - /// <summary> - /// Describes some OpenID Provider endpoint and its capabilities. - /// </summary> - /// <remarks> - /// This is an immutable type. - /// </remarks> - [Serializable] - internal sealed class ProviderEndpointDescription : IProviderEndpoint { - /// <summary> - /// Initializes a new instance of the <see cref="ProviderEndpointDescription"/> class. - /// </summary> - /// <param name="providerEndpoint">The OpenID Provider endpoint URL.</param> - /// <param name="openIdVersion">The OpenID version supported by this particular endpoint.</param> - internal ProviderEndpointDescription(Uri providerEndpoint, Version openIdVersion) { - Contract.Requires<ArgumentNullException>(providerEndpoint != null); - Contract.Requires<ArgumentNullException>(openIdVersion != null); - - this.Uri = providerEndpoint; - this.Version = openIdVersion; - this.Capabilities = new ReadOnlyCollection<string>(EmptyList<string>.Instance); - } - - /// <summary> - /// Initializes a new instance of the <see cref="ProviderEndpointDescription"/> class. - /// </summary> - /// <param name="providerEndpoint">The URI the provider listens on for OpenID requests.</param> - /// <param name="serviceTypeURIs">The set of services offered by this endpoint.</param> - internal ProviderEndpointDescription(Uri providerEndpoint, IEnumerable<string> serviceTypeURIs) { - Contract.Requires<ArgumentNullException>(providerEndpoint != null); - Contract.Requires<ArgumentNullException>(serviceTypeURIs != null); - - this.Uri = providerEndpoint; - this.Capabilities = new ReadOnlyCollection<string>(serviceTypeURIs.ToList()); - - Protocol opIdentifierProtocol = Protocol.FindBestVersion(p => p.OPIdentifierServiceTypeURI, serviceTypeURIs); - Protocol claimedIdentifierProviderVersion = Protocol.FindBestVersion(p => p.ClaimedIdentifierServiceTypeURI, serviceTypeURIs); - if (opIdentifierProtocol != null) { - this.Version = opIdentifierProtocol.Version; - } else if (claimedIdentifierProviderVersion != null) { - this.Version = claimedIdentifierProviderVersion.Version; - } else { - ErrorUtilities.ThrowProtocol(OpenIdStrings.ProviderVersionUnrecognized, this.Uri); - } - } - - /// <summary> - /// Gets the URL that the OpenID Provider listens for incoming OpenID messages on. - /// </summary> - public Uri Uri { get; private set; } - - /// <summary> - /// Gets the OpenID protocol version this endpoint supports. - /// </summary> - /// <remarks> - /// If an endpoint supports multiple versions, each version must be represented - /// by its own <see cref="ProviderEndpointDescription"/> object. - /// </remarks> - public Version Version { get; private set; } - - /// <summary> - /// Gets the collection of service type URIs found in the XRDS document describing this Provider. - /// </summary> - internal ReadOnlyCollection<string> Capabilities { get; private set; } - - #region IProviderEndpoint Members - - /// <summary> - /// Checks whether the OpenId Identifier claims support for a given extension. - /// </summary> - /// <typeparam name="T">The extension whose support is being queried.</typeparam> - /// <returns> - /// True if support for the extension is advertised. False otherwise. - /// </returns> - /// <remarks> - /// Note that a true or false return value is no guarantee of a Provider's - /// support for or lack of support for an extension. The return value is - /// determined by how the authenticating user filled out his/her XRDS document only. - /// The only way to be sure of support for a given extension is to include - /// the extension in the request and see if a response comes back for that extension. - /// </remarks> - bool IProviderEndpoint.IsExtensionSupported<T>() { - throw new NotImplementedException(); - } - - /// <summary> - /// Checks whether the OpenId Identifier claims support for a given extension. - /// </summary> - /// <param name="extensionType">The extension whose support is being queried.</param> - /// <returns> - /// True if support for the extension is advertised. False otherwise. - /// </returns> - /// <remarks> - /// Note that a true or false return value is no guarantee of a Provider's - /// support for or lack of support for an extension. The return value is - /// determined by how the authenticating user filled out his/her XRDS document only. - /// The only way to be sure of support for a given extension is to include - /// the extension in the request and see if a response comes back for that extension. - /// </remarks> - bool IProviderEndpoint.IsExtensionSupported(Type extensionType) { - throw new NotImplementedException(); - } - - #endregion - -#if CONTRACTS_FULL - /// <summary> - /// Verifies conditions that should be true for any valid state of this object. - /// </summary> - [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Called by code contracts.")] - [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called by code contracts.")] - [ContractInvariantMethod] - private void ObjectInvariant() { - Contract.Invariant(this.Capabilities != null); - } -#endif - } -} diff --git a/src/DotNetOpenAuth/OpenId/Realm.cs b/src/DotNetOpenAuth/OpenId/Realm.cs deleted file mode 100644 index 98e3598..0000000 --- a/src/DotNetOpenAuth/OpenId/Realm.cs +++ /dev/null @@ -1,500 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="Realm.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OpenId { - using System; - using System.Collections.Generic; - using System.Diagnostics; - using System.Diagnostics.CodeAnalysis; - using System.Diagnostics.Contracts; - using System.Globalization; - using System.Linq; - using System.Text.RegularExpressions; - using System.Web; - using System.Xml; - using DotNetOpenAuth.Messaging; - using DotNetOpenAuth.Xrds; - using DotNetOpenAuth.Yadis; - - /// <summary> - /// A trust root to validate requests and match return URLs against. - /// </summary> - /// <remarks> - /// This fills the OpenID Authentication 2.0 specification for realms. - /// See http://openid.net/specs/openid-authentication-2_0.html#realms - /// </remarks> - [Serializable] - [Pure] - public class Realm { - /// <summary> - /// A regex used to detect a wildcard that is being used in the realm. - /// </summary> - private const string WildcardDetectionPattern = @"^(\w+://)\*\."; - - /// <summary> - /// A (more or less) comprehensive list of top-level (i.e. ".com") domains, - /// for use by <see cref="IsSane"/> in order to disallow overly-broad realms - /// that allow all web sites ending with '.com', for example. - /// </summary> - private static readonly string[] topLevelDomains = { "com", "edu", "gov", "int", "mil", "net", "org", "biz", "info", "name", "museum", "coop", "aero", "ac", "ad", "ae", - "af", "ag", "ai", "al", "am", "an", "ao", "aq", "ar", "as", "at", "au", "aw", "az", "ba", "bb", "bd", "be", "bf", "bg", "bh", "bi", "bj", - "bm", "bn", "bo", "br", "bs", "bt", "bv", "bw", "by", "bz", "ca", "cc", "cd", "cf", "cg", "ch", "ci", "ck", "cl", "cm", "cn", "co", "cr", - "cu", "cv", "cx", "cy", "cz", "de", "dj", "dk", "dm", "do", "dz", "ec", "ee", "eg", "eh", "er", "es", "et", "fi", "fj", "fk", "fm", "fo", - "fr", "ga", "gd", "ge", "gf", "gg", "gh", "gi", "gl", "gm", "gn", "gp", "gq", "gr", "gs", "gt", "gu", "gw", "gy", "hk", "hm", "hn", "hr", - "ht", "hu", "id", "ie", "il", "im", "in", "io", "iq", "ir", "is", "it", "je", "jm", "jo", "jp", "ke", "kg", "kh", "ki", "km", "kn", "kp", - "kr", "kw", "ky", "kz", "la", "lb", "lc", "li", "lk", "lr", "ls", "lt", "lu", "lv", "ly", "ma", "mc", "md", "mg", "mh", "mk", "ml", "mm", - "mn", "mo", "mp", "mq", "mr", "ms", "mt", "mu", "mv", "mw", "mx", "my", "mz", "na", "nc", "ne", "nf", "ng", "ni", "nl", "no", "np", "nr", - "nu", "nz", "om", "pa", "pe", "pf", "pg", "ph", "pk", "pl", "pm", "pn", "pr", "ps", "pt", "pw", "py", "qa", "re", "ro", "ru", "rw", "sa", - "sb", "sc", "sd", "se", "sg", "sh", "si", "sj", "sk", "sl", "sm", "sn", "so", "sr", "st", "sv", "sy", "sz", "tc", "td", "tf", "tg", "th", - "tj", "tk", "tm", "tn", "to", "tp", "tr", "tt", "tv", "tw", "tz", "ua", "ug", "uk", "um", "us", "uy", "uz", "va", "vc", "ve", "vg", "vi", - "vn", "vu", "wf", "ws", "ye", "yt", "yu", "za", "zm", "zw" }; - - /// <summary> - /// The Uri of the realm, with the wildcard (if any) removed. - /// </summary> - private Uri uri; - - /// <summary> - /// Initializes a new instance of the <see cref="Realm"/> class. - /// </summary> - /// <param name="realmUrl">The realm URL to use in the new instance.</param> - [SuppressMessage("Microsoft.Design", "CA1057:StringUriOverloadsCallSystemUriOverloads", Justification = "Not all realms are valid URLs (because of wildcards).")] - public Realm(string realmUrl) { - Contract.Requires<ArgumentNullException>(realmUrl != null); // not non-zero check so we throw UriFormatException later - this.DomainWildcard = Regex.IsMatch(realmUrl, WildcardDetectionPattern); - this.uri = new Uri(Regex.Replace(realmUrl, WildcardDetectionPattern, m => m.Groups[1].Value)); - if (!this.uri.Scheme.Equals("http", StringComparison.OrdinalIgnoreCase) && - !this.uri.Scheme.Equals("https", StringComparison.OrdinalIgnoreCase)) { - throw new UriFormatException( - string.Format(CultureInfo.CurrentCulture, OpenIdStrings.InvalidScheme, this.uri.Scheme)); - } - } - - /// <summary> - /// Initializes a new instance of the <see cref="Realm"/> class. - /// </summary> - /// <param name="realmUrl">The realm URL of the Relying Party.</param> - public Realm(Uri realmUrl) { - Contract.Requires<ArgumentNullException>(realmUrl != null); - this.uri = realmUrl; - if (!this.uri.Scheme.Equals("http", StringComparison.OrdinalIgnoreCase) && - !this.uri.Scheme.Equals("https", StringComparison.OrdinalIgnoreCase)) { - throw new UriFormatException( - string.Format(CultureInfo.CurrentCulture, OpenIdStrings.InvalidScheme, this.uri.Scheme)); - } - } - - /// <summary> - /// Initializes a new instance of the <see cref="Realm"/> class. - /// </summary> - /// <param name="realmUriBuilder">The realm URI builder.</param> - /// <remarks> - /// This is useful because UriBuilder can construct a host with a wildcard - /// in the Host property, but once there it can't be converted to a Uri. - /// </remarks> - internal Realm(UriBuilder realmUriBuilder) - : this(SafeUriBuilderToString(realmUriBuilder)) { } - - /// <summary> - /// Gets the suggested realm to use for the calling web application. - /// </summary> - /// <value>A realm that matches this applications root URL.</value> - /// <remarks> - /// <para>For most circumstances the Realm generated by this property is sufficient. - /// However a wildcard Realm, such as "http://*.microsoft.com/" may at times be more - /// desirable than "http://www.microsoft.com/" in order to allow identifier - /// correlation across related web sites for directed identity Providers.</para> - /// <para>Requires an <see cref="HttpContext.Current">HttpContext.Current</see> context.</para> - /// </remarks> - public static Realm AutoDetect { - get { - Contract.Requires<InvalidOperationException>(HttpContext.Current != null && HttpContext.Current.Request != null, MessagingStrings.HttpContextRequired); - Contract.Ensures(Contract.Result<Realm>() != null); - - HttpRequestInfo requestInfo = new HttpRequestInfo(HttpContext.Current.Request); - UriBuilder realmUrl = new UriBuilder(requestInfo.UrlBeforeRewriting); - realmUrl.Path = HttpContext.Current.Request.ApplicationPath; - realmUrl.Query = null; - realmUrl.Fragment = null; - - // For RP discovery, the realm url MUST NOT redirect. To prevent this for - // virtual directory hosted apps, we need to make sure that the realm path ends - // in a slash (since our calculation above guarantees it doesn't end in a specific - // page like default.aspx). - if (!realmUrl.Path.EndsWith("/", StringComparison.Ordinal)) { - realmUrl.Path += "/"; - } - - return realmUrl.Uri; - } - } - - /// <summary> - /// Gets a value indicating whether a '*.' prefix to the hostname is - /// used in the realm to allow subdomains or hosts to be added to the URL. - /// </summary> - public bool DomainWildcard { get; private set; } - - /// <summary> - /// Gets the host component of this instance. - /// </summary> - public string Host { - [DebuggerStepThrough] - get { return this.uri.Host; } - } - - /// <summary> - /// Gets the scheme name for this URI. - /// </summary> - public string Scheme { - [DebuggerStepThrough] - get { return this.uri.Scheme; } - } - - /// <summary> - /// Gets the port number of this URI. - /// </summary> - public int Port { - [DebuggerStepThrough] - get { return this.uri.Port; } - } - - /// <summary> - /// Gets the absolute path of the URI. - /// </summary> - public string AbsolutePath { - [DebuggerStepThrough] - get { return this.uri.AbsolutePath; } - } - - /// <summary> - /// Gets the System.Uri.AbsolutePath and System.Uri.Query properties separated - /// by a question mark (?). - /// </summary> - public string PathAndQuery { - [DebuggerStepThrough] - get { return this.uri.PathAndQuery; } - } - - /// <summary> - /// Gets the original string. - /// </summary> - /// <value>The original string.</value> - internal string OriginalString { - get { return this.uri.OriginalString; } - } - - /// <summary> - /// Gets the realm URL. If the realm includes a wildcard, it is not included here. - /// </summary> - internal Uri NoWildcardUri { - [DebuggerStepThrough] - get { return this.uri; } - } - - /// <summary> - /// Gets the Realm discovery URL, where the wildcard (if present) is replaced with "www.". - /// </summary> - /// <remarks> - /// See OpenID 2.0 spec section 9.2.1 for the explanation on the addition of - /// the "www" prefix. - /// </remarks> - internal Uri UriWithWildcardChangedToWww { - get { - if (this.DomainWildcard) { - UriBuilder builder = new UriBuilder(this.NoWildcardUri); - builder.Host = "www." + builder.Host; - return builder.Uri; - } else { - return this.NoWildcardUri; - } - } - } - - /// <summary> - /// Gets a value indicating whether this realm represents a reasonable (sane) set of URLs. - /// </summary> - /// <remarks> - /// 'http://*.com/', for example is not a reasonable pattern, as it cannot meaningfully - /// specify the site claiming it. This function attempts to find many related examples, - /// but it can only work via heuristics. Negative responses from this method should be - /// treated as advisory, used only to alert the user to examine the trust root carefully. - /// </remarks> - internal bool IsSane { - get { - if (this.Host.Equals("localhost", StringComparison.OrdinalIgnoreCase)) { - return true; - } - - string[] host_parts = this.Host.Split('.'); - - string tld = host_parts[host_parts.Length - 1]; - - if (Array.IndexOf(topLevelDomains, tld) < 0) { - return false; - } - - if (tld.Length == 2) { - if (host_parts.Length == 1) { - return false; - } - - if (host_parts[host_parts.Length - 2].Length <= 3) { - return host_parts.Length > 2; - } - } else { - return host_parts.Length > 1; - } - - return false; - } - } - - /// <summary> - /// Implicitly converts the string-form of a URI to a <see cref="Realm"/> object. - /// </summary> - /// <param name="uri">The URI that the new Realm instance will represent.</param> - /// <returns>The result of the conversion.</returns> - [SuppressMessage("Microsoft.Design", "CA1057:StringUriOverloadsCallSystemUriOverloads", Justification = "Not all realms are valid URLs (because of wildcards).")] - [SuppressMessage("Microsoft.Usage", "CA2234:PassSystemUriObjectsInsteadOfStrings", Justification = "Not all Realms are valid URLs.")] - [DebuggerStepThrough] - public static implicit operator Realm(string uri) { - Contract.Ensures((Contract.Result<Realm>() != null) == (uri != null)); - return uri != null ? new Realm(uri) : null; - } - - /// <summary> - /// Implicitly converts a <see cref="Uri"/> to a <see cref="Realm"/> object. - /// </summary> - /// <param name="uri">The URI to convert to a realm.</param> - /// <returns>The result of the conversion.</returns> - [DebuggerStepThrough] - public static implicit operator Realm(Uri uri) { - Contract.Ensures((Contract.Result<Realm>() != null) == (uri != null)); - return uri != null ? new Realm(uri) : null; - } - - /// <summary> - /// Implicitly converts a <see cref="Realm"/> object to its <see cref="String"/> form. - /// </summary> - /// <param name="realm">The realm to convert to a string value.</param> - /// <returns>The result of the conversion.</returns> - [DebuggerStepThrough] - public static implicit operator string(Realm realm) { - Contract.Ensures((Contract.Result<string>() != null) == (realm != null)); - return realm != null ? realm.ToString() : null; - } - - /// <summary> - /// Checks whether one <see cref="Realm"/> is equal to another. - /// </summary> - /// <param name="obj">The <see cref="T:System.Object"/> to compare with the current <see cref="T:System.Object"/>.</param> - /// <returns> - /// true if the specified <see cref="T:System.Object"/> is equal to the current <see cref="T:System.Object"/>; otherwise, false. - /// </returns> - /// <exception cref="T:System.NullReferenceException"> - /// The <paramref name="obj"/> parameter is null. - /// </exception> - public override bool Equals(object obj) { - Realm other = obj as Realm; - if (other == null) { - return false; - } - return this.uri.Equals(other.uri) && this.DomainWildcard == other.DomainWildcard; - } - - /// <summary> - /// Returns the hash code used for storing this object in a hash table. - /// </summary> - /// <returns> - /// A hash code for the current <see cref="T:System.Object"/>. - /// </returns> - public override int GetHashCode() { - return this.uri.GetHashCode() + (this.DomainWildcard ? 1 : 0); - } - - /// <summary> - /// Returns the string form of this <see cref="Realm"/>. - /// </summary> - /// <returns> - /// A <see cref="T:System.String"/> that represents the current <see cref="T:System.Object"/>. - /// </returns> - public override string ToString() { - if (this.DomainWildcard) { - UriBuilder builder = new UriBuilder(this.uri); - builder.Host = "*." + builder.Host; - return builder.ToStringWithImpliedPorts(); - } else { - return this.uri.AbsoluteUri; - } - } - - /// <summary> - /// Validates a URL against this trust root. - /// </summary> - /// <param name="url">A string specifying URL to check.</param> - /// <returns>Whether the given URL is within this trust root.</returns> - internal bool Contains(string url) { - return this.Contains(new Uri(url)); - } - - /// <summary> - /// Validates a URL against this trust root. - /// </summary> - /// <param name="url">The URL to check.</param> - /// <returns>Whether the given URL is within this trust root.</returns> - internal bool Contains(Uri url) { - if (url.Scheme != this.Scheme) { - return false; - } - - if (url.Port != this.Port) { - return false; - } - - if (!this.DomainWildcard) { - if (url.Host != this.Host) { - return false; - } - } else { - Debug.Assert(!string.IsNullOrEmpty(this.Host), "The host part of the Regex should evaluate to at least one char for successful parsed trust roots."); - string[] host_parts = this.Host.Split('.'); - string[] url_parts = url.Host.Split('.'); - - // If the domain containing the wildcard has more parts than the URL to match against, - // it naturally can't be valid. - // Unless *.example.com actually matches example.com too. - if (host_parts.Length > url_parts.Length) { - return false; - } - - // Compare last part first and move forward. - // Maybe could be done by using EndsWith, but piecewies helps ensure that - // *.my.com doesn't match ohmeohmy.com but can still match my.com. - for (int i = 0; i < host_parts.Length; i++) { - string hostPart = host_parts[host_parts.Length - 1 - i]; - string urlPart = url_parts[url_parts.Length - 1 - i]; - if (!string.Equals(hostPart, urlPart, StringComparison.OrdinalIgnoreCase)) { - return false; - } - } - } - - // If path matches or is specified to root ... - // (deliberately case sensitive to protect security on case sensitive systems) - if (this.PathAndQuery.Equals(url.PathAndQuery, StringComparison.Ordinal) - || this.PathAndQuery.Equals("/", StringComparison.Ordinal)) { - return true; - } - - // If trust root has a longer path, the return URL must be invalid. - if (this.PathAndQuery.Length > url.PathAndQuery.Length) { - return false; - } - - // The following code assures that http://example.com/directory isn't below http://example.com/dir, - // but makes sure http://example.com/dir/ectory is below http://example.com/dir - int path_len = this.PathAndQuery.Length; - string url_prefix = url.PathAndQuery.Substring(0, path_len); - - if (this.PathAndQuery != url_prefix) { - return false; - } - - // If trust root includes a query string ... - if (this.PathAndQuery.Contains("?")) { - // ... make sure return URL begins with a new argument - return url.PathAndQuery[path_len] == '&'; - } - - // Or make sure a query string is introduced or a path below trust root - return this.PathAndQuery.EndsWith("/", StringComparison.Ordinal) - || url.PathAndQuery[path_len] == '?' - || url.PathAndQuery[path_len] == '/'; - } - - /// <summary> - /// Searches for an XRDS document at the realm URL, and if found, searches - /// for a description of a relying party endpoints (OpenId login pages). - /// </summary> - /// <param name="requestHandler">The mechanism to use for sending HTTP requests.</param> - /// <param name="allowRedirects">Whether redirects may be followed when discovering the Realm. - /// This may be true when creating an unsolicited assertion, but must be - /// false when performing return URL verification per 2.0 spec section 9.2.1.</param> - /// <returns> - /// The details of the endpoints if found; or <c>null</c> if no service document was discovered. - /// </returns> - internal virtual IEnumerable<RelyingPartyEndpointDescription> DiscoverReturnToEndpoints(IDirectWebRequestHandler requestHandler, bool allowRedirects) { - XrdsDocument xrds = this.Discover(requestHandler, allowRedirects); - if (xrds != null) { - return xrds.FindRelyingPartyReceivingEndpoints(); - } - - return null; - } - - /// <summary> - /// Searches for an XRDS document at the realm URL. - /// </summary> - /// <param name="requestHandler">The mechanism to use for sending HTTP requests.</param> - /// <param name="allowRedirects">Whether redirects may be followed when discovering the Realm. - /// This may be true when creating an unsolicited assertion, but must be - /// false when performing return URL verification per 2.0 spec section 9.2.1.</param> - /// <returns> - /// The XRDS document if found; or <c>null</c> if no service document was discovered. - /// </returns> - internal virtual XrdsDocument Discover(IDirectWebRequestHandler requestHandler, bool allowRedirects) { - // Attempt YADIS discovery - DiscoveryResult yadisResult = Yadis.Discover(requestHandler, this.UriWithWildcardChangedToWww, false); - if (yadisResult != null) { - // Detect disallowed redirects, since realm discovery never allows them for security. - ErrorUtilities.VerifyProtocol(allowRedirects || yadisResult.NormalizedUri == yadisResult.RequestUri, OpenIdStrings.RealmCausedRedirectUponDiscovery, yadisResult.RequestUri); - if (yadisResult.IsXrds) { - try { - return new XrdsDocument(yadisResult.ResponseText); - } catch (XmlException ex) { - throw ErrorUtilities.Wrap(ex, XrdsStrings.InvalidXRDSDocument); - } - } - } - - return null; - } - - /// <summary> - /// Calls <see cref="UriBuilder.ToString"/> if the argument is non-null. - /// Otherwise throws <see cref="ArgumentNullException"/>. - /// </summary> - /// <param name="realmUriBuilder">The realm URI builder.</param> - /// <returns>The result of UriBuilder.ToString()</returns> - /// <remarks> - /// This simple method is worthwhile because it checks for null - /// before dereferencing the UriBuilder. Since this is called from - /// within a constructor's base(...) call, this avoids a <see cref="NullReferenceException"/> - /// when we should be throwing an <see cref="ArgumentNullException"/>. - /// </remarks> - private static string SafeUriBuilderToString(UriBuilder realmUriBuilder) { - Contract.Requires<ArgumentNullException>(realmUriBuilder != null); - - // Note: we MUST use ToString. Uri property throws if wildcard is present. - // Note that Uri.ToString() should generally be avoided, but UriBuilder.ToString() - // is safe: http://blog.nerdbank.net/2008/04/uriabsoluteuri-and-uritostring-are-not.html - return realmUriBuilder.ToString(); - } - -#if CONTRACTS_FULL - /// <summary> - /// Verifies conditions that should be true for any valid state of this object. - /// </summary> - [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called by code contracts.")] - [ContractInvariantMethod] - private void ObjectInvariant() { - Contract.Invariant(this.uri != null); - Contract.Invariant(this.uri.AbsoluteUri != null); - } -#endif - } -} diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/AssociationManager.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/AssociationManager.cs deleted file mode 100644 index 9a43506..0000000 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/AssociationManager.cs +++ /dev/null @@ -1,246 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="AssociationManager.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OpenId.RelyingParty { - using System; - using System.Collections.Generic; - using System.Diagnostics.Contracts; - using System.Linq; - using System.Net; - using System.Security; - using System.Text; - using DotNetOpenAuth.Messaging; - using DotNetOpenAuth.OpenId.ChannelElements; - using DotNetOpenAuth.OpenId.Messages; - - /// <summary> - /// Manages the establishment, storage and retrieval of associations at the relying party. - /// </summary> - internal class AssociationManager { - /// <summary> - /// The storage to use for saving and retrieving associations. May be null. - /// </summary> - private readonly IRelyingPartyAssociationStore associationStore; - - /// <summary> - /// Backing field for the <see cref="Channel"/> property. - /// </summary> - private Channel channel; - - /// <summary> - /// Backing field for the <see cref="SecuritySettings"/> property. - /// </summary> - private RelyingPartySecuritySettings securitySettings; - - /// <summary> - /// Initializes a new instance of the <see cref="AssociationManager"/> class. - /// </summary> - /// <param name="channel">The channel the relying party is using.</param> - /// <param name="associationStore">The association store. May be null for dumb mode relying parties.</param> - /// <param name="securitySettings">The security settings.</param> - internal AssociationManager(Channel channel, IRelyingPartyAssociationStore associationStore, RelyingPartySecuritySettings securitySettings) { - Contract.Requires<ArgumentNullException>(channel != null); - Contract.Requires<ArgumentNullException>(securitySettings != null); - - this.channel = channel; - this.associationStore = associationStore; - this.securitySettings = securitySettings; - } - - /// <summary> - /// Gets or sets the channel to use for establishing associations. - /// </summary> - /// <value>The channel.</value> - internal Channel Channel { - get { - return this.channel; - } - - set { - Contract.Requires<ArgumentNullException>(value != null); - this.channel = value; - } - } - - /// <summary> - /// Gets or sets the security settings to apply in choosing association types to support. - /// </summary> - internal RelyingPartySecuritySettings SecuritySettings { - get { - return this.securitySettings; - } - - set { - Contract.Requires<ArgumentNullException>(value != null); - this.securitySettings = value; - } - } - - /// <summary> - /// Gets a value indicating whether this instance has an association store. - /// </summary> - /// <value> - /// <c>true</c> if the relying party can act in 'smart' mode; - /// <c>false</c> if the relying party must always act in 'dumb' mode. - /// </value> - internal bool HasAssociationStore { - get { return this.associationStore != null; } - } - - /// <summary> - /// Gets the storage to use for saving and retrieving associations. May be null. - /// </summary> - internal IRelyingPartyAssociationStore AssociationStoreTestHook { - get { return this.associationStore; } - } - - /// <summary> - /// Gets an association between this Relying Party and a given Provider - /// if it already exists in the association store. - /// </summary> - /// <param name="provider">The provider to create an association with.</param> - /// <returns>The association if one exists and has useful life remaining. Otherwise <c>null</c>.</returns> - internal Association GetExistingAssociation(IProviderEndpoint provider) { - Contract.Requires<ArgumentNullException>(provider != null); - - // If the RP has no application store for associations, there's no point in creating one. - if (this.associationStore == null) { - return null; - } - - Association association = this.associationStore.GetAssociation(provider.Uri, this.SecuritySettings); - - // If the returned association does not fulfill security requirements, ignore it. - if (association != null && !this.SecuritySettings.IsAssociationInPermittedRange(association)) { - association = null; - } - - if (association != null && !association.HasUsefulLifeRemaining) { - association = null; - } - - return association; - } - - /// <summary> - /// Gets an existing association with the specified Provider, or attempts to create - /// a new association of one does not already exist. - /// </summary> - /// <param name="provider">The provider to get an association for.</param> - /// <returns>The existing or new association; <c>null</c> if none existed and one could not be created.</returns> - internal Association GetOrCreateAssociation(IProviderEndpoint provider) { - return this.GetExistingAssociation(provider) ?? this.CreateNewAssociation(provider); - } - - /// <summary> - /// Creates a new association with a given Provider. - /// </summary> - /// <param name="provider">The provider to create an association with.</param> - /// <returns> - /// The newly created association, or null if no association can be created with - /// the given Provider given the current security settings. - /// </returns> - /// <remarks> - /// A new association is created and returned even if one already exists in the - /// association store. - /// Any new association is automatically added to the <see cref="associationStore"/>. - /// </remarks> - private Association CreateNewAssociation(IProviderEndpoint provider) { - Contract.Requires<ArgumentNullException>(provider != null); - - // If there is no association store, there is no point in creating an association. - if (this.associationStore == null) { - return null; - } - - try { - var associateRequest = AssociateRequest.Create(this.securitySettings, provider); - - const int RenegotiateRetries = 1; - return this.CreateNewAssociation(provider, associateRequest, RenegotiateRetries); - } catch (VerificationException ex) { - // See Trac ticket #163. In partial trust host environments, the - // Diffie-Hellman implementation we're using for HTTP OP endpoints - // sometimes causes the CLR to throw: - // "VerificationException: Operation could destabilize the runtime." - // Just give up and use dumb mode in this case. - Logger.OpenId.ErrorFormat("VerificationException occurred while trying to create an association with {0}. {1}", provider.Uri, ex); - return null; - } - } - - /// <summary> - /// Creates a new association with a given Provider. - /// </summary> - /// <param name="provider">The provider to create an association with.</param> - /// <param name="associateRequest">The associate request. May be <c>null</c>, which will always result in a <c>null</c> return value..</param> - /// <param name="retriesRemaining">The number of times to try the associate request again if the Provider suggests it.</param> - /// <returns> - /// The newly created association, or null if no association can be created with - /// the given Provider given the current security settings. - /// </returns> - private Association CreateNewAssociation(IProviderEndpoint provider, AssociateRequest associateRequest, int retriesRemaining) { - Contract.Requires<ArgumentNullException>(provider != null); - - if (associateRequest == null || retriesRemaining < 0) { - // this can happen if security requirements and protocol conflict - // to where there are no association types to choose from. - return null; - } - - try { - var associateResponse = this.channel.Request(associateRequest); - var associateSuccessfulResponse = associateResponse as AssociateSuccessfulResponse; - var associateUnsuccessfulResponse = associateResponse as AssociateUnsuccessfulResponse; - if (associateSuccessfulResponse != null) { - Association association = associateSuccessfulResponse.CreateAssociation(associateRequest, null, null); - this.associationStore.StoreAssociation(provider.Uri, association); - return association; - } else if (associateUnsuccessfulResponse != null) { - if (string.IsNullOrEmpty(associateUnsuccessfulResponse.AssociationType)) { - Logger.OpenId.Debug("Provider rejected an association request and gave no suggestion as to an alternative association type. Giving up."); - return null; - } - - if (!this.securitySettings.IsAssociationInPermittedRange(Protocol.Lookup(provider.Version), associateUnsuccessfulResponse.AssociationType)) { - Logger.OpenId.DebugFormat("Provider rejected an association request and suggested '{0}' as an association to try, which this Relying Party does not support. Giving up.", associateUnsuccessfulResponse.AssociationType); - return null; - } - - if (retriesRemaining <= 0) { - Logger.OpenId.Debug("Unable to agree on an association type with the Provider in the allowed number of retries. Giving up."); - return null; - } - - // Make sure the Provider isn't suggesting an incompatible pair of association/session types. - Protocol protocol = Protocol.Lookup(provider.Version); - ErrorUtilities.VerifyProtocol( - HmacShaAssociation.IsDHSessionCompatible(protocol, associateUnsuccessfulResponse.AssociationType, associateUnsuccessfulResponse.SessionType), - OpenIdStrings.IncompatibleAssociationAndSessionTypes, - associateUnsuccessfulResponse.AssociationType, - associateUnsuccessfulResponse.SessionType); - - associateRequest = AssociateRequest.Create(this.securitySettings, provider, associateUnsuccessfulResponse.AssociationType, associateUnsuccessfulResponse.SessionType); - return this.CreateNewAssociation(provider, associateRequest, retriesRemaining - 1); - } else { - throw new ProtocolException(MessagingStrings.UnexpectedMessageReceivedOfMany); - } - } catch (ProtocolException ex) { - // If the association failed because the remote server can't handle Expect: 100 Continue headers, - // then our web request handler should have already accomodated for future calls. Go ahead and - // immediately make one of those future calls now to try to get the association to succeed. - if (StandardWebRequestHandler.IsExceptionFrom417ExpectationFailed(ex)) { - return this.CreateNewAssociation(provider, associateRequest, retriesRemaining - 1); - } - - // Since having associations with OPs is not totally critical, we'll log and eat - // the exception so that auth may continue in dumb mode. - Logger.OpenId.ErrorFormat("An error occurred while trying to create an association with {0}. {1}", provider.Uri, ex); - return null; - } - } - } -} diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/Associations.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/Associations.cs deleted file mode 100644 index b171bec..0000000 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/Associations.cs +++ /dev/null @@ -1,127 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="Associations.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OpenId.RelyingParty { - using System; - using System.Collections.Generic; - using System.Collections.ObjectModel; - using System.Diagnostics; - using System.Diagnostics.CodeAnalysis; - using System.Diagnostics.Contracts; - using System.Linq; - using DotNetOpenAuth.Messaging; - - /// <summary> - /// A dictionary of handle/Association pairs. - /// </summary> - /// <remarks> - /// Each method is locked, even if it is only one line, so that they are thread safe - /// against each other, particularly the ones that enumerate over the list, since they - /// can break if the collection is changed by another thread during enumeration. - /// </remarks> - [DebuggerDisplay("Count = {assocs.Count}")] - [ContractVerification(true)] - internal class Associations { - /// <summary> - /// The lookup table where keys are the association handles and values are the associations themselves. - /// </summary> - [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] - private readonly KeyedCollection<string, Association> associations = new KeyedCollectionDelegate<string, Association>(assoc => assoc.Handle); - - /// <summary> - /// Initializes a new instance of the <see cref="Associations"/> class. - /// </summary> - public Associations() { - } - - /// <summary> - /// Gets the <see cref="Association"/>s ordered in order of descending issue date - /// (most recently issued comes first). An empty sequence if no valid associations exist. - /// </summary> - /// <remarks> - /// This property is used by relying parties that are initiating authentication requests. - /// It does not apply to Providers, which always need a specific association by handle. - /// </remarks> - public IEnumerable<Association> Best { - get { - Contract.Ensures(Contract.Result<IEnumerable<Association>>() != null); - - lock (this.associations) { - return this.associations.OrderByDescending(assoc => assoc.Issued); - } - } - } - - /// <summary> - /// Stores an <see cref="Association"/> in the collection. - /// </summary> - /// <param name="association">The association to add to the collection.</param> - public void Set(Association association) { - Contract.Requires<ArgumentNullException>(association != null); - Contract.Ensures(this.Get(association.Handle) == association); - lock (this.associations) { - this.associations.Remove(association.Handle); // just in case one already exists. - this.associations.Add(association); - } - - Contract.Assume(this.Get(association.Handle) == association); - } - - /// <summary> - /// Returns the <see cref="Association"/> with the given handle. Null if not found. - /// </summary> - /// <param name="handle">The handle to the required association.</param> - /// <returns>The desired association, or null if none with the given handle could be found.</returns> - [Pure] - public Association Get(string handle) { - Contract.Requires<ArgumentException>(!string.IsNullOrEmpty(handle)); - - lock (this.associations) { - if (this.associations.Contains(handle)) { - return this.associations[handle]; - } else { - return null; - } - } - } - - /// <summary> - /// Removes the <see cref="Association"/> with the given handle. - /// </summary> - /// <param name="handle">The handle to the required association.</param> - /// <returns>Whether an <see cref="Association"/> with the given handle was in the collection for removal.</returns> - public bool Remove(string handle) { - Contract.Requires<ArgumentException>(!string.IsNullOrEmpty(handle)); - lock (this.associations) { - return this.associations.Remove(handle); - } - } - - /// <summary> - /// Removes all expired associations from the collection. - /// </summary> - public void ClearExpired() { - lock (this.associations) { - var expireds = this.associations.Where(assoc => assoc.IsExpired).ToList(); - foreach (Association assoc in expireds) { - this.associations.Remove(assoc.Handle); - } - } - } - -#if CONTRACTS_FULL - /// <summary> - /// Verifies conditions that should be true for any valid state of this object. - /// </summary> - [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Called by code contracts.")] - [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called by code contracts.")] - [ContractInvariantMethod] - private void ObjectInvariant() { - Contract.Invariant(this.associations != null); - } -#endif - } -} diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/AuthenticationRequest.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/AuthenticationRequest.cs deleted file mode 100644 index 8d8f0da..0000000 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/AuthenticationRequest.cs +++ /dev/null @@ -1,594 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="AuthenticationRequest.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OpenId.RelyingParty { - using System; - using System.Collections.Generic; - using System.Collections.Specialized; - using System.Diagnostics.Contracts; - using System.Linq; - using System.Text; - using System.Threading; - using System.Web; - - using DotNetOpenAuth.Configuration; - using DotNetOpenAuth.Messaging; - using DotNetOpenAuth.OpenId.ChannelElements; - using DotNetOpenAuth.OpenId.Messages; - - /// <summary> - /// Facilitates customization and creation and an authentication request - /// that a Relying Party is preparing to send. - /// </summary> - internal class AuthenticationRequest : IAuthenticationRequest { - /// <summary> - /// The name of the internal callback parameter to use to store the user-supplied identifier. - /// </summary> - internal const string UserSuppliedIdentifierParameterName = OpenIdUtilities.CustomParameterPrefix + "userSuppliedIdentifier"; - - /// <summary> - /// The relying party that created this request object. - /// </summary> - private readonly OpenIdRelyingParty RelyingParty; - - /// <summary> - /// How an association may or should be created or used in the formulation of the - /// authentication request. - /// </summary> - private AssociationPreference associationPreference = AssociationPreference.IfPossible; - - /// <summary> - /// The extensions that have been added to this authentication request. - /// </summary> - private List<IOpenIdMessageExtension> extensions = new List<IOpenIdMessageExtension>(); - - /// <summary> - /// Arguments to add to the return_to part of the query string, so that - /// these values come back to the consumer when the user agent returns. - /// </summary> - private Dictionary<string, string> returnToArgs = new Dictionary<string, string>(); - - /// <summary> - /// A value indicating whether the return_to callback arguments must be signed. - /// </summary> - /// <remarks> - /// This field defaults to false, but is set to true as soon as the first callback argument - /// is added that indicates it must be signed. At which point, all arguments are signed - /// even if individual ones did not need to be. - /// </remarks> - private bool returnToArgsMustBeSigned; - - /// <summary> - /// Initializes a new instance of the <see cref="AuthenticationRequest"/> class. - /// </summary> - /// <param name="discoveryResult">The endpoint that describes the OpenID Identifier and Provider that will complete the authentication.</param> - /// <param name="realm">The realm, or root URL, of the host web site.</param> - /// <param name="returnToUrl">The base return_to URL that the Provider should return the user to to complete authentication. This should not include callback parameters as these should be added using the <see cref="AddCallbackArguments(string, string)"/> method.</param> - /// <param name="relyingParty">The relying party that created this instance.</param> - private AuthenticationRequest(IdentifierDiscoveryResult discoveryResult, Realm realm, Uri returnToUrl, OpenIdRelyingParty relyingParty) { - Contract.Requires<ArgumentNullException>(discoveryResult != null); - Contract.Requires<ArgumentNullException>(realm != null); - Contract.Requires<ArgumentNullException>(returnToUrl != null); - Contract.Requires<ArgumentNullException>(relyingParty != null); - - this.DiscoveryResult = discoveryResult; - this.RelyingParty = relyingParty; - this.Realm = realm; - this.ReturnToUrl = returnToUrl; - - this.Mode = AuthenticationRequestMode.Setup; - } - - #region IAuthenticationRequest Members - - /// <summary> - /// Gets or sets the mode the Provider should use during authentication. - /// </summary> - /// <value></value> - public AuthenticationRequestMode Mode { get; set; } - - /// <summary> - /// Gets the HTTP response the relying party should send to the user agent - /// to redirect it to the OpenID Provider to start the OpenID authentication process. - /// </summary> - /// <value></value> - public OutgoingWebResponse RedirectingResponse { - get { - foreach (var behavior in this.RelyingParty.Behaviors) { - behavior.OnOutgoingAuthenticationRequest(this); - } - - return this.RelyingParty.Channel.PrepareResponse(this.CreateRequestMessage()); - } - } - - /// <summary> - /// Gets the URL that the user agent will return to after authentication - /// completes or fails at the Provider. - /// </summary> - /// <value></value> - public Uri ReturnToUrl { get; private set; } - - /// <summary> - /// Gets the URL that identifies this consumer web application that - /// the Provider will display to the end user. - /// </summary> - public Realm Realm { get; private set; } - - /// <summary> - /// Gets the Claimed Identifier that the User Supplied Identifier - /// resolved to. Null if the user provided an OP Identifier - /// (directed identity). - /// </summary> - /// <value></value> - /// <remarks> - /// Null is returned if the user is using the directed identity feature - /// of OpenID 2.0 to make it nearly impossible for a relying party site - /// to improperly store the reserved OpenID URL used for directed identity - /// as a user's own Identifier. - /// However, to test for the Directed Identity feature, please test the - /// <see cref="IsDirectedIdentity"/> property rather than testing this - /// property for a null value. - /// </remarks> - public Identifier ClaimedIdentifier { - get { return this.IsDirectedIdentity ? null : this.DiscoveryResult.ClaimedIdentifier; } - } - - /// <summary> - /// Gets a value indicating whether the authenticating user has chosen to let the Provider - /// determine and send the ClaimedIdentifier after authentication. - /// </summary> - public bool IsDirectedIdentity { - get { return this.DiscoveryResult.ClaimedIdentifier == this.DiscoveryResult.Protocol.ClaimedIdentifierForOPIdentifier; } - } - - /// <summary> - /// Gets or sets a value indicating whether this request only carries extensions - /// and is not a request to verify that the user controls some identifier. - /// </summary> - /// <value> - /// <c>true</c> if this request is merely a carrier of extensions and is not - /// about an OpenID identifier; otherwise, <c>false</c>. - /// </value> - public bool IsExtensionOnly { get; set; } - - /// <summary> - /// Gets information about the OpenId Provider, as advertised by the - /// OpenId discovery documents found at the <see cref="ClaimedIdentifier"/> - /// location. - /// </summary> - public IProviderEndpoint Provider { - get { return this.DiscoveryResult; } - } - - /// <summary> - /// Gets the discovery result leading to the formulation of this request. - /// </summary> - /// <value>The discovery result.</value> - public IdentifierDiscoveryResult DiscoveryResult { get; private set; } - - #endregion - - /// <summary> - /// Gets or sets how an association may or should be created or used - /// in the formulation of the authentication request. - /// </summary> - internal AssociationPreference AssociationPreference { - get { return this.associationPreference; } - set { this.associationPreference = value; } - } - - /// <summary> - /// Gets the extensions that have been added to the request. - /// </summary> - internal IEnumerable<IOpenIdMessageExtension> AppliedExtensions { - get { return this.extensions; } - } - - /// <summary> - /// Gets the list of extensions for this request. - /// </summary> - internal IList<IOpenIdMessageExtension> Extensions { - get { return this.extensions; } - } - - #region IAuthenticationRequest methods - - /// <summary> - /// Makes a dictionary of key/value pairs available when the authentication is completed. - /// </summary> - /// <param name="arguments">The arguments to add to the request's return_to URI.</param> - /// <remarks> - /// <para>Note that these values are NOT protected against eavesdropping in transit. No - /// privacy-sensitive data should be stored using this method.</para> - /// <para>The values stored here can be retrieved using - /// <see cref="IAuthenticationResponse.GetCallbackArguments"/>, which will only return the value - /// if it hasn't been tampered with in transit.</para> - /// <para>Since the data set here is sent in the querystring of the request and some - /// servers place limits on the size of a request URL, this data should be kept relatively - /// small to ensure successful authentication. About 1.5KB is about all that should be stored.</para> - /// </remarks> - public void AddCallbackArguments(IDictionary<string, string> arguments) { - ErrorUtilities.VerifyOperation(this.RelyingParty.CanSignCallbackArguments, OpenIdStrings.CallbackArgumentsRequireSecretStore, typeof(IRelyingPartyAssociationStore).Name, typeof(OpenIdRelyingParty).Name); - - this.returnToArgsMustBeSigned = true; - foreach (var pair in arguments) { - ErrorUtilities.VerifyArgument(!string.IsNullOrEmpty(pair.Key), MessagingStrings.UnexpectedNullOrEmptyKey); - ErrorUtilities.VerifyArgument(pair.Value != null, MessagingStrings.UnexpectedNullValue, pair.Key); - - this.returnToArgs.Add(pair.Key, pair.Value); - } - } - - /// <summary> - /// Makes a key/value pair available when the authentication is completed. - /// </summary> - /// <param name="key">The parameter name.</param> - /// <param name="value">The value of the argument.</param> - /// <remarks> - /// <para>Note that these values are NOT protected against eavesdropping in transit. No - /// privacy-sensitive data should be stored using this method.</para> - /// <para>The value stored here can be retrieved using - /// <see cref="IAuthenticationResponse.GetCallbackArgument"/>, which will only return the value - /// if it hasn't been tampered with in transit.</para> - /// <para>Since the data set here is sent in the querystring of the request and some - /// servers place limits on the size of a request URL, this data should be kept relatively - /// small to ensure successful authentication. About 1.5KB is about all that should be stored.</para> - /// </remarks> - public void AddCallbackArguments(string key, string value) { - ErrorUtilities.VerifyOperation(this.RelyingParty.CanSignCallbackArguments, OpenIdStrings.CallbackArgumentsRequireSecretStore, typeof(IRelyingPartyAssociationStore).Name, typeof(OpenIdRelyingParty).Name); - - this.returnToArgsMustBeSigned = true; - this.returnToArgs.Add(key, value); - } - - /// <summary> - /// Makes a key/value pair available when the authentication is completed. - /// </summary> - /// <param name="key">The parameter name.</param> - /// <param name="value">The value of the argument. Must not be null.</param> - /// <remarks> - /// <para>Note that these values are NOT protected against tampering in transit. No - /// security-sensitive data should be stored using this method.</para> - /// <para>The value stored here can be retrieved using - /// <see cref="IAuthenticationResponse.GetCallbackArgument"/>.</para> - /// <para>Since the data set here is sent in the querystring of the request and some - /// servers place limits on the size of a request URL, this data should be kept relatively - /// small to ensure successful authentication. About 1.5KB is about all that should be stored.</para> - /// </remarks> - public void SetCallbackArgument(string key, string value) { - ErrorUtilities.VerifyOperation(this.RelyingParty.CanSignCallbackArguments, OpenIdStrings.CallbackArgumentsRequireSecretStore, typeof(IRelyingPartyAssociationStore).Name, typeof(OpenIdRelyingParty).Name); - - this.returnToArgsMustBeSigned = true; - this.returnToArgs[key] = value; - } - - /// <summary> - /// Makes a key/value pair available when the authentication is completed without - /// requiring a return_to signature to protect against tampering of the callback argument. - /// </summary> - /// <param name="key">The parameter name.</param> - /// <param name="value">The value of the argument. Must not be null.</param> - /// <remarks> - /// <para>Note that these values are NOT protected against eavesdropping or tampering in transit. No - /// security-sensitive data should be stored using this method. </para> - /// <para>The value stored here can be retrieved using - /// <see cref="IAuthenticationResponse.GetCallbackArgument"/>.</para> - /// <para>Since the data set here is sent in the querystring of the request and some - /// servers place limits on the size of a request URL, this data should be kept relatively - /// small to ensure successful authentication. About 1.5KB is about all that should be stored.</para> - /// </remarks> - public void SetUntrustedCallbackArgument(string key, string value) { - this.returnToArgs[key] = value; - } - - /// <summary> - /// Adds an OpenID extension to the request directed at the OpenID provider. - /// </summary> - /// <param name="extension">The initialized extension to add to the request.</param> - public void AddExtension(IOpenIdMessageExtension extension) { - this.extensions.Add(extension); - } - - /// <summary> - /// Redirects the user agent to the provider for authentication. - /// </summary> - /// <remarks> - /// This method requires an ASP.NET HttpContext. - /// </remarks> - public void RedirectToProvider() { - this.RedirectingResponse.Send(); - } - - #endregion - - /// <summary> - /// Performs identifier discovery, creates associations and generates authentication requests - /// on-demand for as long as new ones can be generated based on the results of Identifier discovery. - /// </summary> - /// <param name="userSuppliedIdentifier">The user supplied identifier.</param> - /// <param name="relyingParty">The relying party.</param> - /// <param name="realm">The realm.</param> - /// <param name="returnToUrl">The return_to base URL.</param> - /// <param name="createNewAssociationsAsNeeded">if set to <c>true</c>, associations that do not exist between this Relying Party and the asserting Providers are created before the authentication request is created.</param> - /// <returns> - /// A sequence of authentication requests, any of which constitutes a valid identity assertion on the Claimed Identifier. - /// Never null, but may be empty. - /// </returns> - internal static IEnumerable<AuthenticationRequest> Create(Identifier userSuppliedIdentifier, OpenIdRelyingParty relyingParty, Realm realm, Uri returnToUrl, bool createNewAssociationsAsNeeded) { - Contract.Requires<ArgumentNullException>(userSuppliedIdentifier != null); - Contract.Requires<ArgumentNullException>(relyingParty != null); - Contract.Requires<ArgumentNullException>(realm != null); - Contract.Ensures(Contract.Result<IEnumerable<AuthenticationRequest>>() != null); - - // Normalize the portion of the return_to path that correlates to the realm for capitalization. - // (so that if a web app base path is /MyApp/, but the URL of this request happens to be - // /myapp/login.aspx, we bump up the return_to Url to use /MyApp/ so it matches the realm. - UriBuilder returnTo = new UriBuilder(returnToUrl); - if (returnTo.Path.StartsWith(realm.AbsolutePath, StringComparison.OrdinalIgnoreCase) && - !returnTo.Path.StartsWith(realm.AbsolutePath, StringComparison.Ordinal)) { - returnTo.Path = realm.AbsolutePath + returnTo.Path.Substring(realm.AbsolutePath.Length); - returnToUrl = returnTo.Uri; - } - - userSuppliedIdentifier = userSuppliedIdentifier.TrimFragment(); - if (relyingParty.SecuritySettings.RequireSsl) { - // Rather than check for successful SSL conversion at this stage, - // We'll wait for secure discovery to fail on the new identifier. - if (!userSuppliedIdentifier.TryRequireSsl(out userSuppliedIdentifier)) { - // But at least log the failure. - Logger.OpenId.WarnFormat("RequireSsl mode is on, so discovery on insecure identifier {0} will yield no results.", userSuppliedIdentifier); - } - } - - if (Logger.OpenId.IsWarnEnabled && returnToUrl.Query != null) { - NameValueCollection returnToArgs = HttpUtility.ParseQueryString(returnToUrl.Query); - foreach (string key in returnToArgs) { - if (OpenIdRelyingParty.IsOpenIdSupportingParameter(key)) { - Logger.OpenId.WarnFormat("OpenID argument \"{0}\" found in return_to URL. This can corrupt an OpenID response.", key); - } - } - } - - // Throw an exception now if the realm and the return_to URLs don't match - // as required by the provider. We could wait for the provider to test this and - // fail, but this will be faster and give us a better error message. - ErrorUtilities.VerifyProtocol(realm.Contains(returnToUrl), OpenIdStrings.ReturnToNotUnderRealm, returnToUrl, realm); - - // Perform discovery right now (not deferred). - IEnumerable<IdentifierDiscoveryResult> serviceEndpoints; - try { - var results = relyingParty.Discover(userSuppliedIdentifier).CacheGeneratedResults(); - - // If any OP Identifier service elements were found, we must not proceed - // to use any Claimed Identifier services, per OpenID 2.0 sections 7.3.2.2 and 11.2. - // For a discussion on this topic, see - // http://groups.google.com/group/dotnetopenid/browse_thread/thread/4b5a8c6b2210f387/5e25910e4d2252c8 - // Usually the Discover method we called will automatically filter this for us, but - // just to be sure, we'll do it here as well since the RP may be configured to allow - // these dual identifiers for assertion verification purposes. - var opIdentifiers = results.Where(result => result.ClaimedIdentifier == result.Protocol.ClaimedIdentifierForOPIdentifier).CacheGeneratedResults(); - var claimedIdentifiers = results.Where(result => result.ClaimedIdentifier != result.Protocol.ClaimedIdentifierForOPIdentifier); - serviceEndpoints = opIdentifiers.Any() ? opIdentifiers : claimedIdentifiers; - } catch (ProtocolException ex) { - Logger.Yadis.ErrorFormat("Error while performing discovery on: \"{0}\": {1}", userSuppliedIdentifier, ex); - serviceEndpoints = Enumerable.Empty<IdentifierDiscoveryResult>(); - } - - // Filter disallowed endpoints. - serviceEndpoints = relyingParty.SecuritySettings.FilterEndpoints(serviceEndpoints); - - // Call another method that defers request generation. - return CreateInternal(userSuppliedIdentifier, relyingParty, realm, returnToUrl, serviceEndpoints, createNewAssociationsAsNeeded); - } - - /// <summary> - /// Creates an instance of <see cref="AuthenticationRequest"/> FOR TESTING PURPOSES ONLY. - /// </summary> - /// <param name="discoveryResult">The discovery result.</param> - /// <param name="realm">The realm.</param> - /// <param name="returnTo">The return to.</param> - /// <param name="rp">The relying party.</param> - /// <returns>The instantiated <see cref="AuthenticationRequest"/>.</returns> - internal static AuthenticationRequest CreateForTest(IdentifierDiscoveryResult discoveryResult, Realm realm, Uri returnTo, OpenIdRelyingParty rp) { - return new AuthenticationRequest(discoveryResult, realm, returnTo, rp); - } - - /// <summary> - /// Creates the request message to send to the Provider, - /// based on the properties in this instance. - /// </summary> - /// <returns>The message to send to the Provider.</returns> - internal SignedResponseRequest CreateRequestMessageTestHook() - { - return this.CreateRequestMessage(); - } - - /// <summary> - /// Performs deferred request generation for the <see cref="Create"/> method. - /// </summary> - /// <param name="userSuppliedIdentifier">The user supplied identifier.</param> - /// <param name="relyingParty">The relying party.</param> - /// <param name="realm">The realm.</param> - /// <param name="returnToUrl">The return_to base URL.</param> - /// <param name="serviceEndpoints">The discovered service endpoints on the Claimed Identifier.</param> - /// <param name="createNewAssociationsAsNeeded">if set to <c>true</c>, associations that do not exist between this Relying Party and the asserting Providers are created before the authentication request is created.</param> - /// <returns> - /// A sequence of authentication requests, any of which constitutes a valid identity assertion on the Claimed Identifier. - /// Never null, but may be empty. - /// </returns> - /// <remarks> - /// All data validation and cleansing steps must have ALREADY taken place - /// before calling this method. - /// </remarks> - private static IEnumerable<AuthenticationRequest> CreateInternal(Identifier userSuppliedIdentifier, OpenIdRelyingParty relyingParty, Realm realm, Uri returnToUrl, IEnumerable<IdentifierDiscoveryResult> serviceEndpoints, bool createNewAssociationsAsNeeded) { - // DO NOT USE CODE CONTRACTS IN THIS METHOD, since it uses yield return - ErrorUtilities.VerifyArgumentNotNull(userSuppliedIdentifier, "userSuppliedIdentifier"); - ErrorUtilities.VerifyArgumentNotNull(relyingParty, "relyingParty"); - ErrorUtilities.VerifyArgumentNotNull(realm, "realm"); - ErrorUtilities.VerifyArgumentNotNull(serviceEndpoints, "serviceEndpoints"); - ////Contract.Ensures(Contract.Result<IEnumerable<AuthenticationRequest>>() != null); - - // If shared associations are required, then we had better have an association store. - ErrorUtilities.VerifyOperation(!relyingParty.SecuritySettings.RequireAssociation || relyingParty.AssociationManager.HasAssociationStore, OpenIdStrings.AssociationStoreRequired); - - Logger.Yadis.InfoFormat("Performing discovery on user-supplied identifier: {0}", userSuppliedIdentifier); - IEnumerable<IdentifierDiscoveryResult> endpoints = FilterAndSortEndpoints(serviceEndpoints, relyingParty); - - // Maintain a list of endpoints that we could not form an association with. - // We'll fallback to generating requests to these if the ones we CAN create - // an association with run out. - var failedAssociationEndpoints = new List<IdentifierDiscoveryResult>(0); - - foreach (var endpoint in endpoints) { - Logger.OpenId.DebugFormat("Creating authentication request for user supplied Identifier: {0}", userSuppliedIdentifier); - - // The strategy here is to prefer endpoints with whom we can create associations. - Association association = null; - if (relyingParty.AssociationManager.HasAssociationStore) { - // In some scenarios (like the AJAX control wanting ALL auth requests possible), - // we don't want to create associations with every Provider. But we'll use - // associations where they are already formed from previous authentications. - association = createNewAssociationsAsNeeded ? relyingParty.AssociationManager.GetOrCreateAssociation(endpoint) : relyingParty.AssociationManager.GetExistingAssociation(endpoint); - if (association == null && createNewAssociationsAsNeeded) { - Logger.OpenId.WarnFormat("Failed to create association with {0}. Skipping to next endpoint.", endpoint.ProviderEndpoint); - - // No association could be created. Add it to the list of failed association - // endpoints and skip to the next available endpoint. - failedAssociationEndpoints.Add(endpoint); - continue; - } - } - - yield return new AuthenticationRequest(endpoint, realm, returnToUrl, relyingParty); - } - - // Now that we've run out of endpoints that respond to association requests, - // since we apparently are still running, the caller must want another request. - // We'll go ahead and generate the requests to OPs that may be down -- - // unless associations are set as required in our security settings. - if (failedAssociationEndpoints.Count > 0) { - if (relyingParty.SecuritySettings.RequireAssociation) { - Logger.OpenId.Warn("Associations could not be formed with some Providers. Security settings require shared associations for authentication requests so these will be skipped."); - } else { - Logger.OpenId.Debug("Now generating requests for Provider endpoints that failed initial association attempts."); - - foreach (var endpoint in failedAssociationEndpoints) { - Logger.OpenId.DebugFormat("Creating authentication request for user supplied Identifier: {0} at endpoint: {1}", userSuppliedIdentifier, endpoint.ProviderEndpoint.AbsoluteUri); - - // Create the auth request, but prevent it from attempting to create an association - // because we've already tried. Let's not have it waste time trying again. - var authRequest = new AuthenticationRequest(endpoint, realm, returnToUrl, relyingParty); - authRequest.associationPreference = AssociationPreference.IfAlreadyEstablished; - yield return authRequest; - } - } - } - } - - /// <summary> - /// Returns a filtered and sorted list of the available OP endpoints for a discovered Identifier. - /// </summary> - /// <param name="endpoints">The endpoints.</param> - /// <param name="relyingParty">The relying party.</param> - /// <returns>A filtered and sorted list of endpoints; may be empty if the input was empty or the filter removed all endpoints.</returns> - private static List<IdentifierDiscoveryResult> FilterAndSortEndpoints(IEnumerable<IdentifierDiscoveryResult> endpoints, OpenIdRelyingParty relyingParty) { - Contract.Requires<ArgumentNullException>(endpoints != null); - Contract.Requires<ArgumentNullException>(relyingParty != null); - - bool anyFilteredOut = false; - var filteredEndpoints = new List<IdentifierDiscoveryResult>(); - foreach (var endpoint in endpoints) { - if (relyingParty.FilterEndpoint(endpoint)) { - filteredEndpoints.Add(endpoint); - } else { - anyFilteredOut = true; - } - } - - // Sort endpoints so that the first one in the list is the most preferred one. - filteredEndpoints.OrderBy(ep => ep, relyingParty.EndpointOrder); - - var endpointList = new List<IdentifierDiscoveryResult>(filteredEndpoints.Count); - foreach (var endpoint in filteredEndpoints) { - endpointList.Add(endpoint); - } - - if (anyFilteredOut) { - Logger.Yadis.DebugFormat("Some endpoints were filtered out. Total endpoints remaining: {0}", filteredEndpoints.Count); - } - if (Logger.Yadis.IsDebugEnabled) { - if (MessagingUtilities.AreEquivalent(endpoints, endpointList)) { - Logger.Yadis.Debug("Filtering and sorting of endpoints did not affect the list."); - } else { - Logger.Yadis.Debug("After filtering and sorting service endpoints, this is the new prioritized list:"); - Logger.Yadis.Debug(Util.ToStringDeferred(filteredEndpoints, true)); - } - } - - return endpointList; - } - - /// <summary> - /// Creates the request message to send to the Provider, - /// based on the properties in this instance. - /// </summary> - /// <returns>The message to send to the Provider.</returns> - private SignedResponseRequest CreateRequestMessage() { - Association association = this.GetAssociation(); - - SignedResponseRequest request; - if (!this.IsExtensionOnly) { - CheckIdRequest authRequest = new CheckIdRequest(this.DiscoveryResult.Version, this.DiscoveryResult.ProviderEndpoint, this.Mode); - authRequest.ClaimedIdentifier = this.DiscoveryResult.ClaimedIdentifier; - authRequest.LocalIdentifier = this.DiscoveryResult.ProviderLocalIdentifier; - request = authRequest; - } else { - request = new SignedResponseRequest(this.DiscoveryResult.Version, this.DiscoveryResult.ProviderEndpoint, this.Mode); - } - request.Realm = this.Realm; - request.ReturnTo = this.ReturnToUrl; - request.AssociationHandle = association != null ? association.Handle : null; - request.SignReturnTo = this.returnToArgsMustBeSigned; - request.AddReturnToArguments(this.returnToArgs); - if (this.DiscoveryResult.UserSuppliedIdentifier != null && DotNetOpenAuthSection.Configuration.OpenId.RelyingParty.PreserveUserSuppliedIdentifier) { - request.AddReturnToArguments(UserSuppliedIdentifierParameterName, this.DiscoveryResult.UserSuppliedIdentifier.OriginalString); - } - foreach (IOpenIdMessageExtension extension in this.extensions) { - request.Extensions.Add(extension); - } - - return request; - } - - /// <summary> - /// Gets the association to use for this authentication request. - /// </summary> - /// <returns>The association to use; <c>null</c> to use 'dumb mode'.</returns> - private Association GetAssociation() { - Association association = null; - switch (this.associationPreference) { - case AssociationPreference.IfPossible: - association = this.RelyingParty.AssociationManager.GetOrCreateAssociation(this.DiscoveryResult); - if (association == null) { - // Avoid trying to create the association again if the redirecting response - // is generated again. - this.associationPreference = AssociationPreference.IfAlreadyEstablished; - } - break; - case AssociationPreference.IfAlreadyEstablished: - association = this.RelyingParty.AssociationManager.GetExistingAssociation(this.DiscoveryResult); - break; - case AssociationPreference.Never: - break; - default: - throw new InternalErrorException(); - } - - return association; - } - } -} diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/AuthenticationRequestMode.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/AuthenticationRequestMode.cs deleted file mode 100644 index 70b7f3a..0000000 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/AuthenticationRequestMode.cs +++ /dev/null @@ -1,26 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="AuthenticationRequestMode.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OpenId.RelyingParty { - /// <summary> - /// Indicates the mode the Provider should use while authenticating the end user. - /// </summary> - public enum AuthenticationRequestMode { - /// <summary> - /// The Provider should use whatever credentials are immediately available - /// to determine whether the end user owns the Identifier. If sufficient - /// credentials (i.e. cookies) are not immediately available, the Provider - /// should fail rather than prompt the user. - /// </summary> - Immediate, - - /// <summary> - /// The Provider should determine whether the end user owns the Identifier, - /// displaying a web page to the user to login etc., if necessary. - /// </summary> - Setup, - } -} diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/AuthenticationStatus.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/AuthenticationStatus.cs deleted file mode 100644 index d9e5d0a..0000000 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/AuthenticationStatus.cs +++ /dev/null @@ -1,43 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="AuthenticationStatus.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OpenId.RelyingParty { - /// <summary> - /// An enumeration of the possible results of an authentication attempt. - /// </summary> - public enum AuthenticationStatus { - /// <summary> - /// The authentication was canceled by the user agent while at the provider. - /// </summary> - Canceled, - - /// <summary> - /// The authentication failed because an error was detected in the OpenId communication. - /// </summary> - Failed, - - /// <summary> - /// <para>The Provider responded to a request for immediate authentication approval - /// with a message stating that additional user agent interaction is required - /// before authentication can be completed.</para> - /// <para>Casting the <see cref="IAuthenticationResponse"/> to a - /// <see cref="ISetupRequiredAuthenticationResponse"/> in this case can help - /// you retry the authentication using setup (non-immediate) mode.</para> - /// </summary> - SetupRequired, - - /// <summary> - /// Authentication is completed successfully. - /// </summary> - Authenticated, - - /// <summary> - /// The Provider sent a message that did not contain an identity assertion, - /// but may carry OpenID extensions. - /// </summary> - ExtensionsOnly, - } -} diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/CryptoKeyStoreAsRelyingPartyAssociationStore.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/CryptoKeyStoreAsRelyingPartyAssociationStore.cs deleted file mode 100644 index 02ed3b0..0000000 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/CryptoKeyStoreAsRelyingPartyAssociationStore.cs +++ /dev/null @@ -1,87 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="CryptoKeyStoreAsRelyingPartyAssociationStore.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OpenId.RelyingParty { - using System; - using System.Diagnostics.Contracts; - using System.Linq; - using DotNetOpenAuth.Messaging.Bindings; - - /// <summary> - /// Wraps a standard <see cref="ICryptoKeyStore"/> so that it behaves as an association store. - /// </summary> - internal class CryptoKeyStoreAsRelyingPartyAssociationStore : IRelyingPartyAssociationStore { - /// <summary> - /// The underlying key store. - /// </summary> - private readonly ICryptoKeyStore keyStore; - - /// <summary> - /// Initializes a new instance of the <see cref="CryptoKeyStoreAsRelyingPartyAssociationStore"/> class. - /// </summary> - /// <param name="keyStore">The key store.</param> - internal CryptoKeyStoreAsRelyingPartyAssociationStore(ICryptoKeyStore keyStore) { - Contract.Requires<ArgumentNullException>(keyStore != null); - Contract.Ensures(this.keyStore == keyStore); - this.keyStore = keyStore; - } - - /// <summary> - /// Saves an <see cref="Association"/> for later recall. - /// </summary> - /// <param name="providerEndpoint">The OP Endpoint with which the association is established.</param> - /// <param name="association">The association to store.</param> - public void StoreAssociation(Uri providerEndpoint, Association association) { - var cryptoKey = new CryptoKey(association.SerializePrivateData(), association.Expires); - this.keyStore.StoreKey(providerEndpoint.AbsoluteUri, association.Handle, cryptoKey); - } - - /// <summary> - /// Gets the best association (the one with the longest remaining life) for a given key. - /// </summary> - /// <param name="providerEndpoint">The OP Endpoint with which the association is established.</param> - /// <param name="securityRequirements">The security requirements that the returned association must meet.</param> - /// <returns> - /// The requested association, or null if no unexpired <see cref="Association"/>s exist for the given key. - /// </returns> - public Association GetAssociation(Uri providerEndpoint, SecuritySettings securityRequirements) { - var matches = from cryptoKey in this.keyStore.GetKeys(providerEndpoint.AbsoluteUri) - where cryptoKey.Value.ExpiresUtc > DateTime.UtcNow - orderby cryptoKey.Value.ExpiresUtc descending - let assoc = Association.Deserialize(cryptoKey.Key, cryptoKey.Value.ExpiresUtc, cryptoKey.Value.Key) - where assoc.HashBitLength >= securityRequirements.MinimumHashBitLength - where assoc.HashBitLength <= securityRequirements.MaximumHashBitLength - select assoc; - return matches.FirstOrDefault(); - } - - /// <summary> - /// Gets the association for a given key and handle. - /// </summary> - /// <param name="providerEndpoint">The OP Endpoint with which the association is established.</param> - /// <param name="handle">The handle of the specific association that must be recalled.</param> - /// <returns> - /// The requested association, or null if no unexpired <see cref="Association"/>s exist for the given key and handle. - /// </returns> - public Association GetAssociation(Uri providerEndpoint, string handle) { - var cryptoKey = this.keyStore.GetKey(providerEndpoint.AbsoluteUri, handle); - return cryptoKey != null ? Association.Deserialize(handle, cryptoKey.ExpiresUtc, cryptoKey.Key) : null; - } - - /// <summary> - /// Removes a specified handle that may exist in the store. - /// </summary> - /// <param name="providerEndpoint">The OP Endpoint with which the association is established.</param> - /// <param name="handle">The handle of the specific association that must be deleted.</param> - /// <returns> - /// True if the association existed in this store previous to this call. - /// </returns> - public bool RemoveAssociation(Uri providerEndpoint, string handle) { - this.keyStore.RemoveKey(providerEndpoint.AbsoluteUri, handle); - return true; // return value isn't used by DNOA. - } - } -} diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/FailedAuthenticationResponse.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/FailedAuthenticationResponse.cs deleted file mode 100644 index 682e3ff..0000000 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/FailedAuthenticationResponse.cs +++ /dev/null @@ -1,299 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="FailedAuthenticationResponse.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OpenId.RelyingParty { - using System; - using System.Collections.Generic; - using System.Diagnostics; - using System.Diagnostics.Contracts; - using System.Globalization; - using System.Text; - using System.Web; - using DotNetOpenAuth.Messaging; - using DotNetOpenAuth.OpenId; - using DotNetOpenAuth.OpenId.Messages; - using DotNetOpenAuth.OpenId.RelyingParty; - - /// <summary> - /// Wraps a failed authentication response in an <see cref="IAuthenticationResponse"/> instance - /// for public consumption by the host web site. - /// </summary> - [DebuggerDisplay("{Exception.Message}")] - internal class FailedAuthenticationResponse : IAuthenticationResponse { - /// <summary> - /// Initializes a new instance of the <see cref="FailedAuthenticationResponse"/> class. - /// </summary> - /// <param name="exception">The exception that resulted in the failed authentication.</param> - internal FailedAuthenticationResponse(Exception exception) { - Contract.Requires<ArgumentNullException>(exception != null); - - this.Exception = exception; - - string category = string.Empty; - if (Reporting.Enabled) { - var pe = exception as ProtocolException; - if (pe != null) { - var responseMessage = pe.FaultedMessage as IndirectSignedResponse; - if (responseMessage != null && responseMessage.ProviderEndpoint != null) { // check "required" parts because this is a failure after all - category = responseMessage.ProviderEndpoint.AbsoluteUri; - } - } - - Reporting.RecordEventOccurrence(this, category); - } - } - - #region IAuthenticationResponse Members - - /// <summary> - /// Gets the Identifier that the end user claims to own. For use with user database storage and lookup. - /// May be null for some failed authentications (i.e. failed directed identity authentications). - /// </summary> - /// <value></value> - /// <remarks> - /// <para> - /// This is the secure identifier that should be used for database storage and lookup. - /// It is not always friendly (i.e. =Arnott becomes =!9B72.7DD1.50A9.5CCD), but it protects - /// user identities against spoofing and other attacks. - /// </para> - /// <para> - /// For user-friendly identifiers to display, use the - /// <see cref="FriendlyIdentifierForDisplay"/> property. - /// </para> - /// </remarks> - public Identifier ClaimedIdentifier { - get { return null; } - } - - /// <summary> - /// Gets a user-friendly OpenID Identifier for display purposes ONLY. - /// </summary> - /// <value></value> - /// <remarks> - /// <para> - /// This <i>should</i> be put through <see cref="HttpUtility.HtmlEncode(string)"/> before - /// sending to a browser to secure against javascript injection attacks. - /// </para> - /// <para> - /// This property retains some aspects of the user-supplied identifier that get lost - /// in the <see cref="ClaimedIdentifier"/>. For example, XRIs used as user-supplied - /// identifiers (i.e. =Arnott) become unfriendly unique strings (i.e. =!9B72.7DD1.50A9.5CCD). - /// For display purposes, such as text on a web page that says "You're logged in as ...", - /// this property serves to provide the =Arnott string, or whatever else is the most friendly - /// string close to what the user originally typed in. - /// </para> - /// <para> - /// If the user-supplied identifier is a URI, this property will be the URI after all - /// redirects, and with the protocol and fragment trimmed off. - /// If the user-supplied identifier is an XRI, this property will be the original XRI. - /// If the user-supplied identifier is an OpenID Provider identifier (i.e. yahoo.com), - /// this property will be the Claimed Identifier, with the protocol stripped if it is a URI. - /// </para> - /// <para> - /// It is <b>very</b> important that this property <i>never</i> be used for database storage - /// or lookup to avoid identity spoofing and other security risks. For database storage - /// and lookup please use the <see cref="ClaimedIdentifier"/> property. - /// </para> - /// </remarks> - public string FriendlyIdentifierForDisplay { - get { return null; } - } - - /// <summary> - /// Gets the detailed success or failure status of the authentication attempt. - /// </summary> - /// <value></value> - public AuthenticationStatus Status { - get { return AuthenticationStatus.Failed; } - } - - /// <summary> - /// Gets information about the OpenId Provider, as advertised by the - /// OpenID discovery documents found at the <see cref="ClaimedIdentifier"/> - /// location. - /// </summary> - /// <value> - /// The Provider endpoint that issued the positive assertion; - /// or <c>null</c> if information about the Provider is unavailable. - /// </value> - public IProviderEndpoint Provider { - get { return null; } - } - - /// <summary> - /// Gets the details regarding a failed authentication attempt, if available. - /// This will be set if and only if <see cref="Status"/> is <see cref="AuthenticationStatus.Failed"/>. - /// </summary> - public Exception Exception { get; private set; } - - /// <summary> - /// Gets all the callback arguments that were previously added using - /// <see cref="IAuthenticationRequest.AddCallbackArguments(string, string)"/> or as a natural part - /// of the return_to URL. - /// </summary> - /// <returns>A name-value dictionary. Never null.</returns> - /// <remarks> - /// <para>This MAY return any argument on the querystring that came with the authentication response, - /// which may include parameters not explicitly added using - /// <see cref="IAuthenticationRequest.AddCallbackArguments(string, string)"/>.</para> - /// <para>Note that these values are NOT protected against tampering in transit.</para> - /// </remarks> - public IDictionary<string, string> GetCallbackArguments() { - return EmptyDictionary<string, string>.Instance; - } - - /// <summary> - /// Gets all the callback arguments that were previously added using - /// <see cref="IAuthenticationRequest.AddCallbackArguments(string, string)"/> or as a natural part - /// of the return_to URL. - /// </summary> - /// <returns>A name-value dictionary. Never null.</returns> - /// <remarks> - /// Callback parameters are only available even if the RP is in stateless mode, - /// or the callback parameters are otherwise unverifiable as untampered with. - /// Therefore, use this method only when the callback argument is not to be - /// used to make a security-sensitive decision. - /// </remarks> - public IDictionary<string, string> GetUntrustedCallbackArguments() { - return EmptyDictionary<string, string>.Instance; - } - - /// <summary> - /// Gets a callback argument's value that was previously added using - /// <see cref="IAuthenticationRequest.AddCallbackArguments(string, string)"/>. - /// </summary> - /// <param name="key">The name of the parameter whose value is sought.</param> - /// <returns> - /// The value of the argument, or null if the named parameter could not be found. - /// </returns> - /// <remarks> - /// <para>This may return any argument on the querystring that came with the authentication response, - /// which may include parameters not explicitly added using - /// <see cref="IAuthenticationRequest.AddCallbackArguments(string, string)"/>.</para> - /// <para>Note that these values are NOT protected against tampering in transit.</para> - /// </remarks> - public string GetCallbackArgument(string key) { - return null; - } - - /// <summary> - /// Gets a callback argument's value that was previously added using - /// <see cref="IAuthenticationRequest.AddCallbackArguments(string, string)"/>. - /// </summary> - /// <param name="key">The name of the parameter whose value is sought.</param> - /// <returns> - /// The value of the argument, or null if the named parameter could not be found. - /// </returns> - /// <remarks> - /// Callback parameters are only available even if the RP is in stateless mode, - /// or the callback parameters are otherwise unverifiable as untampered with. - /// Therefore, use this method only when the callback argument is not to be - /// used to make a security-sensitive decision. - /// </remarks> - public string GetUntrustedCallbackArgument(string key) { - return null; - } - - /// <summary> - /// Tries to get an OpenID extension that may be present in the response. - /// </summary> - /// <typeparam name="T">The type of extension to look for in the response message.</typeparam> - /// <returns> - /// The extension, if it is found. Null otherwise. - /// </returns> - /// <remarks> - /// <para>Extensions are returned only if the Provider signed them. - /// Relying parties that do not care if the values were modified in - /// transit should use the <see cref="GetUntrustedExtension<T>"/> method - /// in order to allow the Provider to not sign the extension. </para> - /// <para>Unsigned extensions are completely unreliable and should be - /// used only to prefill user forms since the user or any other third - /// party may have tampered with the data carried by the extension.</para> - /// <para>Signed extensions are only reliable if the relying party - /// trusts the OpenID Provider that signed them. Signing does not mean - /// the relying party can trust the values -- it only means that the values - /// have not been tampered with since the Provider sent the message.</para> - /// </remarks> - public T GetExtension<T>() where T : IOpenIdMessageExtension { - return default(T); - } - - /// <summary> - /// Tries to get an OpenID extension that may be present in the response. - /// </summary> - /// <param name="extensionType">Type of the extension to look for in the response.</param> - /// <returns> - /// The extension, if it is found. Null otherwise. - /// </returns> - /// <remarks> - /// <para>Extensions are returned only if the Provider signed them. - /// Relying parties that do not care if the values were modified in - /// transit should use the <see cref="GetUntrustedExtension"/> method - /// in order to allow the Provider to not sign the extension. </para> - /// <para>Unsigned extensions are completely unreliable and should be - /// used only to prefill user forms since the user or any other third - /// party may have tampered with the data carried by the extension.</para> - /// <para>Signed extensions are only reliable if the relying party - /// trusts the OpenID Provider that signed them. Signing does not mean - /// the relying party can trust the values -- it only means that the values - /// have not been tampered with since the Provider sent the message.</para> - /// </remarks> - public IOpenIdMessageExtension GetExtension(Type extensionType) { - return null; - } - - /// <summary> - /// Tries to get an OpenID extension that may be present in the response, without - /// requiring it to be signed by the Provider. - /// </summary> - /// <typeparam name="T">The type of extension to look for in the response message.</typeparam> - /// <returns> - /// The extension, if it is found. Null otherwise. - /// </returns> - /// <remarks> - /// <para>Extensions are returned whether they are signed or not. - /// Use the <see cref="GetExtension<T>"/> method to retrieve - /// extension responses only if they are signed by the Provider to - /// protect against tampering. </para> - /// <para>Unsigned extensions are completely unreliable and should be - /// used only to prefill user forms since the user or any other third - /// party may have tampered with the data carried by the extension.</para> - /// <para>Signed extensions are only reliable if the relying party - /// trusts the OpenID Provider that signed them. Signing does not mean - /// the relying party can trust the values -- it only means that the values - /// have not been tampered with since the Provider sent the message.</para> - /// </remarks> - public T GetUntrustedExtension<T>() where T : IOpenIdMessageExtension { - return default(T); - } - - /// <summary> - /// Tries to get an OpenID extension that may be present in the response. - /// </summary> - /// <param name="extensionType">Type of the extension to look for in the response.</param> - /// <returns> - /// The extension, if it is found. Null otherwise. - /// </returns> - /// <remarks> - /// <para>Extensions are returned whether they are signed or not. - /// Use the <see cref="GetExtension"/> method to retrieve - /// extension responses only if they are signed by the Provider to - /// protect against tampering. </para> - /// <para>Unsigned extensions are completely unreliable and should be - /// used only to prefill user forms since the user or any other third - /// party may have tampered with the data carried by the extension.</para> - /// <para>Signed extensions are only reliable if the relying party - /// trusts the OpenID Provider that signed them. Signing does not mean - /// the relying party can trust the values -- it only means that the values - /// have not been tampered with since the Provider sent the message.</para> - /// </remarks> - public IOpenIdMessageExtension GetUntrustedExtension(Type extensionType) { - return null; - } - - #endregion - } -} diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/IAuthenticationRequest.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/IAuthenticationRequest.cs deleted file mode 100644 index 65db0bd..0000000 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/IAuthenticationRequest.cs +++ /dev/null @@ -1,186 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="IAuthenticationRequest.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OpenId.RelyingParty { - using System; - using System.Collections.Generic; - using System.Diagnostics.Contracts; - using System.Linq; - using System.Text; - using DotNetOpenAuth.Messaging; - using DotNetOpenAuth.OpenId.Messages; - - /// <summary> - /// Instances of this interface represent relying party authentication - /// requests that may be queried/modified in specific ways before being - /// routed to the OpenID Provider. - /// </summary> - [ContractClass(typeof(IAuthenticationRequestContract))] - public interface IAuthenticationRequest { - /// <summary> - /// Gets or sets the mode the Provider should use during authentication. - /// </summary> - AuthenticationRequestMode Mode { get; set; } - - /// <summary> - /// Gets the HTTP response the relying party should send to the user agent - /// to redirect it to the OpenID Provider to start the OpenID authentication process. - /// </summary> - OutgoingWebResponse RedirectingResponse { get; } - - /// <summary> - /// Gets the URL that the user agent will return to after authentication - /// completes or fails at the Provider. - /// </summary> - Uri ReturnToUrl { get; } - - /// <summary> - /// Gets the URL that identifies this consumer web application that - /// the Provider will display to the end user. - /// </summary> - Realm Realm { get; } - - /// <summary> - /// Gets the Claimed Identifier that the User Supplied Identifier - /// resolved to. Null if the user provided an OP Identifier - /// (directed identity). - /// </summary> - /// <remarks> - /// Null is returned if the user is using the directed identity feature - /// of OpenID 2.0 to make it nearly impossible for a relying party site - /// to improperly store the reserved OpenID URL used for directed identity - /// as a user's own Identifier. - /// However, to test for the Directed Identity feature, please test the - /// <see cref="IsDirectedIdentity"/> property rather than testing this - /// property for a null value. - /// </remarks> - Identifier ClaimedIdentifier { get; } - - /// <summary> - /// Gets a value indicating whether the authenticating user has chosen to let the Provider - /// determine and send the ClaimedIdentifier after authentication. - /// </summary> - bool IsDirectedIdentity { get; } - - /// <summary> - /// Gets or sets a value indicating whether this request only carries extensions - /// and is not a request to verify that the user controls some identifier. - /// </summary> - /// <value> - /// <c>true</c> if this request is merely a carrier of extensions and is not - /// about an OpenID identifier; otherwise, <c>false</c>. - /// </value> - /// <remarks> - /// <para>Although OpenID is first and primarily an authentication protocol, its extensions - /// can be interesting all by themselves. For instance, a relying party might want - /// to know that its user is over 21 years old, or perhaps a member of some organization. - /// OpenID extensions can provide this, without any need for asserting the identity of the user.</para> - /// <para>Constructing an OpenID request for only extensions can be done by calling - /// <see cref="OpenIdRelyingParty.CreateRequest(Identifier)"/> with any valid OpenID identifier - /// (claimed identifier or OP identifier). But once this property is set to <c>true</c>, - /// the claimed identifier value in the request is not included in the transmitted message.</para> - /// <para>It is anticipated that an RP would only issue these types of requests to OPs that - /// trusts to make assertions regarding the individual holding an account at that OP, so it - /// is not likely that the RP would allow the user to type in an arbitrary claimed identifier - /// without checking that it resolved to an OP endpoint the RP has on a trust whitelist.</para> - /// </remarks> - bool IsExtensionOnly { get; set; } - - /// <summary> - /// Gets information about the OpenId Provider, as advertised by the - /// OpenID discovery documents found at the <see cref="ClaimedIdentifier"/> - /// location. - /// </summary> - IProviderEndpoint Provider { get; } - - /// <summary> - /// Gets the discovery result leading to the formulation of this request. - /// </summary> - /// <value>The discovery result.</value> - IdentifierDiscoveryResult DiscoveryResult { get; } - - /// <summary> - /// Makes a dictionary of key/value pairs available when the authentication is completed. - /// </summary> - /// <param name="arguments">The arguments to add to the request's return_to URI. Values must not be null.</param> - /// <remarks> - /// <para>Note that these values are NOT protected against eavesdropping in transit. No - /// privacy-sensitive data should be stored using this method.</para> - /// <para>The values stored here can be retrieved using - /// <see cref="IAuthenticationResponse.GetCallbackArgument"/>, which will only return the value - /// if it can be verified as untampered with in transit.</para> - /// <para>Since the data set here is sent in the querystring of the request and some - /// servers place limits on the size of a request URL, this data should be kept relatively - /// small to ensure successful authentication. About 1.5KB is about all that should be stored.</para> - /// </remarks> - void AddCallbackArguments(IDictionary<string, string> arguments); - - /// <summary> - /// Makes a key/value pair available when the authentication is completed. - /// </summary> - /// <param name="key">The parameter name.</param> - /// <param name="value">The value of the argument. Must not be null.</param> - /// <remarks> - /// <para>Note that these values are NOT protected against eavesdropping in transit. No - /// privacy-sensitive data should be stored using this method.</para> - /// <para>The value stored here can be retrieved using - /// <see cref="IAuthenticationResponse.GetCallbackArgument"/>, which will only return the value - /// if it can be verified as untampered with in transit.</para> - /// <para>Since the data set here is sent in the querystring of the request and some - /// servers place limits on the size of a request URL, this data should be kept relatively - /// small to ensure successful authentication. About 1.5KB is about all that should be stored.</para> - /// </remarks> - void AddCallbackArguments(string key, string value); - - /// <summary> - /// Makes a key/value pair available when the authentication is completed. - /// </summary> - /// <param name="key">The parameter name.</param> - /// <param name="value">The value of the argument. Must not be null.</param> - /// <remarks> - /// <para>Note that these values are NOT protected against eavesdropping in transit. No - /// security-sensitive data should be stored using this method.</para> - /// <para>The value stored here can be retrieved using - /// <see cref="IAuthenticationResponse.GetCallbackArgument"/>.</para> - /// <para>Since the data set here is sent in the querystring of the request and some - /// servers place limits on the size of a request URL, this data should be kept relatively - /// small to ensure successful authentication. About 1.5KB is about all that should be stored.</para> - /// </remarks> - void SetCallbackArgument(string key, string value); - - /// <summary> - /// Makes a key/value pair available when the authentication is completed without - /// requiring a return_to signature to protect against tampering of the callback argument. - /// </summary> - /// <param name="key">The parameter name.</param> - /// <param name="value">The value of the argument. Must not be null.</param> - /// <remarks> - /// <para>Note that these values are NOT protected against eavesdropping or tampering in transit. No - /// security-sensitive data should be stored using this method. </para> - /// <para>The value stored here can be retrieved using - /// <see cref="IAuthenticationResponse.GetCallbackArgument"/>.</para> - /// <para>Since the data set here is sent in the querystring of the request and some - /// servers place limits on the size of a request URL, this data should be kept relatively - /// small to ensure successful authentication. About 1.5KB is about all that should be stored.</para> - /// </remarks> - void SetUntrustedCallbackArgument(string key, string value); - - /// <summary> - /// Adds an OpenID extension to the request directed at the OpenID provider. - /// </summary> - /// <param name="extension">The initialized extension to add to the request.</param> - void AddExtension(IOpenIdMessageExtension extension); - - /// <summary> - /// Redirects the user agent to the provider for authentication. - /// Execution of the current page terminates after this call. - /// </summary> - /// <remarks> - /// This method requires an ASP.NET HttpContext. - /// </remarks> - void RedirectToProvider(); - } -} diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/IAuthenticationRequestContract.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/IAuthenticationRequestContract.cs deleted file mode 100644 index cd36cc7..0000000 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/IAuthenticationRequestContract.cs +++ /dev/null @@ -1,111 +0,0 @@ -// <auto-generated /> - -namespace DotNetOpenAuth.OpenId.RelyingParty { - using System; - using System.Collections.Generic; - using System.Diagnostics.Contracts; - using System.Linq; - using System.Text; - using DotNetOpenAuth.Messaging; - using DotNetOpenAuth.OpenId.Messages; - - [ContractClassFor(typeof(IAuthenticationRequest))] - internal abstract class IAuthenticationRequestContract : IAuthenticationRequest { - #region IAuthenticationRequest Members - - AuthenticationRequestMode IAuthenticationRequest.Mode { - get { - throw new NotImplementedException(); - } - - set { - throw new NotImplementedException(); - } - } - - OutgoingWebResponse IAuthenticationRequest.RedirectingResponse { - get { throw new NotImplementedException(); } - } - - Uri IAuthenticationRequest.ReturnToUrl { - get { throw new NotImplementedException(); } - } - - Realm IAuthenticationRequest.Realm { - get { - Contract.Ensures(Contract.Result<Realm>() != null); - throw new NotImplementedException(); - } - } - - Identifier IAuthenticationRequest.ClaimedIdentifier { - get { - throw new NotImplementedException(); - } - } - - bool IAuthenticationRequest.IsDirectedIdentity { - get { throw new NotImplementedException(); } - } - - bool IAuthenticationRequest.IsExtensionOnly { - get { - throw new NotImplementedException(); - } - - set { - throw new NotImplementedException(); - } - } - - IProviderEndpoint IAuthenticationRequest.Provider { - get { - Contract.Ensures(Contract.Result<IProviderEndpoint>() != null); - throw new NotImplementedException(); - } - } - - IdentifierDiscoveryResult IAuthenticationRequest.DiscoveryResult { - get { - Contract.Ensures(Contract.Result<IdentifierDiscoveryResult>() != null); - throw new NotImplementedException(); - } - } - - void IAuthenticationRequest.AddCallbackArguments(IDictionary<string, string> arguments) { - Contract.Requires<ArgumentNullException>(arguments != null); - Contract.Requires<ArgumentException>(arguments.Keys.All(k => !String.IsNullOrEmpty(k))); - Contract.Requires<ArgumentException>(arguments.Values.All(v => v != null)); - throw new NotImplementedException(); - } - - void IAuthenticationRequest.AddCallbackArguments(string key, string value) { - Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(key)); - Contract.Requires<ArgumentNullException>(value != null); - throw new NotImplementedException(); - } - - void IAuthenticationRequest.SetCallbackArgument(string key, string value) { - Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(key)); - Contract.Requires<ArgumentNullException>(value != null); - throw new NotImplementedException(); - } - - void IAuthenticationRequest.AddExtension(IOpenIdMessageExtension extension) { - Contract.Requires<ArgumentNullException>(extension != null); - throw new NotImplementedException(); - } - - void IAuthenticationRequest.RedirectToProvider() { - throw new NotImplementedException(); - } - - void IAuthenticationRequest.SetUntrustedCallbackArgument(string key, string value) { - Contract.Requires<ArgumentException>(!string.IsNullOrEmpty(key)); - Contract.Requires<ArgumentNullException>(value != null); - throw new NotImplementedException(); - } - - #endregion - } -} diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/IAuthenticationResponse.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/IAuthenticationResponse.cs deleted file mode 100644 index a24220f..0000000 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/IAuthenticationResponse.cs +++ /dev/null @@ -1,532 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="IAuthenticationResponse.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OpenId.RelyingParty { - using System; - using System.Collections.Generic; - using System.Diagnostics.CodeAnalysis; - using System.Diagnostics.Contracts; - using System.Globalization; - using System.Text; - using System.Web; - using DotNetOpenAuth.OpenId.Extensions; - using DotNetOpenAuth.OpenId.Messages; - - /// <summary> - /// An instance of this interface represents an identity assertion - /// from an OpenID Provider. It may be in response to an authentication - /// request previously put to it by a Relying Party site or it may be an - /// unsolicited assertion. - /// </summary> - /// <remarks> - /// Relying party web sites should handle both solicited and unsolicited - /// assertions. This interface does not offer a way to discern between - /// solicited and unsolicited assertions as they should be treated equally. - /// </remarks> - [ContractClass(typeof(IAuthenticationResponseContract))] - public interface IAuthenticationResponse { - /// <summary> - /// Gets the Identifier that the end user claims to own. For use with user database storage and lookup. - /// May be null for some failed authentications (i.e. failed directed identity authentications). - /// </summary> - /// <remarks> - /// <para> - /// This is the secure identifier that should be used for database storage and lookup. - /// It is not always friendly (i.e. =Arnott becomes =!9B72.7DD1.50A9.5CCD), but it protects - /// user identities against spoofing and other attacks. - /// </para> - /// <para> - /// For user-friendly identifiers to display, use the - /// <see cref="FriendlyIdentifierForDisplay"/> property. - /// </para> - /// </remarks> - Identifier ClaimedIdentifier { get; } - - /// <summary> - /// Gets a user-friendly OpenID Identifier for display purposes ONLY. - /// </summary> - /// <remarks> - /// <para> - /// This <i>should</i> be put through <see cref="HttpUtility.HtmlEncode(string)"/> before - /// sending to a browser to secure against javascript injection attacks. - /// </para> - /// <para> - /// This property retains some aspects of the user-supplied identifier that get lost - /// in the <see cref="ClaimedIdentifier"/>. For example, XRIs used as user-supplied - /// identifiers (i.e. =Arnott) become unfriendly unique strings (i.e. =!9B72.7DD1.50A9.5CCD). - /// For display purposes, such as text on a web page that says "You're logged in as ...", - /// this property serves to provide the =Arnott string, or whatever else is the most friendly - /// string close to what the user originally typed in. - /// </para> - /// <para> - /// If the user-supplied identifier is a URI, this property will be the URI after all - /// redirects, and with the protocol and fragment trimmed off. - /// If the user-supplied identifier is an XRI, this property will be the original XRI. - /// If the user-supplied identifier is an OpenID Provider identifier (i.e. yahoo.com), - /// this property will be the Claimed Identifier, with the protocol stripped if it is a URI. - /// </para> - /// <para> - /// It is <b>very</b> important that this property <i>never</i> be used for database storage - /// or lookup to avoid identity spoofing and other security risks. For database storage - /// and lookup please use the <see cref="ClaimedIdentifier"/> property. - /// </para> - /// </remarks> - string FriendlyIdentifierForDisplay { get; } - - /// <summary> - /// Gets the detailed success or failure status of the authentication attempt. - /// </summary> - AuthenticationStatus Status { get; } - - /// <summary> - /// Gets information about the OpenId Provider, as advertised by the - /// OpenID discovery documents found at the <see cref="ClaimedIdentifier"/> - /// location, if available. - /// </summary> - /// <value> - /// The Provider endpoint that issued the positive assertion; - /// or <c>null</c> if information about the Provider is unavailable. - /// </value> - IProviderEndpoint Provider { get; } - - /// <summary> - /// Gets the details regarding a failed authentication attempt, if available. - /// This will be set if and only if <see cref="Status"/> is <see cref="AuthenticationStatus.Failed"/>. - /// </summary> - Exception Exception { get; } - - /// <summary> - /// Gets a callback argument's value that was previously added using - /// <see cref="IAuthenticationRequest.AddCallbackArguments(string, string)"/>. - /// </summary> - /// <param name="key">The name of the parameter whose value is sought.</param> - /// <returns> - /// The value of the argument, or null if the named parameter could not be found. - /// </returns> - /// <remarks> - /// Callback parameters are only available if they are complete and untampered with - /// since the original request message (as proven by a signature). - /// If the relying party is operating in stateless mode <c>null</c> is always - /// returned since the callback arguments could not be signed to protect against - /// tampering. - /// </remarks> - string GetCallbackArgument(string key); - - /// <summary> - /// Gets a callback argument's value that was previously added using - /// <see cref="IAuthenticationRequest.AddCallbackArguments(string, string)"/>. - /// </summary> - /// <param name="key">The name of the parameter whose value is sought.</param> - /// <returns> - /// The value of the argument, or null if the named parameter could not be found. - /// </returns> - /// <remarks> - /// Callback parameters are only available even if the RP is in stateless mode, - /// or the callback parameters are otherwise unverifiable as untampered with. - /// Therefore, use this method only when the callback argument is not to be - /// used to make a security-sensitive decision. - /// </remarks> - string GetUntrustedCallbackArgument(string key); - - /// <summary> - /// Gets all the callback arguments that were previously added using - /// <see cref="IAuthenticationRequest.AddCallbackArguments(string, string)"/> or as a natural part - /// of the return_to URL. - /// </summary> - /// <returns>A name-value dictionary. Never null.</returns> - /// <remarks> - /// Callback parameters are only available if they are complete and untampered with - /// since the original request message (as proven by a signature). - /// If the relying party is operating in stateless mode an empty dictionary is always - /// returned since the callback arguments could not be signed to protect against - /// tampering. - /// </remarks> - [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "Historically an expensive operation.")] - IDictionary<string, string> GetCallbackArguments(); - - /// <summary> - /// Gets all the callback arguments that were previously added using - /// <see cref="IAuthenticationRequest.AddCallbackArguments(string, string)"/> or as a natural part - /// of the return_to URL. - /// </summary> - /// <returns>A name-value dictionary. Never null.</returns> - /// <remarks> - /// Callback parameters are only available even if the RP is in stateless mode, - /// or the callback parameters are otherwise unverifiable as untampered with. - /// Therefore, use this method only when the callback argument is not to be - /// used to make a security-sensitive decision. - /// </remarks> - [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "Historically an expensive operation.")] - IDictionary<string, string> GetUntrustedCallbackArguments(); - - /// <summary> - /// Tries to get an OpenID extension that may be present in the response. - /// </summary> - /// <typeparam name="T">The type of extension to look for in the response message.</typeparam> - /// <returns> - /// The extension, if it is found. Null otherwise. - /// </returns> - /// <remarks> - /// <para>Extensions are returned only if the Provider signed them. - /// Relying parties that do not care if the values were modified in - /// transit should use the <see cref="GetUntrustedExtension<T>"/> method - /// in order to allow the Provider to not sign the extension. </para> - /// <para>Unsigned extensions are completely unreliable and should be - /// used only to prefill user forms since the user or any other third - /// party may have tampered with the data carried by the extension.</para> - /// <para>Signed extensions are only reliable if the relying party - /// trusts the OpenID Provider that signed them. Signing does not mean - /// the relying party can trust the values -- it only means that the values - /// have not been tampered with since the Provider sent the message.</para> - /// </remarks> - [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "No parameter at all is required. T is used for return type.")] - T GetExtension<T>() where T : IOpenIdMessageExtension; - - /// <summary> - /// Tries to get an OpenID extension that may be present in the response. - /// </summary> - /// <param name="extensionType">Type of the extension to look for in the response.</param> - /// <returns> - /// The extension, if it is found. Null otherwise. - /// </returns> - /// <remarks> - /// <para>Extensions are returned only if the Provider signed them. - /// Relying parties that do not care if the values were modified in - /// transit should use the <see cref="GetUntrustedExtension"/> method - /// in order to allow the Provider to not sign the extension. </para> - /// <para>Unsigned extensions are completely unreliable and should be - /// used only to prefill user forms since the user or any other third - /// party may have tampered with the data carried by the extension.</para> - /// <para>Signed extensions are only reliable if the relying party - /// trusts the OpenID Provider that signed them. Signing does not mean - /// the relying party can trust the values -- it only means that the values - /// have not been tampered with since the Provider sent the message.</para> - /// </remarks> - IOpenIdMessageExtension GetExtension(Type extensionType); - - /// <summary> - /// Tries to get an OpenID extension that may be present in the response, without - /// requiring it to be signed by the Provider. - /// </summary> - /// <typeparam name="T">The type of extension to look for in the response message.</typeparam> - /// <returns> - /// The extension, if it is found. Null otherwise. - /// </returns> - /// <remarks> - /// <para>Extensions are returned whether they are signed or not. - /// Use the <see cref="GetExtension<T>"/> method to retrieve - /// extension responses only if they are signed by the Provider to - /// protect against tampering. </para> - /// <para>Unsigned extensions are completely unreliable and should be - /// used only to prefill user forms since the user or any other third - /// party may have tampered with the data carried by the extension.</para> - /// <para>Signed extensions are only reliable if the relying party - /// trusts the OpenID Provider that signed them. Signing does not mean - /// the relying party can trust the values -- it only means that the values - /// have not been tampered with since the Provider sent the message.</para> - /// </remarks> - [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "No parameter at all is required. T is used for return type.")] - T GetUntrustedExtension<T>() where T : IOpenIdMessageExtension; - - /// <summary> - /// Tries to get an OpenID extension that may be present in the response, without - /// requiring it to be signed by the Provider. - /// </summary> - /// <param name="extensionType">Type of the extension to look for in the response.</param> - /// <returns> - /// The extension, if it is found. Null otherwise. - /// </returns> - /// <remarks> - /// <para>Extensions are returned whether they are signed or not. - /// Use the <see cref="GetExtension"/> method to retrieve - /// extension responses only if they are signed by the Provider to - /// protect against tampering. </para> - /// <para>Unsigned extensions are completely unreliable and should be - /// used only to prefill user forms since the user or any other third - /// party may have tampered with the data carried by the extension.</para> - /// <para>Signed extensions are only reliable if the relying party - /// trusts the OpenID Provider that signed them. Signing does not mean - /// the relying party can trust the values -- it only means that the values - /// have not been tampered with since the Provider sent the message.</para> - /// </remarks> - IOpenIdMessageExtension GetUntrustedExtension(Type extensionType); - } - - /// <summary> - /// Code contract for the <see cref="IAuthenticationResponse"/> type. - /// </summary> - [ContractClassFor(typeof(IAuthenticationResponse))] - internal abstract class IAuthenticationResponseContract : IAuthenticationResponse { - /// <summary> - /// Initializes a new instance of the <see cref="IAuthenticationResponseContract"/> class. - /// </summary> - protected IAuthenticationResponseContract() { - } - - #region IAuthenticationResponse Members - - /// <summary> - /// Gets the Identifier that the end user claims to own. For use with user database storage and lookup. - /// May be null for some failed authentications (i.e. failed directed identity authentications). - /// </summary> - /// <value></value> - /// <remarks> - /// <para> - /// This is the secure identifier that should be used for database storage and lookup. - /// It is not always friendly (i.e. =Arnott becomes =!9B72.7DD1.50A9.5CCD), but it protects - /// user identities against spoofing and other attacks. - /// </para> - /// <para> - /// For user-friendly identifiers to display, use the - /// <see cref="IAuthenticationResponse.FriendlyIdentifierForDisplay"/> property. - /// </para> - /// </remarks> - Identifier IAuthenticationResponse.ClaimedIdentifier { - get { throw new NotImplementedException(); } - } - - /// <summary> - /// Gets a user-friendly OpenID Identifier for display purposes ONLY. - /// </summary> - /// <value></value> - /// <remarks> - /// <para> - /// This <i>should</i> be put through <see cref="HttpUtility.HtmlEncode(string)"/> before - /// sending to a browser to secure against javascript injection attacks. - /// </para> - /// <para> - /// This property retains some aspects of the user-supplied identifier that get lost - /// in the <see cref="IAuthenticationResponse.ClaimedIdentifier"/>. For example, XRIs used as user-supplied - /// identifiers (i.e. =Arnott) become unfriendly unique strings (i.e. =!9B72.7DD1.50A9.5CCD). - /// For display purposes, such as text on a web page that says "You're logged in as ...", - /// this property serves to provide the =Arnott string, or whatever else is the most friendly - /// string close to what the user originally typed in. - /// </para> - /// <para> - /// If the user-supplied identifier is a URI, this property will be the URI after all - /// redirects, and with the protocol and fragment trimmed off. - /// If the user-supplied identifier is an XRI, this property will be the original XRI. - /// If the user-supplied identifier is an OpenID Provider identifier (i.e. yahoo.com), - /// this property will be the Claimed Identifier, with the protocol stripped if it is a URI. - /// </para> - /// <para> - /// It is <b>very</b> important that this property <i>never</i> be used for database storage - /// or lookup to avoid identity spoofing and other security risks. For database storage - /// and lookup please use the <see cref="IAuthenticationResponse.ClaimedIdentifier"/> property. - /// </para> - /// </remarks> - string IAuthenticationResponse.FriendlyIdentifierForDisplay { - get { throw new NotImplementedException(); } - } - - /// <summary> - /// Gets the detailed success or failure status of the authentication attempt. - /// </summary> - /// <value></value> - AuthenticationStatus IAuthenticationResponse.Status { - get { throw new NotImplementedException(); } - } - - /// <summary> - /// Gets information about the OpenId Provider, as advertised by the - /// OpenID discovery documents found at the <see cref="IAuthenticationResponse.ClaimedIdentifier"/> - /// location, if available. - /// </summary> - /// <value> - /// The Provider endpoint that issued the positive assertion; - /// or <c>null</c> if information about the Provider is unavailable. - /// </value> - IProviderEndpoint IAuthenticationResponse.Provider { - get { throw new NotImplementedException(); } - } - - /// <summary> - /// Gets the details regarding a failed authentication attempt, if available. - /// This will be set if and only if <see cref="IAuthenticationResponse.Status"/> is <see cref="AuthenticationStatus.Failed"/>. - /// </summary> - /// <value></value> - Exception IAuthenticationResponse.Exception { - get { throw new NotImplementedException(); } - } - - /// <summary> - /// Gets a callback argument's value that was previously added using - /// <see cref="IAuthenticationRequest.AddCallbackArguments(string, string)"/>. - /// </summary> - /// <param name="key">The name of the parameter whose value is sought.</param> - /// <returns> - /// The value of the argument, or null if the named parameter could not be found. - /// </returns> - /// <remarks> - /// <para>This may return any argument on the querystring that came with the authentication response, - /// which may include parameters not explicitly added using - /// <see cref="IAuthenticationRequest.AddCallbackArguments(string, string)"/>.</para> - /// <para>Note that these values are NOT protected against tampering in transit.</para> - /// </remarks> - string IAuthenticationResponse.GetCallbackArgument(string key) { - Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(key)); - throw new NotImplementedException(); - } - - /// <summary> - /// Gets all the callback arguments that were previously added using - /// <see cref="IAuthenticationRequest.AddCallbackArguments(string, string)"/> or as a natural part - /// of the return_to URL. - /// </summary> - /// <returns>A name-value dictionary. Never null.</returns> - /// <remarks> - /// <para>This MAY return any argument on the querystring that came with the authentication response, - /// which may include parameters not explicitly added using - /// <see cref="IAuthenticationRequest.AddCallbackArguments(string, string)"/>.</para> - /// <para>Note that these values are NOT protected against tampering in transit.</para> - /// </remarks> - IDictionary<string, string> IAuthenticationResponse.GetCallbackArguments() { - throw new NotImplementedException(); - } - - /// <summary> - /// Tries to get an OpenID extension that may be present in the response. - /// </summary> - /// <typeparam name="T">The type of extension to look for in the response message.</typeparam> - /// <returns> - /// The extension, if it is found. Null otherwise. - /// </returns> - /// <remarks> - /// <para>Extensions are returned only if the Provider signed them. - /// Relying parties that do not care if the values were modified in - /// transit should use the <see cref="IAuthenticationResponse.GetUntrustedExtension<T>"/> method - /// in order to allow the Provider to not sign the extension. </para> - /// <para>Unsigned extensions are completely unreliable and should be - /// used only to prefill user forms since the user or any other third - /// party may have tampered with the data carried by the extension.</para> - /// <para>Signed extensions are only reliable if the relying party - /// trusts the OpenID Provider that signed them. Signing does not mean - /// the relying party can trust the values -- it only means that the values - /// have not been tampered with since the Provider sent the message.</para> - /// </remarks> - T IAuthenticationResponse.GetExtension<T>() { - throw new NotImplementedException(); - } - - /// <summary> - /// Tries to get an OpenID extension that may be present in the response. - /// </summary> - /// <param name="extensionType">Type of the extension to look for in the response.</param> - /// <returns> - /// The extension, if it is found. Null otherwise. - /// </returns> - /// <remarks> - /// <para>Extensions are returned only if the Provider signed them. - /// Relying parties that do not care if the values were modified in - /// transit should use the <see cref="IAuthenticationResponse.GetUntrustedExtension"/> method - /// in order to allow the Provider to not sign the extension. </para> - /// <para>Unsigned extensions are completely unreliable and should be - /// used only to prefill user forms since the user or any other third - /// party may have tampered with the data carried by the extension.</para> - /// <para>Signed extensions are only reliable if the relying party - /// trusts the OpenID Provider that signed them. Signing does not mean - /// the relying party can trust the values -- it only means that the values - /// have not been tampered with since the Provider sent the message.</para> - /// </remarks> - IOpenIdMessageExtension IAuthenticationResponse.GetExtension(Type extensionType) { - Contract.Requires<ArgumentNullException>(extensionType != null); - Contract.Requires<ArgumentException>(typeof(IOpenIdMessageExtension).IsAssignableFrom(extensionType)); - ////ErrorUtilities.VerifyArgument(typeof(IOpenIdMessageExtension).IsAssignableFrom(extensionType), string.Format(CultureInfo.CurrentCulture, OpenIdStrings.TypeMustImplementX, typeof(IOpenIdMessageExtension).FullName)); - throw new NotImplementedException(); - } - - /// <summary> - /// Tries to get an OpenID extension that may be present in the response, without - /// requiring it to be signed by the Provider. - /// </summary> - /// <typeparam name="T">The type of extension to look for in the response message.</typeparam> - /// <returns> - /// The extension, if it is found. Null otherwise. - /// </returns> - /// <remarks> - /// <para>Extensions are returned whether they are signed or not. - /// Use the <see cref="IAuthenticationResponse.GetExtension<T>"/> method to retrieve - /// extension responses only if they are signed by the Provider to - /// protect against tampering. </para> - /// <para>Unsigned extensions are completely unreliable and should be - /// used only to prefill user forms since the user or any other third - /// party may have tampered with the data carried by the extension.</para> - /// <para>Signed extensions are only reliable if the relying party - /// trusts the OpenID Provider that signed them. Signing does not mean - /// the relying party can trust the values -- it only means that the values - /// have not been tampered with since the Provider sent the message.</para> - /// </remarks> - T IAuthenticationResponse.GetUntrustedExtension<T>() { - throw new NotImplementedException(); - } - - /// <summary> - /// Tries to get an OpenID extension that may be present in the response, without - /// requiring it to be signed by the Provider. - /// </summary> - /// <param name="extensionType">Type of the extension to look for in the response.</param> - /// <returns> - /// The extension, if it is found. Null otherwise. - /// </returns> - /// <remarks> - /// <para>Extensions are returned whether they are signed or not. - /// Use the <see cref="IAuthenticationResponse.GetExtension"/> method to retrieve - /// extension responses only if they are signed by the Provider to - /// protect against tampering. </para> - /// <para>Unsigned extensions are completely unreliable and should be - /// used only to prefill user forms since the user or any other third - /// party may have tampered with the data carried by the extension.</para> - /// <para>Signed extensions are only reliable if the relying party - /// trusts the OpenID Provider that signed them. Signing does not mean - /// the relying party can trust the values -- it only means that the values - /// have not been tampered with since the Provider sent the message.</para> - /// </remarks> - IOpenIdMessageExtension IAuthenticationResponse.GetUntrustedExtension(Type extensionType) { - Contract.Requires<ArgumentNullException>(extensionType != null); - Contract.Requires<ArgumentException>(typeof(IOpenIdMessageExtension).IsAssignableFrom(extensionType)); - ////ErrorUtilities.VerifyArgument(typeof(IOpenIdMessageExtension).IsAssignableFrom(extensionType), string.Format(CultureInfo.CurrentCulture, OpenIdStrings.TypeMustImplementX, typeof(IOpenIdMessageExtension).FullName)); - throw new NotImplementedException(); - } - - /// <summary> - /// Gets a callback argument's value that was previously added using - /// <see cref="IAuthenticationRequest.AddCallbackArguments(string, string)"/>. - /// </summary> - /// <param name="key">The name of the parameter whose value is sought.</param> - /// <returns> - /// The value of the argument, or null if the named parameter could not be found. - /// </returns> - /// <remarks> - /// Callback parameters are only available even if the RP is in stateless mode, - /// or the callback parameters are otherwise unverifiable as untampered with. - /// Therefore, use this method only when the callback argument is not to be - /// used to make a security-sensitive decision. - /// </remarks> - string IAuthenticationResponse.GetUntrustedCallbackArgument(string key) { - Contract.Requires<ArgumentException>(!string.IsNullOrEmpty(key)); - throw new NotImplementedException(); - } - - /// <summary> - /// Gets all the callback arguments that were previously added using - /// <see cref="IAuthenticationRequest.AddCallbackArguments(string, string)"/> or as a natural part - /// of the return_to URL. - /// </summary> - /// <returns>A name-value dictionary. Never null.</returns> - /// <remarks> - /// Callback parameters are only available even if the RP is in stateless mode, - /// or the callback parameters are otherwise unverifiable as untampered with. - /// Therefore, use this method only when the callback argument is not to be - /// used to make a security-sensitive decision. - /// </remarks> - IDictionary<string, string> IAuthenticationResponse.GetUntrustedCallbackArguments() { - Contract.Ensures(Contract.Result<IDictionary<string, string>>() != null); - throw new NotImplementedException(); - } - - #endregion - } -} diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/IProviderEndpoint.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/IProviderEndpoint.cs deleted file mode 100644 index 5d8918d..0000000 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/IProviderEndpoint.cs +++ /dev/null @@ -1,144 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="IProviderEndpoint.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OpenId.RelyingParty { - using System; - using System.Collections.ObjectModel; - using System.Diagnostics.CodeAnalysis; - using System.Diagnostics.Contracts; - using System.Globalization; - using DotNetOpenAuth.Messaging; - using DotNetOpenAuth.OpenId.Messages; - - /// <summary> - /// Information published about an OpenId Provider by the - /// OpenId discovery documents found at a user's Claimed Identifier. - /// </summary> - /// <remarks> - /// Because information provided by this interface is suppplied by a - /// user's individually published documents, it may be incomplete or inaccurate. - /// </remarks> - [ContractClass(typeof(IProviderEndpointContract))] - public interface IProviderEndpoint { - /// <summary> - /// Gets the detected version of OpenID implemented by the Provider. - /// </summary> - Version Version { get; } - - /// <summary> - /// Gets the URL that the OpenID Provider receives authentication requests at. - /// </summary> - /// <value> - /// This value MUST be an absolute HTTP or HTTPS URL. - /// </value> - Uri Uri { get; } - - /// <summary> - /// Checks whether the OpenId Identifier claims support for a given extension. - /// </summary> - /// <typeparam name="T">The extension whose support is being queried.</typeparam> - /// <returns>True if support for the extension is advertised. False otherwise.</returns> - /// <remarks> - /// Note that a true or false return value is no guarantee of a Provider's - /// support for or lack of support for an extension. The return value is - /// determined by how the authenticating user filled out his/her XRDS document only. - /// The only way to be sure of support for a given extension is to include - /// the extension in the request and see if a response comes back for that extension. - /// </remarks> - [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "No parameter at all.")] - [Obsolete("Use IAuthenticationRequest.DiscoveryResult.IsExtensionSupported instead.")] - bool IsExtensionSupported<T>() where T : IOpenIdMessageExtension, new(); - - /// <summary> - /// Checks whether the OpenId Identifier claims support for a given extension. - /// </summary> - /// <param name="extensionType">The extension whose support is being queried.</param> - /// <returns>True if support for the extension is advertised. False otherwise.</returns> - /// <remarks> - /// Note that a true or false return value is no guarantee of a Provider's - /// support for or lack of support for an extension. The return value is - /// determined by how the authenticating user filled out his/her XRDS document only. - /// The only way to be sure of support for a given extension is to include - /// the extension in the request and see if a response comes back for that extension. - /// </remarks> - [Obsolete("Use IAuthenticationRequest.DiscoveryResult.IsExtensionSupported instead.")] - bool IsExtensionSupported(Type extensionType); - } - - /// <summary> - /// Code contract for the <see cref="IProviderEndpoint"/> type. - /// </summary> - [ContractClassFor(typeof(IProviderEndpoint))] - internal abstract class IProviderEndpointContract : IProviderEndpoint { - /// <summary> - /// Prevents a default instance of the <see cref="IProviderEndpointContract"/> class from being created. - /// </summary> - private IProviderEndpointContract() { - } - - #region IProviderEndpoint Members - - /// <summary> - /// Gets the detected version of OpenID implemented by the Provider. - /// </summary> - Version IProviderEndpoint.Version { - get { - Contract.Ensures(Contract.Result<Version>() != null); - throw new System.NotImplementedException(); - } - } - - /// <summary> - /// Gets the URL that the OpenID Provider receives authentication requests at. - /// </summary> - Uri IProviderEndpoint.Uri { - get { - Contract.Ensures(Contract.Result<Uri>() != null); - throw new System.NotImplementedException(); - } - } - - /// <summary> - /// Checks whether the OpenId Identifier claims support for a given extension. - /// </summary> - /// <typeparam name="T">The extension whose support is being queried.</typeparam> - /// <returns> - /// True if support for the extension is advertised. False otherwise. - /// </returns> - /// <remarks> - /// Note that a true or false return value is no guarantee of a Provider's - /// support for or lack of support for an extension. The return value is - /// determined by how the authenticating user filled out his/her XRDS document only. - /// The only way to be sure of support for a given extension is to include - /// the extension in the request and see if a response comes back for that extension. - /// </remarks> - bool IProviderEndpoint.IsExtensionSupported<T>() { - throw new NotImplementedException(); - } - - /// <summary> - /// Checks whether the OpenId Identifier claims support for a given extension. - /// </summary> - /// <param name="extensionType">The extension whose support is being queried.</param> - /// <returns> - /// True if support for the extension is advertised. False otherwise. - /// </returns> - /// <remarks> - /// Note that a true or false return value is no guarantee of a Provider's - /// support for or lack of support for an extension. The return value is - /// determined by how the authenticating user filled out his/her XRDS document only. - /// The only way to be sure of support for a given extension is to include - /// the extension in the request and see if a response comes back for that extension. - /// </remarks> - bool IProviderEndpoint.IsExtensionSupported(Type extensionType) { - Contract.Requires<ArgumentNullException>(extensionType != null); - Contract.Requires<ArgumentException>(typeof(IOpenIdMessageExtension).IsAssignableFrom(extensionType)); - throw new NotImplementedException(); - } - - #endregion - } -} diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/IRelyingPartyAssociationStore.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/IRelyingPartyAssociationStore.cs deleted file mode 100644 index 21a2c53..0000000 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/IRelyingPartyAssociationStore.cs +++ /dev/null @@ -1,153 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="IRelyingPartyAssociationStore.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OpenId.RelyingParty { - using System; - using System.Diagnostics.Contracts; - - /// <summary> - /// Stores <see cref="Association"/>s for lookup by their handle, keeping - /// associations separated by a given OP Endpoint. - /// </summary> - /// <remarks> - /// Expired associations should be periodically cleared out of an association store. - /// This should be done frequently enough to avoid a memory leak, but sparingly enough - /// to not be a performance drain. Because this balance can vary by host, it is the - /// responsibility of the host to initiate this cleaning. - /// </remarks> - [ContractClass(typeof(IRelyingPartyAssociationStoreContract))] - public interface IRelyingPartyAssociationStore { - /// <summary> - /// Saves an <see cref="Association"/> for later recall. - /// </summary> - /// <param name="providerEndpoint">The OP Endpoint with which the association is established.</param> - /// <param name="association">The association to store.</param> - /// <remarks> - /// If the new association conflicts (in OP endpoint and association handle) with an existing association, - /// (which should never happen by the way) implementations may overwrite the previously saved association. - /// </remarks> - void StoreAssociation(Uri providerEndpoint, Association association); - - /// <summary> - /// Gets the best association (the one with the longest remaining life) for a given key. - /// </summary> - /// <param name="providerEndpoint">The OP Endpoint with which the association is established.</param> - /// <param name="securityRequirements">The security requirements that the returned association must meet.</param> - /// <returns> - /// The requested association, or null if no unexpired <see cref="Association"/>s exist for the given key. - /// </returns> - /// <remarks> - /// In the event that multiple associations exist for the given - /// <paramref name="providerEndpoint"/>, it is important for the - /// implementation for this method to use the <paramref name="securityRequirements"/> - /// to pick the best (highest grade or longest living as the host's policy may dictate) - /// association that fits the security requirements. - /// Associations that are returned that do not meet the security requirements will be - /// ignored and a new association created. - /// </remarks> - Association GetAssociation(Uri providerEndpoint, SecuritySettings securityRequirements); - - /// <summary> - /// Gets the association for a given key and handle. - /// </summary> - /// <param name="providerEndpoint">The OP Endpoint with which the association is established.</param> - /// <param name="handle">The handle of the specific association that must be recalled.</param> - /// <returns>The requested association, or null if no unexpired <see cref="Association"/>s exist for the given key and handle.</returns> - Association GetAssociation(Uri providerEndpoint, string handle); - - /// <summary>Removes a specified handle that may exist in the store.</summary> - /// <param name="providerEndpoint">The OP Endpoint with which the association is established.</param> - /// <param name="handle">The handle of the specific association that must be deleted.</param> - /// <returns> - /// Deprecated. The return value is insignificant. - /// Previously: True if the association existed in this store previous to this call. - /// </returns> - /// <remarks> - /// No exception should be thrown if the association does not exist in the store - /// before this call. - /// </remarks> - bool RemoveAssociation(Uri providerEndpoint, string handle); - } - - /// <summary> - /// Code Contract for the <see cref="IRelyingPartyAssociationStore"/> class. - /// </summary> - [ContractClassFor(typeof(IRelyingPartyAssociationStore))] - internal abstract class IRelyingPartyAssociationStoreContract : IRelyingPartyAssociationStore { - #region IAssociationStore Members - - /// <summary> - /// Saves an <see cref="Association"/> for later recall. - /// </summary> - /// <param name="providerEndpoint">The Uri (for relying parties) or Smart/Dumb (for providers).</param> - /// <param name="association">The association to store.</param> - /// <remarks> - /// TODO: what should implementations do on association handle conflict? - /// </remarks> - void IRelyingPartyAssociationStore.StoreAssociation(Uri providerEndpoint, Association association) { - Contract.Requires<ArgumentNullException>(providerEndpoint != null); - Contract.Requires<ArgumentNullException>(association != null); - throw new NotImplementedException(); - } - - /// <summary> - /// Gets the best association (the one with the longest remaining life) for a given key. - /// </summary> - /// <param name="providerEndpoint">The Uri (for relying parties) or Smart/Dumb (for Providers).</param> - /// <param name="securityRequirements">The security requirements that the returned association must meet.</param> - /// <returns> - /// The requested association, or null if no unexpired <see cref="Association"/>s exist for the given key. - /// </returns> - /// <remarks> - /// In the event that multiple associations exist for the given - /// <paramref name="providerEndpoint"/>, it is important for the - /// implementation for this method to use the <paramref name="securityRequirements"/> - /// to pick the best (highest grade or longest living as the host's policy may dictate) - /// association that fits the security requirements. - /// Associations that are returned that do not meet the security requirements will be - /// ignored and a new association created. - /// </remarks> - Association IRelyingPartyAssociationStore.GetAssociation(Uri providerEndpoint, SecuritySettings securityRequirements) { - Contract.Requires<ArgumentNullException>(providerEndpoint != null); - Contract.Requires<ArgumentNullException>(securityRequirements != null); - throw new NotImplementedException(); - } - - /// <summary> - /// Gets the association for a given key and handle. - /// </summary> - /// <param name="providerEndpoint">The Uri (for relying parties) or Smart/Dumb (for Providers).</param> - /// <param name="handle">The handle of the specific association that must be recalled.</param> - /// <returns> - /// The requested association, or null if no unexpired <see cref="Association"/>s exist for the given key and handle. - /// </returns> - Association IRelyingPartyAssociationStore.GetAssociation(Uri providerEndpoint, string handle) { - Contract.Requires<ArgumentNullException>(providerEndpoint != null); - Contract.Requires(!String.IsNullOrEmpty(handle)); - throw new NotImplementedException(); - } - - /// <summary> - /// Removes a specified handle that may exist in the store. - /// </summary> - /// <param name="providerEndpoint">The Uri (for relying parties) or Smart/Dumb (for Providers).</param> - /// <param name="handle">The handle of the specific association that must be deleted.</param> - /// <returns> - /// True if the association existed in this store previous to this call. - /// </returns> - /// <remarks> - /// No exception should be thrown if the association does not exist in the store - /// before this call. - /// </remarks> - bool IRelyingPartyAssociationStore.RemoveAssociation(Uri providerEndpoint, string handle) { - Contract.Requires<ArgumentNullException>(providerEndpoint != null); - Contract.Requires(!String.IsNullOrEmpty(handle)); - throw new NotImplementedException(); - } - - #endregion - } -} diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/IRelyingPartyBehavior.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/IRelyingPartyBehavior.cs deleted file mode 100644 index 1bfa0db..0000000 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/IRelyingPartyBehavior.cs +++ /dev/null @@ -1,92 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="IRelyingPartyBehavior.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OpenId.RelyingParty { - using System; - using System.Diagnostics.Contracts; - - /// <summary> - /// Applies a custom security policy to certain OpenID security settings and behaviors. - /// </summary> - [ContractClass(typeof(IRelyingPartyBehaviorContract))] - public interface IRelyingPartyBehavior { - /// <summary> - /// Applies a well known set of security requirements to a default set of security settings. - /// </summary> - /// <param name="securitySettings">The security settings to enhance with the requirements of this profile.</param> - /// <remarks> - /// Care should be taken to never decrease security when applying a profile. - /// Profiles should only enhance security requirements to avoid being - /// incompatible with each other. - /// </remarks> - void ApplySecuritySettings(RelyingPartySecuritySettings securitySettings); - - /// <summary> - /// Called when an authentication request is about to be sent. - /// </summary> - /// <param name="request">The request.</param> - /// <remarks> - /// Implementations should be prepared to be called multiple times on the same outgoing message - /// without malfunctioning. - /// </remarks> - void OnOutgoingAuthenticationRequest(IAuthenticationRequest request); - - /// <summary> - /// Called when an incoming positive assertion is received. - /// </summary> - /// <param name="assertion">The positive assertion.</param> - void OnIncomingPositiveAssertion(IAuthenticationResponse assertion); - } - - /// <summary> - /// Contract class for the <see cref="IRelyingPartyBehavior"/> interface. - /// </summary> - [ContractClassFor(typeof(IRelyingPartyBehavior))] - internal abstract class IRelyingPartyBehaviorContract : IRelyingPartyBehavior { - /// <summary> - /// Prevents a default instance of the <see cref="IRelyingPartyBehaviorContract"/> class from being created. - /// </summary> - private IRelyingPartyBehaviorContract() { - } - - #region IRelyingPartyBehavior Members - - /// <summary> - /// Applies a well known set of security requirements to a default set of security settings. - /// </summary> - /// <param name="securitySettings">The security settings to enhance with the requirements of this profile.</param> - /// <remarks> - /// Care should be taken to never decrease security when applying a profile. - /// Profiles should only enhance security requirements to avoid being - /// incompatible with each other. - /// </remarks> - void IRelyingPartyBehavior.ApplySecuritySettings(RelyingPartySecuritySettings securitySettings) { - Contract.Requires<ArgumentNullException>(securitySettings != null); - } - - /// <summary> - /// Called when an authentication request is about to be sent. - /// </summary> - /// <param name="request">The request.</param> - /// <remarks> - /// Implementations should be prepared to be called multiple times on the same outgoing message - /// without malfunctioning. - /// </remarks> - void IRelyingPartyBehavior.OnOutgoingAuthenticationRequest(IAuthenticationRequest request) { - Contract.Requires<ArgumentNullException>(request != null); - } - - /// <summary> - /// Called when an incoming positive assertion is received. - /// </summary> - /// <param name="assertion">The positive assertion.</param> - void IRelyingPartyBehavior.OnIncomingPositiveAssertion(IAuthenticationResponse assertion) { - Contract.Requires<ArgumentNullException>(assertion != null); - } - - #endregion - } -} diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/ISetupRequiredAuthenticationResponse.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/ISetupRequiredAuthenticationResponse.cs deleted file mode 100644 index cfbccef..0000000 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/ISetupRequiredAuthenticationResponse.cs +++ /dev/null @@ -1,51 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="ISetupRequiredAuthenticationResponse.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OpenId.RelyingParty { - using System; - using System.Diagnostics.Contracts; - - /// <summary> - /// An interface to expose useful properties and functionality for handling - /// authentication responses that are returned from Immediate authentication - /// requests that require a subsequent request to be made in non-immediate mode. - /// </summary> - [ContractClass(typeof(ISetupRequiredAuthenticationResponseContract))] - public interface ISetupRequiredAuthenticationResponse { - /// <summary> - /// Gets the <see cref="Identifier"/> to pass to <see cref="OpenIdRelyingParty.CreateRequest(Identifier)"/> - /// in a subsequent authentication attempt. - /// </summary> - Identifier UserSuppliedIdentifier { get; } - } - - /// <summary> - /// Code contract class for the <see cref="ISetupRequiredAuthenticationResponse"/> type. - /// </summary> - [ContractClassFor(typeof(ISetupRequiredAuthenticationResponse))] - internal abstract class ISetupRequiredAuthenticationResponseContract : ISetupRequiredAuthenticationResponse { - /// <summary> - /// Initializes a new instance of the <see cref="ISetupRequiredAuthenticationResponseContract"/> class. - /// </summary> - protected ISetupRequiredAuthenticationResponseContract() { - } - - #region ISetupRequiredAuthenticationResponse Members - - /// <summary> - /// Gets the <see cref="Identifier"/> to pass to <see cref="OpenIdRelyingParty.CreateRequest(Identifier)"/> - /// in a subsequent authentication attempt. - /// </summary> - Identifier ISetupRequiredAuthenticationResponse.UserSuppliedIdentifier { - get { - Contract.Requires<InvalidOperationException>(((IAuthenticationResponse)this).Status == AuthenticationStatus.SetupRequired, OpenIdStrings.OperationOnlyValidForSetupRequiredState); - throw new System.NotImplementedException(); - } - } - - #endregion - } -} diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/NegativeAuthenticationResponse.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/NegativeAuthenticationResponse.cs deleted file mode 100644 index 9e3824d..0000000 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/NegativeAuthenticationResponse.cs +++ /dev/null @@ -1,312 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="NegativeAuthenticationResponse.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OpenId.RelyingParty { - using System; - using System.Collections.Generic; - using System.Diagnostics.Contracts; - using System.Linq; - using System.Web; - using DotNetOpenAuth.Messaging; - using DotNetOpenAuth.OpenId.Messages; - - /// <summary> - /// Wraps a negative assertion response in an <see cref="IAuthenticationResponse"/> instance - /// for public consumption by the host web site. - /// </summary> - internal class NegativeAuthenticationResponse : IAuthenticationResponse, ISetupRequiredAuthenticationResponse { - /// <summary> - /// The negative assertion message that was received by the RP that was used - /// to create this instance. - /// </summary> - private readonly NegativeAssertionResponse response; - - /// <summary> - /// Initializes a new instance of the <see cref="NegativeAuthenticationResponse"/> class. - /// </summary> - /// <param name="response">The negative assertion response received by the Relying Party.</param> - internal NegativeAuthenticationResponse(NegativeAssertionResponse response) { - Contract.Requires<ArgumentNullException>(response != null); - this.response = response; - - Reporting.RecordEventOccurrence(this, string.Empty); - } - - #region IAuthenticationResponse Properties - - /// <summary> - /// Gets the Identifier that the end user claims to own. For use with user database storage and lookup. - /// May be null for some failed authentications (i.e. failed directed identity authentications). - /// </summary> - /// <value></value> - /// <remarks> - /// <para> - /// This is the secure identifier that should be used for database storage and lookup. - /// It is not always friendly (i.e. =Arnott becomes =!9B72.7DD1.50A9.5CCD), but it protects - /// user identities against spoofing and other attacks. - /// </para> - /// <para> - /// For user-friendly identifiers to display, use the - /// <see cref="FriendlyIdentifierForDisplay"/> property. - /// </para> - /// </remarks> - public Identifier ClaimedIdentifier { - get { return null; } - } - - /// <summary> - /// Gets a user-friendly OpenID Identifier for display purposes ONLY. - /// </summary> - /// <value></value> - /// <remarks> - /// <para> - /// This <i>should</i> be put through <see cref="HttpUtility.HtmlEncode(string)"/> before - /// sending to a browser to secure against javascript injection attacks. - /// </para> - /// <para> - /// This property retains some aspects of the user-supplied identifier that get lost - /// in the <see cref="ClaimedIdentifier"/>. For example, XRIs used as user-supplied - /// identifiers (i.e. =Arnott) become unfriendly unique strings (i.e. =!9B72.7DD1.50A9.5CCD). - /// For display purposes, such as text on a web page that says "You're logged in as ...", - /// this property serves to provide the =Arnott string, or whatever else is the most friendly - /// string close to what the user originally typed in. - /// </para> - /// <para> - /// If the user-supplied identifier is a URI, this property will be the URI after all - /// redirects, and with the protocol and fragment trimmed off. - /// If the user-supplied identifier is an XRI, this property will be the original XRI. - /// If the user-supplied identifier is an OpenID Provider identifier (i.e. yahoo.com), - /// this property will be the Claimed Identifier, with the protocol stripped if it is a URI. - /// </para> - /// <para> - /// It is <b>very</b> important that this property <i>never</i> be used for database storage - /// or lookup to avoid identity spoofing and other security risks. For database storage - /// and lookup please use the <see cref="ClaimedIdentifier"/> property. - /// </para> - /// </remarks> - public string FriendlyIdentifierForDisplay { - get { return null; } - } - - /// <summary> - /// Gets the detailed success or failure status of the authentication attempt. - /// </summary> - /// <value></value> - public AuthenticationStatus Status { - get { return this.response.Immediate ? AuthenticationStatus.SetupRequired : AuthenticationStatus.Canceled; } - } - - /// <summary> - /// Gets information about the OpenId Provider, as advertised by the - /// OpenID discovery documents found at the <see cref="ClaimedIdentifier"/> - /// location. - /// </summary> - /// <value> - /// The Provider endpoint that issued the positive assertion; - /// or <c>null</c> if information about the Provider is unavailable. - /// </value> - public IProviderEndpoint Provider { - get { return null; } - } - - /// <summary> - /// Gets the details regarding a failed authentication attempt, if available. - /// This will be set if and only if <see cref="Status"/> is <see cref="AuthenticationStatus.Failed"/>. - /// </summary> - /// <value></value> - public Exception Exception { - get { return null; } - } - - #endregion - - #region ISetupRequiredAuthenticationResponse Members - - /// <summary> - /// Gets the <see cref="Identifier"/> to pass to <see cref="OpenIdRelyingParty.CreateRequest(Identifier)"/> - /// in a subsequent authentication attempt. - /// </summary> - /// <value></value> - public Identifier UserSuppliedIdentifier { - get { - string userSuppliedIdentifier; - this.response.ExtraData.TryGetValue(AuthenticationRequest.UserSuppliedIdentifierParameterName, out userSuppliedIdentifier); - return userSuppliedIdentifier; - } - } - - #endregion - - #region IAuthenticationResponse Methods - - /// <summary> - /// Gets a callback argument's value that was previously added using - /// <see cref="IAuthenticationRequest.AddCallbackArguments(string, string)"/>. - /// </summary> - /// <param name="key">The name of the parameter whose value is sought.</param> - /// <returns> - /// The value of the argument, or null if the named parameter could not be found. - /// </returns> - /// <remarks> - /// <para>This may return any argument on the querystring that came with the authentication response, - /// which may include parameters not explicitly added using - /// <see cref="IAuthenticationRequest.AddCallbackArguments(string, string)"/>.</para> - /// <para>Note that these values are NOT protected against tampering in transit.</para> - /// </remarks> - public string GetCallbackArgument(string key) { - return null; - } - - /// <summary> - /// Gets a callback argument's value that was previously added using - /// <see cref="IAuthenticationRequest.AddCallbackArguments(string, string)"/>. - /// </summary> - /// <param name="key">The name of the parameter whose value is sought.</param> - /// <returns> - /// The value of the argument, or null if the named parameter could not be found. - /// </returns> - /// <remarks> - /// Callback parameters are only available even if the RP is in stateless mode, - /// or the callback parameters are otherwise unverifiable as untampered with. - /// Therefore, use this method only when the callback argument is not to be - /// used to make a security-sensitive decision. - /// </remarks> - public string GetUntrustedCallbackArgument(string key) { - return null; - } - - /// <summary> - /// Gets all the callback arguments that were previously added using - /// <see cref="IAuthenticationRequest.AddCallbackArguments(string, string)"/> or as a natural part - /// of the return_to URL. - /// </summary> - /// <returns>A name-value dictionary. Never null.</returns> - /// <remarks> - /// <para>This MAY return any argument on the querystring that came with the authentication response, - /// which may include parameters not explicitly added using - /// <see cref="IAuthenticationRequest.AddCallbackArguments(string, string)"/>.</para> - /// <para>Note that these values are NOT protected against tampering in transit.</para> - /// </remarks> - public IDictionary<string, string> GetCallbackArguments() { - return EmptyDictionary<string, string>.Instance; - } - - /// <summary> - /// Gets all the callback arguments that were previously added using - /// <see cref="IAuthenticationRequest.AddCallbackArguments(string, string)"/> or as a natural part - /// of the return_to URL. - /// </summary> - /// <returns>A name-value dictionary. Never null.</returns> - /// <remarks> - /// Callback parameters are only available even if the RP is in stateless mode, - /// or the callback parameters are otherwise unverifiable as untampered with. - /// Therefore, use this method only when the callback argument is not to be - /// used to make a security-sensitive decision. - /// </remarks> - public IDictionary<string, string> GetUntrustedCallbackArguments() { - return EmptyDictionary<string, string>.Instance; - } - - /// <summary> - /// Tries to get an OpenID extension that may be present in the response. - /// </summary> - /// <typeparam name="T">The type of extension to look for in the response message.</typeparam> - /// <returns> - /// The extension, if it is found. Null otherwise. - /// </returns> - /// <remarks> - /// <para>Extensions are returned only if the Provider signed them. - /// Relying parties that do not care if the values were modified in - /// transit should use the <see cref="GetUntrustedExtension<T>"/> method - /// in order to allow the Provider to not sign the extension. </para> - /// <para>Unsigned extensions are completely unreliable and should be - /// used only to prefill user forms since the user or any other third - /// party may have tampered with the data carried by the extension.</para> - /// <para>Signed extensions are only reliable if the relying party - /// trusts the OpenID Provider that signed them. Signing does not mean - /// the relying party can trust the values -- it only means that the values - /// have not been tampered with since the Provider sent the message.</para> - /// </remarks> - public T GetExtension<T>() where T : IOpenIdMessageExtension { - return default(T); - } - - /// <summary> - /// Tries to get an OpenID extension that may be present in the response. - /// </summary> - /// <param name="extensionType">Type of the extension to look for in the response.</param> - /// <returns> - /// The extension, if it is found. Null otherwise. - /// </returns> - /// <remarks> - /// <para>Extensions are returned only if the Provider signed them. - /// Relying parties that do not care if the values were modified in - /// transit should use the <see cref="GetUntrustedExtension"/> method - /// in order to allow the Provider to not sign the extension. </para> - /// <para>Unsigned extensions are completely unreliable and should be - /// used only to prefill user forms since the user or any other third - /// party may have tampered with the data carried by the extension.</para> - /// <para>Signed extensions are only reliable if the relying party - /// trusts the OpenID Provider that signed them. Signing does not mean - /// the relying party can trust the values -- it only means that the values - /// have not been tampered with since the Provider sent the message.</para> - /// </remarks> - public IOpenIdMessageExtension GetExtension(Type extensionType) { - return null; - } - - /// <summary> - /// Tries to get an OpenID extension that may be present in the response, without - /// requiring it to be signed by the Provider. - /// </summary> - /// <typeparam name="T">The type of extension to look for in the response message.</typeparam> - /// <returns> - /// The extension, if it is found. Null otherwise. - /// </returns> - /// <remarks> - /// <para>Extensions are returned whether they are signed or not. - /// Use the <see cref="GetExtension<T>"/> method to retrieve - /// extension responses only if they are signed by the Provider to - /// protect against tampering. </para> - /// <para>Unsigned extensions are completely unreliable and should be - /// used only to prefill user forms since the user or any other third - /// party may have tampered with the data carried by the extension.</para> - /// <para>Signed extensions are only reliable if the relying party - /// trusts the OpenID Provider that signed them. Signing does not mean - /// the relying party can trust the values -- it only means that the values - /// have not been tampered with since the Provider sent the message.</para> - /// </remarks> - public T GetUntrustedExtension<T>() where T : IOpenIdMessageExtension { - return this.response.Extensions.OfType<T>().FirstOrDefault(); - } - - /// <summary> - /// Tries to get an OpenID extension that may be present in the response. - /// </summary> - /// <param name="extensionType">Type of the extension to look for in the response.</param> - /// <returns> - /// The extension, if it is found. Null otherwise. - /// </returns> - /// <remarks> - /// <para>Extensions are returned whether they are signed or not. - /// Use the <see cref="GetExtension"/> method to retrieve - /// extension responses only if they are signed by the Provider to - /// protect against tampering. </para> - /// <para>Unsigned extensions are completely unreliable and should be - /// used only to prefill user forms since the user or any other third - /// party may have tampered with the data carried by the extension.</para> - /// <para>Signed extensions are only reliable if the relying party - /// trusts the OpenID Provider that signed them. Signing does not mean - /// the relying party can trust the values -- it only means that the values - /// have not been tampered with since the Provider sent the message.</para> - /// </remarks> - public IOpenIdMessageExtension GetUntrustedExtension(Type extensionType) { - return this.response.Extensions.OfType<IOpenIdMessageExtension>().Where(ext => extensionType.IsInstanceOfType(ext)).FirstOrDefault(); - } - - #endregion - } -} diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdAjaxRelyingParty.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdAjaxRelyingParty.cs deleted file mode 100644 index 652fa6b..0000000 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdAjaxRelyingParty.cs +++ /dev/null @@ -1,242 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="OpenIdAjaxRelyingParty.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OpenId.RelyingParty { - using System; - using System.Collections.Generic; - using System.Diagnostics.CodeAnalysis; - using System.Diagnostics.Contracts; - using System.Globalization; - using System.Linq; - using System.Net; - using System.Net.Mime; - using System.Text; - using System.Web; - using System.Web.Script.Serialization; - - using DotNetOpenAuth.Configuration; - using DotNetOpenAuth.Messaging; - using DotNetOpenAuth.OpenId.Extensions; - using DotNetOpenAuth.OpenId.Extensions.UI; - - /// <summary> - /// Provides the programmatic facilities to act as an AJAX-enabled OpenID relying party. - /// </summary> - public class OpenIdAjaxRelyingParty : OpenIdRelyingParty { - /// <summary> - /// Initializes a new instance of the <see cref="OpenIdAjaxRelyingParty"/> class. - /// </summary> - public OpenIdAjaxRelyingParty() { - Reporting.RecordFeatureUse(this); - } - - /// <summary> - /// Initializes a new instance of the <see cref="OpenIdAjaxRelyingParty"/> class. - /// </summary> - /// <param name="applicationStore">The application store. If <c>null</c>, the relying party will always operate in "dumb mode".</param> - public OpenIdAjaxRelyingParty(IOpenIdApplicationStore applicationStore) - : base(applicationStore) { - Reporting.RecordFeatureUse(this); - } - - /// <summary> - /// Generates AJAX-ready authentication requests that can satisfy the requirements of some OpenID Identifier. - /// </summary> - /// <param name="userSuppliedIdentifier">The Identifier supplied by the user. This may be a URL, an XRI or i-name.</param> - /// <param name="realm">The shorest URL that describes this relying party web site's address. - /// For example, if your login page is found at https://www.example.com/login.aspx, - /// your realm would typically be https://www.example.com/.</param> - /// <param name="returnToUrl">The URL of the login page, or the page prepared to receive authentication - /// responses from the OpenID Provider.</param> - /// <returns> - /// A sequence of authentication requests, any of which constitutes a valid identity assertion on the Claimed Identifier. - /// Never null, but may be empty. - /// </returns> - /// <remarks> - /// <para>Any individual generated request can satisfy the authentication. - /// The generated requests are sorted in preferred order. - /// Each request is generated as it is enumerated to. Associations are created only as - /// <see cref="IAuthenticationRequest.RedirectingResponse"/> is called.</para> - /// <para>No exception is thrown if no OpenID endpoints were discovered. - /// An empty enumerable is returned instead.</para> - /// </remarks> - public override IEnumerable<IAuthenticationRequest> CreateRequests(Identifier userSuppliedIdentifier, Realm realm, Uri returnToUrl) { - var requests = base.CreateRequests(userSuppliedIdentifier, realm, returnToUrl); - - // Alter the requests so that have AJAX characteristics. - // Some OPs may be listed multiple times (one with HTTPS and the other with HTTP, for example). - // Since we're gathering OPs to try one after the other, just take the first choice of each OP - // and don't try it multiple times. - requests = requests.Distinct(DuplicateRequestedHostsComparer.Instance); - - // Configure each generated request. - int reqIndex = 0; - foreach (var req in requests) { - // Inform ourselves in return_to that we're in a popup. - req.SetUntrustedCallbackArgument(OpenIdRelyingPartyControlBase.UIPopupCallbackKey, "1"); - - if (req.DiscoveryResult.IsExtensionSupported<UIRequest>()) { - // Inform the OP that we'll be using a popup window consistent with the UI extension. - req.AddExtension(new UIRequest()); - - // Provide a hint for the client javascript about whether the OP supports the UI extension. - // This is so the window can be made the correct size for the extension. - // If the OP doesn't advertise support for the extension, the javascript will use - // a bigger popup window. - req.SetUntrustedCallbackArgument(OpenIdRelyingPartyControlBase.PopupUISupportedJSHint, "1"); - } - - req.SetUntrustedCallbackArgument("index", (reqIndex++).ToString(CultureInfo.InvariantCulture)); - - // If the ReturnToUrl was explicitly set, we'll need to reset our first parameter - if (DotNetOpenAuthSection.Configuration.OpenId.RelyingParty.PreserveUserSuppliedIdentifier) { - if (string.IsNullOrEmpty(HttpUtility.ParseQueryString(req.ReturnToUrl.Query)[AuthenticationRequest.UserSuppliedIdentifierParameterName])) { - req.SetUntrustedCallbackArgument(AuthenticationRequest.UserSuppliedIdentifierParameterName, userSuppliedIdentifier.OriginalString); - } - } - - // Our javascript needs to let the user know which endpoint responded. So we force it here. - // This gives us the info even for 1.0 OPs and 2.0 setup_required responses. - req.SetUntrustedCallbackArgument(OpenIdRelyingPartyAjaxControlBase.OPEndpointParameterName, req.Provider.Uri.AbsoluteUri); - req.SetUntrustedCallbackArgument(OpenIdRelyingPartyAjaxControlBase.ClaimedIdParameterName, (string)req.ClaimedIdentifier ?? string.Empty); - - // Inform ourselves in return_to that we're in a popup or iframe. - req.SetUntrustedCallbackArgument(OpenIdRelyingPartyAjaxControlBase.UIPopupCallbackKey, "1"); - - // We append a # at the end so that if the OP happens to support it, - // the OpenID response "query string" is appended after the hash rather than before, resulting in the - // browser being super-speedy in closing the popup window since it doesn't try to pull a newer version - // of the static resource down from the server merely because of a changed URL. - // http://www.nabble.com/Re:-Defining-how-OpenID-should-behave-with-fragments-in-the-return_to-url-p22694227.html - ////TODO: - - yield return req; - } - } - - /// <summary> - /// Serializes discovery results on some <i>single</i> identifier on behalf of Javascript running on the browser. - /// </summary> - /// <param name="requests">The discovery results from just <i>one</i> identifier to serialize as a JSON response.</param> - /// <returns> - /// The JSON result to return to the user agent. - /// </returns> - /// <remarks> - /// We prepare a JSON object with this interface: - /// <code> - /// class jsonResponse { - /// string claimedIdentifier; - /// Array requests; // never null - /// string error; // null if no error - /// } - /// </code> - /// Each element in the requests array looks like this: - /// <code> - /// class jsonAuthRequest { - /// string endpoint; // URL to the OP endpoint - /// string immediate; // URL to initiate an immediate request - /// string setup; // URL to initiate a setup request. - /// } - /// </code> - /// </remarks> - public OutgoingWebResponse AsAjaxDiscoveryResult(IEnumerable<IAuthenticationRequest> requests) { - Contract.Requires<ArgumentNullException>(requests != null); - - var serializer = new JavaScriptSerializer(); - return new OutgoingWebResponse { - Body = serializer.Serialize(this.AsJsonDiscoveryResult(requests)), - }; - } - - /// <summary> - /// Serializes discovery on a set of identifiers for preloading into an HTML page that carries - /// an AJAX-aware OpenID control. - /// </summary> - /// <param name="requests">The discovery results to serialize as a JSON response.</param> - /// <returns> - /// The JSON result to return to the user agent. - /// </returns> - public string AsAjaxPreloadedDiscoveryResult(IEnumerable<IAuthenticationRequest> requests) { - Contract.Requires<ArgumentNullException>(requests != null); - - var serializer = new JavaScriptSerializer(); - string json = serializer.Serialize(this.AsJsonPreloadedDiscoveryResult(requests)); - - string script = "window.dnoa_internal.loadPreloadedDiscoveryResults(" + json + ");"; - return script; - } - - /// <summary> - /// Converts a sequence of authentication requests to a JSON object for seeding an AJAX-enabled login page. - /// </summary> - /// <param name="requests">The discovery results from just <i>one</i> identifier to serialize as a JSON response.</param> - /// <returns>A JSON object, not yet serialized.</returns> - internal object AsJsonDiscoveryResult(IEnumerable<IAuthenticationRequest> requests) { - Contract.Requires<ArgumentNullException>(requests != null); - - requests = requests.CacheGeneratedResults(); - - if (requests.Any()) { - return new { - claimedIdentifier = (string)requests.First().ClaimedIdentifier, - requests = requests.Select(req => new { - endpoint = req.Provider.Uri.AbsoluteUri, - immediate = this.GetRedirectUrl(req, true), - setup = this.GetRedirectUrl(req, false), - }).ToArray() - }; - } else { - return new { - requests = new object[0], - error = OpenIdStrings.OpenIdEndpointNotFound, - }; - } - } - - /// <summary> - /// Serializes discovery on a set of identifiers for preloading into an HTML page that carries - /// an AJAX-aware OpenID control. - /// </summary> - /// <param name="requests">The discovery results to serialize as a JSON response.</param> - /// <returns> - /// A JSON object, not yet serialized to a string. - /// </returns> - private object AsJsonPreloadedDiscoveryResult(IEnumerable<IAuthenticationRequest> requests) { - Contract.Requires<ArgumentNullException>(requests != null); - - // We prepare a JSON object with this interface: - // Array discoveryWrappers; - // Where each element in the above array has this interface: - // class discoveryWrapper { - // string userSuppliedIdentifier; - // jsonResponse discoveryResult; // contains result of call to SerializeDiscoveryAsJson(Identifier) - // } - var json = (from request in requests - group request by request.DiscoveryResult.UserSuppliedIdentifier into requestsByIdentifier - select new { - userSuppliedIdentifier = (string)requestsByIdentifier.Key, - discoveryResult = this.AsJsonDiscoveryResult(requestsByIdentifier), - }).ToArray(); - - return json; - } - - /// <summary> - /// Gets the full URL that carries an OpenID message, even if it exceeds the normal maximum size of a URL, - /// for purposes of sending to an AJAX component running in the browser. - /// </summary> - /// <param name="request">The authentication request.</param> - /// <param name="immediate"><c>true</c>to create a checkid_immediate request; - /// <c>false</c> to create a checkid_setup request.</param> - /// <returns>The absolute URL that carries the entire OpenID message.</returns> - private Uri GetRedirectUrl(IAuthenticationRequest request, bool immediate) { - Contract.Requires<ArgumentNullException>(request != null); - - request.Mode = immediate ? AuthenticationRequestMode.Immediate : AuthenticationRequestMode.Setup; - return request.RedirectingResponse.GetDirectUriRequest(this.Channel); - } - } -} diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdAjaxTextBox.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdAjaxTextBox.cs deleted file mode 100644 index 8be097f..0000000 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdAjaxTextBox.cs +++ /dev/null @@ -1,877 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="OpenIdAjaxTextBox.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -[assembly: System.Web.UI.WebResource(DotNetOpenAuth.OpenId.RelyingParty.OpenIdAjaxTextBox.EmbeddedScriptResourceName, "text/javascript")] -[assembly: System.Web.UI.WebResource(DotNetOpenAuth.OpenId.RelyingParty.OpenIdAjaxTextBox.EmbeddedStylesheetResourceName, "text/css")] -[assembly: System.Web.UI.WebResource(DotNetOpenAuth.OpenId.RelyingParty.OpenIdAjaxTextBox.EmbeddedSpinnerResourceName, "image/gif")] -[assembly: System.Web.UI.WebResource(DotNetOpenAuth.OpenId.RelyingParty.OpenIdAjaxTextBox.EmbeddedLoginSuccessResourceName, "image/png")] -[assembly: System.Web.UI.WebResource(DotNetOpenAuth.OpenId.RelyingParty.OpenIdAjaxTextBox.EmbeddedLoginFailureResourceName, "image/png")] - -#pragma warning disable 0809 // marking inherited, unsupported properties as obsolete to discourage their use - -namespace DotNetOpenAuth.OpenId.RelyingParty { - using System; - using System.Collections.Generic; - using System.Collections.Specialized; - using System.ComponentModel; - using System.Diagnostics; - using System.Diagnostics.CodeAnalysis; - using System.Diagnostics.Contracts; - using System.Drawing.Design; - using System.Globalization; - using System.Text; - using System.Web.UI; - using System.Web.UI.HtmlControls; - using DotNetOpenAuth.Messaging; - - /// <summary> - /// An ASP.NET control that provides a minimal text box that is OpenID-aware and uses AJAX for - /// a premium login experience. - /// </summary> - [DefaultProperty("Text"), ValidationProperty("Text")] - [ToolboxData("<{0}:OpenIdAjaxTextBox runat=\"server\" />")] - public class OpenIdAjaxTextBox : OpenIdRelyingPartyAjaxControlBase, IEditableTextControl, ITextControl, IPostBackDataHandler { - /// <summary> - /// The name of the manifest stream containing the OpenIdAjaxTextBox.js file. - /// </summary> - internal const string EmbeddedScriptResourceName = Util.DefaultNamespace + ".OpenId.RelyingParty.OpenIdAjaxTextBox.js"; - - /// <summary> - /// The name of the manifest stream containing the OpenIdAjaxTextBox.css file. - /// </summary> - internal const string EmbeddedStylesheetResourceName = Util.DefaultNamespace + ".OpenId.RelyingParty.OpenIdAjaxTextBox.css"; - - /// <summary> - /// The name of the manifest stream containing the spinner.gif file. - /// </summary> - internal const string EmbeddedSpinnerResourceName = Util.DefaultNamespace + ".OpenId.RelyingParty.spinner.gif"; - - /// <summary> - /// The name of the manifest stream containing the login_success.png file. - /// </summary> - internal const string EmbeddedLoginSuccessResourceName = Util.DefaultNamespace + ".OpenId.RelyingParty.login_success.png"; - - /// <summary> - /// The name of the manifest stream containing the login_failure.png file. - /// </summary> - internal const string EmbeddedLoginFailureResourceName = Util.DefaultNamespace + ".OpenId.RelyingParty.login_failure.png"; - - /// <summary> - /// The default value for the <see cref="DownloadYahooUILibrary"/> property. - /// </summary> - internal const bool DownloadYahooUILibraryDefault = true; - - /// <summary> - /// The default value for the <see cref="Throttle"/> property. - /// </summary> - internal const int ThrottleDefault = 3; - - #region Property viewstate keys - - /// <summary> - /// The viewstate key to use for storing the value of the <see cref="AutoPostBack"/> property. - /// </summary> - private const string AutoPostBackViewStateKey = "AutoPostback"; - - /// <summary> - /// The viewstate key to use for the <see cref="Text"/> property. - /// </summary> - private const string TextViewStateKey = "Text"; - - /// <summary> - /// The viewstate key to use for storing the value of the <see cref="Columns"/> property. - /// </summary> - private const string ColumnsViewStateKey = "Columns"; - - /// <summary> - /// The viewstate key to use for the <see cref="CssClass"/> property. - /// </summary> - private const string CssClassViewStateKey = "CssClass"; - - /// <summary> - /// The viewstate key to use for storing the value of the <see cref="OnClientAssertionReceived"/> property. - /// </summary> - private const string OnClientAssertionReceivedViewStateKey = "OnClientAssertionReceived"; - - /// <summary> - /// The viewstate key to use for storing the value of the <see cref="AuthenticatedAsToolTip"/> property. - /// </summary> - private const string AuthenticatedAsToolTipViewStateKey = "AuthenticatedAsToolTip"; - - /// <summary> - /// The viewstate key to use for storing the value of the <see cref="AuthenticationSucceededToolTip"/> property. - /// </summary> - private const string AuthenticationSucceededToolTipViewStateKey = "AuthenticationSucceededToolTip"; - - /// <summary> - /// The viewstate key to use for storing the value of the <see cref="LogOnInProgressMessage"/> property. - /// </summary> - private const string LogOnInProgressMessageViewStateKey = "BusyToolTip"; - - /// <summary> - /// The viewstate key to use for storing the value of the <see cref="AuthenticationFailedToolTip"/> property. - /// </summary> - private const string AuthenticationFailedToolTipViewStateKey = "AuthenticationFailedToolTip"; - - /// <summary> - /// The viewstate key to use for storing the value of the <see cref="IdentifierRequiredMessage"/> property. - /// </summary> - private const string IdentifierRequiredMessageViewStateKey = "BusyToolTip"; - - /// <summary> - /// The viewstate key to use for storing the value of the <see cref="BusyToolTip"/> property. - /// </summary> - private const string BusyToolTipViewStateKey = "BusyToolTip"; - - /// <summary> - /// The viewstate key to use for storing the value of the <see cref="LogOnText"/> property. - /// </summary> - private const string LogOnTextViewStateKey = "LoginText"; - - /// <summary> - /// The viewstate key to use for storing the value of the <see cref="Throttle"/> property. - /// </summary> - private const string ThrottleViewStateKey = "Throttle"; - - /// <summary> - /// The viewstate key to use for storing the value of the <see cref="LogOnToolTip"/> property. - /// </summary> - private const string LogOnToolTipViewStateKey = "LoginToolTip"; - - /// <summary> - /// The viewstate key to use for storing the value of the <see cref="LogOnPostBackToolTip"/> property. - /// </summary> - private const string LogOnPostBackToolTipViewStateKey = "LoginPostBackToolTip"; - - /// <summary> - /// The viewstate key to use for storing the value of the <see cref="Name"/> property. - /// </summary> - private const string NameViewStateKey = "Name"; - - /// <summary> - /// The viewstate key to use for storing the value of the <see cref="Timeout"/> property. - /// </summary> - private const string TimeoutViewStateKey = "Timeout"; - - /// <summary> - /// The viewstate key to use for storing the value of the <see cref="TabIndex"/> property. - /// </summary> - private const string TabIndexViewStateKey = "TabIndex"; - - /// <summary> - /// The viewstate key to use for the <see cref="Enabled"/> property. - /// </summary> - private const string EnabledViewStateKey = "Enabled"; - - /// <summary> - /// The viewstate key to use for storing the value of the <see cref="RetryToolTip"/> property. - /// </summary> - private const string RetryToolTipViewStateKey = "RetryToolTip"; - - /// <summary> - /// The viewstate key to use for storing the value of the <see cref="RetryText"/> property. - /// </summary> - private const string RetryTextViewStateKey = "RetryText"; - - /// <summary> - /// The viewstate key to use for storing the value of the <see cref="DownloadYahooUILibrary"/> property. - /// </summary> - private const string DownloadYahooUILibraryViewStateKey = "DownloadYahooUILibrary"; - - /// <summary> - /// The viewstate key to use for storing the value of the <see cref="ShowLogOnPostBackButton"/> property. - /// </summary> - private const string ShowLogOnPostBackButtonViewStateKey = "ShowLogOnPostBackButton"; - - #endregion - - #region Property defaults - - /// <summary> - /// The default value for the <see cref="AutoPostBack"/> property. - /// </summary> - private const bool AutoPostBackDefault = false; - - /// <summary> - /// The default value for the <see cref="Columns"/> property. - /// </summary> - private const int ColumnsDefault = 40; - - /// <summary> - /// The default value for the <see cref="CssClass"/> property. - /// </summary> - private const string CssClassDefault = "openid"; - - /// <summary> - /// The default value for the <see cref="LogOnInProgressMessage"/> property. - /// </summary> - private const string LogOnInProgressMessageDefault = "Please wait for login to complete."; - - /// <summary> - /// The default value for the <see cref="AuthenticationSucceededToolTip"/> property. - /// </summary> - private const string AuthenticationSucceededToolTipDefault = "Authenticated by {0}."; - - /// <summary> - /// The default value for the <see cref="AuthenticatedAsToolTip"/> property. - /// </summary> - private const string AuthenticatedAsToolTipDefault = "Authenticated as {0}."; - - /// <summary> - /// The default value for the <see cref="AuthenticationFailedToolTip"/> property. - /// </summary> - private const string AuthenticationFailedToolTipDefault = "Authentication failed."; - - /// <summary> - /// The default value for the <see cref="LogOnText"/> property. - /// </summary> - private const string LogOnTextDefault = "LOG IN"; - - /// <summary> - /// The default value for the <see cref="BusyToolTip"/> property. - /// </summary> - private const string BusyToolTipDefault = "Discovering/authenticating"; - - /// <summary> - /// The default value for the <see cref="IdentifierRequiredMessage"/> property. - /// </summary> - private const string IdentifierRequiredMessageDefault = "Please correct errors in OpenID identifier and allow login to complete before submitting."; - - /// <summary> - /// The default value for the <see cref="Name"/> property. - /// </summary> - private const string NameDefault = "openid_identifier"; - - /// <summary> - /// Default value for <see cref="TabIndex"/> property. - /// </summary> - private const short TabIndexDefault = 0; - - /// <summary> - /// The default value for the <see cref="RetryToolTip"/> property. - /// </summary> - private const string RetryToolTipDefault = "Retry a failed identifier discovery."; - - /// <summary> - /// The default value for the <see cref="LogOnToolTip"/> property. - /// </summary> - private const string LogOnToolTipDefault = "Click here to log in using a pop-up window."; - - /// <summary> - /// The default value for the <see cref="LogOnPostBackToolTip"/> property. - /// </summary> - private const string LogOnPostBackToolTipDefault = "Click here to log in immediately."; - - /// <summary> - /// The default value for the <see cref="RetryText"/> property. - /// </summary> - private const string RetryTextDefault = "RETRY"; - - /// <summary> - /// The default value for the <see cref="ShowLogOnPostBackButton"/> property. - /// </summary> - private const bool ShowLogOnPostBackButtonDefault = false; - - #endregion - - /// <summary> - /// The path where the YUI control library should be downloaded from for HTTP pages. - /// </summary> - private const string YuiLoaderHttp = "http://ajax.googleapis.com/ajax/libs/yui/2.8.0r4/build/yuiloader/yuiloader-min.js"; - - /// <summary> - /// The path where the YUI control library should be downloaded from for HTTPS pages. - /// </summary> - private const string YuiLoaderHttps = "https://ajax.googleapis.com/ajax/libs/yui/2.8.0r4/build/yuiloader/yuiloader-min.js"; - - /// <summary> - /// Initializes a new instance of the <see cref="OpenIdAjaxTextBox"/> class. - /// </summary> - public OpenIdAjaxTextBox() { - this.HookFormSubmit = true; - } - - #region Events - - /// <summary> - /// Fired when the content of the text changes between posts to the server. - /// </summary> - [Description("Occurs when the content of the text changes between posts to the server."), Category(BehaviorCategory)] - public event EventHandler TextChanged; - - /// <summary> - /// Gets or sets the client-side script that executes when an authentication - /// assertion is received (but before it is verified). - /// </summary> - /// <remarks> - /// <para>In the context of the executing javascript set in this property, the - /// local variable <i>sender</i> is set to the openid_identifier input box - /// that is executing this code. - /// This variable has a getClaimedIdentifier() method that may be used to - /// identify the user who is being authenticated.</para> - /// <para>It is <b>very</b> important to note that when this code executes, - /// the authentication has not been verified and may have been spoofed. - /// No security-sensitive operations should take place in this javascript code. - /// The authentication is verified on the server by the time the - /// <see cref="OpenIdRelyingPartyControlBase.LoggedIn"/> server-side event fires.</para> - /// </remarks> - [Description("Gets or sets the client-side script that executes when an authentication assertion is received (but before it is verified).")] - [Bindable(true), DefaultValue(""), Category(BehaviorCategory)] - public string OnClientAssertionReceived { - get { return this.ViewState[OnClientAssertionReceivedViewStateKey] as string; } - set { this.ViewState[OnClientAssertionReceivedViewStateKey] = value; } - } - - #endregion - - #region Properties - - /// <summary> - /// Gets or sets the value in the text field, completely unprocessed or normalized. - /// </summary> - [Bindable(true), DefaultValue(""), Category(AppearanceCategory)] - [Description("The content of the text box.")] - public string Text { - get { - return this.Identifier != null ? this.Identifier.OriginalString : (this.ViewState[TextViewStateKey] as string ?? string.Empty); - } - - set { - // Try to store it as a validated identifier, - // but failing that at least store the text. - Identifier id; - if (Identifier.TryParse(value, out id)) { - this.Identifier = id; - } else { - // Be sure to set the viewstate AFTER setting the Identifier, - // since setting the Identifier clears the viewstate in OnIdentifierChanged. - this.Identifier = null; - this.ViewState[TextViewStateKey] = value; - } - } - } - - /// <summary> - /// Gets or sets a value indicating whether a postback is made to fire the - /// <see cref="OpenIdRelyingPartyControlBase.LoggedIn"/> event as soon as authentication has completed - /// successfully. - /// </summary> - /// <value> - /// <c>true</c> if a postback should be made automatically upon authentication; - /// otherwise, <c>false</c> to delay the <see cref="OpenIdRelyingPartyControlBase.LoggedIn"/> - /// event from firing at the server until a postback is made by some other control. - /// </value> - [Bindable(true), Category(BehaviorCategory), DefaultValue(AutoPostBackDefault)] - [Description("Whether the LoggedIn event fires on the server as soon as authentication completes successfully.")] - public bool AutoPostBack { - get { return (bool)(this.ViewState[AutoPostBackViewStateKey] ?? AutoPostBackDefault); } - set { this.ViewState[AutoPostBackViewStateKey] = value; } - } - - /// <summary> - /// Gets or sets the width of the text box in characters. - /// </summary> - [Bindable(true), Category(AppearanceCategory), DefaultValue(ColumnsDefault)] - [Description("The width of the text box in characters.")] - public int Columns { - get { - return (int)(this.ViewState[ColumnsViewStateKey] ?? ColumnsDefault); - } - - set { - Contract.Requires<ArgumentOutOfRangeException>(value >= 0); - this.ViewState[ColumnsViewStateKey] = value; - } - } - - /// <summary> - /// Gets or sets the CSS class assigned to the text box. - /// </summary> - [Bindable(true), DefaultValue(CssClassDefault), Category(AppearanceCategory)] - [Description("The CSS class assigned to the text box.")] - public string CssClass { - get { return (string)this.ViewState[CssClassViewStateKey]; } - set { this.ViewState[CssClassViewStateKey] = value; } - } - - /// <summary> - /// Gets or sets the tab index of the text box control. Use 0 to omit an explicit tabindex. - /// </summary> - [Bindable(true), Category(BehaviorCategory), DefaultValue(TabIndexDefault)] - [Description("The tab index of the text box control. Use 0 to omit an explicit tabindex.")] - public virtual short TabIndex { - get { return (short)(this.ViewState[TabIndexViewStateKey] ?? TabIndexDefault); } - set { this.ViewState[TabIndexViewStateKey] = value; } - } - - /// <summary> - /// Gets or sets a value indicating whether this <see cref="OpenIdTextBox"/> is enabled - /// in the browser for editing and will respond to incoming OpenID messages. - /// </summary> - /// <value><c>true</c> if enabled; otherwise, <c>false</c>.</value> - [Bindable(true), DefaultValue(true), Category(BehaviorCategory)] - [Description("Whether the control is editable in the browser and will respond to OpenID messages.")] - public bool Enabled { - get { return (bool)(this.ViewState[EnabledViewStateKey] ?? true); } - set { this.ViewState[EnabledViewStateKey] = value; } - } - - /// <summary> - /// Gets or sets the HTML name to assign to the text field. - /// </summary> - [Bindable(true), DefaultValue(NameDefault), Category("Misc")] - [Description("The HTML name to assign to the text field.")] - public string Name { - get { - return (string)(this.ViewState[NameViewStateKey] ?? NameDefault); - } - - set { - Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(value)); - this.ViewState[NameViewStateKey] = value ?? string.Empty; - } - } - - /// <summary> - /// Gets or sets the time duration for the AJAX control to wait for an OP to respond before reporting failure to the user. - /// </summary> - [Browsable(true), DefaultValue(typeof(TimeSpan), "00:00:08"), Category(BehaviorCategory)] - [Description("The time duration for the AJAX control to wait for an OP to respond before reporting failure to the user.")] - public TimeSpan Timeout { - get { - return (TimeSpan)(this.ViewState[TimeoutViewStateKey] ?? TimeoutDefault); - } - - set { - Contract.Requires<ArgumentOutOfRangeException>(value.TotalMilliseconds > 0); - this.ViewState[TimeoutViewStateKey] = value; - } - } - - /// <summary> - /// Gets or sets the maximum number of OpenID Providers to simultaneously try to authenticate with. - /// </summary> - [Browsable(true), DefaultValue(ThrottleDefault), Category(BehaviorCategory)] - [Description("The maximum number of OpenID Providers to simultaneously try to authenticate with.")] - public int Throttle { - get { - return (int)(this.ViewState[ThrottleViewStateKey] ?? ThrottleDefault); - } - - set { - Contract.Requires<ArgumentOutOfRangeException>(value > 0); - this.ViewState[ThrottleViewStateKey] = value; - } - } - - /// <summary> - /// Gets or sets the text that appears on the LOG IN button in cases where immediate (invisible) authentication fails. - /// </summary> - [Bindable(true), DefaultValue(LogOnTextDefault), Localizable(true), Category(AppearanceCategory)] - [Description("The text that appears on the LOG IN button in cases where immediate (invisible) authentication fails.")] - public string LogOnText { - get { - return (string)(this.ViewState[LogOnTextViewStateKey] ?? LogOnTextDefault); - } - - set { - Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(value)); - this.ViewState[LogOnTextViewStateKey] = value ?? string.Empty; - } - } - - /// <summary> - /// Gets or sets the rool tip text that appears on the LOG IN button in cases where immediate (invisible) authentication fails. - /// </summary> - [Bindable(true), DefaultValue(LogOnToolTipDefault), Localizable(true), Category(AppearanceCategory)] - [Description("The tool tip text that appears on the LOG IN button in cases where immediate (invisible) authentication fails.")] - public string LogOnToolTip { - get { return (string)(this.ViewState[LogOnToolTipViewStateKey] ?? LogOnToolTipDefault); } - set { this.ViewState[LogOnToolTipViewStateKey] = value ?? string.Empty; } - } - - /// <summary> - /// Gets or sets the rool tip text that appears on the LOG IN button when clicking the button will result in an immediate postback. - /// </summary> - [Bindable(true), DefaultValue(LogOnPostBackToolTipDefault), Localizable(true), Category(AppearanceCategory)] - [Description("The tool tip text that appears on the LOG IN button when clicking the button will result in an immediate postback.")] - public string LogOnPostBackToolTip { - get { return (string)(this.ViewState[LogOnPostBackToolTipViewStateKey] ?? LogOnPostBackToolTipDefault); } - set { this.ViewState[LogOnPostBackToolTipViewStateKey] = value ?? string.Empty; } - } - - /// <summary> - /// Gets or sets the text that appears on the RETRY button in cases where authentication times out. - /// </summary> - [Bindable(true), DefaultValue(RetryTextDefault), Localizable(true), Category(AppearanceCategory)] - [Description("The text that appears on the RETRY button in cases where authentication times out.")] - public string RetryText { - get { - return (string)(this.ViewState[RetryTextViewStateKey] ?? RetryTextDefault); - } - - set { - Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(value)); - this.ViewState[RetryTextViewStateKey] = value ?? string.Empty; - } - } - - /// <summary> - /// Gets or sets the tool tip text that appears on the RETRY button in cases where authentication times out. - /// </summary> - [Bindable(true), DefaultValue(RetryToolTipDefault), Localizable(true), Category(AppearanceCategory)] - [Description("The tool tip text that appears on the RETRY button in cases where authentication times out.")] - public string RetryToolTip { - get { return (string)(this.ViewState[RetryToolTipViewStateKey] ?? RetryToolTipDefault); } - set { this.ViewState[RetryToolTipViewStateKey] = value ?? string.Empty; } - } - - /// <summary> - /// Gets or sets the tool tip text that appears when authentication succeeds. - /// </summary> - [Bindable(true), DefaultValue(AuthenticationSucceededToolTipDefault), Localizable(true), Category(AppearanceCategory)] - [Description("The tool tip text that appears when authentication succeeds.")] - public string AuthenticationSucceededToolTip { - get { return (string)(this.ViewState[AuthenticationSucceededToolTipViewStateKey] ?? AuthenticationSucceededToolTipDefault); } - set { this.ViewState[AuthenticationSucceededToolTipViewStateKey] = value ?? string.Empty; } - } - - /// <summary> - /// Gets or sets the tool tip text that appears on the green checkmark when authentication succeeds. - /// </summary> - [Bindable(true), DefaultValue(AuthenticatedAsToolTipDefault), Localizable(true), Category(AppearanceCategory)] - [Description("The tool tip text that appears on the green checkmark when authentication succeeds.")] - public string AuthenticatedAsToolTip { - get { return (string)(this.ViewState[AuthenticatedAsToolTipViewStateKey] ?? AuthenticatedAsToolTipDefault); } - set { this.ViewState[AuthenticatedAsToolTipViewStateKey] = value ?? string.Empty; } - } - - /// <summary> - /// Gets or sets the tool tip text that appears when authentication fails. - /// </summary> - [Bindable(true), DefaultValue(AuthenticationFailedToolTipDefault), Localizable(true), Category(AppearanceCategory)] - [Description("The tool tip text that appears when authentication fails.")] - public string AuthenticationFailedToolTip { - get { return (string)(this.ViewState[AuthenticationFailedToolTipViewStateKey] ?? AuthenticationFailedToolTipDefault); } - set { this.ViewState[AuthenticationFailedToolTipViewStateKey] = value ?? string.Empty; } - } - - /// <summary> - /// Gets or sets the tool tip text that appears over the text box when it is discovering and authenticating. - /// </summary> - [Bindable(true), DefaultValue(BusyToolTipDefault), Localizable(true), Category(AppearanceCategory)] - [Description("The tool tip text that appears over the text box when it is discovering and authenticating.")] - public string BusyToolTip { - get { return (string)(this.ViewState[BusyToolTipViewStateKey] ?? BusyToolTipDefault); } - set { this.ViewState[BusyToolTipViewStateKey] = value ?? string.Empty; } - } - - /// <summary> - /// Gets or sets the message that is displayed if a postback is about to occur before the identifier has been supplied. - /// </summary> - [Bindable(true), DefaultValue(IdentifierRequiredMessageDefault), Localizable(true), Category(AppearanceCategory)] - [Description("The message that is displayed if a postback is about to occur before the identifier has been supplied.")] - public string IdentifierRequiredMessage { - get { return (string)(this.ViewState[IdentifierRequiredMessageViewStateKey] ?? IdentifierRequiredMessageDefault); } - set { this.ViewState[IdentifierRequiredMessageViewStateKey] = value ?? string.Empty; } - } - - /// <summary> - /// Gets or sets the message that is displayed if a postback is attempted while login is in process. - /// </summary> - [Bindable(true), DefaultValue(LogOnInProgressMessageDefault), Localizable(true), Category(AppearanceCategory)] - [Description("The message that is displayed if a postback is attempted while login is in process.")] - public string LogOnInProgressMessage { - get { return (string)(this.ViewState[LogOnInProgressMessageViewStateKey] ?? LogOnInProgressMessageDefault); } - set { this.ViewState[LogOnInProgressMessageViewStateKey] = value ?? string.Empty; } - } - - /// <summary> - /// Gets or sets a value indicating whether the Yahoo! User Interface Library (YUI) - /// will be downloaded in order to provide a login split button. - /// </summary> - /// <value> - /// <c>true</c> to use a split button; otherwise, <c>false</c> to use a standard HTML button - /// or a split button by downloading the YUI library yourself on the hosting web page. - /// </value> - /// <remarks> - /// The split button brings in about 180KB of YUI javascript dependencies. - /// </remarks> - [Bindable(true), DefaultValue(DownloadYahooUILibraryDefault), Category(BehaviorCategory)] - [Description("Whether a split button will be used for the \"log in\" when the user provides an identifier that delegates to more than one Provider.")] - public bool DownloadYahooUILibrary { - get { return (bool)(this.ViewState[DownloadYahooUILibraryViewStateKey] ?? DownloadYahooUILibraryDefault); } - set { this.ViewState[DownloadYahooUILibraryViewStateKey] = value; } - } - - /// <summary> - /// Gets or sets a value indicating whether the "Log in" button will be shown - /// to initiate a postback containing the positive assertion. - /// </summary> - [Bindable(true), DefaultValue(ShowLogOnPostBackButtonDefault), Category(AppearanceCategory)] - [Description("Whether the log in button will be shown to initiate a postback containing the positive assertion.")] - public bool ShowLogOnPostBackButton { - get { return (bool)(this.ViewState[ShowLogOnPostBackButtonViewStateKey] ?? ShowLogOnPostBackButtonDefault); } - set { this.ViewState[ShowLogOnPostBackButtonViewStateKey] = value; } - } - - #endregion - - /// <summary> - /// Gets or sets a value indicating whether the ajax text box should hook the form's submit event for special behavior. - /// </summary> - internal bool HookFormSubmit { get; set; } - - /// <summary> - /// Gets the name of the open id auth data form key. - /// </summary> - /// <value> - /// A concatenation of <see cref="Name"/> and <c>"_openidAuthData"</c>. - /// </value> - protected override string OpenIdAuthDataFormKey { - get { return this.Name + "_openidAuthData"; } - } - - /// <summary> - /// Gets the default value for the <see cref="Timeout"/> property. - /// </summary> - /// <value>8 seconds; or eternity if the debugger is attached.</value> - private static TimeSpan TimeoutDefault { - get { - if (Debugger.IsAttached) { - Logger.OpenId.Warn("Debugger is attached. Inflating default OpenIdAjaxTextbox.Timeout value to infinity."); - return TimeSpan.MaxValue; - } else { - return TimeSpan.FromSeconds(8); - } - } - } - - #region IPostBackDataHandler Members - - /// <summary> - /// When implemented by a class, processes postback data for an ASP.NET server control. - /// </summary> - /// <param name="postDataKey">The key identifier for the control.</param> - /// <param name="postCollection">The collection of all incoming name values.</param> - /// <returns> - /// true if the server control's state changes as a result of the postback; otherwise, false. - /// </returns> - bool IPostBackDataHandler.LoadPostData(string postDataKey, NameValueCollection postCollection) { - return this.LoadPostData(postDataKey, postCollection); - } - - /// <summary> - /// When implemented by a class, signals the server control to notify the ASP.NET application that the state of the control has changed. - /// </summary> - void IPostBackDataHandler.RaisePostDataChangedEvent() { - this.RaisePostDataChangedEvent(); - } - - #endregion - - /// <summary> - /// Raises the <see cref="E:Load"/> event. - /// </summary> - /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param> - protected override void OnLoad(EventArgs e) { - base.OnLoad(e); - - this.Page.RegisterRequiresPostBack(this); - } - - /// <summary> - /// Called when the <see cref="Identifier"/> property is changed. - /// </summary> - protected override void OnIdentifierChanged() { - this.ViewState.Remove(TextViewStateKey); - base.OnIdentifierChanged(); - } - - /// <summary> - /// Prepares to render the control. - /// </summary> - /// <param name="e">An <see cref="T:System.EventArgs"/> object that contains the event data.</param> - protected override void OnPreRender(EventArgs e) { - base.OnPreRender(e); - - if (!this.Visible) { - return; - } - - if (this.DownloadYahooUILibrary) { - // Although we'll add the <script> tag to download the YAHOO component, - // a download failure may have occurred, so protect ourselves from a - // script error using an if (YAHOO) block. But apparently at least in IE - // that's not even enough, so we use a try/catch. - string yuiLoadScript = @"try { if (YAHOO) { - var loader = new YAHOO.util.YUILoader({ - require: ['button', 'menu'], - loadOptional: false, - combine: true - }); - - loader.insert(); -} } catch (e) { }"; - this.Page.ClientScript.RegisterClientScriptInclude("yuiloader", this.Page.Request.Url.IsTransportSecure() ? YuiLoaderHttps : YuiLoaderHttp); - this.Page.ClientScript.RegisterClientScriptBlock(this.GetType(), "requiredYuiComponents", yuiLoadScript, true); - } - - var css = new HtmlLink(); - try { - css.Href = this.Page.ClientScript.GetWebResourceUrl(this.GetType(), EmbeddedStylesheetResourceName); - css.Attributes["rel"] = "stylesheet"; - css.Attributes["type"] = "text/css"; - ErrorUtilities.VerifyHost(this.Page.Header != null, OpenIdStrings.HeadTagMustIncludeRunatServer); - this.Page.Header.Controls.AddAt(0, css); // insert at top so host page can override - } catch { - css.Dispose(); - throw; - } - - this.PrepareClientJavascript(); - - // If an Identifier is preset on this control, preload discovery on that identifier, - // but only if we're not already persisting an authentication result since that would - // be redundant. - if (this.Identifier != null && this.AuthenticationResponse == null) { - this.PreloadDiscovery(this.Identifier); - } - } - - /// <summary> - /// Renders the control. - /// </summary> - /// <param name="writer">The <see cref="T:System.Web.UI.HtmlTextWriter"/> object that receives the control content.</param> - protected override void Render(HtmlTextWriter writer) { - base.Render(writer); - - // We surround the textbox with a span so that the .js file can inject a - // login button within the text box with easy placement. - string css = this.CssClass ?? string.Empty; - css += " OpenIdAjaxTextBox"; - writer.AddAttribute(HtmlTextWriterAttribute.Class, css); - - writer.AddStyleAttribute(HtmlTextWriterStyle.Display, "inline-block"); - writer.AddStyleAttribute(HtmlTextWriterStyle.Position, "relative"); - writer.AddStyleAttribute(HtmlTextWriterStyle.FontSize, "16px"); - writer.RenderBeginTag(HtmlTextWriterTag.Span); - - writer.AddAttribute(HtmlTextWriterAttribute.Name, this.Name); - writer.AddAttribute(HtmlTextWriterAttribute.Id, this.ClientID); - writer.AddAttribute(HtmlTextWriterAttribute.Size, this.Columns.ToString(CultureInfo.InvariantCulture)); - if (!string.IsNullOrEmpty(this.Text)) { - writer.AddAttribute(HtmlTextWriterAttribute.Value, this.Text, true); - } - - if (this.TabIndex > 0) { - writer.AddAttribute(HtmlTextWriterAttribute.Tabindex, this.TabIndex.ToString(CultureInfo.InvariantCulture)); - } - if (!this.Enabled) { - writer.AddAttribute(HtmlTextWriterAttribute.Disabled, "true"); - } - if (!string.IsNullOrEmpty(this.CssClass)) { - writer.AddAttribute(HtmlTextWriterAttribute.Class, this.CssClass); - } - writer.AddStyleAttribute(HtmlTextWriterStyle.PaddingLeft, "18px"); - writer.AddStyleAttribute(HtmlTextWriterStyle.BorderStyle, "solid"); - writer.AddStyleAttribute(HtmlTextWriterStyle.BorderWidth, "1px"); - writer.AddStyleAttribute(HtmlTextWriterStyle.BorderColor, "lightgray"); - writer.RenderBeginTag(HtmlTextWriterTag.Input); - writer.RenderEndTag(); // </input> - writer.RenderEndTag(); // </span> - } - - /// <summary> - /// When implemented by a class, processes postback data for an ASP.NET server control. - /// </summary> - /// <param name="postDataKey">The key identifier for the control.</param> - /// <param name="postCollection">The collection of all incoming name values.</param> - /// <returns> - /// true if the server control's state changes as a result of the postback; otherwise, false. - /// </returns> - protected virtual bool LoadPostData(string postDataKey, NameValueCollection postCollection) { - // If the control was temporarily hidden, it won't be in the Form data, - // and we'll just implicitly keep the last Text setting. - if (postCollection[this.Name] != null) { - Identifier identifier = postCollection[this.Name].Length == 0 ? null : postCollection[this.Name]; - if (identifier != this.Identifier) { - this.Identifier = identifier; - return true; - } - } - - return false; - } - - /// <summary> - /// When implemented by a class, signals the server control to notify the ASP.NET application that the state of the control has changed. - /// </summary> - [SuppressMessage("Microsoft.Design", "CA1030:UseEventsWhereAppropriate", Justification = "Predefined signature.")] - protected virtual void RaisePostDataChangedEvent() { - this.OnTextChanged(); - } - - /// <summary> - /// Called on a postback when the Text property has changed. - /// </summary> - protected virtual void OnTextChanged() { - EventHandler textChanged = this.TextChanged; - if (textChanged != null) { - textChanged(this, EventArgs.Empty); - } - } - - /// <summary> - /// Assembles the javascript to send to the client and registers it with ASP.NET for transmission. - /// </summary> - private void PrepareClientJavascript() { - // Import the .js file where most of the code is. - this.Page.ClientScript.RegisterClientScriptResource(typeof(OpenIdAjaxTextBox), EmbeddedScriptResourceName); - - // Call into the .js file with initialization information. - StringBuilder startupScript = new StringBuilder(); - startupScript.AppendFormat("var box = document.getElementsByName('{0}')[0];{1}", this.Name, Environment.NewLine); - startupScript.AppendFormat( - CultureInfo.InvariantCulture, - "initAjaxOpenId(box, {0}, {1}, {2}, {3}, {4}, {5}, {6}, {7}, {8}, {9}, {10}, {11}, {12}, {13}, {14}, {15}, {16}, {17}, {18}, {19}, function() {{{20};}});{21}", - MessagingUtilities.GetSafeJavascriptValue(this.Page.ClientScript.GetWebResourceUrl(this.GetType(), OpenIdTextBox.EmbeddedLogoResourceName)), - MessagingUtilities.GetSafeJavascriptValue(this.Page.ClientScript.GetWebResourceUrl(this.GetType(), EmbeddedSpinnerResourceName)), - MessagingUtilities.GetSafeJavascriptValue(this.Page.ClientScript.GetWebResourceUrl(this.GetType(), EmbeddedLoginSuccessResourceName)), - MessagingUtilities.GetSafeJavascriptValue(this.Page.ClientScript.GetWebResourceUrl(this.GetType(), EmbeddedLoginFailureResourceName)), - this.Throttle, - this.Timeout.TotalMilliseconds, - string.IsNullOrEmpty(this.OnClientAssertionReceived) ? "null" : "'" + this.OnClientAssertionReceived.Replace(@"\", @"\\").Replace("'", @"\'") + "'", - MessagingUtilities.GetSafeJavascriptValue(this.LogOnText), - MessagingUtilities.GetSafeJavascriptValue(this.LogOnToolTip), - this.ShowLogOnPostBackButton ? "true" : "false", - MessagingUtilities.GetSafeJavascriptValue(this.LogOnPostBackToolTip), - MessagingUtilities.GetSafeJavascriptValue(this.RetryText), - MessagingUtilities.GetSafeJavascriptValue(this.RetryToolTip), - MessagingUtilities.GetSafeJavascriptValue(this.BusyToolTip), - MessagingUtilities.GetSafeJavascriptValue(this.IdentifierRequiredMessage), - MessagingUtilities.GetSafeJavascriptValue(this.LogOnInProgressMessage), - MessagingUtilities.GetSafeJavascriptValue(this.AuthenticationSucceededToolTip), - MessagingUtilities.GetSafeJavascriptValue(this.AuthenticatedAsToolTip), - MessagingUtilities.GetSafeJavascriptValue(this.AuthenticationFailedToolTip), - this.AutoPostBack ? "true" : "false", - Page.ClientScript.GetPostBackEventReference(this, null), - Environment.NewLine); - - ScriptManager.RegisterStartupScript(this, this.GetType(), "ajaxstartup", startupScript.ToString(), true); - if (this.HookFormSubmit) { - string htmlFormat = @" -var openidbox = document.getElementsByName('{0}')[0]; -if (!openidbox.dnoi_internal.onSubmit()) {{ return false; }} -"; - Page.ClientScript.RegisterOnSubmitStatement( - this.GetType(), - "loginvalidation", - string.Format(CultureInfo.InvariantCulture, htmlFormat, this.Name)); - } - } - } -} diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdEventArgs.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdEventArgs.cs deleted file mode 100644 index 5668cf4..0000000 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdEventArgs.cs +++ /dev/null @@ -1,74 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="OpenIdEventArgs.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OpenId.RelyingParty { - using System; - using System.Collections.Generic; - using System.Diagnostics.Contracts; - using System.Linq; - using System.Text; - using DotNetOpenAuth.Messaging; - - /// <summary> - /// The event details passed to event handlers. - /// </summary> - public class OpenIdEventArgs : EventArgs { - /// <summary> - /// Initializes a new instance of the <see cref="OpenIdEventArgs"/> class - /// with minimal information of an incomplete or failed authentication attempt. - /// </summary> - /// <param name="request">The outgoing authentication request.</param> - internal OpenIdEventArgs(IAuthenticationRequest request) { - Contract.Requires<ArgumentNullException>(request != null); - - this.Request = request; - this.ClaimedIdentifier = request.ClaimedIdentifier; - this.IsDirectedIdentity = request.IsDirectedIdentity; - } - - /// <summary> - /// Initializes a new instance of the <see cref="OpenIdEventArgs"/> class - /// with information on a completed authentication attempt - /// (whether that attempt was successful or not). - /// </summary> - /// <param name="response">The incoming authentication response.</param> - internal OpenIdEventArgs(IAuthenticationResponse response) { - Contract.Requires<ArgumentNullException>(response != null); - - this.Response = response; - this.ClaimedIdentifier = response.ClaimedIdentifier; - } - - /// <summary> - /// Gets or sets a value indicating whether to cancel - /// the OpenID authentication and/or login process. - /// </summary> - public bool Cancel { get; set; } - - /// <summary> - /// Gets the Identifier the user is claiming to own. Or null if the user - /// is using Directed Identity. - /// </summary> - public Identifier ClaimedIdentifier { get; private set; } - - /// <summary> - /// Gets a value indicating whether the user has selected to let his Provider determine - /// the ClaimedIdentifier to use as part of successful authentication. - /// </summary> - public bool IsDirectedIdentity { get; private set; } - - /// <summary> - /// Gets the details of the OpenID authentication request, - /// and allows for adding extensions. - /// </summary> - public IAuthenticationRequest Request { get; private set; } - - /// <summary> - /// Gets the details of the OpenID authentication response. - /// </summary> - public IAuthenticationResponse Response { get; private set; } - } -} diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdLogin.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdLogin.cs deleted file mode 100644 index eccdacf..0000000 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdLogin.cs +++ /dev/null @@ -1,1001 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="OpenIdLogin.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OpenId.RelyingParty { - using System; - using System.ComponentModel; - using System.Diagnostics.CodeAnalysis; - using System.Diagnostics.Contracts; - using System.Globalization; - using System.Linq; - using System.Web.UI; - using System.Web.UI.HtmlControls; - using System.Web.UI.WebControls; - using DotNetOpenAuth.Messaging; - - /// <summary> - /// An ASP.NET control providing a complete OpenID login experience. - /// </summary> - [SuppressMessage("Microsoft.Naming", "CA1726:UsePreferredTerms", MessageId = "Login", Justification = "Legacy code")] - [DefaultProperty("Text"), ValidationProperty("Text")] - [ToolboxData("<{0}:OpenIdLogin runat=\"server\" />")] - public class OpenIdLogin : OpenIdTextBox { - #region Property defaults - - /// <summary> - /// The default value for the <see cref="RegisterToolTip"/> property. - /// </summary> - private const string RegisterToolTipDefault = "Sign up free for an OpenID with MyOpenID now."; - - /// <summary> - /// The default value for the <see cref="RememberMeText"/> property. - /// </summary> - private const string RememberMeTextDefault = "Remember me"; - - /// <summary> - /// The default value for the <see cref="ButtonText"/> property. - /// </summary> - private const string ButtonTextDefault = "Login "; - - /// <summary> - /// The default value for the <see cref="CanceledText"/> property. - /// </summary> - private const string CanceledTextDefault = "Login canceled."; - - /// <summary> - /// The default value for the <see cref="FailedMessageText"/> property. - /// </summary> - private const string FailedMessageTextDefault = "Login failed: {0}"; - - /// <summary> - /// The default value for the <see cref="ExamplePrefix"/> property. - /// </summary> - private const string ExamplePrefixDefault = "Example:"; - - /// <summary> - /// The default value for the <see cref="ExampleUrl"/> property. - /// </summary> - private const string ExampleUrlDefault = "http://your.name.myopenid.com"; - - /// <summary> - /// The default value for the <see cref="LabelText"/> property. - /// </summary> - private const string LabelTextDefault = "OpenID Login:"; - - /// <summary> - /// The default value for the <see cref="RequiredText"/> property. - /// </summary> - private const string RequiredTextDefault = "Provide an OpenID first."; - - /// <summary> - /// The default value for the <see cref="UriFormatText"/> property. - /// </summary> - private const string UriFormatTextDefault = "Invalid OpenID URL."; - - /// <summary> - /// The default value for the <see cref="RegisterText"/> property. - /// </summary> - private const string RegisterTextDefault = "register"; - - /// <summary> - /// The default value for the <see cref="RegisterUrl"/> property. - /// </summary> - private const string RegisterUrlDefault = "https://www.myopenid.com/signup"; - - /// <summary> - /// The default value for the <see cref="ButtonToolTip"/> property. - /// </summary> - private const string ButtonToolTipDefault = "Account login"; - - /// <summary> - /// The default value for the <see cref="ValidationGroup"/> property. - /// </summary> - private const string ValidationGroupDefault = "OpenIdLogin"; - - /// <summary> - /// The default value for the <see cref="RegisterVisible"/> property. - /// </summary> - private const bool RegisterVisibleDefault = true; - - /// <summary> - /// The default value for the <see cref="RememberMeVisible"/> property. - /// </summary> - private const bool RememberMeVisibleDefault = false; - - /// <summary> - /// The default value for the <see cref="RememberMe"/> property. - /// </summary> - private const bool RememberMeDefault = false; - - /// <summary> - /// The default value for the <see cref="UriValidatorEnabled"/> property. - /// </summary> - private const bool UriValidatorEnabledDefault = true; - - #endregion - - #region Property viewstate keys - - /// <summary> - /// The viewstate key to use for the <see cref="FailedMessageText"/> property. - /// </summary> - private const string FailedMessageTextViewStateKey = "FailedMessageText"; - - /// <summary> - /// The viewstate key to use for the <see cref="CanceledText"/> property. - /// </summary> - private const string CanceledTextViewStateKey = "CanceledText"; - - /// <summary> - /// The viewstate key to use for the <see cref="IdSelectorIdentifier"/> property. - /// </summary> - private const string IdSelectorIdentifierViewStateKey = "IdSelectorIdentifier"; - - #endregion - - /// <summary> - /// The HTML to append to the <see cref="RequiredText"/> property value when rendering. - /// </summary> - private const string RequiredTextSuffix = "<br/>"; - - /// <summary> - /// The number to add to <see cref="TabIndex"/> to get the tab index of the textbox control. - /// </summary> - private const short TextBoxTabIndexOffset = 0; - - /// <summary> - /// The number to add to <see cref="TabIndex"/> to get the tab index of the login button control. - /// </summary> - private const short LoginButtonTabIndexOffset = 1; - - /// <summary> - /// The number to add to <see cref="TabIndex"/> to get the tab index of the remember me checkbox control. - /// </summary> - private const short RememberMeTabIndexOffset = 2; - - /// <summary> - /// The number to add to <see cref="TabIndex"/> to get the tab index of the register link control. - /// </summary> - private const short RegisterTabIndexOffset = 3; - - #region Controls - - /// <summary> - /// The control into which all other controls are added. - /// </summary> - private Panel panel; - - /// <summary> - /// The Login button. - /// </summary> - private Button loginButton; - - /// <summary> - /// The label that presents the text box. - /// </summary> - private HtmlGenericControl label; - - /// <summary> - /// The validator that flags an empty text box. - /// </summary> - private RequiredFieldValidator requiredValidator; - - /// <summary> - /// The validator that flags invalid formats of OpenID identifiers. - /// </summary> - private CustomValidator identifierFormatValidator; - - /// <summary> - /// The label that precedes an example OpenID identifier. - /// </summary> - private Label examplePrefixLabel; - - /// <summary> - /// The label that contains the example OpenID identifier. - /// </summary> - private Label exampleUrlLabel; - - /// <summary> - /// A link to allow the user to create an account with a popular OpenID Provider. - /// </summary> - private HyperLink registerLink; - - /// <summary> - /// The Remember Me checkbox. - /// </summary> - private CheckBox rememberMeCheckBox; - - /// <summary> - /// The javascript snippet that activates the ID Selector javascript control. - /// </summary> - private Literal idselectorJavascript; - - /// <summary> - /// The label that will display login failure messages. - /// </summary> - private Label errorLabel; - - #endregion - - /// <summary> - /// Initializes a new instance of the <see cref="OpenIdLogin"/> class. - /// </summary> - public OpenIdLogin() { - } - - #region Events - - /// <summary> - /// Fired when the Remember Me checkbox is changed by the user. - /// </summary> - [Description("Fires when the Remember Me checkbox is changed by the user.")] - public event EventHandler RememberMeChanged; - - #endregion - - #region Properties - - /// <summary> - /// Gets a <see cref="T:System.Web.UI.ControlCollection"/> object that represents the child controls for a specified server control in the UI hierarchy. - /// </summary> - /// <returns> - /// The collection of child controls for the specified server control. - /// </returns> - public override ControlCollection Controls { - get { - this.EnsureChildControls(); - return base.Controls; - } - } - - /// <summary> - /// Gets or sets the caption that appears before the text box. - /// </summary> - [Bindable(true)] - [Category("Appearance")] - [DefaultValue(LabelTextDefault)] - [Localizable(true)] - [Description("The caption that appears before the text box.")] - public string LabelText { - get { - EnsureChildControls(); - return this.label.InnerText; - } - - set { - EnsureChildControls(); - this.label.InnerText = value; - } - } - - /// <summary> - /// Gets or sets the text that introduces the example OpenID url. - /// </summary> - [Bindable(true)] - [Category("Appearance")] - [DefaultValue(ExamplePrefixDefault)] - [Localizable(true)] - [Description("The text that introduces the example OpenID url.")] - public string ExamplePrefix { - get { - EnsureChildControls(); - return this.examplePrefixLabel.Text; - } - - set { - EnsureChildControls(); - this.examplePrefixLabel.Text = value; - } - } - - /// <summary> - /// Gets or sets the example OpenID Identifier to display to the user. - /// </summary> - [SuppressMessage("Microsoft.Design", "CA1056:UriPropertiesShouldNotBeStrings", Justification = "Property grid only supports primitive types.")] - [Bindable(true)] - [Category("Appearance")] - [DefaultValue(ExampleUrlDefault)] - [Localizable(true)] - [Description("The example OpenID Identifier to display to the user.")] - public string ExampleUrl { - get { - EnsureChildControls(); - return this.exampleUrlLabel.Text; - } - - set { - EnsureChildControls(); - this.exampleUrlLabel.Text = value; - } - } - - /// <summary> - /// Gets or sets the text to display if the user attempts to login - /// without providing an Identifier. - /// </summary> - [SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "br", Justification = "HTML"), Bindable(true)] - [Category("Appearance")] - [DefaultValue(RequiredTextDefault)] - [Localizable(true)] - [Description("The text to display if the user attempts to login without providing an Identifier.")] - public string RequiredText { - get { - EnsureChildControls(); - return this.requiredValidator.Text.Substring(0, this.requiredValidator.Text.Length - RequiredTextSuffix.Length); - } - - set { - EnsureChildControls(); - this.requiredValidator.ErrorMessage = this.requiredValidator.Text = value + RequiredTextSuffix; - } - } - - /// <summary> - /// Gets or sets the text to display if the user provides an invalid form for an Identifier. - /// </summary> - [SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "br", Justification = "HTML"), SuppressMessage("Microsoft.Design", "CA1056:UriPropertiesShouldNotBeStrings", Justification = "Property grid only supports primitive types.")] - [Bindable(true)] - [Category("Appearance")] - [DefaultValue(UriFormatTextDefault)] - [Localizable(true)] - [Description("The text to display if the user provides an invalid form for an Identifier.")] - public string UriFormatText { - get { - EnsureChildControls(); - return this.identifierFormatValidator.Text.Substring(0, this.identifierFormatValidator.Text.Length - RequiredTextSuffix.Length); - } - - set { - EnsureChildControls(); - this.identifierFormatValidator.ErrorMessage = this.identifierFormatValidator.Text = value + RequiredTextSuffix; - } - } - - /// <summary> - /// Gets or sets a value indicating whether to perform Identifier - /// format validation prior to an authentication attempt. - /// </summary> - [Bindable(true)] - [Category("Behavior")] - [DefaultValue(UriValidatorEnabledDefault)] - [Description("Whether to perform Identifier format validation prior to an authentication attempt.")] - public bool UriValidatorEnabled { - get { - EnsureChildControls(); - return this.identifierFormatValidator.Enabled; - } - - set { - EnsureChildControls(); - this.identifierFormatValidator.Enabled = value; - } - } - - /// <summary> - /// Gets or sets the text of the link users can click on to obtain an OpenID. - /// </summary> - [Bindable(true)] - [Category("Appearance")] - [DefaultValue(RegisterTextDefault)] - [Localizable(true)] - [Description("The text of the link users can click on to obtain an OpenID.")] - public string RegisterText { - get { - EnsureChildControls(); - return this.registerLink.Text; - } - - set { - EnsureChildControls(); - this.registerLink.Text = value; - } - } - - /// <summary> - /// Gets or sets the URL to link users to who click the link to obtain a new OpenID. - /// </summary> - [SuppressMessage("Microsoft.Design", "CA1056:UriPropertiesShouldNotBeStrings", Justification = "Property grid only supports primitive types.")] - [Bindable(true)] - [Category("Appearance")] - [DefaultValue(RegisterUrlDefault)] - [Localizable(true)] - [Description("The URL to link users to who click the link to obtain a new OpenID.")] - public string RegisterUrl { - get { - EnsureChildControls(); - return this.registerLink.NavigateUrl; - } - - set { - EnsureChildControls(); - this.registerLink.NavigateUrl = value; - } - } - - /// <summary> - /// Gets or sets the text of the tooltip to display when the user hovers - /// over the link to obtain a new OpenID. - /// </summary> - [Bindable(true)] - [Category("Appearance")] - [DefaultValue(RegisterToolTipDefault)] - [Localizable(true)] - [Description("The text of the tooltip to display when the user hovers over the link to obtain a new OpenID.")] - public string RegisterToolTip { - get { - EnsureChildControls(); - return this.registerLink.ToolTip; - } - - set { - EnsureChildControls(); - this.registerLink.ToolTip = value; - } - } - - /// <summary> - /// Gets or sets a value indicating whether to display a link to - /// allow users to easily obtain a new OpenID. - /// </summary> - [Bindable(true)] - [Category("Appearance")] - [DefaultValue(RegisterVisibleDefault)] - [Description("Whether to display a link to allow users to easily obtain a new OpenID.")] - public bool RegisterVisible { - get { - EnsureChildControls(); - return this.registerLink.Visible; - } - - set { - EnsureChildControls(); - this.registerLink.Visible = value; - } - } - - /// <summary> - /// Gets or sets the text that appears on the button that initiates login. - /// </summary> - [Bindable(true)] - [Category("Appearance")] - [DefaultValue(ButtonTextDefault)] - [Localizable(true)] - [Description("The text that appears on the button that initiates login.")] - public string ButtonText { - get { - EnsureChildControls(); - return this.loginButton.Text; - } - - set { - EnsureChildControls(); - this.loginButton.Text = value; - } - } - - /// <summary> - /// Gets or sets the text of the "Remember Me" checkbox. - /// </summary> - [Bindable(true)] - [Category("Appearance")] - [DefaultValue(RememberMeTextDefault)] - [Localizable(true)] - [Description("The text of the \"Remember Me\" checkbox.")] - public string RememberMeText { - get { - EnsureChildControls(); - return this.rememberMeCheckBox.Text; - } - - set { - EnsureChildControls(); - this.rememberMeCheckBox.Text = value; - } - } - - /// <summary> - /// Gets or sets the message display in the event of a failed - /// authentication. {0} may be used to insert the actual error. - /// </summary> - [Bindable(true)] - [Category("Appearance")] - [DefaultValue(FailedMessageTextDefault)] - [Localizable(true)] - [Description("The message display in the event of a failed authentication. {0} may be used to insert the actual error.")] - public string FailedMessageText { - get { return (string)ViewState[FailedMessageTextViewStateKey] ?? FailedMessageTextDefault; } - set { ViewState[FailedMessageTextViewStateKey] = value; } - } - - /// <summary> - /// Gets or sets the text to display in the event of an authentication canceled at the Provider. - /// </summary> - [Bindable(true)] - [Category("Appearance")] - [DefaultValue(CanceledTextDefault)] - [Localizable(true)] - [Description("The text to display in the event of an authentication canceled at the Provider.")] - public string CanceledText { - get { return (string)ViewState[CanceledTextViewStateKey] ?? CanceledTextDefault; } - set { ViewState[CanceledTextViewStateKey] = value; } - } - - /// <summary> - /// Gets or sets a value indicating whether the "Remember Me" checkbox should be displayed. - /// </summary> - [Bindable(true)] - [Category("Appearance")] - [DefaultValue(RememberMeVisibleDefault)] - [Description("Whether the \"Remember Me\" checkbox should be displayed.")] - public bool RememberMeVisible { - get { - EnsureChildControls(); - return this.rememberMeCheckBox.Visible; - } - - set { - EnsureChildControls(); - this.rememberMeCheckBox.Visible = value; - } - } - - /// <summary> - /// Gets or sets a value indicating whether a successful authentication should result in a persistent - /// cookie being saved to the browser. - /// </summary> - [Bindable(true)] - [Category("Appearance")] - [DefaultValue(RememberMeDefault)] - [Description("Whether a successful authentication should result in a persistent cookie being saved to the browser.")] - public bool RememberMe { - get { return this.UsePersistentCookie != LogOnPersistence.Session; } - set { this.UsePersistentCookie = value ? LogOnPersistence.PersistentAuthentication : LogOnPersistence.Session; } - } - - /// <summary> - /// Gets or sets the starting tab index to distribute across the controls. - /// </summary> - [SuppressMessage("Microsoft.Usage", "CA2233:OperationsShouldNotOverflow", MessageId = "value+1", Justification = "Overflow would provide desired UI behavior.")] - [SuppressMessage("Microsoft.Usage", "CA2233:OperationsShouldNotOverflow", MessageId = "value+2", Justification = "Overflow would provide desired UI behavior.")] - [SuppressMessage("Microsoft.Usage", "CA2233:OperationsShouldNotOverflow", MessageId = "value+3", Justification = "Overflow would provide desired UI behavior.")] - public override short TabIndex { - get { - return base.TabIndex; - } - - set { - unchecked { - EnsureChildControls(); - base.TabIndex = (short)(value + TextBoxTabIndexOffset); - this.loginButton.TabIndex = (short)(value + LoginButtonTabIndexOffset); - this.rememberMeCheckBox.TabIndex = (short)(value + RememberMeTabIndexOffset); - this.registerLink.TabIndex = (short)(value + RegisterTabIndexOffset); - } - } - } - - /// <summary> - /// Gets or sets the tooltip to display when the user hovers over the login button. - /// </summary> - [Bindable(true)] - [Category("Appearance")] - [DefaultValue(ButtonToolTipDefault)] - [Localizable(true)] - [Description("The tooltip to display when the user hovers over the login button.")] - public string ButtonToolTip { - get { - EnsureChildControls(); - return this.loginButton.ToolTip; - } - - set { - EnsureChildControls(); - this.loginButton.ToolTip = value; - } - } - - /// <summary> - /// Gets or sets the validation group that the login button and text box validator belong to. - /// </summary> - [Category("Behavior")] - [DefaultValue(ValidationGroupDefault)] - [Description("The validation group that the login button and text box validator belong to.")] - public string ValidationGroup { - get { - EnsureChildControls(); - return this.requiredValidator.ValidationGroup; - } - - set { - EnsureChildControls(); - this.requiredValidator.ValidationGroup = value; - this.loginButton.ValidationGroup = value; - } - } - - /// <summary> - /// Gets or sets the unique hash string that ends your idselector.com account. - /// </summary> - [Category("Behavior")] - [Description("The unique hash string that ends your idselector.com account.")] - public string IdSelectorIdentifier { - get { return (string)(ViewState[IdSelectorIdentifierViewStateKey]); } - set { ViewState[IdSelectorIdentifierViewStateKey] = value; } - } - - #endregion - - #region Properties to hide - - /// <summary> - /// Gets or sets a value indicating whether a FormsAuthentication - /// cookie should persist across user sessions. - /// </summary> - [Browsable(false), Bindable(false)] - public override LogOnPersistence UsePersistentCookie { - get { - return base.UsePersistentCookie; - } - - set { - base.UsePersistentCookie = value; - - if (this.rememberMeCheckBox != null) { - // use conditional here to prevent infinite recursion - // with CheckedChanged event. - bool rememberMe = value != LogOnPersistence.Session; - if (this.rememberMeCheckBox.Checked != rememberMe) { - this.rememberMeCheckBox.Checked = rememberMe; - } - } - } - } - - #endregion - - /// <summary> - /// Outputs server control content to a provided <see cref="T:System.Web.UI.HtmlTextWriter"/> object and stores tracing information about the control if tracing is enabled. - /// </summary> - /// <param name="writer">The <see cref="T:System.Web.UI.HTmlTextWriter"/> object that receives the control content.</param> - public override void RenderControl(HtmlTextWriter writer) { - this.RenderChildren(writer); - } - - /// <summary> - /// Creates the child controls. - /// </summary> - protected override void CreateChildControls() { - this.InitializeControls(); - - // Just add the panel we've assembled earlier. - base.Controls.Add(this.panel); - } - - /// <summary> - /// Raises the <see cref="E:System.Web.UI.Control.PreRender"/> event. - /// </summary> - /// <param name="e">An <see cref="T:System.EventArgs"/> object that contains the event data.</param> - protected override void OnPreRender(EventArgs e) { - base.OnPreRender(e); - - this.EnsureChildControls(); - } - - /// <summary> - /// Initializes the child controls. - /// </summary> - [SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", MessageId = "System.Web.UI.WebControls.WebControl.set_ToolTip(System.String)", Justification = "By design"), SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", MessageId = "System.Web.UI.WebControls.Label.set_Text(System.String)", Justification = "By design"), SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", MessageId = "System.Web.UI.WebControls.HyperLink.set_Text(System.String)", Justification = "By design"), SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", MessageId = "System.Web.UI.WebControls.CheckBox.set_Text(System.String)", Justification = "By design"), SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", MessageId = "System.Web.UI.WebControls.Button.set_Text(System.String)", Justification = "By design"), SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", MessageId = "System.Web.UI.WebControls.BaseValidator.set_ErrorMessage(System.String)", Justification = "By design"), SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "br", Justification = "HTML"), SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "OpenID", Justification = "It is correct"), SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "MyOpenID", Justification = "Correct spelling"), SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "myopenid", Justification = "URL")] - protected void InitializeControls() { - this.panel = new Panel(); - - Table table = new Table(); - try { - TableRow row1, row2, row3; - TableCell cell; - table.Rows.Add(row1 = new TableRow()); - table.Rows.Add(row2 = new TableRow()); - table.Rows.Add(row3 = new TableRow()); - - // top row, left cell - cell = new TableCell(); - try { - this.label = new HtmlGenericControl("label"); - this.label.InnerText = LabelTextDefault; - cell.Controls.Add(this.label); - row1.Cells.Add(cell); - } catch { - cell.Dispose(); - throw; - } - - // top row, middle cell - cell = new TableCell(); - try { - cell.Controls.Add(new InPlaceControl(this)); - row1.Cells.Add(cell); - } catch { - cell.Dispose(); - throw; - } - - // top row, right cell - cell = new TableCell(); - try { - this.loginButton = new Button(); - this.loginButton.ID = this.ID + "_loginButton"; - this.loginButton.Text = ButtonTextDefault; - this.loginButton.ToolTip = ButtonToolTipDefault; - this.loginButton.Click += this.LoginButton_Click; - this.loginButton.ValidationGroup = ValidationGroupDefault; -#if !Mono - this.panel.DefaultButton = this.loginButton.ID; -#endif - cell.Controls.Add(this.loginButton); - row1.Cells.Add(cell); - } catch { - cell.Dispose(); - throw; - } - - // middle row, left cell - row2.Cells.Add(new TableCell()); - - // middle row, middle cell - cell = new TableCell(); - try { - cell.Style[HtmlTextWriterStyle.Color] = "gray"; - cell.Style[HtmlTextWriterStyle.FontSize] = "smaller"; - this.requiredValidator = new RequiredFieldValidator(); - this.requiredValidator.ErrorMessage = RequiredTextDefault + RequiredTextSuffix; - this.requiredValidator.Text = RequiredTextDefault + RequiredTextSuffix; - this.requiredValidator.Display = ValidatorDisplay.Dynamic; - this.requiredValidator.ValidationGroup = ValidationGroupDefault; - cell.Controls.Add(this.requiredValidator); - this.identifierFormatValidator = new CustomValidator(); - this.identifierFormatValidator.ErrorMessage = UriFormatTextDefault + RequiredTextSuffix; - this.identifierFormatValidator.Text = UriFormatTextDefault + RequiredTextSuffix; - this.identifierFormatValidator.ServerValidate += this.IdentifierFormatValidator_ServerValidate; - this.identifierFormatValidator.Enabled = UriValidatorEnabledDefault; - this.identifierFormatValidator.Display = ValidatorDisplay.Dynamic; - this.identifierFormatValidator.ValidationGroup = ValidationGroupDefault; - cell.Controls.Add(this.identifierFormatValidator); - this.errorLabel = new Label(); - this.errorLabel.EnableViewState = false; - this.errorLabel.ForeColor = System.Drawing.Color.Red; - this.errorLabel.Style[HtmlTextWriterStyle.Display] = "block"; // puts it on its own line - this.errorLabel.Visible = false; - cell.Controls.Add(this.errorLabel); - this.examplePrefixLabel = new Label(); - this.examplePrefixLabel.Text = ExamplePrefixDefault; - cell.Controls.Add(this.examplePrefixLabel); - cell.Controls.Add(new LiteralControl(" ")); - this.exampleUrlLabel = new Label(); - this.exampleUrlLabel.Font.Bold = true; - this.exampleUrlLabel.Text = ExampleUrlDefault; - cell.Controls.Add(this.exampleUrlLabel); - row2.Cells.Add(cell); - } catch { - cell.Dispose(); - throw; - } - - // middle row, right cell - cell = new TableCell(); - try { - cell.Style[HtmlTextWriterStyle.Color] = "gray"; - cell.Style[HtmlTextWriterStyle.FontSize] = "smaller"; - cell.Style[HtmlTextWriterStyle.TextAlign] = "center"; - this.registerLink = new HyperLink(); - this.registerLink.Text = RegisterTextDefault; - this.registerLink.ToolTip = RegisterToolTipDefault; - this.registerLink.NavigateUrl = RegisterUrlDefault; - this.registerLink.Visible = RegisterVisibleDefault; - cell.Controls.Add(this.registerLink); - row2.Cells.Add(cell); - } catch { - cell.Dispose(); - throw; - } - - // bottom row, left cell - cell = new TableCell(); - row3.Cells.Add(cell); - - // bottom row, middle cell - cell = new TableCell(); - try { - this.rememberMeCheckBox = new CheckBox(); - this.rememberMeCheckBox.Text = RememberMeTextDefault; - this.rememberMeCheckBox.Checked = this.UsePersistentCookie != LogOnPersistence.Session; - this.rememberMeCheckBox.Visible = RememberMeVisibleDefault; - this.rememberMeCheckBox.CheckedChanged += this.RememberMeCheckBox_CheckedChanged; - cell.Controls.Add(this.rememberMeCheckBox); - row3.Cells.Add(cell); - } catch { - cell.Dispose(); - throw; - } - - // bottom row, right cell - cell = new TableCell(); - try { - row3.Cells.Add(cell); - } catch { - cell.Dispose(); - throw; - } - - // this sets all the controls' tab indexes - this.TabIndex = TabIndexDefault; - - this.panel.Controls.Add(table); - } catch { - table.Dispose(); - throw; - } - - this.idselectorJavascript = new Literal(); - this.panel.Controls.Add(this.idselectorJavascript); - } - - /// <summary> - /// Raises the <see cref="E:System.Web.UI.Control.Init"/> event. - /// </summary> - /// <param name="e">An <see cref="T:System.EventArgs"/> object that contains the event data.</param> - protected override void OnInit(EventArgs e) { - this.SetChildControlReferenceIds(); - - base.OnInit(e); - } - - /// <summary> - /// Renders the child controls. - /// </summary> - /// <param name="writer">The <see cref="T:System.Web.UI.HtmlTextWriter"/> object that receives the rendered content.</param> - [SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", MessageId = "System.Web.UI.WebControls.Literal.set_Text(System.String)", Justification = "By design"), SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "idselector", Justification = "HTML"), SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "charset", Justification = "html"), SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "src", Justification = "html"), SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "openidselector", Justification = "html"), SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "idselectorinputid", Justification = "html")] - protected override void RenderChildren(HtmlTextWriter writer) { - if (!this.DesignMode) { - this.label.Attributes["for"] = this.ClientID; - - if (!string.IsNullOrEmpty(this.IdSelectorIdentifier)) { - this.idselectorJavascript.Visible = true; - this.idselectorJavascript.Text = @"<script type='text/javascript'><!-- -idselector_input_id = '" + this.ClientID + @"'; -// --></script> -<script type='text/javascript' id='__openidselector' src='https://www.idselector.com/selector/" + this.IdSelectorIdentifier + @"' charset='utf-8'></script>"; - } else { - this.idselectorJavascript.Visible = false; - } - } - - base.RenderChildren(writer); - } - - /// <summary> - /// Adds failure handling to display an error message to the user. - /// </summary> - /// <param name="response">The response.</param> - protected override void OnFailed(IAuthenticationResponse response) { - base.OnFailed(response); - - if (!string.IsNullOrEmpty(this.FailedMessageText)) { - this.errorLabel.Text = string.Format(CultureInfo.CurrentCulture, this.FailedMessageText, response.Exception.ToStringDescriptive()); - this.errorLabel.Visible = true; - } - } - - /// <summary> - /// Adds authentication cancellation behavior to display a message to the user. - /// </summary> - /// <param name="response">The response.</param> - protected override void OnCanceled(IAuthenticationResponse response) { - base.OnCanceled(response); - - if (!string.IsNullOrEmpty(this.CanceledText)) { - this.errorLabel.Text = this.CanceledText; - this.errorLabel.Visible = true; - } - } - - /// <summary> - /// Fires the <see cref="RememberMeChanged"/> event. - /// </summary> - protected virtual void OnRememberMeChanged() { - EventHandler rememberMeChanged = this.RememberMeChanged; - if (rememberMeChanged != null) { - rememberMeChanged(this, new EventArgs()); - } - } - - /// <summary> - /// Handles the ServerValidate event of the identifierFormatValidator control. - /// </summary> - /// <param name="source">The source of the event.</param> - /// <param name="args">The <see cref="System.Web.UI.WebControls.ServerValidateEventArgs"/> instance containing the event data.</param> - private void IdentifierFormatValidator_ServerValidate(object source, ServerValidateEventArgs args) { - args.IsValid = Identifier.IsValid(args.Value); - } - - /// <summary> - /// Handles the CheckedChanged event of the rememberMeCheckBox control. - /// </summary> - /// <param name="sender">The source of the event.</param> - /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param> - private void RememberMeCheckBox_CheckedChanged(object sender, EventArgs e) { - this.RememberMe = this.rememberMeCheckBox.Checked; - this.OnRememberMeChanged(); - } - - /// <summary> - /// Handles the Click event of the loginButton control. - /// </summary> - /// <param name="sender">The source of the event.</param> - /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param> - private void LoginButton_Click(object sender, EventArgs e) { - if (!this.Page.IsValid) { - return; - } - - IAuthenticationRequest request = this.CreateRequests().FirstOrDefault(); - if (request != null) { - this.LogOn(request); - } else { - if (!string.IsNullOrEmpty(this.FailedMessageText)) { - this.errorLabel.Text = string.Format(CultureInfo.CurrentCulture, this.FailedMessageText, OpenIdStrings.OpenIdEndpointNotFound); - this.errorLabel.Visible = true; - } - } - } - - /// <summary> - /// Renders the control inner. - /// </summary> - /// <param name="writer">The writer.</param> - private void RenderControlInner(HtmlTextWriter writer) { - base.RenderControl(writer); - } - - /// <summary> - /// Sets child control properties that depend on this control's ID. - /// </summary> - private void SetChildControlReferenceIds() { - this.EnsureChildControls(); - this.EnsureID(); - ErrorUtilities.VerifyInternal(!string.IsNullOrEmpty(this.ID), "No control ID available yet!"); - this.requiredValidator.ControlToValidate = this.ID; - this.requiredValidator.ID = this.ID + "_requiredValidator"; - this.identifierFormatValidator.ControlToValidate = this.ID; - this.identifierFormatValidator.ID = this.ID + "_identifierFormatValidator"; - } - - /// <summary> - /// A control that acts as a placeholder to indicate where - /// the OpenIdLogin control should render its OpenIdTextBox parent. - /// </summary> - private class InPlaceControl : PlaceHolder { - /// <summary> - /// The owning control to render. - /// </summary> - private OpenIdLogin renderControl; - - /// <summary> - /// Initializes a new instance of the <see cref="InPlaceControl"/> class. - /// </summary> - /// <param name="renderControl">The render control.</param> - internal InPlaceControl(OpenIdLogin renderControl) { - this.renderControl = renderControl; - } - - /// <summary> - /// Sends server control content to a provided <see cref="T:System.Web.UI.HtmlTextWriter"/> object, which writes the content to be rendered on the client. - /// </summary> - /// <param name="writer">The <see cref="T:System.Web.UI.HtmlTextWriter"/> object that receives the server control content.</param> - protected override void Render(HtmlTextWriter writer) { - this.renderControl.RenderControlInner(writer); - } - } - } -} diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdMobileTextBox.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdMobileTextBox.cs deleted file mode 100644 index 9cafb74..0000000 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdMobileTextBox.cs +++ /dev/null @@ -1,778 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="OpenIdMobileTextBox.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -[assembly: System.Web.UI.WebResource(DotNetOpenAuth.OpenId.RelyingParty.OpenIdMobileTextBox.EmbeddedLogoResourceName, "image/gif")] - -namespace DotNetOpenAuth.OpenId.RelyingParty { - using System; - using System.ComponentModel; - using System.Diagnostics; - using System.Diagnostics.CodeAnalysis; - using System.Diagnostics.Contracts; - using System.Globalization; - using System.Text.RegularExpressions; - using System.Web.Security; - using System.Web.UI; - using System.Web.UI.MobileControls; - using DotNetOpenAuth.Configuration; - using DotNetOpenAuth.Messaging; - using DotNetOpenAuth.OpenId.Extensions.SimpleRegistration; - - /// <summary> - /// An ASP.NET control for mobile devices that provides a minimal text box that is OpenID-aware. - /// </summary> - [DefaultProperty("Text"), ValidationProperty("Text")] - [ToolboxData("<{0}:OpenIdMobileTextBox runat=\"server\" />")] - public class OpenIdMobileTextBox : TextBox { - /// <summary> - /// The name of the manifest stream containing the - /// OpenID logo that is placed inside the text box. - /// </summary> - internal const string EmbeddedLogoResourceName = OpenIdTextBox.EmbeddedLogoResourceName; - - /// <summary> - /// Default value of <see cref="UsePersistentCookie"/>. - /// </summary> - protected const bool UsePersistentCookieDefault = false; - - #region Property category constants - - /// <summary> - /// The "Appearance" category for properties. - /// </summary> - private const string AppearanceCategory = "Appearance"; - - /// <summary> - /// The "Simple Registration" category for properties. - /// </summary> - private const string ProfileCategory = "Simple Registration"; - - /// <summary> - /// The "Behavior" category for properties. - /// </summary> - private const string BehaviorCategory = "Behavior"; - - #endregion - - #region Property viewstate keys - - /// <summary> - /// The viewstate key to use for the <see cref="RequestEmail"/> property. - /// </summary> - private const string RequestEmailViewStateKey = "RequestEmail"; - - /// <summary> - /// The viewstate key to use for the <see cref="RequestNickname"/> property. - /// </summary> - private const string RequestNicknameViewStateKey = "RequestNickname"; - - /// <summary> - /// The viewstate key to use for the <see cref="RequestPostalCode"/> property. - /// </summary> - private const string RequestPostalCodeViewStateKey = "RequestPostalCode"; - - /// <summary> - /// The viewstate key to use for the <see cref="RequestCountry"/> property. - /// </summary> - private const string RequestCountryViewStateKey = "RequestCountry"; - - /// <summary> - /// The viewstate key to use for the <see cref="RequireSsl"/> property. - /// </summary> - private const string RequireSslViewStateKey = "RequireSsl"; - - /// <summary> - /// The viewstate key to use for the <see cref="RequestLanguage"/> property. - /// </summary> - private const string RequestLanguageViewStateKey = "RequestLanguage"; - - /// <summary> - /// The viewstate key to use for the <see cref="RequestTimeZone"/> property. - /// </summary> - private const string RequestTimeZoneViewStateKey = "RequestTimeZone"; - - /// <summary> - /// The viewstate key to use for the <see cref="EnableRequestProfile"/> property. - /// </summary> - private const string EnableRequestProfileViewStateKey = "EnableRequestProfile"; - - /// <summary> - /// The viewstate key to use for the <see cref="PolicyUrl"/> property. - /// </summary> - private const string PolicyUrlViewStateKey = "PolicyUrl"; - - /// <summary> - /// The viewstate key to use for the <see cref="RequestFullName"/> property. - /// </summary> - private const string RequestFullNameViewStateKey = "RequestFullName"; - - /// <summary> - /// The viewstate key to use for the <see cref="UsePersistentCookie"/> property. - /// </summary> - private const string UsePersistentCookieViewStateKey = "UsePersistentCookie"; - - /// <summary> - /// The viewstate key to use for the <see cref="RequestGender"/> property. - /// </summary> - private const string RequestGenderViewStateKey = "RequestGender"; - - /// <summary> - /// The viewstate key to use for the <see cref="ReturnToUrl"/> property. - /// </summary> - private const string ReturnToUrlViewStateKey = "ReturnToUrl"; - - /// <summary> - /// The viewstate key to use for the <see cref="Stateless"/> property. - /// </summary> - private const string StatelessViewStateKey = "Stateless"; - - /// <summary> - /// The viewstate key to use for the <see cref="ImmediateMode"/> property. - /// </summary> - private const string ImmediateModeViewStateKey = "ImmediateMode"; - - /// <summary> - /// The viewstate key to use for the <see cref="RequestBirthDate"/> property. - /// </summary> - private const string RequestBirthDateViewStateKey = "RequestBirthDate"; - - /// <summary> - /// The viewstate key to use for the <see cref="RealmUrl"/> property. - /// </summary> - private const string RealmUrlViewStateKey = "RealmUrl"; - - #endregion - - #region Property defaults - - /// <summary> - /// The default value for the <see cref="EnableRequestProfile"/> property. - /// </summary> - private const bool EnableRequestProfileDefault = true; - - /// <summary> - /// The default value for the <see cref="RequireSsl"/> property. - /// </summary> - private const bool RequireSslDefault = false; - - /// <summary> - /// The default value for the <see cref="ImmediateMode"/> property. - /// </summary> - private const bool ImmediateModeDefault = false; - - /// <summary> - /// The default value for the <see cref="Stateless"/> property. - /// </summary> - private const bool StatelessDefault = false; - - /// <summary> - /// The default value for the <see cref="PolicyUrl"/> property. - /// </summary> - private const string PolicyUrlDefault = ""; - - /// <summary> - /// The default value for the <see cref="ReturnToUrl"/> property. - /// </summary> - private const string ReturnToUrlDefault = ""; - - /// <summary> - /// The default value for the <see cref="RealmUrl"/> property. - /// </summary> - private const string RealmUrlDefault = "~/"; - - /// <summary> - /// The default value for the <see cref="RequestEmail"/> property. - /// </summary> - private const DemandLevel RequestEmailDefault = DemandLevel.NoRequest; - - /// <summary> - /// The default value for the <see cref="RequestPostalCode"/> property. - /// </summary> - private const DemandLevel RequestPostalCodeDefault = DemandLevel.NoRequest; - - /// <summary> - /// The default value for the <see cref="RequestCountry"/> property. - /// </summary> - private const DemandLevel RequestCountryDefault = DemandLevel.NoRequest; - - /// <summary> - /// The default value for the <see cref="RequestLanguage"/> property. - /// </summary> - private const DemandLevel RequestLanguageDefault = DemandLevel.NoRequest; - - /// <summary> - /// The default value for the <see cref="RequestTimeZone"/> property. - /// </summary> - private const DemandLevel RequestTimeZoneDefault = DemandLevel.NoRequest; - - /// <summary> - /// The default value for the <see cref="RequestNickname"/> property. - /// </summary> - private const DemandLevel RequestNicknameDefault = DemandLevel.NoRequest; - - /// <summary> - /// The default value for the <see cref="RequestFullName"/> property. - /// </summary> - private const DemandLevel RequestFullNameDefault = DemandLevel.NoRequest; - - /// <summary> - /// The default value for the <see cref="RequestBirthDate"/> property. - /// </summary> - private const DemandLevel RequestBirthDateDefault = DemandLevel.NoRequest; - - /// <summary> - /// The default value for the <see cref="RequestGender"/> property. - /// </summary> - private const DemandLevel RequestGenderDefault = DemandLevel.NoRequest; - - #endregion - - /// <summary> - /// The callback parameter for use with persisting the <see cref="UsePersistentCookie"/> property. - /// </summary> - private const string UsePersistentCookieCallbackKey = "OpenIdTextBox_UsePersistentCookie"; - - /// <summary> - /// Backing field for the <see cref="RelyingParty"/> property. - /// </summary> - private OpenIdRelyingParty relyingParty; - - /// <summary> - /// Initializes a new instance of the <see cref="OpenIdMobileTextBox"/> class. - /// </summary> - public OpenIdMobileTextBox() { - Reporting.RecordFeatureUse(this); - } - - #region Events - - /// <summary> - /// Fired upon completion of a successful login. - /// </summary> - [Description("Fired upon completion of a successful login.")] - public event EventHandler<OpenIdEventArgs> LoggedIn; - - /// <summary> - /// Fired when a login attempt fails. - /// </summary> - [Description("Fired when a login attempt fails.")] - public event EventHandler<OpenIdEventArgs> Failed; - - /// <summary> - /// Fired when an authentication attempt is canceled at the OpenID Provider. - /// </summary> - [Description("Fired when an authentication attempt is canceled at the OpenID Provider.")] - public event EventHandler<OpenIdEventArgs> Canceled; - - /// <summary> - /// Fired when an Immediate authentication attempt fails, and the Provider suggests using non-Immediate mode. - /// </summary> - [Description("Fired when an Immediate authentication attempt fails, and the Provider suggests using non-Immediate mode.")] - public event EventHandler<OpenIdEventArgs> SetupRequired; - - #endregion - - #region Properties - - /// <summary> - /// Gets or sets the OpenID <see cref="Realm"/> of the relying party web site. - /// </summary> - [SuppressMessage("Microsoft.Usage", "CA1806:DoNotIgnoreMethodResults", MessageId = "DotNetOpenAuth.OpenId.Realm", Justification = "Using Realm.ctor for validation.")] - [SuppressMessage("Microsoft.Usage", "CA1806:DoNotIgnoreMethodResults", MessageId = "System.Uri", Justification = "Using Uri.ctor for validation.")] - [SuppressMessage("Microsoft.Usage", "CA1806:DoNotIgnoreMethodResults", MessageId = "DotNetOpenAuth.OpenId", Justification = "Using ctor for validation.")] - [SuppressMessage("Microsoft.Design", "CA1056:UriPropertiesShouldNotBeStrings", Justification = "Bindable property must be simple type")] - [Bindable(true), DefaultValue(RealmUrlDefault), Category(BehaviorCategory)] - [Description("The OpenID Realm of the relying party web site.")] - public string RealmUrl { - get { - return (string)(ViewState[RealmUrlViewStateKey] ?? RealmUrlDefault); - } - - set { - if (Page != null && !DesignMode) { - // Validate new value by trying to construct a Realm object based on it. - new Realm(OpenIdUtilities.GetResolvedRealm(this.Page, value, this.RelyingParty.Channel.GetRequestFromContext())); // throws an exception on failure. - } else { - // We can't fully test it, but it should start with either ~/ or a protocol. - if (Regex.IsMatch(value, @"^https?://")) { - new Uri(value.Replace("*.", string.Empty)); // make sure it's fully-qualified, but ignore wildcards - } else if (value.StartsWith("~/", StringComparison.Ordinal)) { - // this is valid too - } else { - throw new UriFormatException(); - } - } - ViewState[RealmUrlViewStateKey] = value; - } - } - - /// <summary> - /// Gets or sets the OpenID ReturnTo of the relying party web site. - /// </summary> - [SuppressMessage("Microsoft.Usage", "CA2234:PassSystemUriObjectsInsteadOfStrings", Justification = "Uri(Uri, string) accepts second arguments that Uri(Uri, new Uri(string)) does not that we must support.")] - [SuppressMessage("Microsoft.Usage", "CA1806:DoNotIgnoreMethodResults", MessageId = "System.Uri", Justification = "Using Uri.ctor for validation.")] - [SuppressMessage("Microsoft.Design", "CA1056:UriPropertiesShouldNotBeStrings", Justification = "Bindable property must be simple type")] - [Bindable(true), DefaultValue(ReturnToUrlDefault), Category(BehaviorCategory)] - [Description("The OpenID ReturnTo of the relying party web site.")] - public string ReturnToUrl { - get { - return (string)(ViewState[ReturnToUrlViewStateKey] ?? ReturnToUrlDefault); - } - - set { - if (Page != null && !DesignMode) { - // Validate new value by trying to construct a Uri based on it. - new Uri(this.RelyingParty.Channel.GetRequestFromContext().UrlBeforeRewriting, this.Page.ResolveUrl(value)); // throws an exception on failure. - } else { - // We can't fully test it, but it should start with either ~/ or a protocol. - if (Regex.IsMatch(value, @"^https?://")) { - new Uri(value); // make sure it's fully-qualified, but ignore wildcards - } else if (value.StartsWith("~/", StringComparison.Ordinal)) { - // this is valid too - } else { - throw new UriFormatException(); - } - } - - ViewState[ReturnToUrlViewStateKey] = value; - } - } - - /// <summary> - /// Gets or sets a value indicating whether to use immediate mode in the - /// OpenID protocol. - /// </summary> - /// <value> - /// True if a Provider should reply immediately to the authentication request - /// without interacting with the user. False if the Provider can take time - /// to authenticate the user in order to complete an authentication attempt. - /// </value> - /// <remarks> - /// Setting this to true is sometimes useful in AJAX scenarios. Setting this to - /// true can cause failed authentications when the user truly controls an - /// Identifier, but must complete an authentication step with the Provider before - /// the Provider will approve the login from this relying party. - /// </remarks> - [Bindable(true), DefaultValue(ImmediateModeDefault), Category(BehaviorCategory)] - [Description("Whether the Provider should respond immediately to an authentication attempt without interacting with the user.")] - public bool ImmediateMode { - get { return (bool)(ViewState[ImmediateModeViewStateKey] ?? ImmediateModeDefault); } - set { ViewState[ImmediateModeViewStateKey] = value; } - } - - /// <summary> - /// Gets or sets a value indicating whether stateless mode is used. - /// </summary> - [Bindable(true), DefaultValue(StatelessDefault), Category(BehaviorCategory)] - [Description("Controls whether stateless mode is used.")] - public bool Stateless { - get { return (bool)(ViewState[StatelessViewStateKey] ?? StatelessDefault); } - set { ViewState[StatelessViewStateKey] = value; } - } - - /// <summary> - /// Gets or sets a value indicating whether to send a persistent cookie upon successful - /// login so the user does not have to log in upon returning to this site. - /// </summary> - [Bindable(true), DefaultValue(UsePersistentCookieDefault), Category(BehaviorCategory)] - [Description("Whether to send a persistent cookie upon successful " + - "login so the user does not have to log in upon returning to this site.")] - public virtual bool UsePersistentCookie { - get { return (bool)(this.ViewState[UsePersistentCookieViewStateKey] ?? UsePersistentCookieDefault); } - set { this.ViewState[UsePersistentCookieViewStateKey] = value; } - } - - /// <summary> - /// Gets or sets your level of interest in receiving the user's nickname from the Provider. - /// </summary> - [Bindable(true), DefaultValue(RequestNicknameDefault), Category(ProfileCategory)] - [Description("Your level of interest in receiving the user's nickname from the Provider.")] - public DemandLevel RequestNickname { - get { return (DemandLevel)(ViewState[RequestNicknameViewStateKey] ?? RequestNicknameDefault); } - set { ViewState[RequestNicknameViewStateKey] = value; } - } - - /// <summary> - /// Gets or sets your level of interest in receiving the user's email address from the Provider. - /// </summary> - [Bindable(true), DefaultValue(RequestEmailDefault), Category(ProfileCategory)] - [Description("Your level of interest in receiving the user's email address from the Provider.")] - public DemandLevel RequestEmail { - get { return (DemandLevel)(ViewState[RequestEmailViewStateKey] ?? RequestEmailDefault); } - set { ViewState[RequestEmailViewStateKey] = value; } - } - - /// <summary> - /// Gets or sets your level of interest in receiving the user's full name from the Provider. - /// </summary> - [Bindable(true), DefaultValue(RequestFullNameDefault), Category(ProfileCategory)] - [Description("Your level of interest in receiving the user's full name from the Provider")] - public DemandLevel RequestFullName { - get { return (DemandLevel)(ViewState[RequestFullNameViewStateKey] ?? RequestFullNameDefault); } - set { ViewState[RequestFullNameViewStateKey] = value; } - } - - /// <summary> - /// Gets or sets your level of interest in receiving the user's birthdate from the Provider. - /// </summary> - [Bindable(true), DefaultValue(RequestBirthDateDefault), Category(ProfileCategory)] - [Description("Your level of interest in receiving the user's birthdate from the Provider.")] - public DemandLevel RequestBirthDate { - get { return (DemandLevel)(ViewState[RequestBirthDateViewStateKey] ?? RequestBirthDateDefault); } - set { ViewState[RequestBirthDateViewStateKey] = value; } - } - - /// <summary> - /// Gets or sets your level of interest in receiving the user's gender from the Provider. - /// </summary> - [Bindable(true), DefaultValue(RequestGenderDefault), Category(ProfileCategory)] - [Description("Your level of interest in receiving the user's gender from the Provider.")] - public DemandLevel RequestGender { - get { return (DemandLevel)(ViewState[RequestGenderViewStateKey] ?? RequestGenderDefault); } - set { ViewState[RequestGenderViewStateKey] = value; } - } - - /// <summary> - /// Gets or sets your level of interest in receiving the user's postal code from the Provider. - /// </summary> - [Bindable(true), DefaultValue(RequestPostalCodeDefault), Category(ProfileCategory)] - [Description("Your level of interest in receiving the user's postal code from the Provider.")] - public DemandLevel RequestPostalCode { - get { return (DemandLevel)(ViewState[RequestPostalCodeViewStateKey] ?? RequestPostalCodeDefault); } - set { ViewState[RequestPostalCodeViewStateKey] = value; } - } - - /// <summary> - /// Gets or sets your level of interest in receiving the user's country from the Provider. - /// </summary> - [Bindable(true)] - [Category(ProfileCategory)] - [DefaultValue(RequestCountryDefault)] - [Description("Your level of interest in receiving the user's country from the Provider.")] - public DemandLevel RequestCountry { - get { return (DemandLevel)(ViewState[RequestCountryViewStateKey] ?? RequestCountryDefault); } - set { ViewState[RequestCountryViewStateKey] = value; } - } - - /// <summary> - /// Gets or sets your level of interest in receiving the user's preferred language from the Provider. - /// </summary> - [Bindable(true), DefaultValue(RequestLanguageDefault), Category(ProfileCategory)] - [Description("Your level of interest in receiving the user's preferred language from the Provider.")] - public DemandLevel RequestLanguage { - get { return (DemandLevel)(ViewState[RequestLanguageViewStateKey] ?? RequestLanguageDefault); } - set { ViewState[RequestLanguageViewStateKey] = value; } - } - - /// <summary> - /// Gets or sets your level of interest in receiving the user's time zone from the Provider. - /// </summary> - [Bindable(true), DefaultValue(RequestTimeZoneDefault), Category(ProfileCategory)] - [Description("Your level of interest in receiving the user's time zone from the Provider.")] - public DemandLevel RequestTimeZone { - get { return (DemandLevel)(ViewState[RequestTimeZoneViewStateKey] ?? RequestTimeZoneDefault); } - set { ViewState[RequestTimeZoneViewStateKey] = value; } - } - - /// <summary> - /// Gets or sets the URL to your privacy policy page that describes how - /// claims will be used and/or shared. - /// </summary> - [SuppressMessage("Microsoft.Design", "CA1056:UriPropertiesShouldNotBeStrings", Justification = "Bindable property must be simple type")] - [Bindable(true), DefaultValue(PolicyUrlDefault), Category(ProfileCategory)] - [Description("The URL to your privacy policy page that describes how claims will be used and/or shared.")] - public string PolicyUrl { - get { - return (string)ViewState[PolicyUrlViewStateKey] ?? PolicyUrlDefault; - } - - set { - UriUtil.ValidateResolvableUrl(Page, DesignMode, value); - ViewState[PolicyUrlViewStateKey] = value; - } - } - - /// <summary> - /// Gets or sets a value indicating whether to use OpenID extensions - /// to retrieve profile data of the authenticating user. - /// </summary> - [Bindable(true), DefaultValue(EnableRequestProfileDefault), Category(ProfileCategory)] - [Description("Turns the entire Simple Registration extension on or off.")] - public bool EnableRequestProfile { - get { return (bool)(ViewState[EnableRequestProfileViewStateKey] ?? EnableRequestProfileDefault); } - set { ViewState[EnableRequestProfileViewStateKey] = value; } - } - - /// <summary> - /// Gets or sets a value indicating whether to enforce on high security mode, - /// which requires the full authentication pipeline to be protected by SSL. - /// </summary> - [Bindable(true), DefaultValue(RequireSslDefault), Category(BehaviorCategory)] - [Description("Turns on high security mode, requiring the full authentication pipeline to be protected by SSL.")] - public bool RequireSsl { - get { return (bool)(ViewState[RequireSslViewStateKey] ?? RequireSslDefault); } - set { ViewState[RequireSslViewStateKey] = value; } - } - - /// <summary> - /// Gets or sets the type of the custom application store to use, or <c>null</c> to use the default. - /// </summary> - /// <remarks> - /// If set, this property must be set in each Page Load event - /// as it is not persisted across postbacks. - /// </remarks> - public IOpenIdApplicationStore CustomApplicationStore { get; set; } - - #endregion - - /// <summary> - /// Gets or sets the <see cref="OpenIdRelyingParty"/> instance to use. - /// </summary> - /// <value>The default value is an <see cref="OpenIdRelyingParty"/> instance initialized according to the web.config file.</value> - /// <remarks> - /// A performance optimization would be to store off the - /// instance as a static member in your web site and set it - /// to this property in your <see cref="Control.Load">Page.Load</see> - /// event since instantiating these instances can be expensive on - /// heavily trafficked web pages. - /// </remarks> - public OpenIdRelyingParty RelyingParty { - get { - if (this.relyingParty == null) { - this.relyingParty = this.CreateRelyingParty(); - } - return this.relyingParty; - } - - set { - this.relyingParty = value; - } - } - - /// <summary> - /// Gets or sets the OpenID authentication request that is about to be sent. - /// </summary> - protected IAuthenticationRequest Request { get; set; } - - /// <summary> - /// Immediately redirects to the OpenID Provider to verify the Identifier - /// provided in the text box. - /// </summary> - public void LogOn() { - if (this.Request == null) { - this.CreateRequest(); // sets this.Request - } - - if (this.Request != null) { - this.Request.RedirectToProvider(); - } - } - - /// <summary> - /// Constructs the authentication request and returns it. - /// </summary> - /// <returns>The instantiated authentication request.</returns> - /// <remarks> - /// <para>This method need not be called before calling the <see cref="LogOn"/> method, - /// but is offered in the event that adding extensions to the request is desired.</para> - /// <para>The Simple Registration extension arguments are added to the request - /// before returning if <see cref="EnableRequestProfile"/> is set to true.</para> - /// </remarks> - [SuppressMessage("Microsoft.Usage", "CA2234:PassSystemUriObjectsInsteadOfStrings", Justification = "Uri(Uri, string) accepts second arguments that Uri(Uri, new Uri(string)) does not that we must support.")] - public IAuthenticationRequest CreateRequest() { - Contract.Requires<InvalidOperationException>(this.Request == null, OpenIdStrings.CreateRequestAlreadyCalled); - Contract.Requires<InvalidOperationException>(!string.IsNullOrEmpty(this.Text), OpenIdStrings.OpenIdTextBoxEmpty); - - try { - // Resolve the trust root, and swap out the scheme and port if necessary to match the - // return_to URL, since this match is required by OpenId, and the consumer app - // may be using HTTP at some times and HTTPS at others. - UriBuilder realm = OpenIdUtilities.GetResolvedRealm(this.Page, this.RealmUrl, this.RelyingParty.Channel.GetRequestFromContext()); - realm.Scheme = Page.Request.Url.Scheme; - realm.Port = Page.Request.Url.Port; - - // Initiate openid request - // We use TryParse here to avoid throwing an exception which - // might slip through our validator control if it is disabled. - Identifier userSuppliedIdentifier; - if (Identifier.TryParse(this.Text, out userSuppliedIdentifier)) { - Realm typedRealm = new Realm(realm); - if (string.IsNullOrEmpty(this.ReturnToUrl)) { - this.Request = this.RelyingParty.CreateRequest(userSuppliedIdentifier, typedRealm); - } else { - Uri returnTo = new Uri(this.RelyingParty.Channel.GetRequestFromContext().UrlBeforeRewriting, this.ReturnToUrl); - this.Request = this.RelyingParty.CreateRequest(userSuppliedIdentifier, typedRealm, returnTo); - } - this.Request.Mode = this.ImmediateMode ? AuthenticationRequestMode.Immediate : AuthenticationRequestMode.Setup; - if (this.EnableRequestProfile) { - this.AddProfileArgs(this.Request); - } - - // Add state that needs to survive across the redirect. - this.Request.SetUntrustedCallbackArgument(UsePersistentCookieCallbackKey, this.UsePersistentCookie.ToString(CultureInfo.InvariantCulture)); - } else { - Logger.OpenId.WarnFormat("An invalid identifier was entered ({0}), but not caught by any validation routine.", this.Text); - this.Request = null; - } - } catch (ProtocolException ex) { - this.OnFailed(new FailedAuthenticationResponse(ex)); - } - - return this.Request; - } - - /// <summary> - /// Checks for incoming OpenID authentication responses and fires appropriate events. - /// </summary> - /// <param name="e">The <see cref="T:System.EventArgs"/> object that contains the event data.</param> - protected override void OnLoad(EventArgs e) { - base.OnLoad(e); - - if (Page.IsPostBack) { - return; - } - - var response = this.RelyingParty.GetResponse(); - if (response != null) { - string persistentString = response.GetUntrustedCallbackArgument(UsePersistentCookieCallbackKey); - bool persistentBool; - if (persistentString != null && bool.TryParse(persistentString, out persistentBool)) { - this.UsePersistentCookie = persistentBool; - } - - switch (response.Status) { - case AuthenticationStatus.Canceled: - this.OnCanceled(response); - break; - case AuthenticationStatus.Authenticated: - this.OnLoggedIn(response); - break; - case AuthenticationStatus.SetupRequired: - this.OnSetupRequired(response); - break; - case AuthenticationStatus.Failed: - this.OnFailed(response); - break; - default: - throw new InvalidOperationException("Unexpected response status code."); - } - } - } - - #region Events - - /// <summary> - /// Fires the <see cref="LoggedIn"/> event. - /// </summary> - /// <param name="response">The response.</param> - protected virtual void OnLoggedIn(IAuthenticationResponse response) { - Contract.Requires<ArgumentNullException>(response != null); - ErrorUtilities.VerifyInternal(response.Status == AuthenticationStatus.Authenticated, "Firing OnLoggedIn event without an authenticated response."); - - var loggedIn = this.LoggedIn; - OpenIdEventArgs args = new OpenIdEventArgs(response); - if (loggedIn != null) { - loggedIn(this, args); - } - - if (!args.Cancel) { - FormsAuthentication.RedirectFromLoginPage(response.ClaimedIdentifier, this.UsePersistentCookie); - } - } - - /// <summary> - /// Fires the <see cref="Failed"/> event. - /// </summary> - /// <param name="response">The response.</param> - protected virtual void OnFailed(IAuthenticationResponse response) { - Contract.Requires<ArgumentNullException>(response != null); - ErrorUtilities.VerifyInternal(response.Status == AuthenticationStatus.Failed, "Firing Failed event for the wrong response type."); - - var failed = this.Failed; - if (failed != null) { - failed(this, new OpenIdEventArgs(response)); - } - } - - /// <summary> - /// Fires the <see cref="Canceled"/> event. - /// </summary> - /// <param name="response">The response.</param> - protected virtual void OnCanceled(IAuthenticationResponse response) { - Contract.Requires<ArgumentNullException>(response != null); - ErrorUtilities.VerifyInternal(response.Status == AuthenticationStatus.Canceled, "Firing Canceled event for the wrong response type."); - - var canceled = this.Canceled; - if (canceled != null) { - canceled(this, new OpenIdEventArgs(response)); - } - } - - /// <summary> - /// Fires the <see cref="SetupRequired"/> event. - /// </summary> - /// <param name="response">The response.</param> - protected virtual void OnSetupRequired(IAuthenticationResponse response) { - Contract.Requires<ArgumentNullException>(response != null); - ErrorUtilities.VerifyInternal(response.Status == AuthenticationStatus.SetupRequired, "Firing SetupRequired event for the wrong response type."); - - // Why are we firing Failed when we're OnSetupRequired? Backward compatibility. - var setupRequired = this.SetupRequired; - if (setupRequired != null) { - setupRequired(this, new OpenIdEventArgs(response)); - } - } - - #endregion - - /// <summary> - /// Adds extensions to a given authentication request to ask the Provider - /// for user profile data. - /// </summary> - /// <param name="request">The authentication request to add the extensions to.</param> - private void AddProfileArgs(IAuthenticationRequest request) { - Contract.Requires<ArgumentNullException>(request != null); - - request.AddExtension(new ClaimsRequest() { - Nickname = this.RequestNickname, - Email = this.RequestEmail, - FullName = this.RequestFullName, - BirthDate = this.RequestBirthDate, - Gender = this.RequestGender, - PostalCode = this.RequestPostalCode, - Country = this.RequestCountry, - Language = this.RequestLanguage, - TimeZone = this.RequestTimeZone, - PolicyUrl = string.IsNullOrEmpty(this.PolicyUrl) ? - null : new Uri(this.RelyingParty.Channel.GetRequestFromContext().UrlBeforeRewriting, this.Page.ResolveUrl(this.PolicyUrl)), - }); - } - - /// <summary> - /// Creates the relying party instance used to generate authentication requests. - /// </summary> - /// <returns>The instantiated relying party.</returns> - private OpenIdRelyingParty CreateRelyingParty() { - // If we're in stateful mode, first use the explicitly given one on this control if there - // is one. Then try the configuration file specified one. Finally, use the default - // in-memory one that's built into OpenIdRelyingParty. - IOpenIdApplicationStore store = this.Stateless ? null : - (this.CustomApplicationStore ?? DotNetOpenAuthSection.Configuration.OpenId.RelyingParty.ApplicationStore.CreateInstance(OpenIdRelyingParty.HttpApplicationStore)); - var rp = new OpenIdRelyingParty(store); - try { - // Only set RequireSsl to true, as we don't want to override - // a .config setting of true with false. - if (this.RequireSsl) { - rp.SecuritySettings.RequireSsl = true; - } - return rp; - } catch { - rp.Dispose(); - throw; - } - } - } -} diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingParty.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingParty.cs deleted file mode 100644 index aec59b8..0000000 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingParty.cs +++ /dev/null @@ -1,891 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="OpenIdRelyingParty.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OpenId.RelyingParty { - using System; - using System.Collections.Generic; - using System.Collections.ObjectModel; - using System.Collections.Specialized; - using System.ComponentModel; - using System.Diagnostics.CodeAnalysis; - using System.Diagnostics.Contracts; - using System.Globalization; - using System.Linq; - using System.Net; - using System.Net.Mime; - using System.Text; - using System.Web; - using DotNetOpenAuth.Configuration; - using DotNetOpenAuth.Messaging; - using DotNetOpenAuth.Messaging.Bindings; - using DotNetOpenAuth.OpenId.ChannelElements; - using DotNetOpenAuth.OpenId.Extensions; - using DotNetOpenAuth.OpenId.Messages; - - /// <summary> - /// A delegate that decides whether a given OpenID Provider endpoint may be - /// considered for authenticating a user. - /// </summary> - /// <param name="endpoint">The endpoint for consideration.</param> - /// <returns> - /// <c>True</c> if the endpoint should be considered. - /// <c>False</c> to remove it from the pool of acceptable providers. - /// </returns> - public delegate bool EndpointSelector(IProviderEndpoint endpoint); - - /// <summary> - /// Provides the programmatic facilities to act as an OpenID relying party. - /// </summary> - [SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Justification = "Unavoidable")] - [ContractVerification(true)] - public class OpenIdRelyingParty : IDisposable { - /// <summary> - /// The name of the key to use in the HttpApplication cache to store the - /// instance of <see cref="StandardRelyingPartyApplicationStore"/> to use. - /// </summary> - private const string ApplicationStoreKey = "DotNetOpenAuth.OpenId.RelyingParty.OpenIdRelyingParty.HttpApplicationStore"; - - /// <summary> - /// Backing store for the <see cref="Behaviors"/> property. - /// </summary> - private readonly ObservableCollection<IRelyingPartyBehavior> behaviors = new ObservableCollection<IRelyingPartyBehavior>(); - - /// <summary> - /// Backing field for the <see cref="DiscoveryServices"/> property. - /// </summary> - private readonly IList<IIdentifierDiscoveryService> discoveryServices = new List<IIdentifierDiscoveryService>(2); - - /// <summary> - /// Backing field for the <see cref="NonVerifyingRelyingParty"/> property. - /// </summary> - private OpenIdRelyingParty nonVerifyingRelyingParty; - - /// <summary> - /// The lock to obtain when initializing the <see cref="nonVerifyingRelyingParty"/> member. - /// </summary> - private object nonVerifyingRelyingPartyInitLock = new object(); - - /// <summary> - /// A dictionary of extension response types and the javascript member - /// name to map them to on the user agent. - /// </summary> - private Dictionary<Type, string> clientScriptExtensions = new Dictionary<Type, string>(); - - /// <summary> - /// Backing field for the <see cref="SecuritySettings"/> property. - /// </summary> - private RelyingPartySecuritySettings securitySettings; - - /// <summary> - /// Backing store for the <see cref="EndpointOrder"/> property. - /// </summary> - private Comparison<IdentifierDiscoveryResult> endpointOrder = DefaultEndpointOrder; - - /// <summary> - /// Backing field for the <see cref="Channel"/> property. - /// </summary> - private Channel channel; - - /// <summary> - /// Initializes a new instance of the <see cref="OpenIdRelyingParty"/> class. - /// </summary> - public OpenIdRelyingParty() - : this(DotNetOpenAuthSection.Configuration.OpenId.RelyingParty.ApplicationStore.CreateInstance(HttpApplicationStore)) { - } - - /// <summary> - /// Initializes a new instance of the <see cref="OpenIdRelyingParty"/> class. - /// </summary> - /// <param name="applicationStore">The application store. If <c>null</c>, the relying party will always operate in "dumb mode".</param> - public OpenIdRelyingParty(IOpenIdApplicationStore applicationStore) - : this(applicationStore, applicationStore) { - } - - /// <summary> - /// Initializes a new instance of the <see cref="OpenIdRelyingParty"/> class. - /// </summary> - /// <param name="cryptoKeyStore">The association store. If null, the relying party will always operate in "dumb mode".</param> - /// <param name="nonceStore">The nonce store to use. If null, the relying party will always operate in "dumb mode".</param> - [SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Justification = "Unavoidable")] - private OpenIdRelyingParty(ICryptoKeyStore cryptoKeyStore, INonceStore nonceStore) { - // If we are a smart-mode RP (supporting associations), then we MUST also be - // capable of storing nonces to prevent replay attacks. - // If we're a dumb-mode RP, then 2.0 OPs are responsible for preventing replays. - Contract.Requires<ArgumentException>(cryptoKeyStore == null || nonceStore != null, OpenIdStrings.AssociationStoreRequiresNonceStore); - - this.securitySettings = DotNetOpenAuthSection.Configuration.OpenId.RelyingParty.SecuritySettings.CreateSecuritySettings(); - - foreach (var discoveryService in DotNetOpenAuthSection.Configuration.OpenId.RelyingParty.DiscoveryServices.CreateInstances(true)) { - this.discoveryServices.Add(discoveryService); - } - - this.behaviors.CollectionChanged += this.OnBehaviorsChanged; - foreach (var behavior in DotNetOpenAuthSection.Configuration.OpenId.RelyingParty.Behaviors.CreateInstances(false)) { - this.behaviors.Add(behavior); - } - - // Without a nonce store, we must rely on the Provider to protect against - // replay attacks. But only 2.0+ Providers can be expected to provide - // replay protection. - if (nonceStore == null && - this.SecuritySettings.ProtectDownlevelReplayAttacks && - this.SecuritySettings.MinimumRequiredOpenIdVersion < ProtocolVersion.V20) { - Logger.OpenId.Warn("Raising minimum OpenID version requirement for Providers to 2.0 to protect this stateless RP from replay attacks."); - this.SecuritySettings.MinimumRequiredOpenIdVersion = ProtocolVersion.V20; - } - - if (cryptoKeyStore == null) { - cryptoKeyStore = new MemoryCryptoKeyStore(); - } - - this.channel = new OpenIdChannel(cryptoKeyStore, nonceStore, this.SecuritySettings); - this.AssociationManager = new AssociationManager(this.Channel, new CryptoKeyStoreAsRelyingPartyAssociationStore(cryptoKeyStore), this.SecuritySettings); - - Reporting.RecordFeatureAndDependencyUse(this, cryptoKeyStore, nonceStore); - } - - /// <summary> - /// Gets an XRDS sorting routine that uses the XRDS Service/@Priority - /// attribute to determine order. - /// </summary> - /// <remarks> - /// Endpoints lacking any priority value are sorted to the end of the list. - /// </remarks> - [EditorBrowsable(EditorBrowsableState.Advanced)] - public static Comparison<IdentifierDiscoveryResult> DefaultEndpointOrder { - get { return IdentifierDiscoveryResult.EndpointOrder; } - } - - /// <summary> - /// Gets the standard state storage mechanism that uses ASP.NET's - /// HttpApplication state dictionary to store associations and nonces. - /// </summary> - [EditorBrowsable(EditorBrowsableState.Advanced)] - public static IOpenIdApplicationStore HttpApplicationStore { - get { - Contract.Ensures(Contract.Result<IOpenIdApplicationStore>() != null); - - HttpContext context = HttpContext.Current; - ErrorUtilities.VerifyOperation(context != null, Strings.StoreRequiredWhenNoHttpContextAvailable, typeof(IOpenIdApplicationStore).Name); - var store = (IOpenIdApplicationStore)context.Application[ApplicationStoreKey]; - if (store == null) { - context.Application.Lock(); - try { - if ((store = (IOpenIdApplicationStore)context.Application[ApplicationStoreKey]) == null) { - context.Application[ApplicationStoreKey] = store = new StandardRelyingPartyApplicationStore(); - } - } finally { - context.Application.UnLock(); - } - } - - return store; - } - } - - /// <summary> - /// Gets or sets the channel to use for sending/receiving messages. - /// </summary> - public Channel Channel { - get { - return this.channel; - } - - set { - Contract.Requires<ArgumentNullException>(value != null); - this.channel = value; - this.AssociationManager.Channel = value; - } - } - - /// <summary> - /// Gets the security settings used by this Relying Party. - /// </summary> - public RelyingPartySecuritySettings SecuritySettings { - get { - Contract.Ensures(Contract.Result<RelyingPartySecuritySettings>() != null); - return this.securitySettings; - } - - internal set { - Contract.Requires<ArgumentNullException>(value != null); - this.securitySettings = value; - this.AssociationManager.SecuritySettings = value; - } - } - - /// <summary> - /// Gets or sets the optional Provider Endpoint filter to use. - /// </summary> - /// <remarks> - /// Provides a way to optionally filter the providers that may be used in authenticating a user. - /// If provided, the delegate should return true to accept an endpoint, and false to reject it. - /// If null, all identity providers will be accepted. This is the default. - /// </remarks> - [EditorBrowsable(EditorBrowsableState.Advanced)] - public EndpointSelector EndpointFilter { get; set; } - - /// <summary> - /// Gets or sets the ordering routine that will determine which XRDS - /// Service element to try first - /// </summary> - /// <value>Default is <see cref="DefaultEndpointOrder"/>.</value> - /// <remarks> - /// This may never be null. To reset to default behavior this property - /// can be set to the value of <see cref="DefaultEndpointOrder"/>. - /// </remarks> - [EditorBrowsable(EditorBrowsableState.Advanced)] - public Comparison<IdentifierDiscoveryResult> EndpointOrder { - get { - return this.endpointOrder; - } - - set { - Contract.Requires<ArgumentNullException>(value != null); - this.endpointOrder = value; - } - } - - /// <summary> - /// Gets the extension factories. - /// </summary> - public IList<IOpenIdExtensionFactory> ExtensionFactories { - get { return this.Channel.GetExtensionFactories(); } - } - - /// <summary> - /// Gets a list of custom behaviors to apply to OpenID actions. - /// </summary> - /// <remarks> - /// Adding behaviors can impact the security settings of this <see cref="OpenIdRelyingParty"/> - /// instance in ways that subsequently removing the behaviors will not reverse. - /// </remarks> - public ICollection<IRelyingPartyBehavior> Behaviors { - get { return this.behaviors; } - } - - /// <summary> - /// Gets the list of services that can perform discovery on identifiers given to this relying party. - /// </summary> - public IList<IIdentifierDiscoveryService> DiscoveryServices { - get { return this.discoveryServices; } - } - - /// <summary> - /// Gets a value indicating whether this Relying Party can sign its return_to - /// parameter in outgoing authentication requests. - /// </summary> - internal bool CanSignCallbackArguments { - get { return this.Channel.BindingElements.OfType<ReturnToSignatureBindingElement>().Any(); } - } - - /// <summary> - /// Gets the web request handler to use for discovery and the part of - /// authentication where direct messages are sent to an untrusted remote party. - /// </summary> - internal IDirectWebRequestHandler WebRequestHandler { - get { return this.Channel.WebRequestHandler; } - } - - /// <summary> - /// Gets the association manager. - /// </summary> - internal AssociationManager AssociationManager { get; private set; } - - /// <summary> - /// Gets the <see cref="OpenIdRelyingParty"/> instance used to process authentication responses - /// without verifying the assertion or consuming nonces. - /// </summary> - protected OpenIdRelyingParty NonVerifyingRelyingParty { - get { - if (this.nonVerifyingRelyingParty == null) { - lock (this.nonVerifyingRelyingPartyInitLock) { - if (this.nonVerifyingRelyingParty == null) { - this.nonVerifyingRelyingParty = OpenIdRelyingParty.CreateNonVerifying(); - } - } - } - - return this.nonVerifyingRelyingParty; - } - } - - /// <summary> - /// Creates an authentication request to verify that a user controls - /// some given Identifier. - /// </summary> - /// <param name="userSuppliedIdentifier"> - /// The Identifier supplied by the user. This may be a URL, an XRI or i-name. - /// </param> - /// <param name="realm"> - /// The shorest URL that describes this relying party web site's address. - /// For example, if your login page is found at https://www.example.com/login.aspx, - /// your realm would typically be https://www.example.com/. - /// </param> - /// <param name="returnToUrl"> - /// The URL of the login page, or the page prepared to receive authentication - /// responses from the OpenID Provider. - /// </param> - /// <returns> - /// An authentication request object to customize the request and generate - /// an object to send to the user agent to initiate the authentication. - /// </returns> - /// <exception cref="ProtocolException">Thrown if no OpenID endpoint could be found.</exception> - public IAuthenticationRequest CreateRequest(Identifier userSuppliedIdentifier, Realm realm, Uri returnToUrl) { - Contract.Requires<ArgumentNullException>(userSuppliedIdentifier != null); - Contract.Requires<ArgumentNullException>(realm != null); - Contract.Requires<ArgumentNullException>(returnToUrl != null); - Contract.Ensures(Contract.Result<IAuthenticationRequest>() != null); - try { - return this.CreateRequests(userSuppliedIdentifier, realm, returnToUrl).First(); - } catch (InvalidOperationException ex) { - throw ErrorUtilities.Wrap(ex, OpenIdStrings.OpenIdEndpointNotFound); - } - } - - /// <summary> - /// Creates an authentication request to verify that a user controls - /// some given Identifier. - /// </summary> - /// <param name="userSuppliedIdentifier"> - /// The Identifier supplied by the user. This may be a URL, an XRI or i-name. - /// </param> - /// <param name="realm"> - /// The shorest URL that describes this relying party web site's address. - /// For example, if your login page is found at https://www.example.com/login.aspx, - /// your realm would typically be https://www.example.com/. - /// </param> - /// <returns> - /// An authentication request object that describes the HTTP response to - /// send to the user agent to initiate the authentication. - /// </returns> - /// <remarks> - /// <para>Requires an <see cref="HttpContext.Current">HttpContext.Current</see> context.</para> - /// </remarks> - /// <exception cref="ProtocolException">Thrown if no OpenID endpoint could be found.</exception> - /// <exception cref="InvalidOperationException">Thrown if <see cref="HttpContext.Current">HttpContext.Current</see> == <c>null</c>.</exception> - public IAuthenticationRequest CreateRequest(Identifier userSuppliedIdentifier, Realm realm) { - Contract.Requires<ArgumentNullException>(userSuppliedIdentifier != null); - Contract.Requires<ArgumentNullException>(realm != null); - Contract.Ensures(Contract.Result<IAuthenticationRequest>() != null); - try { - var result = this.CreateRequests(userSuppliedIdentifier, realm).First(); - Contract.Assume(result != null); - return result; - } catch (InvalidOperationException ex) { - throw ErrorUtilities.Wrap(ex, OpenIdStrings.OpenIdEndpointNotFound); - } - } - - /// <summary> - /// Creates an authentication request to verify that a user controls - /// some given Identifier. - /// </summary> - /// <param name="userSuppliedIdentifier"> - /// The Identifier supplied by the user. This may be a URL, an XRI or i-name. - /// </param> - /// <returns> - /// An authentication request object that describes the HTTP response to - /// send to the user agent to initiate the authentication. - /// </returns> - /// <remarks> - /// <para>Requires an <see cref="HttpContext.Current">HttpContext.Current</see> context.</para> - /// </remarks> - /// <exception cref="ProtocolException">Thrown if no OpenID endpoint could be found.</exception> - /// <exception cref="InvalidOperationException">Thrown if <see cref="HttpContext.Current">HttpContext.Current</see> == <c>null</c>.</exception> - public IAuthenticationRequest CreateRequest(Identifier userSuppliedIdentifier) { - Contract.Requires<ArgumentNullException>(userSuppliedIdentifier != null); - Contract.Ensures(Contract.Result<IAuthenticationRequest>() != null); - try { - return this.CreateRequests(userSuppliedIdentifier).First(); - } catch (InvalidOperationException ex) { - throw ErrorUtilities.Wrap(ex, OpenIdStrings.OpenIdEndpointNotFound); - } - } - - /// <summary> - /// Generates the authentication requests that can satisfy the requirements of some OpenID Identifier. - /// </summary> - /// <param name="userSuppliedIdentifier"> - /// The Identifier supplied by the user. This may be a URL, an XRI or i-name. - /// </param> - /// <param name="realm"> - /// The shorest URL that describes this relying party web site's address. - /// For example, if your login page is found at https://www.example.com/login.aspx, - /// your realm would typically be https://www.example.com/. - /// </param> - /// <param name="returnToUrl"> - /// The URL of the login page, or the page prepared to receive authentication - /// responses from the OpenID Provider. - /// </param> - /// <returns> - /// A sequence of authentication requests, any of which constitutes a valid identity assertion on the Claimed Identifier. - /// Never null, but may be empty. - /// </returns> - /// <remarks> - /// <para>Any individual generated request can satisfy the authentication. - /// The generated requests are sorted in preferred order. - /// Each request is generated as it is enumerated to. Associations are created only as - /// <see cref="IAuthenticationRequest.RedirectingResponse"/> is called.</para> - /// <para>No exception is thrown if no OpenID endpoints were discovered. - /// An empty enumerable is returned instead.</para> - /// </remarks> - public virtual IEnumerable<IAuthenticationRequest> CreateRequests(Identifier userSuppliedIdentifier, Realm realm, Uri returnToUrl) { - Contract.Requires<ArgumentNullException>(userSuppliedIdentifier != null); - Contract.Requires<ArgumentNullException>(realm != null); - Contract.Requires<ArgumentNullException>(returnToUrl != null); - Contract.Ensures(Contract.Result<IEnumerable<IAuthenticationRequest>>() != null); - - return AuthenticationRequest.Create(userSuppliedIdentifier, this, realm, returnToUrl, true).Cast<IAuthenticationRequest>().CacheGeneratedResults(); - } - - /// <summary> - /// Generates the authentication requests that can satisfy the requirements of some OpenID Identifier. - /// </summary> - /// <param name="userSuppliedIdentifier"> - /// The Identifier supplied by the user. This may be a URL, an XRI or i-name. - /// </param> - /// <param name="realm"> - /// The shorest URL that describes this relying party web site's address. - /// For example, if your login page is found at https://www.example.com/login.aspx, - /// your realm would typically be https://www.example.com/. - /// </param> - /// <returns> - /// A sequence of authentication requests, any of which constitutes a valid identity assertion on the Claimed Identifier. - /// Never null, but may be empty. - /// </returns> - /// <remarks> - /// <para>Any individual generated request can satisfy the authentication. - /// The generated requests are sorted in preferred order. - /// Each request is generated as it is enumerated to. Associations are created only as - /// <see cref="IAuthenticationRequest.RedirectingResponse"/> is called.</para> - /// <para>No exception is thrown if no OpenID endpoints were discovered. - /// An empty enumerable is returned instead.</para> - /// <para>Requires an <see cref="HttpContext.Current">HttpContext.Current</see> context.</para> - /// </remarks> - /// <exception cref="InvalidOperationException">Thrown if <see cref="HttpContext.Current">HttpContext.Current</see> == <c>null</c>.</exception> - public IEnumerable<IAuthenticationRequest> CreateRequests(Identifier userSuppliedIdentifier, Realm realm) { - Contract.Requires<InvalidOperationException>(HttpContext.Current != null && HttpContext.Current.Request != null, MessagingStrings.HttpContextRequired); - Contract.Requires<ArgumentNullException>(userSuppliedIdentifier != null); - Contract.Requires<ArgumentNullException>(realm != null); - Contract.Ensures(Contract.Result<IEnumerable<IAuthenticationRequest>>() != null); - - // This next code contract is a BAD idea, because it causes each authentication request to be generated - // at least an extra time. - ////Contract.Ensures(Contract.ForAll(Contract.Result<IEnumerable<IAuthenticationRequest>>(), el => el != null)); - - // Build the return_to URL - UriBuilder returnTo = new UriBuilder(this.Channel.GetRequestFromContext().UrlBeforeRewriting); - - // Trim off any parameters with an "openid." prefix, and a few known others - // to avoid carrying state from a prior login attempt. - returnTo.Query = string.Empty; - NameValueCollection queryParams = this.Channel.GetRequestFromContext().QueryStringBeforeRewriting; - var returnToParams = new Dictionary<string, string>(queryParams.Count); - foreach (string key in queryParams) { - if (!IsOpenIdSupportingParameter(key) && key != null) { - returnToParams.Add(key, queryParams[key]); - } - } - returnTo.AppendQueryArgs(returnToParams); - - return this.CreateRequests(userSuppliedIdentifier, realm, returnTo.Uri); - } - - /// <summary> - /// Generates the authentication requests that can satisfy the requirements of some OpenID Identifier. - /// </summary> - /// <param name="userSuppliedIdentifier"> - /// The Identifier supplied by the user. This may be a URL, an XRI or i-name. - /// </param> - /// <returns> - /// A sequence of authentication requests, any of which constitutes a valid identity assertion on the Claimed Identifier. - /// Never null, but may be empty. - /// </returns> - /// <remarks> - /// <para>Any individual generated request can satisfy the authentication. - /// The generated requests are sorted in preferred order. - /// Each request is generated as it is enumerated to. Associations are created only as - /// <see cref="IAuthenticationRequest.RedirectingResponse"/> is called.</para> - /// <para>No exception is thrown if no OpenID endpoints were discovered. - /// An empty enumerable is returned instead.</para> - /// <para>Requires an <see cref="HttpContext.Current">HttpContext.Current</see> context.</para> - /// </remarks> - /// <exception cref="InvalidOperationException">Thrown if <see cref="HttpContext.Current">HttpContext.Current</see> == <c>null</c>.</exception> - public IEnumerable<IAuthenticationRequest> CreateRequests(Identifier userSuppliedIdentifier) { - Contract.Requires<ArgumentNullException>(userSuppliedIdentifier != null); - Contract.Requires<InvalidOperationException>(HttpContext.Current != null && HttpContext.Current.Request != null, MessagingStrings.HttpContextRequired); - Contract.Ensures(Contract.Result<IEnumerable<IAuthenticationRequest>>() != null); - - return this.CreateRequests(userSuppliedIdentifier, Realm.AutoDetect); - } - - /// <summary> - /// Gets an authentication response from a Provider. - /// </summary> - /// <returns>The processed authentication response if there is any; <c>null</c> otherwise.</returns> - /// <remarks> - /// <para>Requires an <see cref="HttpContext.Current">HttpContext.Current</see> context.</para> - /// </remarks> - public IAuthenticationResponse GetResponse() { - Contract.Requires<InvalidOperationException>(HttpContext.Current != null && HttpContext.Current.Request != null, MessagingStrings.HttpContextRequired); - return this.GetResponse(this.Channel.GetRequestFromContext()); - } - - /// <summary> - /// Gets an authentication response from a Provider. - /// </summary> - /// <param name="httpRequestInfo">The HTTP request that may be carrying an authentication response from the Provider.</param> - /// <returns>The processed authentication response if there is any; <c>null</c> otherwise.</returns> - public IAuthenticationResponse GetResponse(HttpRequestInfo httpRequestInfo) { - Contract.Requires<ArgumentNullException>(httpRequestInfo != null); - try { - var message = this.Channel.ReadFromRequest(httpRequestInfo); - PositiveAssertionResponse positiveAssertion; - NegativeAssertionResponse negativeAssertion; - IndirectSignedResponse positiveExtensionOnly; - if ((positiveAssertion = message as PositiveAssertionResponse) != null) { - // We need to make sure that this assertion is coming from an endpoint - // that the host deems acceptable. - var providerEndpoint = new SimpleXrdsProviderEndpoint(positiveAssertion); - ErrorUtilities.VerifyProtocol( - this.FilterEndpoint(providerEndpoint), - OpenIdStrings.PositiveAssertionFromNonQualifiedProvider, - providerEndpoint.Uri); - - var response = new PositiveAuthenticationResponse(positiveAssertion, this); - foreach (var behavior in this.Behaviors) { - behavior.OnIncomingPositiveAssertion(response); - } - - return response; - } else if ((positiveExtensionOnly = message as IndirectSignedResponse) != null) { - return new PositiveAnonymousResponse(positiveExtensionOnly); - } else if ((negativeAssertion = message as NegativeAssertionResponse) != null) { - return new NegativeAuthenticationResponse(negativeAssertion); - } else if (message != null) { - Logger.OpenId.WarnFormat("Received unexpected message type {0} when expecting an assertion message.", message.GetType().Name); - } - - return null; - } catch (ProtocolException ex) { - return new FailedAuthenticationResponse(ex); - } - } - - /// <summary> - /// Processes the response received in a popup window or iframe to an AJAX-directed OpenID authentication. - /// </summary> - /// <returns>The HTTP response to send to this HTTP request.</returns> - /// <remarks> - /// <para>Requires an <see cref="HttpContext.Current">HttpContext.Current</see> context.</para> - /// </remarks> - public OutgoingWebResponse ProcessResponseFromPopup() { - Contract.Requires<InvalidOperationException>(HttpContext.Current != null && HttpContext.Current.Request != null, MessagingStrings.HttpContextRequired); - Contract.Ensures(Contract.Result<OutgoingWebResponse>() != null); - - return this.ProcessResponseFromPopup(this.Channel.GetRequestFromContext()); - } - - /// <summary> - /// Processes the response received in a popup window or iframe to an AJAX-directed OpenID authentication. - /// </summary> - /// <param name="request">The incoming HTTP request that is expected to carry an OpenID authentication response.</param> - /// <returns>The HTTP response to send to this HTTP request.</returns> - public OutgoingWebResponse ProcessResponseFromPopup(HttpRequestInfo request) { - Contract.Requires<ArgumentNullException>(request != null); - Contract.Ensures(Contract.Result<OutgoingWebResponse>() != null); - - return this.ProcessResponseFromPopup(request, null); - } - - /// <summary> - /// Allows an OpenID extension to read data out of an unverified positive authentication assertion - /// and send it down to the client browser so that Javascript running on the page can perform - /// some preprocessing on the extension data. - /// </summary> - /// <typeparam name="T">The extension <i>response</i> type that will read data from the assertion.</typeparam> - /// <param name="propertyName">The property name on the openid_identifier input box object that will be used to store the extension data. For example: sreg</param> - /// <remarks> - /// This method should be called before <see cref="ProcessResponseFromPopup()"/>. - /// </remarks> - [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "By design")] - public void RegisterClientScriptExtension<T>(string propertyName) where T : IClientScriptExtensionResponse { - Contract.Requires<ArgumentException>(!string.IsNullOrEmpty(propertyName)); - ErrorUtilities.VerifyArgumentNamed(!this.clientScriptExtensions.ContainsValue(propertyName), "propertyName", OpenIdStrings.ClientScriptExtensionPropertyNameCollision, propertyName); - foreach (var ext in this.clientScriptExtensions.Keys) { - ErrorUtilities.VerifyArgument(ext != typeof(T), OpenIdStrings.ClientScriptExtensionTypeCollision, typeof(T).FullName); - } - this.clientScriptExtensions.Add(typeof(T), propertyName); - } - - #region IDisposable Members - - /// <summary> - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. - /// </summary> - public void Dispose() { - this.Dispose(true); - GC.SuppressFinalize(this); - } - - #endregion - - /// <summary> - /// Determines whether some parameter name belongs to OpenID or this library - /// as a protocol or internal parameter name. - /// </summary> - /// <param name="parameterName">Name of the parameter.</param> - /// <returns> - /// <c>true</c> if the named parameter is a library- or protocol-specific parameter; otherwise, <c>false</c>. - /// </returns> - internal static bool IsOpenIdSupportingParameter(string parameterName) { - // Yes, it is possible with some query strings to have a null or empty parameter name - if (string.IsNullOrEmpty(parameterName)) { - return false; - } - - Protocol protocol = Protocol.Default; - return parameterName.StartsWith(protocol.openid.Prefix, StringComparison.OrdinalIgnoreCase) - || parameterName.StartsWith(OpenIdUtilities.CustomParameterPrefix, StringComparison.Ordinal); - } - - /// <summary> - /// Creates a relying party that does not verify incoming messages against - /// nonce or association stores. - /// </summary> - /// <returns>The instantiated <see cref="OpenIdRelyingParty"/>.</returns> - /// <remarks> - /// Useful for previewing messages while - /// allowing them to be fully processed and verified later. - /// </remarks> - internal static OpenIdRelyingParty CreateNonVerifying() { - OpenIdRelyingParty rp = new OpenIdRelyingParty(); - try { - rp.Channel = OpenIdChannel.CreateNonVerifyingChannel(); - return rp; - } catch { - rp.Dispose(); - throw; - } - } - - /// <summary> - /// Processes the response received in a popup window or iframe to an AJAX-directed OpenID authentication. - /// </summary> - /// <param name="request">The incoming HTTP request that is expected to carry an OpenID authentication response.</param> - /// <param name="callback">The callback fired after the response status has been determined but before the Javascript response is formulated.</param> - /// <returns> - /// The HTTP response to send to this HTTP request. - /// </returns> - [SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "OpenID", Justification = "real word"), SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "iframe", Justification = "Code contracts")] - internal OutgoingWebResponse ProcessResponseFromPopup(HttpRequestInfo request, Action<AuthenticationStatus> callback) { - Contract.Requires<ArgumentNullException>(request != null); - Contract.Ensures(Contract.Result<OutgoingWebResponse>() != null); - - string extensionsJson = null; - var authResponse = this.NonVerifyingRelyingParty.GetResponse(); - ErrorUtilities.VerifyProtocol(authResponse != null, OpenIdStrings.PopupRedirectMissingResponse); - - // Give the caller a chance to notify the hosting page and fill up the clientScriptExtensions collection. - if (callback != null) { - callback(authResponse.Status); - } - - Logger.OpenId.DebugFormat("Popup or iframe callback from OP: {0}", request.Url); - Logger.Controls.DebugFormat( - "An authentication response was found in a popup window or iframe using a non-verifying RP with status: {0}", - authResponse.Status); - if (authResponse.Status == AuthenticationStatus.Authenticated) { - var extensionsDictionary = new Dictionary<string, string>(); - foreach (var pair in this.clientScriptExtensions) { - IClientScriptExtensionResponse extension = (IClientScriptExtensionResponse)authResponse.GetExtension(pair.Key); - if (extension == null) { - continue; - } - var positiveResponse = (PositiveAuthenticationResponse)authResponse; - string js = extension.InitializeJavaScriptData(positiveResponse.Response); - if (!string.IsNullOrEmpty(js)) { - extensionsDictionary[pair.Value] = js; - } - } - - extensionsJson = MessagingUtilities.CreateJsonObject(extensionsDictionary, true); - } - - string payload = "document.URL"; - if (request.HttpMethod == "POST") { - // Promote all form variables to the query string, but since it won't be passed - // to any server (this is a javascript window-to-window transfer) the length of - // it can be arbitrarily long, whereas it was POSTed here probably because it - // was too long for HTTP transit. - UriBuilder payloadUri = new UriBuilder(request.Url); - payloadUri.AppendQueryArgs(request.Form.ToDictionary()); - payload = MessagingUtilities.GetSafeJavascriptValue(payloadUri.Uri.AbsoluteUri); - } - - if (!string.IsNullOrEmpty(extensionsJson)) { - payload += ", " + extensionsJson; - } - - return InvokeParentPageScript("dnoa_internal.processAuthorizationResult(" + payload + ")"); - } - - /// <summary> - /// Performs discovery on the specified identifier. - /// </summary> - /// <param name="identifier">The identifier to discover services for.</param> - /// <returns>A non-null sequence of services discovered for the identifier.</returns> - internal IEnumerable<IdentifierDiscoveryResult> Discover(Identifier identifier) { - Contract.Requires<ArgumentNullException>(identifier != null); - Contract.Ensures(Contract.Result<IEnumerable<IdentifierDiscoveryResult>>() != null); - - IEnumerable<IdentifierDiscoveryResult> results = Enumerable.Empty<IdentifierDiscoveryResult>(); - foreach (var discoverer in this.DiscoveryServices) { - bool abortDiscoveryChain; - var discoveryResults = discoverer.Discover(identifier, this.WebRequestHandler, out abortDiscoveryChain).CacheGeneratedResults(); - results = results.Concat(discoveryResults); - if (abortDiscoveryChain) { - Logger.OpenId.InfoFormat("Further discovery on '{0}' was stopped by the {1} discovery service.", identifier, discoverer.GetType().Name); - break; - } - } - - // If any OP Identifier service elements were found, we must not proceed - // to use any Claimed Identifier services, per OpenID 2.0 sections 7.3.2.2 and 11.2. - // For a discussion on this topic, see - // http://groups.google.com/group/dotnetopenid/browse_thread/thread/4b5a8c6b2210f387/5e25910e4d2252c8 - // Sometimes the IIdentifierDiscoveryService will automatically filter this for us, but - // just to be sure, we'll do it here as well. - if (!this.SecuritySettings.AllowDualPurposeIdentifiers) { - results = results.CacheGeneratedResults(); // avoid performing discovery repeatedly - var opIdentifiers = results.Where(result => result.ClaimedIdentifier == result.Protocol.ClaimedIdentifierForOPIdentifier); - var claimedIdentifiers = results.Where(result => result.ClaimedIdentifier != result.Protocol.ClaimedIdentifierForOPIdentifier); - results = opIdentifiers.Any() ? opIdentifiers : claimedIdentifiers; - } - - return results; - } - - /// <summary> - /// Checks whether a given OP Endpoint is permitted by the host relying party. - /// </summary> - /// <param name="endpoint">The OP endpoint.</param> - /// <returns><c>true</c> if the OP Endpoint is allowed; <c>false</c> otherwise.</returns> - protected internal bool FilterEndpoint(IProviderEndpoint endpoint) { - if (this.SecuritySettings.RejectAssertionsFromUntrustedProviders) { - if (!this.SecuritySettings.TrustedProviderEndpoints.Contains(endpoint.Uri)) { - Logger.OpenId.InfoFormat("Filtering out OP endpoint {0} because it is not on the exclusive trusted provider whitelist.", endpoint.Uri.AbsoluteUri); - return false; - } - } - - if (endpoint.Version < Protocol.Lookup(this.SecuritySettings.MinimumRequiredOpenIdVersion).Version) { - Logger.OpenId.InfoFormat( - "Filtering out OP endpoint {0} because it implements OpenID {1} but this relying party requires OpenID {2} or later.", - endpoint.Uri.AbsoluteUri, - endpoint.Version, - Protocol.Lookup(this.SecuritySettings.MinimumRequiredOpenIdVersion).Version); - return false; - } - - if (this.EndpointFilter != null) { - if (!this.EndpointFilter(endpoint)) { - Logger.OpenId.InfoFormat("Filtering out OP endpoint {0} because the host rejected it.", endpoint.Uri.AbsoluteUri); - return false; - } - } - - return true; - } - - /// <summary> - /// Releases unmanaged and - optionally - managed resources - /// </summary> - /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param> - protected virtual void Dispose(bool disposing) { - if (disposing) { - if (this.nonVerifyingRelyingParty != null) { - this.nonVerifyingRelyingParty.Dispose(); - this.nonVerifyingRelyingParty = null; - } - - // Tear off the instance member as a local variable for thread safety. - IDisposable disposableChannel = this.channel as IDisposable; - if (disposableChannel != null) { - disposableChannel.Dispose(); - } - } - } - - /// <summary> - /// Invokes a method on a parent frame or window and closes the calling popup window if applicable. - /// </summary> - /// <param name="methodCall">The method to call on the parent window, including - /// parameters. (i.e. "callback('arg1', 2)"). No escaping is done by this method.</param> - /// <returns>The entire HTTP response to send to the popup window or iframe to perform the invocation.</returns> - private static OutgoingWebResponse InvokeParentPageScript(string methodCall) { - Contract.Requires<ArgumentException>(!string.IsNullOrEmpty(methodCall)); - - Logger.OpenId.DebugFormat("Sending Javascript callback: {0}", methodCall); - StringBuilder builder = new StringBuilder(); - builder.AppendLine("<html><body><script type='text/javascript' language='javascript'><!--"); - builder.AppendLine("//<![CDATA["); - builder.Append(@" var inPopup = !window.frameElement; - var objSrc = inPopup ? window.opener : window.frameElement; -"); - - // Something about calling objSrc.{0} can somehow cause FireFox to forget about the inPopup variable, - // so we have to actually put the test for it ABOVE the call to objSrc.{0} so that it already - // whether to call window.self.close() after the call. - string htmlFormat = @" if (inPopup) {{ - try {{ - objSrc.{0}; - }} catch (ex) {{ - alert(ex); - }} finally {{ - window.self.close(); - }} - }} else {{ - objSrc.{0}; - }}"; - builder.AppendFormat(CultureInfo.InvariantCulture, htmlFormat, methodCall); - builder.AppendLine("//]]>--></script>"); - builder.AppendLine("</body></html>"); - - var response = new OutgoingWebResponse(); - response.Body = builder.ToString(); - response.Headers.Add(HttpResponseHeader.ContentType, new ContentType("text/html").ToString()); - return response; - } - - /// <summary> - /// Called by derived classes when behaviors are added or removed. - /// </summary> - /// <param name="sender">The collection being modified.</param> - /// <param name="e">The <see cref="System.Collections.Specialized.NotifyCollectionChangedEventArgs"/> instance containing the event data.</param> - private void OnBehaviorsChanged(object sender, NotifyCollectionChangedEventArgs e) { - foreach (IRelyingPartyBehavior profile in e.NewItems) { - profile.ApplySecuritySettings(this.SecuritySettings); - Reporting.RecordFeatureUse(profile); - } - } - -#if CONTRACTS_FULL - /// <summary> - /// Verifies conditions that should be true for any valid state of this object. - /// </summary> - [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Called by code contracts.")] - [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called by code contracts.")] - [ContractInvariantMethod] - private void ObjectInvariant() { - Contract.Invariant(this.SecuritySettings != null); - Contract.Invariant(this.Channel != null); - Contract.Invariant(this.EndpointOrder != null); - } -#endif - } -} diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingPartyAjaxControlBase.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingPartyAjaxControlBase.cs deleted file mode 100644 index 866f942..0000000 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingPartyAjaxControlBase.cs +++ /dev/null @@ -1,468 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="OpenIdRelyingPartyAjaxControlBase.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -[assembly: System.Web.UI.WebResource(DotNetOpenAuth.OpenId.RelyingParty.OpenIdRelyingPartyAjaxControlBase.EmbeddedAjaxJavascriptResource, "text/javascript")] - -namespace DotNetOpenAuth.OpenId.RelyingParty { - using System; - using System.Collections.Generic; - using System.ComponentModel; - using System.Diagnostics.CodeAnalysis; - using System.Diagnostics.Contracts; - using System.Globalization; - using System.Linq; - using System.Text; - using System.Web; - using System.Web.Script.Serialization; - using System.Web.UI; - using DotNetOpenAuth.Configuration; - using DotNetOpenAuth.Messaging; - using DotNetOpenAuth.OpenId.Extensions; - - /// <summary> - /// A common base class for OpenID Relying Party controls. - /// </summary> - public abstract class OpenIdRelyingPartyAjaxControlBase : OpenIdRelyingPartyControlBase, ICallbackEventHandler { - /// <summary> - /// The manifest resource name of the javascript file to include on the hosting page. - /// </summary> - internal const string EmbeddedAjaxJavascriptResource = Util.DefaultNamespace + ".OpenId.RelyingParty.OpenIdRelyingPartyAjaxControlBase.js"; - - /// <summary> - /// The "dnoa.op_endpoint" string. - /// </summary> - internal const string OPEndpointParameterName = OpenIdUtilities.CustomParameterPrefix + "op_endpoint"; - - /// <summary> - /// The "dnoa.claimed_id" string. - /// </summary> - internal const string ClaimedIdParameterName = OpenIdUtilities.CustomParameterPrefix + "claimed_id"; - - /// <summary> - /// The name of the javascript field that stores the maximum time a positive assertion is - /// good for before it must be refreshed. - /// </summary> - internal const string MaxPositiveAssertionLifetimeJsName = "window.dnoa_internal.maxPositiveAssertionLifetime"; - - /// <summary> - /// The name of the javascript function that will initiate an asynchronous callback. - /// </summary> - protected internal const string CallbackJSFunctionAsync = "window.dnoa_internal.callbackAsync"; - - /// <summary> - /// The name of the javascript function that will initiate a synchronous callback. - /// </summary> - protected const string CallbackJSFunction = "window.dnoa_internal.callback"; - - #region Property viewstate keys - - /// <summary> - /// The viewstate key to use for storing the value of a successful authentication. - /// </summary> - private const string AuthDataViewStateKey = "AuthData"; - - /// <summary> - /// The viewstate key to use for storing the value of the <see cref="AuthenticationResponse"/> property. - /// </summary> - private const string AuthenticationResponseViewStateKey = "AuthenticationResponse"; - - /// <summary> - /// The viewstate key to use for storing the value of the <see cref="AuthenticationProcessedAlready"/> property. - /// </summary> - private const string AuthenticationProcessedAlreadyViewStateKey = "AuthenticationProcessedAlready"; - - #endregion - - /// <summary> - /// Default value of the <see cref="Popup"/> property. - /// </summary> - private const PopupBehavior PopupDefault = PopupBehavior.Always; - - /// <summary> - /// Default value of <see cref="LogOnMode"/> property.. - /// </summary> - private const LogOnSiteNotification LogOnModeDefault = LogOnSiteNotification.None; - - /// <summary> - /// The authentication response that just came in. - /// </summary> - private IAuthenticationResponse authenticationResponse; - - /// <summary> - /// Stores the result of an AJAX discovery request while it is waiting - /// to be picked up by ASP.NET on the way down to the user agent. - /// </summary> - private string discoveryResult; - - /// <summary> - /// Initializes a new instance of the <see cref="OpenIdRelyingPartyAjaxControlBase"/> class. - /// </summary> - protected OpenIdRelyingPartyAjaxControlBase() { - // The AJAX login style always uses popups (or invisible iframes). - base.Popup = PopupDefault; - - // The expected use case for the AJAX login box is for comments... not logging in. - this.LogOnMode = LogOnModeDefault; - } - - /// <summary> - /// Fired when a Provider sends back a positive assertion to this control, - /// but the authentication has not yet been verified. - /// </summary> - /// <remarks> - /// <b>No security critical decisions should be made within event handlers - /// for this event</b> as the authenticity of the assertion has not been - /// verified yet. All security related code should go in the event handler - /// for the <see cref="OpenIdRelyingPartyControlBase.LoggedIn"/> event. - /// </remarks> - [Description("Fired when a Provider sends back a positive assertion to this control, but the authentication has not yet been verified.")] - public event EventHandler<OpenIdEventArgs> UnconfirmedPositiveAssertion; - - /// <summary> - /// Gets or sets a value indicating when to use a popup window to complete the login experience. - /// </summary> - /// <value>The default value is <see cref="PopupBehavior.Never"/>.</value> - [Bindable(false), Browsable(false), DefaultValue(PopupDefault)] - public override PopupBehavior Popup { - get { return base.Popup; } - set { ErrorUtilities.VerifySupported(value == base.Popup, OpenIdStrings.PropertyValueNotSupported); } - } - - /// <summary> - /// Gets or sets the way a completed login is communicated to the rest of the web site. - /// </summary> - [Bindable(true), DefaultValue(LogOnModeDefault), Category(BehaviorCategory)] - [Description("The way a completed login is communicated to the rest of the web site.")] - public override LogOnSiteNotification LogOnMode { // override to set new DefaultValue - get { return base.LogOnMode; } - set { base.LogOnMode = value; } - } - - /// <summary> - /// Gets or sets the <see cref="OpenIdRelyingParty"/> instance to use. - /// </summary> - /// <value> - /// The default value is an <see cref="OpenIdRelyingParty"/> instance initialized according to the web.config file. - /// </value> - /// <remarks> - /// A performance optimization would be to store off the - /// instance as a static member in your web site and set it - /// to this property in your <see cref="Control.Load">Page.Load</see> - /// event since instantiating these instances can be expensive on - /// heavily trafficked web pages. - /// </remarks> - public override OpenIdRelyingParty RelyingParty { - get { - return base.RelyingParty; - } - - set { - // Make sure we get an AJAX-ready instance. - ErrorUtilities.VerifyArgument(value is OpenIdAjaxRelyingParty, OpenIdStrings.TypeMustImplementX, typeof(OpenIdAjaxRelyingParty).Name); - base.RelyingParty = value; - } - } - - /// <summary> - /// Gets the completed authentication response. - /// </summary> - public IAuthenticationResponse AuthenticationResponse { - get { - if (this.authenticationResponse == null) { - // We will either validate a new response and return a live AuthenticationResponse - // or we will try to deserialize a previous IAuthenticationResponse (snapshot) - // from viewstate and return that. - IAuthenticationResponse viewstateResponse = this.ViewState[AuthenticationResponseViewStateKey] as IAuthenticationResponse; - string viewstateAuthData = this.ViewState[AuthDataViewStateKey] as string; - string formAuthData = this.Page.Request.Form[this.OpenIdAuthDataFormKey]; - - // First see if there is fresh auth data to be processed into a response. - if (!string.IsNullOrEmpty(formAuthData) && !string.Equals(viewstateAuthData, formAuthData, StringComparison.Ordinal)) { - this.ViewState[AuthDataViewStateKey] = formAuthData; - - Uri authUri = new Uri(formAuthData); - HttpRequestInfo clientResponseInfo = new HttpRequestInfo { - UrlBeforeRewriting = authUri, - }; - - this.authenticationResponse = this.RelyingParty.GetResponse(clientResponseInfo); - Logger.Controls.DebugFormat( - "The {0} control checked for an authentication response and found: {1}", - this.ID, - this.authenticationResponse.Status); - this.AuthenticationProcessedAlready = false; - - // Save out the authentication response to viewstate so we can find it on - // a subsequent postback. - this.ViewState[AuthenticationResponseViewStateKey] = new PositiveAuthenticationResponseSnapshot(this.authenticationResponse); - } else { - this.authenticationResponse = viewstateResponse; - } - } - - return this.authenticationResponse; - } - } - - /// <summary> - /// Gets the relying party as its AJAX type. - /// </summary> - protected OpenIdAjaxRelyingParty AjaxRelyingParty { - get { return (OpenIdAjaxRelyingParty)this.RelyingParty; } - } - - /// <summary> - /// Gets the name of the open id auth data form key (for the value as stored at the user agent as a FORM field). - /// </summary> - /// <value>Usually a concatenation of the control's name and <c>"_openidAuthData"</c>.</value> - protected abstract string OpenIdAuthDataFormKey { get; } - - /// <summary> - /// Gets or sets a value indicating whether an authentication in the page's view state - /// has already been processed and appropriate events fired. - /// </summary> - private bool AuthenticationProcessedAlready { - get { return (bool)(ViewState[AuthenticationProcessedAlreadyViewStateKey] ?? false); } - set { ViewState[AuthenticationProcessedAlreadyViewStateKey] = value; } - } - - /// <summary> - /// Allows an OpenID extension to read data out of an unverified positive authentication assertion - /// and send it down to the client browser so that Javascript running on the page can perform - /// some preprocessing on the extension data. - /// </summary> - /// <typeparam name="T">The extension <i>response</i> type that will read data from the assertion.</typeparam> - /// <param name="propertyName">The property name on the openid_identifier input box object that will be used to store the extension data. For example: sreg</param> - /// <remarks> - /// This method should be called from the <see cref="UnconfirmedPositiveAssertion"/> event handler. - /// </remarks> - [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "By design")] - public void RegisterClientScriptExtension<T>(string propertyName) where T : IClientScriptExtensionResponse { - Contract.Requires<ArgumentException>(!string.IsNullOrEmpty(propertyName)); - this.RelyingParty.RegisterClientScriptExtension<T>(propertyName); - } - - #region ICallbackEventHandler Members - - /// <summary> - /// Returns the result of discovery on some Identifier passed to <see cref="ICallbackEventHandler.RaiseCallbackEvent"/>. - /// </summary> - /// <returns>The result of the callback.</returns> - /// <value>A whitespace delimited list of URLs that can be used to initiate authentication.</value> - string ICallbackEventHandler.GetCallbackResult() { - return this.GetCallbackResult(); - } - - /// <summary> - /// Performs discovery on some OpenID Identifier. Called directly from the user agent via - /// AJAX callback mechanisms. - /// </summary> - /// <param name="eventArgument">The identifier to perform discovery on.</param> - [SuppressMessage("Microsoft.Design", "CA1030:UseEventsWhereAppropriate", Justification = "We want to preserve the signature of the interface.")] - void ICallbackEventHandler.RaiseCallbackEvent(string eventArgument) { - this.RaiseCallbackEvent(eventArgument); - } - - #endregion - - /// <summary> - /// Returns the results of a callback event that targets a control. - /// </summary> - /// <returns>The result of the callback.</returns> - [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "We want to preserve the signature of the interface.")] - protected virtual string GetCallbackResult() { - this.Page.Response.ContentType = "text/javascript"; - return this.discoveryResult; - } - - /// <summary> - /// Processes a callback event that targets a control. - /// </summary> - /// <param name="eventArgument">A string that represents an event argument to pass to the event handler.</param> - [SuppressMessage("Microsoft.Design", "CA1030:UseEventsWhereAppropriate", Justification = "We want to preserve the signature of the interface.")] - protected virtual void RaiseCallbackEvent(string eventArgument) { - string userSuppliedIdentifier = eventArgument; - - ErrorUtilities.VerifyNonZeroLength(userSuppliedIdentifier, "userSuppliedIdentifier"); - Logger.OpenId.InfoFormat("AJAX discovery on {0} requested.", userSuppliedIdentifier); - - this.Identifier = userSuppliedIdentifier; - - var serializer = new JavaScriptSerializer(); - IEnumerable<IAuthenticationRequest> requests = this.CreateRequests(this.Identifier); - this.discoveryResult = serializer.Serialize(this.AjaxRelyingParty.AsJsonDiscoveryResult(requests)); - } - - /// <summary> - /// Creates the relying party instance used to generate authentication requests. - /// </summary> - /// <param name="store">The store to pass to the relying party constructor.</param> - /// <returns>The instantiated relying party.</returns> - protected override OpenIdRelyingParty CreateRelyingParty(IOpenIdApplicationStore store) { - return new OpenIdAjaxRelyingParty(store); - } - - /// <summary> - /// Pre-discovers an identifier and makes the results available to the - /// user agent for javascript as soon as the page loads. - /// </summary> - /// <param name="identifier">The identifier.</param> - protected void PreloadDiscovery(Identifier identifier) { - this.PreloadDiscovery(new[] { identifier }); - } - - /// <summary> - /// Pre-discovers a given set of identifiers and makes the results available to the - /// user agent for javascript as soon as the page loads. - /// </summary> - /// <param name="identifiers">The identifiers to perform discovery on.</param> - protected void PreloadDiscovery(IEnumerable<Identifier> identifiers) { - string script = this.AjaxRelyingParty.AsAjaxPreloadedDiscoveryResult( - identifiers.SelectMany(id => this.CreateRequests(id))); - this.Page.ClientScript.RegisterClientScriptBlock(typeof(OpenIdRelyingPartyAjaxControlBase), this.ClientID, script, true); - } - - /// <summary> - /// Fires the <see cref="UnconfirmedPositiveAssertion"/> event. - /// </summary> - protected virtual void OnUnconfirmedPositiveAssertion() { - var unconfirmedPositiveAssertion = this.UnconfirmedPositiveAssertion; - if (unconfirmedPositiveAssertion != null) { - unconfirmedPositiveAssertion(this, null); - } - } - - /// <summary> - /// Raises the <see cref="E:Load"/> event. - /// </summary> - /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param> - protected override void OnLoad(EventArgs e) { - base.OnLoad(e); - - // Our parent control ignores all OpenID messages included in a postback, - // but our AJAX controls hide an old OpenID message in a postback payload, - // so we deserialize it and process it when appropriate. - if (this.Page.IsPostBack) { - if (this.AuthenticationResponse != null && !this.AuthenticationProcessedAlready) { - // Only process messages targeted at this control. - // Note that Stateless mode causes no receiver to be indicated. - string receiver = this.AuthenticationResponse.GetUntrustedCallbackArgument(ReturnToReceivingControlId); - if (receiver == null || receiver == this.ClientID) { - this.ProcessResponse(this.AuthenticationResponse); - this.AuthenticationProcessedAlready = true; - } - } - } - } - - /// <summary> - /// Called when the <see cref="Identifier"/> property is changed. - /// </summary> - protected override void OnIdentifierChanged() { - base.OnIdentifierChanged(); - - // Since the identifier changed, make sure we reset any cached authentication on the user agent. - this.ViewState.Remove(AuthDataViewStateKey); - } - - /// <summary> - /// Raises the <see cref="E:System.Web.UI.Control.PreRender"/> event. - /// </summary> - /// <param name="e">An <see cref="T:System.EventArgs"/> object that contains the event data.</param> - protected override void OnPreRender(EventArgs e) { - base.OnPreRender(e); - - this.SetWebAppPathOnUserAgent(); - this.Page.ClientScript.RegisterClientScriptResource(typeof(OpenIdRelyingPartyAjaxControlBase), EmbeddedAjaxJavascriptResource); - - StringBuilder initScript = new StringBuilder(); - - initScript.AppendLine(CallbackJSFunctionAsync + " = " + this.GetJsCallbackConvenienceFunction(true)); - initScript.AppendLine(CallbackJSFunction + " = " + this.GetJsCallbackConvenienceFunction(false)); - - // Positive assertions can last no longer than this library is willing to consider them valid, - // and when they come with OP private associations they last no longer than the OP is willing - // to consider them valid. We assume the OP will hold them valid for at least five minutes. - double assertionLifetimeInMilliseconds = Math.Min(TimeSpan.FromMinutes(5).TotalMilliseconds, Math.Min(DotNetOpenAuthSection.Configuration.OpenId.MaxAuthenticationTime.TotalMilliseconds, DotNetOpenAuthSection.Configuration.Messaging.MaximumMessageLifetime.TotalMilliseconds)); - initScript.AppendLine(MaxPositiveAssertionLifetimeJsName + " = " + assertionLifetimeInMilliseconds.ToString(CultureInfo.InvariantCulture) + ";"); - - // We register this callback code explicitly with a specific type rather than the derived-type of the control - // to ensure that this discovery callback function is only set ONCE for the HTML document. - this.Page.ClientScript.RegisterClientScriptBlock(typeof(OpenIdRelyingPartyControlBase), "initializer", initScript.ToString(), true); - } - - /// <summary> - /// Sends server control content to a provided <see cref="T:System.Web.UI.HtmlTextWriter"/> object, which writes the content to be rendered on the client. - /// </summary> - /// <param name="writer">The <see cref="T:System.Web.UI.HtmlTextWriter"/> object that receives the server control content.</param> - protected override void Render(HtmlTextWriter writer) { - Contract.Assume(writer != null, "Missing contract."); - base.Render(writer); - - // Emit a hidden field to let the javascript on the user agent know if an - // authentication has already successfully taken place. - string viewstateAuthData = this.ViewState[AuthDataViewStateKey] as string; - if (!string.IsNullOrEmpty(viewstateAuthData)) { - writer.AddAttribute(HtmlTextWriterAttribute.Name, this.OpenIdAuthDataFormKey); - writer.AddAttribute(HtmlTextWriterAttribute.Value, viewstateAuthData, true); - writer.AddAttribute(HtmlTextWriterAttribute.Type, "hidden"); - writer.RenderBeginTag(HtmlTextWriterTag.Input); - writer.RenderEndTag(); - } - } - - /// <summary> - /// Notifies the user agent via an AJAX response of a completed authentication attempt. - /// </summary> - protected override void ScriptClosingPopupOrIFrame() { - Action<AuthenticationStatus> callback = status => { - if (status == AuthenticationStatus.Authenticated) { - this.OnUnconfirmedPositiveAssertion(); // event handler will fill the clientScriptExtensions collection. - } - }; - - OutgoingWebResponse response = this.RelyingParty.ProcessResponseFromPopup( - this.RelyingParty.Channel.GetRequestFromContext(), - callback); - - response.Send(); - } - - /// <summary> - /// Constructs a function that will initiate an AJAX callback. - /// </summary> - /// <param name="async">if set to <c>true</c> causes the AJAX callback to be a little more asynchronous. Note that <c>false</c> does not mean the call is absolutely synchronous.</param> - /// <returns>The string defining a javascript anonymous function that initiates a callback.</returns> - private string GetJsCallbackConvenienceFunction(bool async) { - string argumentParameterName = "argument"; - string callbackResultParameterName = "resultFunction"; - string callbackErrorCallbackParameterName = "errorCallback"; - string callback = Page.ClientScript.GetCallbackEventReference( - this, - argumentParameterName, - callbackResultParameterName, - argumentParameterName, - callbackErrorCallbackParameterName, - async); - return string.Format( - CultureInfo.InvariantCulture, - "function({1}, {2}, {3}) {{{0}\treturn {4};{0}}};", - Environment.NewLine, - argumentParameterName, - callbackResultParameterName, - callbackErrorCallbackParameterName, - callback); - } - - /// <summary> - /// Sets the window.aspnetapppath variable on the user agent so that cookies can be set with the proper path. - /// </summary> - private void SetWebAppPathOnUserAgent() { - string script = "window.aspnetapppath = " + MessagingUtilities.GetSafeJavascriptValue(this.Page.Request.ApplicationPath) + ";"; - this.Page.ClientScript.RegisterClientScriptBlock(typeof(OpenIdRelyingPartyAjaxControlBase), "webapppath", script, true); - } - } -} diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingPartyControlBase.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingPartyControlBase.cs deleted file mode 100644 index b1106e6..0000000 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingPartyControlBase.cs +++ /dev/null @@ -1,1054 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="OpenIdRelyingPartyControlBase.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -[assembly: System.Web.UI.WebResource(DotNetOpenAuth.OpenId.RelyingParty.OpenIdRelyingPartyControlBase.EmbeddedJavascriptResource, "text/javascript")] - -namespace DotNetOpenAuth.OpenId.RelyingParty { - using System; - using System.Collections.Generic; - using System.Collections.ObjectModel; - using System.ComponentModel; - using System.Diagnostics.CodeAnalysis; - using System.Diagnostics.Contracts; - using System.Drawing.Design; - using System.Globalization; - using System.Linq; - using System.Text; - using System.Text.RegularExpressions; - using System.Web; - using System.Web.Security; - using System.Web.UI; - using DotNetOpenAuth.ComponentModel; - using DotNetOpenAuth.Configuration; - using DotNetOpenAuth.Messaging; - using DotNetOpenAuth.OpenId.Extensions; - using DotNetOpenAuth.OpenId.Extensions.UI; - using DotNetOpenAuth.OpenId.Messages; - - /// <summary> - /// Methods of indicating to the rest of the web site that the user has logged in. - /// </summary> - [SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "OnSite", Justification = "Two words intended.")] - public enum LogOnSiteNotification { - /// <summary> - /// The rest of the web site is unaware that the user just completed an OpenID login. - /// </summary> - None, - - /// <summary> - /// After the <see cref="OpenIdRelyingPartyControlBase.LoggedIn"/> event is fired - /// the control automatically calls <see cref="System.Web.Security.FormsAuthentication.RedirectFromLoginPage(string, bool)"/> - /// with the <see cref="IAuthenticationResponse.ClaimedIdentifier"/> as the username - /// unless the <see cref="OpenIdRelyingPartyControlBase.LoggedIn"/> event handler sets - /// <see cref="OpenIdEventArgs.Cancel"/> property to true. - /// </summary> - FormsAuthentication, - } - - /// <summary> - /// How an OpenID user session should be persisted across visits. - /// </summary> - public enum LogOnPersistence { - /// <summary> - /// The user should only be logged in as long as the browser window remains open. - /// Nothing is persisted to help the user on a return visit. Public kiosk mode. - /// </summary> - Session, - - /// <summary> - /// The user should only be logged in as long as the browser window remains open. - /// The OpenID Identifier is persisted to help expedite re-authentication when - /// the user visits the next time. - /// </summary> - SessionAndPersistentIdentifier, - - /// <summary> - /// The user is issued a persistent authentication ticket so that no login is - /// necessary on their return visit. - /// </summary> - PersistentAuthentication, - } - - /// <summary> - /// A common base class for OpenID Relying Party controls. - /// </summary> - [DefaultProperty("Identifier"), ValidationProperty("Identifier")] - [ParseChildren(true), PersistChildren(false)] - public abstract class OpenIdRelyingPartyControlBase : Control, IPostBackEventHandler, IDisposable { - /// <summary> - /// The manifest resource name of the javascript file to include on the hosting page. - /// </summary> - internal const string EmbeddedJavascriptResource = Util.DefaultNamespace + ".OpenId.RelyingParty.OpenIdRelyingPartyControlBase.js"; - - /// <summary> - /// The cookie used to persist the Identifier the user logged in with. - /// </summary> - internal const string PersistentIdentifierCookieName = OpenIdUtilities.CustomParameterPrefix + "OpenIDIdentifier"; - - /// <summary> - /// The callback parameter name to use to store which control initiated the auth request. - /// </summary> - internal const string ReturnToReceivingControlId = OpenIdUtilities.CustomParameterPrefix + "receiver"; - - #region Protected internal callback parameter names - - /// <summary> - /// The callback parameter to use for recognizing when the callback is in a popup window or hidden iframe. - /// </summary> - protected internal const string UIPopupCallbackKey = OpenIdUtilities.CustomParameterPrefix + "uipopup"; - - /// <summary> - /// The parameter name to include in the formulated auth request so that javascript can know whether - /// the OP advertises support for the UI extension. - /// </summary> - protected internal const string PopupUISupportedJSHint = OpenIdUtilities.CustomParameterPrefix + "popupUISupported"; - - #endregion - - #region Property category constants - - /// <summary> - /// The "Appearance" category for properties. - /// </summary> - protected const string AppearanceCategory = "Appearance"; - - /// <summary> - /// The "Behavior" category for properties. - /// </summary> - protected const string BehaviorCategory = "Behavior"; - - /// <summary> - /// The "OpenID" category for properties and events. - /// </summary> - protected const string OpenIdCategory = "OpenID"; - - #endregion - - #region Private callback parameter names - - /// <summary> - /// The callback parameter for use with persisting the <see cref="UsePersistentCookie"/> property. - /// </summary> - private const string UsePersistentCookieCallbackKey = OpenIdUtilities.CustomParameterPrefix + "UsePersistentCookie"; - - /// <summary> - /// The callback parameter to use for recognizing when the callback is in the parent window. - /// </summary> - private const string UIPopupCallbackParentKey = OpenIdUtilities.CustomParameterPrefix + "uipopupParent"; - - #endregion - - #region Property default values - - /// <summary> - /// The default value for the <see cref="Stateless"/> property. - /// </summary> - private const bool StatelessDefault = false; - - /// <summary> - /// The default value for the <see cref="ReturnToUrl"/> property. - /// </summary> - private const string ReturnToUrlDefault = ""; - - /// <summary> - /// Default value of <see cref="UsePersistentCookie"/>. - /// </summary> - private const LogOnPersistence UsePersistentCookieDefault = LogOnPersistence.Session; - - /// <summary> - /// Default value of <see cref="LogOnMode"/>. - /// </summary> - private const LogOnSiteNotification LogOnModeDefault = LogOnSiteNotification.FormsAuthentication; - - /// <summary> - /// The default value for the <see cref="RealmUrl"/> property. - /// </summary> - private const string RealmUrlDefault = "~/"; - - /// <summary> - /// The default value for the <see cref="Popup"/> property. - /// </summary> - private const PopupBehavior PopupDefault = PopupBehavior.Never; - - /// <summary> - /// The default value for the <see cref="RequireSsl"/> property. - /// </summary> - private const bool RequireSslDefault = false; - - #endregion - - #region Property view state keys - - /// <summary> - /// The viewstate key to use for storing the value of the <see cref="Extensions"/> property. - /// </summary> - private const string ExtensionsViewStateKey = "Extensions"; - - /// <summary> - /// The viewstate key to use for the <see cref="Stateless"/> property. - /// </summary> - private const string StatelessViewStateKey = "Stateless"; - - /// <summary> - /// The viewstate key to use for the <see cref="UsePersistentCookie"/> property. - /// </summary> - private const string UsePersistentCookieViewStateKey = "UsePersistentCookie"; - - /// <summary> - /// The viewstate key to use for the <see cref="LogOnMode"/> property. - /// </summary> - private const string LogOnModeViewStateKey = "LogOnMode"; - - /// <summary> - /// The viewstate key to use for the <see cref="RealmUrl"/> property. - /// </summary> - private const string RealmUrlViewStateKey = "RealmUrl"; - - /// <summary> - /// The viewstate key to use for the <see cref="ReturnToUrl"/> property. - /// </summary> - private const string ReturnToUrlViewStateKey = "ReturnToUrl"; - - /// <summary> - /// The key under which the value for the <see cref="Identifier"/> property will be stored. - /// </summary> - private const string IdentifierViewStateKey = "Identifier"; - - /// <summary> - /// The viewstate key to use for the <see cref="Popup"/> property. - /// </summary> - private const string PopupViewStateKey = "Popup"; - - /// <summary> - /// The viewstate key to use for the <see cref="RequireSsl"/> property. - /// </summary> - private const string RequireSslViewStateKey = "RequireSsl"; - - #endregion - - /// <summary> - /// The lifetime of the cookie used to persist the Identifier the user logged in with. - /// </summary> - private static readonly TimeSpan PersistentIdentifierTimeToLiveDefault = TimeSpan.FromDays(14); - - /// <summary> - /// Backing field for the <see cref="RelyingParty"/> property. - /// </summary> - private OpenIdRelyingParty relyingParty; - - /// <summary> - /// A value indicating whether the <see cref="relyingParty"/> field contains - /// an instance that we own and should Dispose. - /// </summary> - private bool relyingPartyOwned; - - /// <summary> - /// Initializes a new instance of the <see cref="OpenIdRelyingPartyControlBase"/> class. - /// </summary> - protected OpenIdRelyingPartyControlBase() { - Reporting.RecordFeatureUse(this); - } - - #region Events - - /// <summary> - /// Fired when the user has typed in their identifier, discovery was successful - /// and a login attempt is about to begin. - /// </summary> - [Description("Fired when the user has typed in their identifier, discovery was successful and a login attempt is about to begin."), Category(OpenIdCategory)] - public event EventHandler<OpenIdEventArgs> LoggingIn; - - /// <summary> - /// Fired upon completion of a successful login. - /// </summary> - [Description("Fired upon completion of a successful login."), Category(OpenIdCategory)] - public event EventHandler<OpenIdEventArgs> LoggedIn; - - /// <summary> - /// Fired when a login attempt fails. - /// </summary> - [Description("Fired when a login attempt fails."), Category(OpenIdCategory)] - public event EventHandler<OpenIdEventArgs> Failed; - - /// <summary> - /// Fired when an authentication attempt is canceled at the OpenID Provider. - /// </summary> - [Description("Fired when an authentication attempt is canceled at the OpenID Provider."), Category(OpenIdCategory)] - public event EventHandler<OpenIdEventArgs> Canceled; - - /// <summary> - /// Occurs when the <see cref="Identifier"/> property is changed. - /// </summary> - protected event EventHandler IdentifierChanged; - - #endregion - - /// <summary> - /// Gets or sets the <see cref="OpenIdRelyingParty"/> instance to use. - /// </summary> - /// <value>The default value is an <see cref="OpenIdRelyingParty"/> instance initialized according to the web.config file.</value> - /// <remarks> - /// A performance optimization would be to store off the - /// instance as a static member in your web site and set it - /// to this property in your <see cref="Control.Load">Page.Load</see> - /// event since instantiating these instances can be expensive on - /// heavily trafficked web pages. - /// </remarks> - [Browsable(false)] - public virtual OpenIdRelyingParty RelyingParty { - get { - if (this.relyingParty == null) { - this.relyingParty = this.CreateRelyingParty(); - this.ConfigureRelyingParty(this.relyingParty); - this.relyingPartyOwned = true; - } - return this.relyingParty; - } - - set { - if (this.relyingPartyOwned && this.relyingParty != null) { - this.relyingParty.Dispose(); - } - - this.relyingParty = value; - this.relyingPartyOwned = false; - } - } - - /// <summary> - /// Gets the collection of extension requests this selector should include in generated requests. - /// </summary> - [PersistenceMode(PersistenceMode.InnerProperty)] - public Collection<IOpenIdMessageExtension> Extensions { - get { - if (this.ViewState[ExtensionsViewStateKey] == null) { - var extensions = new Collection<IOpenIdMessageExtension>(); - this.ViewState[ExtensionsViewStateKey] = extensions; - return extensions; - } else { - return (Collection<IOpenIdMessageExtension>)this.ViewState[ExtensionsViewStateKey]; - } - } - } - - /// <summary> - /// Gets or sets a value indicating whether stateless mode is used. - /// </summary> - [Bindable(true), DefaultValue(StatelessDefault), Category(OpenIdCategory)] - [Description("Controls whether stateless mode is used.")] - public bool Stateless { - get { return (bool)(ViewState[StatelessViewStateKey] ?? StatelessDefault); } - set { ViewState[StatelessViewStateKey] = value; } - } - - /// <summary> - /// Gets or sets the OpenID <see cref="Realm"/> of the relying party web site. - /// </summary> - [SuppressMessage("Microsoft.Usage", "CA1806:DoNotIgnoreMethodResults", MessageId = "System.Uri", Justification = "Using Uri.ctor for validation.")] - [SuppressMessage("Microsoft.Usage", "CA1806:DoNotIgnoreMethodResults", MessageId = "DotNetOpenAuth.OpenId.Realm", Justification = "Using ctor for validation.")] - [SuppressMessage("Microsoft.Design", "CA1056:UriPropertiesShouldNotBeStrings", Justification = "Bindable property must be simple type")] - [Bindable(true), DefaultValue(RealmUrlDefault), Category(OpenIdCategory)] - [Description("The OpenID Realm of the relying party web site.")] - [UrlProperty, Editor("System.Web.UI.Design.UrlEditor, System.Design, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", typeof(UITypeEditor))] - public string RealmUrl { - get { - return (string)(ViewState[RealmUrlViewStateKey] ?? RealmUrlDefault); - } - - set { - Contract.Requires<ArgumentException>(!string.IsNullOrEmpty(value)); - - if (Page != null && !DesignMode) { - // Validate new value by trying to construct a Realm object based on it. - new Realm(OpenIdUtilities.GetResolvedRealm(this.Page, value, this.RelyingParty.Channel.GetRequestFromContext())); // throws an exception on failure. - } else { - // We can't fully test it, but it should start with either ~/ or a protocol. - if (Regex.IsMatch(value, @"^https?://")) { - new Uri(value.Replace("*.", string.Empty)); // make sure it's fully-qualified, but ignore wildcards - } else if (value.StartsWith("~/", StringComparison.Ordinal)) { - // this is valid too - } else { - throw new UriFormatException(); - } - } - ViewState[RealmUrlViewStateKey] = value; - } - } - - /// <summary> - /// Gets or sets the OpenID ReturnTo of the relying party web site. - /// </summary> - [SuppressMessage("Microsoft.Usage", "CA2234:PassSystemUriObjectsInsteadOfStrings", Justification = "Bindable property must be simple type")] - [SuppressMessage("Microsoft.Usage", "CA1806:DoNotIgnoreMethodResults", MessageId = "System.Uri", Justification = "Using Uri.ctor for validation.")] - [SuppressMessage("Microsoft.Design", "CA1056:UriPropertiesShouldNotBeStrings", Justification = "Bindable property must be simple type")] - [Bindable(true), DefaultValue(ReturnToUrlDefault), Category(OpenIdCategory)] - [Description("The OpenID ReturnTo of the relying party web site.")] - [UrlProperty, Editor("System.Web.UI.Design.UrlEditor, System.Design, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", typeof(UITypeEditor))] - public string ReturnToUrl { - get { - return (string)(this.ViewState[ReturnToUrlViewStateKey] ?? ReturnToUrlDefault); - } - - set { - if (this.Page != null && !this.DesignMode) { - // Validate new value by trying to construct a Uri based on it. - new Uri(this.RelyingParty.Channel.GetRequestFromContext().UrlBeforeRewriting, this.Page.ResolveUrl(value)); // throws an exception on failure. - } else { - // We can't fully test it, but it should start with either ~/ or a protocol. - if (Regex.IsMatch(value, @"^https?://")) { - new Uri(value); // make sure it's fully-qualified, but ignore wildcards - } else if (value.StartsWith("~/", StringComparison.Ordinal)) { - // this is valid too - } else { - throw new UriFormatException(); - } - } - - this.ViewState[ReturnToUrlViewStateKey] = value; - } - } - - /// <summary> - /// Gets or sets a value indicating whether to send a persistent cookie upon successful - /// login so the user does not have to log in upon returning to this site. - /// </summary> - [Bindable(true), DefaultValue(UsePersistentCookieDefault), Category(BehaviorCategory)] - [Description("Whether to send a persistent cookie upon successful " + - "login so the user does not have to log in upon returning to this site.")] - public virtual LogOnPersistence UsePersistentCookie { - get { return (LogOnPersistence)(this.ViewState[UsePersistentCookieViewStateKey] ?? UsePersistentCookieDefault); } - set { this.ViewState[UsePersistentCookieViewStateKey] = value; } - } - - /// <summary> - /// Gets or sets the way a completed login is communicated to the rest of the web site. - /// </summary> - [Bindable(true), DefaultValue(LogOnModeDefault), Category(BehaviorCategory)] - [Description("The way a completed login is communicated to the rest of the web site.")] - public virtual LogOnSiteNotification LogOnMode { - get { return (LogOnSiteNotification)(this.ViewState[LogOnModeViewStateKey] ?? LogOnModeDefault); } - set { this.ViewState[LogOnModeViewStateKey] = value; } - } - - /// <summary> - /// Gets or sets a value indicating when to use a popup window to complete the login experience. - /// </summary> - /// <value>The default value is <see cref="PopupBehavior.Never"/>.</value> - [Bindable(true), DefaultValue(PopupDefault), Category(BehaviorCategory)] - [Description("When to use a popup window to complete the login experience.")] - public virtual PopupBehavior Popup { - get { return (PopupBehavior)(ViewState[PopupViewStateKey] ?? PopupDefault); } - set { ViewState[PopupViewStateKey] = value; } - } - - /// <summary> - /// Gets or sets a value indicating whether to enforce on high security mode, - /// which requires the full authentication pipeline to be protected by SSL. - /// </summary> - [Bindable(true), DefaultValue(RequireSslDefault), Category(OpenIdCategory)] - [Description("Turns on high security mode, requiring the full authentication pipeline to be protected by SSL.")] - public bool RequireSsl { - get { return (bool)(ViewState[RequireSslViewStateKey] ?? RequireSslDefault); } - set { ViewState[RequireSslViewStateKey] = value; } - } - - /// <summary> - /// Gets or sets the Identifier that will be used to initiate login. - /// </summary> - [Bindable(true), Category(OpenIdCategory)] - [Description("The OpenID Identifier that this button will use to initiate login.")] - [TypeConverter(typeof(IdentifierConverter))] - public virtual Identifier Identifier { - get { - return (Identifier)ViewState[IdentifierViewStateKey]; - } - - set { - ViewState[IdentifierViewStateKey] = value; - this.OnIdentifierChanged(); - } - } - - /// <summary> - /// Gets or sets the default association preference to set on authentication requests. - /// </summary> - internal AssociationPreference AssociationPreference { get; set; } - - /// <summary> - /// Gets ancestor controls, starting with the immediate parent, and progressing to more distant ancestors. - /// </summary> - protected IEnumerable<Control> ParentControls { - get { - Control parent = this; - while ((parent = parent.Parent) != null) { - yield return parent; - } - } - } - - /// <summary> - /// Gets a value indicating whether this control is a child control of a composite OpenID control. - /// </summary> - /// <value> - /// <c>true</c> if this instance is embedded in parent OpenID control; otherwise, <c>false</c>. - /// </value> - protected bool IsEmbeddedInParentOpenIdControl { - get { return this.ParentControls.OfType<OpenIdRelyingPartyControlBase>().Any(); } - } - - /// <summary> - /// Clears any cookie set by this control to help the user on a returning visit next time. - /// </summary> - public static void LogOff() { - HttpContext.Current.Response.SetCookie(CreateIdentifierPersistingCookie(null)); - } - - /// <summary> - /// Immediately redirects to the OpenID Provider to verify the Identifier - /// provided in the text box. - /// </summary> - public void LogOn() { - IAuthenticationRequest request = this.CreateRequests().FirstOrDefault(); - ErrorUtilities.VerifyProtocol(request != null, OpenIdStrings.OpenIdEndpointNotFound); - this.LogOn(request); - } - - /// <summary> - /// Immediately redirects to the OpenID Provider to verify the Identifier - /// provided in the text box. - /// </summary> - /// <param name="request">The request.</param> - public void LogOn(IAuthenticationRequest request) { - Contract.Requires<ArgumentNullException>(request != null); - - if (this.IsPopupAppropriate(request)) { - this.ScriptPopupWindow(request); - } else { - request.RedirectToProvider(); - } - } - - #region IPostBackEventHandler Members - - /// <summary> - /// When implemented by a class, enables a server control to process an event raised when a form is posted to the server. - /// </summary> - /// <param name="eventArgument">A <see cref="T:System.String"/> that represents an optional event argument to be passed to the event handler.</param> - void IPostBackEventHandler.RaisePostBackEvent(string eventArgument) { - this.RaisePostBackEvent(eventArgument); - } - - #endregion - - /// <summary> - /// Enables a server control to perform final clean up before it is released from memory. - /// </summary> - [SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times", Justification = "Unavoidable because base class does not expose a protected virtual Dispose(bool) method."), SuppressMessage("Microsoft.Design", "CA1063:ImplementIDisposableCorrectly", Justification = "Base class doesn't implement virtual Dispose(bool), so we must call its Dispose() method.")] - public sealed override void Dispose() { - this.Dispose(true); - base.Dispose(); - GC.SuppressFinalize(this); - } - - /// <summary> - /// Creates the authentication requests for a given user-supplied Identifier. - /// </summary> - /// <param name="identifier">The identifier to create a request for.</param> - /// <returns> - /// A sequence of authentication requests, any one of which may be - /// used to determine the user's control of the <see cref="IAuthenticationRequest.ClaimedIdentifier"/>. - /// </returns> - protected internal virtual IEnumerable<IAuthenticationRequest> CreateRequests(Identifier identifier) { - Contract.Requires<ArgumentNullException>(identifier != null); - - // If this control is actually a member of another OpenID RP control, - // delegate creation of requests to the parent control. - var parentOwner = this.ParentControls.OfType<OpenIdRelyingPartyControlBase>().FirstOrDefault(); - if (parentOwner != null) { - return parentOwner.CreateRequests(identifier); - } else { - // Delegate to a private method to keep 'yield return' and Code Contract separate. - return this.CreateRequestsCore(identifier); - } - } - - /// <summary> - /// Releases unmanaged and - optionally - managed resources - /// </summary> - /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param> - protected virtual void Dispose(bool disposing) { - if (disposing) { - if (this.relyingPartyOwned && this.relyingParty != null) { - this.relyingParty.Dispose(); - this.relyingParty = null; - } - } - } - - /// <summary> - /// When implemented by a class, enables a server control to process an event raised when a form is posted to the server. - /// </summary> - /// <param name="eventArgument">A <see cref="T:System.String"/> that represents an optional event argument to be passed to the event handler.</param> - [SuppressMessage("Microsoft.Design", "CA1030:UseEventsWhereAppropriate", Justification = "Predefined signature.")] - protected virtual void RaisePostBackEvent(string eventArgument) { - } - - /// <summary> - /// Creates the authentication requests for the value set in the <see cref="Identifier"/> property. - /// </summary> - /// <returns> - /// A sequence of authentication requests, any one of which may be - /// used to determine the user's control of the <see cref="IAuthenticationRequest.ClaimedIdentifier"/>. - /// </returns> - protected IEnumerable<IAuthenticationRequest> CreateRequests() { - Contract.Requires<InvalidOperationException>(this.Identifier != null, OpenIdStrings.NoIdentifierSet); - return this.CreateRequests(this.Identifier); - } - - /// <summary> - /// Raises the <see cref="E:Load"/> event. - /// </summary> - /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param> - protected override void OnLoad(EventArgs e) { - base.OnLoad(e); - - if (Page.IsPostBack) { - // OpenID responses NEVER come in the form of a postback. - return; - } - - if (this.Identifier == null) { - this.TryPresetIdentifierWithCookie(); - } - - // Take an unreliable sneek peek to see if we're in a popup and an OpenID - // assertion is coming in. We shouldn't process assertions in a popup window. - if (this.Page.Request.QueryString[UIPopupCallbackKey] == "1" && this.Page.Request.QueryString[UIPopupCallbackParentKey] == null) { - // We're in a popup window. We need to close it and pass the - // message back to the parent window for processing. - this.ScriptClosingPopupOrIFrame(); - return; // don't do any more processing on it now - } - - // Only sniff for an OpenID response if it is targeted at this control. - // Note that Stateless mode causes no receiver to be indicated, and - // we want to handle that, but only if there isn't a parent control that - // will be handling that. - string receiver = this.Page.Request.QueryString[ReturnToReceivingControlId] ?? this.Page.Request.Form[ReturnToReceivingControlId]; - if (receiver == this.ClientID || (receiver == null && !this.IsEmbeddedInParentOpenIdControl)) { - var response = this.RelyingParty.GetResponse(); - Logger.Controls.DebugFormat( - "The {0} control checked for an authentication response and found: {1}", - this.ID, - response != null ? response.Status.ToString() : "nothing"); - this.ProcessResponse(response); - } - } - - /// <summary> - /// Notifies the user agent via an AJAX response of a completed authentication attempt. - /// </summary> - protected virtual void ScriptClosingPopupOrIFrame() { - this.RelyingParty.ProcessResponseFromPopup(); - } - - /// <summary> - /// Called when the <see cref="Identifier"/> property is changed. - /// </summary> - protected virtual void OnIdentifierChanged() { - var identifierChanged = this.IdentifierChanged; - if (identifierChanged != null) { - identifierChanged(this, EventArgs.Empty); - } - } - - /// <summary> - /// Processes the response. - /// </summary> - /// <param name="response">The response.</param> - protected virtual void ProcessResponse(IAuthenticationResponse response) { - if (response == null) { - return; - } - string persistentString = response.GetUntrustedCallbackArgument(UsePersistentCookieCallbackKey); - if (persistentString != null) { - this.UsePersistentCookie = (LogOnPersistence)Enum.Parse(typeof(LogOnPersistence), persistentString); - } - - switch (response.Status) { - case AuthenticationStatus.Authenticated: - this.OnLoggedIn(response); - break; - case AuthenticationStatus.Canceled: - this.OnCanceled(response); - break; - case AuthenticationStatus.Failed: - this.OnFailed(response); - break; - case AuthenticationStatus.SetupRequired: - case AuthenticationStatus.ExtensionsOnly: - default: - // The NotApplicable (extension-only assertion) is NOT one that we support - // in this control because that scenario is primarily interesting to RPs - // that are asking a specific OP, and it is not user-initiated as this textbox - // is designed for. - throw new InvalidOperationException(MessagingStrings.UnexpectedMessageReceivedOfMany); - } - } - - /// <summary> - /// Raises the <see cref="E:System.Web.UI.Control.PreRender"/> event. - /// </summary> - /// <param name="e">An <see cref="T:System.EventArgs"/> object that contains the event data.</param> - protected override void OnPreRender(EventArgs e) { - base.OnPreRender(e); - - this.Page.ClientScript.RegisterClientScriptResource(typeof(OpenIdRelyingPartyControlBase), EmbeddedJavascriptResource); - } - - /// <summary> - /// Fires the <see cref="LoggedIn"/> event. - /// </summary> - /// <param name="response">The response.</param> - protected virtual void OnLoggedIn(IAuthenticationResponse response) { - Contract.Requires<ArgumentNullException>(response != null); - Contract.Requires<ArgumentException>(response.Status == AuthenticationStatus.Authenticated); - - var loggedIn = this.LoggedIn; - OpenIdEventArgs args = new OpenIdEventArgs(response); - if (loggedIn != null) { - loggedIn(this, args); - } - - if (!args.Cancel) { - if (this.UsePersistentCookie == LogOnPersistence.SessionAndPersistentIdentifier) { - Page.Response.SetCookie(CreateIdentifierPersistingCookie(response)); - } - - switch (this.LogOnMode) { - case LogOnSiteNotification.FormsAuthentication: - FormsAuthentication.RedirectFromLoginPage(response.ClaimedIdentifier, this.UsePersistentCookie == LogOnPersistence.PersistentAuthentication); - break; - case LogOnSiteNotification.None: - default: - break; - } - } - } - - /// <summary> - /// Fires the <see cref="LoggingIn"/> event. - /// </summary> - /// <param name="request">The request.</param> - /// <returns> - /// Returns whether the login should proceed. False if some event handler canceled the request. - /// </returns> - protected virtual bool OnLoggingIn(IAuthenticationRequest request) { - Contract.Requires<ArgumentNullException>(request != null); - - EventHandler<OpenIdEventArgs> loggingIn = this.LoggingIn; - - OpenIdEventArgs args = new OpenIdEventArgs(request); - if (loggingIn != null) { - loggingIn(this, args); - } - - return !args.Cancel; - } - - /// <summary> - /// Fires the <see cref="Canceled"/> event. - /// </summary> - /// <param name="response">The response.</param> - protected virtual void OnCanceled(IAuthenticationResponse response) { - Contract.Requires<ArgumentNullException>(response != null); - Contract.Requires<ArgumentException>(response.Status == AuthenticationStatus.Canceled); - - var canceled = this.Canceled; - if (canceled != null) { - canceled(this, new OpenIdEventArgs(response)); - } - } - - /// <summary> - /// Fires the <see cref="Failed"/> event. - /// </summary> - /// <param name="response">The response.</param> - protected virtual void OnFailed(IAuthenticationResponse response) { - Contract.Requires<ArgumentNullException>(response != null); - Contract.Requires<ArgumentException>(response.Status == AuthenticationStatus.Failed); - - var failed = this.Failed; - if (failed != null) { - failed(this, new OpenIdEventArgs(response)); - } - } - - /// <summary> - /// Creates the relying party instance used to generate authentication requests. - /// </summary> - /// <returns>The instantiated relying party.</returns> - protected OpenIdRelyingParty CreateRelyingParty() { - IOpenIdApplicationStore store = this.Stateless ? null : DotNetOpenAuthSection.Configuration.OpenId.RelyingParty.ApplicationStore.CreateInstance(OpenIdRelyingParty.HttpApplicationStore); - return this.CreateRelyingParty(store); - } - - /// <summary> - /// Creates the relying party instance used to generate authentication requests. - /// </summary> - /// <param name="store">The store to pass to the relying party constructor.</param> - /// <returns>The instantiated relying party.</returns> - protected virtual OpenIdRelyingParty CreateRelyingParty(IOpenIdApplicationStore store) { - return new OpenIdRelyingParty(store); - } - - /// <summary> - /// Configures the relying party. - /// </summary> - /// <param name="relyingParty">The relying party.</param> - [SuppressMessage("Microsoft.Maintainability", "CA1500:VariableNamesShouldNotMatchFieldNames", MessageId = "relyingParty", Justification = "This makes it possible for overrides to see the value before it is set on a field.")] - protected virtual void ConfigureRelyingParty(OpenIdRelyingParty relyingParty) { - Contract.Requires<ArgumentNullException>(relyingParty != null); - - // Only set RequireSsl to true, as we don't want to override - // a .config setting of true with false. - if (this.RequireSsl) { - relyingParty.SecuritySettings.RequireSsl = true; - } - } - - /// <summary> - /// Detects whether a popup window should be used to show the Provider's UI. - /// </summary> - /// <param name="request">The request.</param> - /// <returns> - /// <c>true</c> if a popup should be used; <c>false</c> otherwise. - /// </returns> - protected virtual bool IsPopupAppropriate(IAuthenticationRequest request) { - Contract.Requires<ArgumentNullException>(request != null); - - switch (this.Popup) { - case PopupBehavior.Never: - return false; - case PopupBehavior.Always: - return true; - case PopupBehavior.IfProviderSupported: - return request.DiscoveryResult.IsExtensionSupported<UIRequest>(); - default: - throw ErrorUtilities.ThrowInternal("Unexpected value for Popup property."); - } - } - - /// <summary> - /// Adds attributes to an HTML <A> tag that will be written by the caller using - /// <see cref="HtmlTextWriter.RenderBeginTag(HtmlTextWriterTag)"/> after this method. - /// </summary> - /// <param name="writer">The HTML writer.</param> - /// <param name="request">The outgoing authentication request.</param> - /// <param name="windowStatus">The text to try to display in the status bar on mouse hover.</param> - protected void RenderOpenIdMessageTransmissionAsAnchorAttributes(HtmlTextWriter writer, IAuthenticationRequest request, string windowStatus) { - Contract.Requires<ArgumentNullException>(writer != null); - Contract.Requires<ArgumentNullException>(request != null); - - // We render a standard HREF attribute for non-javascript browsers. - writer.AddAttribute(HtmlTextWriterAttribute.Href, request.RedirectingResponse.GetDirectUriRequest(this.RelyingParty.Channel).AbsoluteUri); - - // And for the Javascript ones we do the extra work to use form POST where necessary. - writer.AddAttribute(HtmlTextWriterAttribute.Onclick, this.CreateGetOrPostAHrefValue(request) + " return false;"); - - writer.AddStyleAttribute(HtmlTextWriterStyle.Cursor, "pointer"); - if (!string.IsNullOrEmpty(windowStatus)) { - writer.AddAttribute("onMouseOver", "window.status = " + MessagingUtilities.GetSafeJavascriptValue(windowStatus)); - writer.AddAttribute("onMouseOut", "window.status = null"); - } - } - - /// <summary> - /// Creates the identifier-persisting cookie, either for saving or deleting. - /// </summary> - /// <param name="response">The positive authentication response; or <c>null</c> to clear the cookie.</param> - /// <returns>An persistent cookie.</returns> - private static HttpCookie CreateIdentifierPersistingCookie(IAuthenticationResponse response) { - HttpCookie cookie = new HttpCookie(PersistentIdentifierCookieName); - bool clearingCookie = false; - - // We'll try to store whatever it was the user originally typed in, but fallback - // to the final claimed_id. - if (response != null && response.Status == AuthenticationStatus.Authenticated) { - var positiveResponse = (PositiveAuthenticationResponse)response; - - // We must escape the value because XRIs start with =, and any leading '=' gets dropped (by ASP.NET?) - cookie.Value = Uri.EscapeDataString(positiveResponse.Endpoint.UserSuppliedIdentifier ?? response.ClaimedIdentifier); - } else { - clearingCookie = true; - cookie.Value = string.Empty; - if (HttpContext.Current.Request.Browser["supportsEmptyStringInCookieValue"] == "false") { - cookie.Value = "NoCookie"; - } - } - - if (clearingCookie) { - // mark the cookie has having already expired to cause the user agent to delete - // the old persisted cookie. - cookie.Expires = DateTime.Now.Subtract(TimeSpan.FromDays(1)); - } else { - // Make the cookie persistent by setting an expiration date - cookie.Expires = DateTime.Now + PersistentIdentifierTimeToLiveDefault; - } - - return cookie; - } - - /// <summary> - /// Creates the authentication requests for a given user-supplied Identifier. - /// </summary> - /// <param name="identifier">The identifier to create a request for.</param> - /// <returns> - /// A sequence of authentication requests, any one of which may be - /// used to determine the user's control of the <see cref="IAuthenticationRequest.ClaimedIdentifier"/>. - /// </returns> - private IEnumerable<IAuthenticationRequest> CreateRequestsCore(Identifier identifier) { - ErrorUtilities.VerifyArgumentNotNull(identifier, "identifier"); // NO CODE CONTRACTS! (yield return used here) - IEnumerable<IAuthenticationRequest> requests; - - // Approximate the returnTo (either based on the customize property or the page URL) - // so we can use it to help with Realm resolution. - Uri returnToApproximation; - if (this.ReturnToUrl != null) { - string returnToResolvedPath = this.ResolveUrl(this.ReturnToUrl); - returnToApproximation = new Uri(this.RelyingParty.Channel.GetRequestFromContext().UrlBeforeRewriting, returnToResolvedPath); - } else { - returnToApproximation = this.Page.Request.Url; - } - - // Resolve the trust root, and swap out the scheme and port if necessary to match the - // return_to URL, since this match is required by OpenID, and the consumer app - // may be using HTTP at some times and HTTPS at others. - UriBuilder realm = OpenIdUtilities.GetResolvedRealm(this.Page, this.RealmUrl, this.RelyingParty.Channel.GetRequestFromContext()); - realm.Scheme = returnToApproximation.Scheme; - realm.Port = returnToApproximation.Port; - - // Initiate OpenID request - // We use TryParse here to avoid throwing an exception which - // might slip through our validator control if it is disabled. - Realm typedRealm = new Realm(realm); - if (string.IsNullOrEmpty(this.ReturnToUrl)) { - requests = this.RelyingParty.CreateRequests(identifier, typedRealm); - } else { - // Since the user actually gave us a return_to value, - // the "approximation" is exactly what we want. - requests = this.RelyingParty.CreateRequests(identifier, typedRealm, returnToApproximation); - } - - // Some OPs may be listed multiple times (one with HTTPS and the other with HTTP, for example). - // Since we're gathering OPs to try one after the other, just take the first choice of each OP - // and don't try it multiple times. - requests = requests.Distinct(DuplicateRequestedHostsComparer.Instance); - - // Configure each generated request. - foreach (var req in requests) { - if (this.IsPopupAppropriate(req)) { - // Inform ourselves in return_to that we're in a popup. - req.SetUntrustedCallbackArgument(UIPopupCallbackKey, "1"); - - if (req.DiscoveryResult.IsExtensionSupported<UIRequest>()) { - // Inform the OP that we'll be using a popup window consistent with the UI extension. - // But beware that the extension MAY have already been added if we're using - // the OpenIdAjaxRelyingParty class. - if (!((AuthenticationRequest)req).Extensions.OfType<UIRequest>().Any()) { - req.AddExtension(new UIRequest()); - } - - // Provide a hint for the client javascript about whether the OP supports the UI extension. - // This is so the window can be made the correct size for the extension. - // If the OP doesn't advertise support for the extension, the javascript will use - // a bigger popup window. - req.SetUntrustedCallbackArgument(PopupUISupportedJSHint, "1"); - } - } - - // Add the extensions injected into the control. - foreach (var extension in this.Extensions) { - req.AddExtension(extension); - } - - // Add state that needs to survive across the redirect, but at this point - // only save those properties that are not expected to be changed by a - // LoggingIn event handler. - req.SetUntrustedCallbackArgument(ReturnToReceivingControlId, this.ClientID); - - // Apply the control's association preference to this auth request, but only if - // it is less demanding (greater ordinal value) than the existing one. - // That way, we protect against retrying an association that was already attempted. - var authReq = ((AuthenticationRequest)req); - if (authReq.AssociationPreference < this.AssociationPreference) { - authReq.AssociationPreference = this.AssociationPreference; - } - - if (this.OnLoggingIn(req)) { - // We save this property after firing OnLoggingIn so that the host page can - // change its value and have that value saved. - req.SetUntrustedCallbackArgument(UsePersistentCookieCallbackKey, this.UsePersistentCookie.ToString()); - - yield return req; - } - } - } - - /// <summary> - /// Gets the javascript to executee to redirect or POST an OpenID message to a remote party. - /// </summary> - /// <param name="request">The authentication request to send.</param> - /// <returns>The javascript that should execute.</returns> - private string CreateGetOrPostAHrefValue(IAuthenticationRequest request) { - Contract.Requires<ArgumentNullException>(request != null); - - Uri directUri = request.RedirectingResponse.GetDirectUriRequest(this.RelyingParty.Channel); - return "window.dnoa_internal.GetOrPost(" + MessagingUtilities.GetSafeJavascriptValue(directUri.AbsoluteUri) + ");"; - } - - /// <summary> - /// Wires the return page to immediately display a popup window with the Provider in it. - /// </summary> - /// <param name="request">The request.</param> - private void ScriptPopupWindow(IAuthenticationRequest request) { - Contract.Requires<ArgumentNullException>(request != null); - Contract.Requires<InvalidOperationException>(this.RelyingParty != null); - - StringBuilder startupScript = new StringBuilder(); - - // Add a callback function that the popup window can call on this, the - // parent window, to pass back the authentication result. - startupScript.AppendLine("window.dnoa_internal = {};"); - startupScript.AppendLine("window.dnoa_internal.processAuthorizationResult = function(uri) { window.location = uri; };"); - startupScript.AppendLine("window.dnoa_internal.popupWindow = function() {"); - startupScript.AppendFormat( - @"\tvar openidPopup = {0}", - UIUtilities.GetWindowPopupScript(this.RelyingParty, request, "openidPopup")); - startupScript.AppendLine("};"); - - this.Page.ClientScript.RegisterClientScriptBlock(this.GetType(), "loginPopup", startupScript.ToString(), true); - } - - /// <summary> - /// Tries to preset the <see cref="Identifier"/> property based on a persistent - /// cookie on the browser. - /// </summary> - /// <returns> - /// A value indicating whether the <see cref="Identifier"/> property was - /// successfully preset to some non-empty value. - /// </returns> - private bool TryPresetIdentifierWithCookie() { - HttpCookie cookie = this.Page.Request.Cookies[PersistentIdentifierCookieName]; - if (cookie != null) { - this.Identifier = Uri.UnescapeDataString(cookie.Value); - return true; - } - - return false; - } - } -} diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdSelector.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdSelector.cs deleted file mode 100644 index 538e181..0000000 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdSelector.cs +++ /dev/null @@ -1,455 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="OpenIdSelector.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -[assembly: System.Web.UI.WebResource(DotNetOpenAuth.OpenId.RelyingParty.OpenIdSelector.EmbeddedScriptResourceName, "text/javascript")] -[assembly: System.Web.UI.WebResource(DotNetOpenAuth.OpenId.RelyingParty.OpenIdSelector.EmbeddedStylesheetResourceName, "text/css")] - -namespace DotNetOpenAuth.OpenId.RelyingParty { - using System; - using System.Collections.Generic; - using System.Collections.ObjectModel; - using System.ComponentModel; - using System.Diagnostics.Contracts; - using System.Globalization; - using System.IdentityModel.Claims; - using System.Linq; - using System.Text; - using System.Web; - using System.Web.UI; - using System.Web.UI.HtmlControls; - using System.Web.UI.WebControls; - using DotNetOpenAuth.ComponentModel; - using DotNetOpenAuth.InfoCard; - using DotNetOpenAuth.Messaging; - - /// <summary> - /// An ASP.NET control that provides a user-friendly way of logging into a web site using OpenID. - /// </summary> - [ToolboxData("<{0}:OpenIdSelector runat=\"server\"></{0}:OpenIdSelector>")] - public class OpenIdSelector : OpenIdRelyingPartyAjaxControlBase { - /// <summary> - /// The name of the manifest stream containing the OpenIdButtonPanel.js file. - /// </summary> - internal const string EmbeddedScriptResourceName = Util.DefaultNamespace + ".OpenId.RelyingParty.OpenIdSelector.js"; - - /// <summary> - /// The name of the manifest stream containing the OpenIdButtonPanel.css file. - /// </summary> - internal const string EmbeddedStylesheetResourceName = Util.DefaultNamespace + ".OpenId.RelyingParty.OpenIdSelector.css"; - - /// <summary> - /// The substring to append to the end of the id or name of this control to form the - /// unique name of the hidden field that will carry the positive assertion on postback. - /// </summary> - private const string AuthDataFormKeySuffix = "_openidAuthData"; - - #region ViewState keys - - /// <summary> - /// The viewstate key to use for storing the value of the <see cref="Buttons"/> property. - /// </summary> - private const string ButtonsViewStateKey = "Buttons"; - - /// <summary> - /// The viewstate key to use for storing the value of the <see cref="AuthenticatedAsToolTip"/> property. - /// </summary> - private const string AuthenticatedAsToolTipViewStateKey = "AuthenticatedAsToolTip"; - - #endregion - - #region Property defaults - - /// <summary> - /// The default value for the <see cref="AuthenticatedAsToolTip"/> property. - /// </summary> - private const string AuthenticatedAsToolTipDefault = "We recognize you!"; - - #endregion - - /// <summary> - /// The OpenIdAjaxTextBox that remains hidden until the user clicks the OpenID button. - /// </summary> - private OpenIdAjaxTextBox textBox; - - /// <summary> - /// The hidden field that will transmit the positive assertion to the RP. - /// </summary> - private HiddenField positiveAssertionField; - - /// <summary> - /// Initializes a new instance of the <see cref="OpenIdSelector"/> class. - /// </summary> - public OpenIdSelector() { - } - - /// <summary> - /// Occurs when an InfoCard has been submitted and decoded. - /// </summary> - public event EventHandler<ReceivedTokenEventArgs> ReceivedToken; - - /// <summary> - /// Occurs when [token processing error]. - /// </summary> - public event EventHandler<TokenProcessingErrorEventArgs> TokenProcessingError; - - /// <summary> - /// Gets the text box where applicable. - /// </summary> - public OpenIdAjaxTextBox TextBox { - get { - this.EnsureChildControlsAreCreatedSafe(); - return this.textBox; - } - } - - /// <summary> - /// Gets or sets the maximum number of OpenID Providers to simultaneously try to authenticate with. - /// </summary> - [Browsable(true), DefaultValue(OpenIdAjaxTextBox.ThrottleDefault), Category(BehaviorCategory)] - [Description("The maximum number of OpenID Providers to simultaneously try to authenticate with.")] - public int Throttle { - get { - this.EnsureChildControlsAreCreatedSafe(); - return this.textBox.Throttle; - } - - set { - this.EnsureChildControlsAreCreatedSafe(); - this.textBox.Throttle = value; - } - } - - /// <summary> - /// Gets or sets the time duration for the AJAX control to wait for an OP to respond before reporting failure to the user. - /// </summary> - [Browsable(true), DefaultValue(typeof(TimeSpan), "00:00:08"), Category(BehaviorCategory)] - [Description("The time duration for the AJAX control to wait for an OP to respond before reporting failure to the user.")] - public TimeSpan Timeout { - get { - this.EnsureChildControlsAreCreatedSafe(); - return this.textBox.Timeout; - } - - set { - this.EnsureChildControlsAreCreatedSafe(); - this.textBox.Timeout = value; - } - } - - /// <summary> - /// Gets or sets the tool tip text that appears on the green checkmark when authentication succeeds. - /// </summary> - [Bindable(true), DefaultValue(AuthenticatedAsToolTipDefault), Localizable(true), Category(AppearanceCategory)] - [Description("The tool tip text that appears on the green checkmark when authentication succeeds.")] - public string AuthenticatedAsToolTip { - get { return (string)(this.ViewState[AuthenticatedAsToolTipViewStateKey] ?? AuthenticatedAsToolTipDefault); } - set { this.ViewState[AuthenticatedAsToolTipViewStateKey] = value ?? string.Empty; } - } - - /// <summary> - /// Gets or sets a value indicating whether the Yahoo! User Interface Library (YUI) - /// will be downloaded in order to provide a login split button. - /// </summary> - /// <value> - /// <c>true</c> to use a split button; otherwise, <c>false</c> to use a standard HTML button - /// or a split button by downloading the YUI library yourself on the hosting web page. - /// </value> - /// <remarks> - /// The split button brings in about 180KB of YUI javascript dependencies. - /// </remarks> - [Bindable(true), DefaultValue(OpenIdAjaxTextBox.DownloadYahooUILibraryDefault), Category(BehaviorCategory)] - [Description("Whether a split button will be used for the \"log in\" when the user provides an identifier that delegates to more than one Provider.")] - public bool DownloadYahooUILibrary { - get { - this.EnsureChildControlsAreCreatedSafe(); - return this.textBox.DownloadYahooUILibrary; - } - - set { - this.EnsureChildControlsAreCreatedSafe(); - this.textBox.DownloadYahooUILibrary = value; - } - } - - /// <summary> - /// Gets the collection of buttons this selector should render to the browser. - /// </summary> - [PersistenceMode(PersistenceMode.InnerProperty)] - public Collection<SelectorButton> Buttons { - get { - if (this.ViewState[ButtonsViewStateKey] == null) { - var providers = new Collection<SelectorButton>(); - this.ViewState[ButtonsViewStateKey] = providers; - return providers; - } else { - return (Collection<SelectorButton>)this.ViewState[ButtonsViewStateKey]; - } - } - } - - /// <summary> - /// Gets a <see cref="T:System.Web.UI.ControlCollection"/> object that represents the child controls for a specified server control in the UI hierarchy. - /// </summary> - /// <returns> - /// The collection of child controls for the specified server control. - /// </returns> - public override ControlCollection Controls { - get { - this.EnsureChildControls(); - return base.Controls; - } - } - - /// <summary> - /// Gets the name of the open id auth data form key (for the value as stored at the user agent as a FORM field). - /// </summary> - /// <value> - /// Usually a concatenation of the control's name and <c>"_openidAuthData"</c>. - /// </value> - protected override string OpenIdAuthDataFormKey { - get { return this.UniqueID + AuthDataFormKeySuffix; } - } - - /// <summary> - /// Gets a value indicating whether some button in the selector will want - /// to display the <see cref="OpenIdAjaxTextBox"/> control. - /// </summary> - protected virtual bool OpenIdTextBoxVisible { - get { return this.Buttons.OfType<SelectorOpenIdButton>().Any(); } - } - - /// <summary> - /// Releases unmanaged and - optionally - managed resources - /// </summary> - /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param> - protected override void Dispose(bool disposing) { - if (disposing) { - foreach (var button in this.Buttons.OfType<IDisposable>()) { - button.Dispose(); - } - } - - base.Dispose(disposing); - } - - /// <summary> - /// Called by the ASP.NET page framework to notify server controls that use composition-based implementation to create any child controls they contain in preparation for posting back or rendering. - /// </summary> - protected override void CreateChildControls() { - this.EnsureChildControlsAreCreatedSafe(); - - base.CreateChildControls(); - - // Now do the ID specific work. - this.EnsureID(); - ErrorUtilities.VerifyInternal(!string.IsNullOrEmpty(this.UniqueID), "Control.EnsureID() failed to give us a unique ID. Try setting an ID on the OpenIdSelector control. But please also file this bug with the project owners."); - - this.Controls.Add(this.textBox); - - this.positiveAssertionField.ID = this.ID + AuthDataFormKeySuffix; - this.Controls.Add(this.positiveAssertionField); - } - - /// <summary> - /// Ensures that the child controls have been built, but doesn't set control - /// properties that require executing <see cref="Control.EnsureID"/> in order to avoid - /// certain initialization order problems. - /// </summary> - /// <remarks> - /// We don't just call EnsureChildControls() and then set the property on - /// this.textBox itself because (apparently) setting this property in the ASPX - /// page and thus calling this EnsureID() via EnsureChildControls() this early - /// results in no ID. - /// </remarks> - protected virtual void EnsureChildControlsAreCreatedSafe() { - // If we've already created the child controls, this method is a no-op. - if (this.textBox != null) { - return; - } - - var selectorButton = this.Buttons.OfType<SelectorInfoCardButton>().FirstOrDefault(); - if (selectorButton != null) { - var selector = selectorButton.InfoCardSelector; - selector.ClaimsRequested.Add(new ClaimType { Name = ClaimTypes.PPID }); - selector.ImageSize = InfoCardImageSize.Size60x42; - selector.ReceivedToken += this.InfoCardSelector_ReceivedToken; - selector.TokenProcessingError += this.InfoCardSelector_TokenProcessingError; - this.Controls.Add(selector); - } - - this.textBox = new OpenIdAjaxTextBox(); - this.textBox.ID = "openid_identifier"; - this.textBox.HookFormSubmit = false; - this.textBox.ShowLogOnPostBackButton = true; - - this.positiveAssertionField = new HiddenField(); - } - - /// <summary> - /// Raises the <see cref="E:System.Web.UI.Control.Init"/> event. - /// </summary> - /// <param name="e">An <see cref="T:System.EventArgs"/> object that contains the event data.</param> - protected override void OnInit(EventArgs e) { - base.OnInit(e); - - // We force child control creation here so that they can get postback events. - EnsureChildControls(); - } - - /// <summary> - /// Raises the <see cref="E:System.Web.UI.Control.PreRender"/> event. - /// </summary> - /// <param name="e">An <see cref="T:System.EventArgs"/> object that contains the event data.</param> - protected override void OnPreRender(EventArgs e) { - base.OnPreRender(e); - - this.EnsureValidButtons(); - - var css = new HtmlLink(); - try { - css.Href = this.Page.ClientScript.GetWebResourceUrl(this.GetType(), EmbeddedStylesheetResourceName); - css.Attributes["rel"] = "stylesheet"; - css.Attributes["type"] = "text/css"; - ErrorUtilities.VerifyHost(this.Page.Header != null, OpenIdStrings.HeadTagMustIncludeRunatServer); - this.Page.Header.Controls.AddAt(0, css); // insert at top so host page can override - } catch { - css.Dispose(); - throw; - } - - // Import the .js file where most of the code is. - this.Page.ClientScript.RegisterClientScriptResource(typeof(OpenIdSelector), EmbeddedScriptResourceName); - - // Provide javascript with a way to post the login assertion. - const string PostLoginAssertionMethodName = "postLoginAssertion"; - const string PositiveAssertionParameterName = "positiveAssertion"; - const string ScriptFormat = @"window.{2} = function({0}) {{ - $('#{3}')[0].setAttribute('value', {0}); - {1}; -}};"; - string script = string.Format( - CultureInfo.InvariantCulture, - ScriptFormat, - PositiveAssertionParameterName, - this.Page.ClientScript.GetPostBackEventReference(this, null, false), - PostLoginAssertionMethodName, - this.positiveAssertionField.ClientID); - this.Page.ClientScript.RegisterClientScriptBlock(this.GetType(), "Postback", script, true); - - this.PreloadDiscovery(this.Buttons.OfType<SelectorProviderButton>().Select(op => op.OPIdentifier).Where(id => id != null)); - this.textBox.Visible = this.OpenIdTextBoxVisible; - } - - /// <summary> - /// Sends server control content to a provided <see cref="T:System.Web.UI.HtmlTextWriter"/> object, which writes the content to be rendered on the client. - /// </summary> - /// <param name="writer">The <see cref="T:System.Web.UI.HtmlTextWriter"/> object that receives the server control content.</param> - protected override void Render(HtmlTextWriter writer) { - Contract.Assume(writer != null, "Missing contract"); - writer.AddAttribute(HtmlTextWriterAttribute.Class, "OpenIdProviders"); - writer.RenderBeginTag(HtmlTextWriterTag.Ul); - - foreach (var button in this.Buttons) { - button.RenderLeadingAttributes(writer); - - writer.RenderBeginTag(HtmlTextWriterTag.Li); - - writer.AddAttribute(HtmlTextWriterAttribute.Href, "#"); - writer.RenderBeginTag(HtmlTextWriterTag.A); - - writer.RenderBeginTag(HtmlTextWriterTag.Div); - writer.RenderBeginTag(HtmlTextWriterTag.Div); - - button.RenderButtonContent(writer, this); - - writer.RenderEndTag(); // </div> - - writer.AddAttribute(HtmlTextWriterAttribute.Class, "ui-widget-overlay"); - writer.RenderBeginTag(HtmlTextWriterTag.Div); - writer.RenderEndTag(); - - writer.RenderEndTag(); // </div> - writer.RenderEndTag(); // </a> - writer.RenderEndTag(); // </li> - } - - writer.RenderEndTag(); // </ul> - - if (this.textBox.Visible) { - writer.AddStyleAttribute(HtmlTextWriterStyle.Display, "none"); - writer.AddAttribute(HtmlTextWriterAttribute.Id, "OpenIDForm"); - writer.RenderBeginTag(HtmlTextWriterTag.Div); - - this.textBox.RenderControl(writer); - - writer.RenderEndTag(); // </div> - } - - this.positiveAssertionField.RenderControl(writer); - } - - /// <summary> - /// Fires the <see cref="ReceivedToken"/> event. - /// </summary> - /// <param name="e">The token, if it was decrypted.</param> - protected virtual void OnReceivedToken(ReceivedTokenEventArgs e) { - Contract.Requires(e != null); - ErrorUtilities.VerifyArgumentNotNull(e, "e"); - - var receivedInfoCard = this.ReceivedToken; - if (receivedInfoCard != null) { - receivedInfoCard(this, e); - } - } - - /// <summary> - /// Raises the <see cref="E:TokenProcessingError"/> event. - /// </summary> - /// <param name="e">The <see cref="DotNetOpenAuth.InfoCard.TokenProcessingErrorEventArgs"/> instance containing the event data.</param> - protected virtual void OnTokenProcessingError(TokenProcessingErrorEventArgs e) { - Contract.Requires(e != null); - ErrorUtilities.VerifyArgumentNotNull(e, "e"); - - var tokenProcessingError = this.TokenProcessingError; - if (tokenProcessingError != null) { - tokenProcessingError(this, e); - } - } - - /// <summary> - /// Handles the ReceivedToken event of the infoCardSelector control. - /// </summary> - /// <param name="sender">The source of the event.</param> - /// <param name="e">The <see cref="DotNetOpenAuth.InfoCard.ReceivedTokenEventArgs"/> instance containing the event data.</param> - private void InfoCardSelector_ReceivedToken(object sender, ReceivedTokenEventArgs e) { - this.Page.Response.SetCookie(new HttpCookie("openid_identifier", "infocard") { - Path = this.Page.Request.ApplicationPath, - }); - this.OnReceivedToken(e); - } - - /// <summary> - /// Handles the TokenProcessingError event of the infoCardSelector control. - /// </summary> - /// <param name="sender">The source of the event.</param> - /// <param name="e">The <see cref="DotNetOpenAuth.InfoCard.TokenProcessingErrorEventArgs"/> instance containing the event data.</param> - private void InfoCardSelector_TokenProcessingError(object sender, TokenProcessingErrorEventArgs e) { - this.OnTokenProcessingError(e); - } - - /// <summary> - /// Ensures the <see cref="Buttons"/> collection has a valid set of buttons. - /// </summary> - private void EnsureValidButtons() { - foreach (var button in this.Buttons) { - button.EnsureValid(); - } - - // Also make sure that there are appropriate numbers of each type of button. - // TODO: code here - } - } -} diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdTextBox.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdTextBox.cs deleted file mode 100644 index 335b435..0000000 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdTextBox.cs +++ /dev/null @@ -1,708 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="OpenIdTextBox.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -[assembly: System.Web.UI.WebResource(DotNetOpenAuth.OpenId.RelyingParty.OpenIdTextBox.EmbeddedLogoResourceName, "image/png")] - -#pragma warning disable 0809 // marking inherited, unsupported properties as obsolete to discourage their use - -namespace DotNetOpenAuth.OpenId.RelyingParty { - using System; - using System.Collections.Generic; - using System.Collections.Specialized; - using System.ComponentModel; - using System.Diagnostics; - using System.Diagnostics.CodeAnalysis; - using System.Diagnostics.Contracts; - using System.Drawing.Design; - using System.Globalization; - using System.Net; - using System.Text; - using System.Text.RegularExpressions; - using System.Web; - using System.Web.Security; - using System.Web.UI; - using System.Web.UI.WebControls; - using DotNetOpenAuth.Configuration; - using DotNetOpenAuth.Messaging; - using DotNetOpenAuth.OpenId.Extensions.SimpleRegistration; - using DotNetOpenAuth.OpenId.Extensions.UI; - - /// <summary> - /// An ASP.NET control that provides a minimal text box that is OpenID-aware. - /// </summary> - /// <remarks> - /// This control offers greater UI flexibility than the <see cref="OpenIdLogin"/> - /// control, but requires more work to be done by the hosting web site to - /// assemble a complete login experience. - /// </remarks> - [DefaultProperty("Text"), ValidationProperty("Text")] - [ToolboxData("<{0}:OpenIdTextBox runat=\"server\" />")] - public class OpenIdTextBox : OpenIdRelyingPartyControlBase, IEditableTextControl, ITextControl, IPostBackDataHandler { - /// <summary> - /// The name of the manifest stream containing the - /// OpenID logo that is placed inside the text box. - /// </summary> - internal const string EmbeddedLogoResourceName = Util.DefaultNamespace + ".OpenId.RelyingParty.openid_login.png"; - - /// <summary> - /// Default value for <see cref="TabIndex"/> property. - /// </summary> - protected const short TabIndexDefault = 0; - - #region Property category constants - - /// <summary> - /// The "Simple Registration" category for properties. - /// </summary> - private const string ProfileCategory = "Simple Registration"; - - #endregion - - #region Property viewstate keys - - /// <summary> - /// The viewstate key to use for the <see cref="RequestEmail"/> property. - /// </summary> - private const string RequestEmailViewStateKey = "RequestEmail"; - - /// <summary> - /// The viewstate key to use for the <see cref="RequestNickname"/> property. - /// </summary> - private const string RequestNicknameViewStateKey = "RequestNickname"; - - /// <summary> - /// The viewstate key to use for the <see cref="RequestPostalCode"/> property. - /// </summary> - private const string RequestPostalCodeViewStateKey = "RequestPostalCode"; - - /// <summary> - /// The viewstate key to use for the <see cref="RequestCountry"/> property. - /// </summary> - private const string RequestCountryViewStateKey = "RequestCountry"; - - /// <summary> - /// The viewstate key to use for the <see cref="RequestLanguage"/> property. - /// </summary> - private const string RequestLanguageViewStateKey = "RequestLanguage"; - - /// <summary> - /// The viewstate key to use for the <see cref="RequestTimeZone"/> property. - /// </summary> - private const string RequestTimeZoneViewStateKey = "RequestTimeZone"; - - /// <summary> - /// The viewstate key to use for the <see cref="EnableRequestProfile"/> property. - /// </summary> - private const string EnableRequestProfileViewStateKey = "EnableRequestProfile"; - - /// <summary> - /// The viewstate key to use for the <see cref="PolicyUrl"/> property. - /// </summary> - private const string PolicyUrlViewStateKey = "PolicyUrl"; - - /// <summary> - /// The viewstate key to use for the <see cref="RequestFullName"/> property. - /// </summary> - private const string RequestFullNameViewStateKey = "RequestFullName"; - - /// <summary> - /// The viewstate key to use for the <see cref="PresetBorder"/> property. - /// </summary> - private const string PresetBorderViewStateKey = "PresetBorder"; - - /// <summary> - /// The viewstate key to use for the <see cref="ShowLogo"/> property. - /// </summary> - private const string ShowLogoViewStateKey = "ShowLogo"; - - /// <summary> - /// The viewstate key to use for the <see cref="RequestGender"/> property. - /// </summary> - private const string RequestGenderViewStateKey = "RequestGender"; - - /// <summary> - /// The viewstate key to use for the <see cref="RequestBirthDate"/> property. - /// </summary> - private const string RequestBirthDateViewStateKey = "RequestBirthDate"; - - /// <summary> - /// The viewstate key to use for the <see cref="CssClass"/> property. - /// </summary> - private const string CssClassViewStateKey = "CssClass"; - - /// <summary> - /// The viewstate key to use for the <see cref="MaxLength"/> property. - /// </summary> - private const string MaxLengthViewStateKey = "MaxLength"; - - /// <summary> - /// The viewstate key to use for the <see cref="Columns"/> property. - /// </summary> - private const string ColumnsViewStateKey = "Columns"; - - /// <summary> - /// The viewstate key to use for the <see cref="TabIndex"/> property. - /// </summary> - private const string TabIndexViewStateKey = "TabIndex"; - - /// <summary> - /// The viewstate key to use for the <see cref="Enabled"/> property. - /// </summary> - private const string EnabledViewStateKey = "Enabled"; - - /// <summary> - /// The viewstate key to use for the <see cref="Name"/> property. - /// </summary> - private const string NameViewStateKey = "Name"; - - /// <summary> - /// The viewstate key to use for the <see cref="Text"/> property. - /// </summary> - private const string TextViewStateKey = "Text"; - - #endregion - - #region Property defaults - - /// <summary> - /// The default value for the <see cref="Columns"/> property. - /// </summary> - private const int ColumnsDefault = 40; - - /// <summary> - /// The default value for the <see cref="MaxLength"/> property. - /// </summary> - private const int MaxLengthDefault = 40; - - /// <summary> - /// The default value for the <see cref="Name"/> property. - /// </summary> - private const string NameDefault = "openid_identifier"; - - /// <summary> - /// The default value for the <see cref="EnableRequestProfile"/> property. - /// </summary> - private const bool EnableRequestProfileDefault = true; - - /// <summary> - /// The default value for the <see cref="ShowLogo"/> property. - /// </summary> - private const bool ShowLogoDefault = true; - - /// <summary> - /// The default value for the <see cref="PresetBorder"/> property. - /// </summary> - private const bool PresetBorderDefault = true; - - /// <summary> - /// The default value for the <see cref="PolicyUrl"/> property. - /// </summary> - private const string PolicyUrlDefault = ""; - - /// <summary> - /// The default value for the <see cref="CssClass"/> property. - /// </summary> - private const string CssClassDefault = "openid"; - - /// <summary> - /// The default value for the <see cref="Text"/> property. - /// </summary> - private const string TextDefault = ""; - - /// <summary> - /// The default value for the <see cref="RequestEmail"/> property. - /// </summary> - private const DemandLevel RequestEmailDefault = DemandLevel.NoRequest; - - /// <summary> - /// The default value for the <see cref="RequestPostalCode"/> property. - /// </summary> - private const DemandLevel RequestPostalCodeDefault = DemandLevel.NoRequest; - - /// <summary> - /// The default value for the <see cref="RequestCountry"/> property. - /// </summary> - private const DemandLevel RequestCountryDefault = DemandLevel.NoRequest; - - /// <summary> - /// The default value for the <see cref="RequestLanguage"/> property. - /// </summary> - private const DemandLevel RequestLanguageDefault = DemandLevel.NoRequest; - - /// <summary> - /// The default value for the <see cref="RequestTimeZone"/> property. - /// </summary> - private const DemandLevel RequestTimeZoneDefault = DemandLevel.NoRequest; - - /// <summary> - /// The default value for the <see cref="RequestNickname"/> property. - /// </summary> - private const DemandLevel RequestNicknameDefault = DemandLevel.NoRequest; - - /// <summary> - /// The default value for the <see cref="RequestFullName"/> property. - /// </summary> - private const DemandLevel RequestFullNameDefault = DemandLevel.NoRequest; - - /// <summary> - /// The default value for the <see cref="RequestBirthDate"/> property. - /// </summary> - private const DemandLevel RequestBirthDateDefault = DemandLevel.NoRequest; - - /// <summary> - /// The default value for the <see cref="RequestGender"/> property. - /// </summary> - private const DemandLevel RequestGenderDefault = DemandLevel.NoRequest; - - #endregion - - /// <summary> - /// An empty sreg request, used to compare with others to see if they too are empty. - /// </summary> - private static readonly ClaimsRequest EmptyClaimsRequest = new ClaimsRequest(); - - /// <summary> - /// Initializes a new instance of the <see cref="OpenIdTextBox"/> class. - /// </summary> - public OpenIdTextBox() { - } - - #region IEditableTextControl Members - - /// <summary> - /// Occurs when the content of the text changes between posts to the server. - /// </summary> - public event EventHandler TextChanged; - - #endregion - - #region Properties - - /// <summary> - /// Gets or sets the content of the text box. - /// </summary> - [Bindable(true), DefaultValue(""), Category(AppearanceCategory)] - [Description("The content of the text box.")] - public string Text { - get { - return this.Identifier != null ? this.Identifier.OriginalString : (this.ViewState[TextViewStateKey] as string ?? string.Empty); - } - - set { - // Try to store it as a validated identifier, - // but failing that at least store the text. - Identifier id; - if (Identifier.TryParse(value, out id)) { - this.Identifier = id; - } else { - // Be sure to set the viewstate AFTER setting the Identifier, - // since setting the Identifier clears the viewstate in OnIdentifierChanged. - this.Identifier = null; - this.ViewState[TextViewStateKey] = value; - } - } - } - - /// <summary> - /// Gets or sets the form name to use for this input field. - /// </summary> - [Bindable(true), DefaultValue(NameDefault), Category(BehaviorCategory)] - [Description("The form name of this input field.")] - public string Name { - get { return (string)(this.ViewState[NameViewStateKey] ?? NameDefault); } - set { this.ViewState[NameViewStateKey] = value; } - } - - /// <summary> - /// Gets or sets the CSS class assigned to the text box. - /// </summary> - [Bindable(true), DefaultValue(CssClassDefault), Category(AppearanceCategory)] - [Description("The CSS class assigned to the text box.")] - public string CssClass { - get { return (string)this.ViewState[CssClassViewStateKey]; } - set { this.ViewState[CssClassViewStateKey] = value; } - } - - /// <summary> - /// Gets or sets a value indicating whether to show the OpenID logo in the text box. - /// </summary> - [Bindable(true), DefaultValue(ShowLogoDefault), Category(AppearanceCategory)] - [Description("The visibility of the OpenID logo in the text box.")] - public bool ShowLogo { - get { return (bool)(this.ViewState[ShowLogoViewStateKey] ?? ShowLogoDefault); } - set { this.ViewState[ShowLogoViewStateKey] = value; } - } - - /// <summary> - /// Gets or sets a value indicating whether to use inline styling to force a solid gray border. - /// </summary> - [Bindable(true), DefaultValue(PresetBorderDefault), Category(AppearanceCategory)] - [Description("Whether to use inline styling to force a solid gray border.")] - public bool PresetBorder { - get { return (bool)(this.ViewState[PresetBorderViewStateKey] ?? PresetBorderDefault); } - set { this.ViewState[PresetBorderViewStateKey] = value; } - } - - /// <summary> - /// Gets or sets the width of the text box in characters. - /// </summary> - [Bindable(true), DefaultValue(ColumnsDefault), Category(AppearanceCategory)] - [Description("The width of the text box in characters.")] - public int Columns { - get { return (int)(this.ViewState[ColumnsViewStateKey] ?? ColumnsDefault); } - set { this.ViewState[ColumnsViewStateKey] = value; } - } - - /// <summary> - /// Gets or sets the maximum number of characters the browser should allow - /// </summary> - [Bindable(true), DefaultValue(MaxLengthDefault), Category(AppearanceCategory)] - [Description("The maximum number of characters the browser should allow.")] - public int MaxLength { - get { return (int)(this.ViewState[MaxLengthViewStateKey] ?? MaxLengthDefault); } - set { this.ViewState[MaxLengthViewStateKey] = value; } - } - - /// <summary> - /// Gets or sets the tab index of the Web server control. - /// </summary> - /// <value></value> - /// <returns> - /// The tab index of the Web server control. The default is 0, which indicates that this property is not set. - /// </returns> - /// <exception cref="T:System.ArgumentOutOfRangeException"> - /// The specified tab index is not between -32768 and 32767. - /// </exception> - [Bindable(true), DefaultValue(TabIndexDefault), Category(BehaviorCategory)] - [Description("The tab index of the text box control.")] - public virtual short TabIndex { - get { return (short)(this.ViewState[TabIndexViewStateKey] ?? TabIndexDefault); } - set { this.ViewState[TabIndexViewStateKey] = value; } - } - - /// <summary> - /// Gets or sets a value indicating whether this <see cref="OpenIdTextBox"/> is enabled - /// in the browser for editing and will respond to incoming OpenID messages. - /// </summary> - /// <value><c>true</c> if enabled; otherwise, <c>false</c>.</value> - [Bindable(true), DefaultValue(true), Category(BehaviorCategory)] - [Description("Whether the control is editable in the browser and will respond to OpenID messages.")] - public bool Enabled { - get { return (bool)(this.ViewState[EnabledViewStateKey] ?? true); } - set { this.ViewState[EnabledViewStateKey] = value; } - } - - /// <summary> - /// Gets or sets your level of interest in receiving the user's nickname from the Provider. - /// </summary> - [Bindable(true), DefaultValue(RequestNicknameDefault), Category(ProfileCategory)] - [Description("Your level of interest in receiving the user's nickname from the Provider.")] - public DemandLevel RequestNickname { - get { return (DemandLevel)(ViewState[RequestNicknameViewStateKey] ?? RequestNicknameDefault); } - set { ViewState[RequestNicknameViewStateKey] = value; } - } - - /// <summary> - /// Gets or sets your level of interest in receiving the user's email address from the Provider. - /// </summary> - [Bindable(true), DefaultValue(RequestEmailDefault), Category(ProfileCategory)] - [Description("Your level of interest in receiving the user's email address from the Provider.")] - public DemandLevel RequestEmail { - get { return (DemandLevel)(ViewState[RequestEmailViewStateKey] ?? RequestEmailDefault); } - set { ViewState[RequestEmailViewStateKey] = value; } - } - - /// <summary> - /// Gets or sets your level of interest in receiving the user's full name from the Provider. - /// </summary> - [Bindable(true), DefaultValue(RequestFullNameDefault), Category(ProfileCategory)] - [Description("Your level of interest in receiving the user's full name from the Provider")] - public DemandLevel RequestFullName { - get { return (DemandLevel)(ViewState[RequestFullNameViewStateKey] ?? RequestFullNameDefault); } - set { ViewState[RequestFullNameViewStateKey] = value; } - } - - /// <summary> - /// Gets or sets your level of interest in receiving the user's birthdate from the Provider. - /// </summary> - [Bindable(true), DefaultValue(RequestBirthDateDefault), Category(ProfileCategory)] - [Description("Your level of interest in receiving the user's birthdate from the Provider.")] - public DemandLevel RequestBirthDate { - get { return (DemandLevel)(ViewState[RequestBirthDateViewStateKey] ?? RequestBirthDateDefault); } - set { ViewState[RequestBirthDateViewStateKey] = value; } - } - - /// <summary> - /// Gets or sets your level of interest in receiving the user's gender from the Provider. - /// </summary> - [Bindable(true), DefaultValue(RequestGenderDefault), Category(ProfileCategory)] - [Description("Your level of interest in receiving the user's gender from the Provider.")] - public DemandLevel RequestGender { - get { return (DemandLevel)(ViewState[RequestGenderViewStateKey] ?? RequestGenderDefault); } - set { ViewState[RequestGenderViewStateKey] = value; } - } - - /// <summary> - /// Gets or sets your level of interest in receiving the user's postal code from the Provider. - /// </summary> - [Bindable(true), DefaultValue(RequestPostalCodeDefault), Category(ProfileCategory)] - [Description("Your level of interest in receiving the user's postal code from the Provider.")] - public DemandLevel RequestPostalCode { - get { return (DemandLevel)(ViewState[RequestPostalCodeViewStateKey] ?? RequestPostalCodeDefault); } - set { ViewState[RequestPostalCodeViewStateKey] = value; } - } - - /// <summary> - /// Gets or sets your level of interest in receiving the user's country from the Provider. - /// </summary> - [Bindable(true)] - [Category(ProfileCategory)] - [DefaultValue(RequestCountryDefault)] - [Description("Your level of interest in receiving the user's country from the Provider.")] - public DemandLevel RequestCountry { - get { return (DemandLevel)(ViewState[RequestCountryViewStateKey] ?? RequestCountryDefault); } - set { ViewState[RequestCountryViewStateKey] = value; } - } - - /// <summary> - /// Gets or sets your level of interest in receiving the user's preferred language from the Provider. - /// </summary> - [Bindable(true), DefaultValue(RequestLanguageDefault), Category(ProfileCategory)] - [Description("Your level of interest in receiving the user's preferred language from the Provider.")] - public DemandLevel RequestLanguage { - get { return (DemandLevel)(ViewState[RequestLanguageViewStateKey] ?? RequestLanguageDefault); } - set { ViewState[RequestLanguageViewStateKey] = value; } - } - - /// <summary> - /// Gets or sets your level of interest in receiving the user's time zone from the Provider. - /// </summary> - [Bindable(true), DefaultValue(RequestTimeZoneDefault), Category(ProfileCategory)] - [Description("Your level of interest in receiving the user's time zone from the Provider.")] - public DemandLevel RequestTimeZone { - get { return (DemandLevel)(ViewState[RequestTimeZoneViewStateKey] ?? RequestTimeZoneDefault); } - set { ViewState[RequestTimeZoneViewStateKey] = value; } - } - - /// <summary> - /// Gets or sets the URL to your privacy policy page that describes how - /// claims will be used and/or shared. - /// </summary> - [SuppressMessage("Microsoft.Design", "CA1056:UriPropertiesShouldNotBeStrings", Justification = "Bindable property must be simple type")] - [Bindable(true), DefaultValue(PolicyUrlDefault), Category(ProfileCategory)] - [Description("The URL to your privacy policy page that describes how claims will be used and/or shared.")] - [UrlProperty, Editor("System.Web.UI.Design.UrlEditor, System.Design, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", typeof(UITypeEditor))] - public string PolicyUrl { - get { - return (string)ViewState[PolicyUrlViewStateKey] ?? PolicyUrlDefault; - } - - set { - UriUtil.ValidateResolvableUrl(Page, DesignMode, value); - ViewState[PolicyUrlViewStateKey] = value; - } - } - - /// <summary> - /// Gets or sets a value indicating whether to use OpenID extensions - /// to retrieve profile data of the authenticating user. - /// </summary> - [Bindable(true), DefaultValue(EnableRequestProfileDefault), Category(ProfileCategory)] - [Description("Turns the entire Simple Registration extension on or off.")] - public bool EnableRequestProfile { - get { return (bool)(ViewState[EnableRequestProfileViewStateKey] ?? EnableRequestProfileDefault); } - set { ViewState[EnableRequestProfileViewStateKey] = value; } - } - - #endregion - - #region IPostBackDataHandler Members - - /// <summary> - /// When implemented by a class, processes postback data for an ASP.NET server control. - /// </summary> - /// <param name="postDataKey">The key identifier for the control.</param> - /// <param name="postCollection">The collection of all incoming name values.</param> - /// <returns> - /// true if the server control's state changes as a result of the postback; otherwise, false. - /// </returns> - bool IPostBackDataHandler.LoadPostData(string postDataKey, NameValueCollection postCollection) { - return this.LoadPostData(postDataKey, postCollection); - } - - /// <summary> - /// When implemented by a class, signals the server control to notify the ASP.NET application that the state of the control has changed. - /// </summary> - void IPostBackDataHandler.RaisePostDataChangedEvent() { - this.RaisePostDataChangedEvent(); - } - - #endregion - - /// <summary> - /// Creates the authentication requests for a given user-supplied Identifier. - /// </summary> - /// <param name="identifier">The identifier to create a request for.</param> - /// <returns> - /// A sequence of authentication requests, any one of which may be - /// used to determine the user's control of the <see cref="IAuthenticationRequest.ClaimedIdentifier"/>. - /// </returns> - protected internal override IEnumerable<IAuthenticationRequest> CreateRequests(Identifier identifier) { - ErrorUtilities.VerifyArgumentNotNull(identifier, "identifier"); - - // We delegate all our logic to another method, since invoking base. methods - // within an iterator method results in unverifiable code. - return this.CreateRequestsCore(base.CreateRequests(identifier)); - } - - /// <summary> - /// Checks for incoming OpenID authentication responses and fires appropriate events. - /// </summary> - /// <param name="e">The <see cref="T:System.EventArgs"/> object that contains the event data.</param> - protected override void OnLoad(EventArgs e) { - if (!this.Enabled) { - return; - } - - this.Page.RegisterRequiresPostBack(this); - base.OnLoad(e); - } - - /// <summary> - /// Called when the <see cref="Identifier"/> property is changed. - /// </summary> - protected override void OnIdentifierChanged() { - this.ViewState.Remove(TextViewStateKey); - base.OnIdentifierChanged(); - } - - /// <summary> - /// Sends server control content to a provided <see cref="T:System.Web.UI.HtmlTextWriter"/> object, which writes the content to be rendered on the client. - /// </summary> - /// <param name="writer">The <see cref="T:System.Web.UI.HtmlTextWriter"/> object that receives the server control content.</param> - protected override void Render(HtmlTextWriter writer) { - Contract.Assume(writer != null, "Missing contract."); - - if (this.ShowLogo) { - string logoUrl = Page.ClientScript.GetWebResourceUrl( - typeof(OpenIdTextBox), EmbeddedLogoResourceName); - writer.AddStyleAttribute( - HtmlTextWriterStyle.BackgroundImage, - string.Format(CultureInfo.InvariantCulture, "url({0})", HttpUtility.HtmlEncode(logoUrl))); - writer.AddStyleAttribute("background-repeat", "no-repeat"); - writer.AddStyleAttribute("background-position", "0 50%"); - writer.AddStyleAttribute(HtmlTextWriterStyle.PaddingLeft, "18px"); - } - - if (this.PresetBorder) { - writer.AddStyleAttribute(HtmlTextWriterStyle.BorderStyle, "solid"); - writer.AddStyleAttribute(HtmlTextWriterStyle.BorderWidth, "1px"); - writer.AddStyleAttribute(HtmlTextWriterStyle.BorderColor, "lightgray"); - } - - if (!string.IsNullOrEmpty(this.CssClass)) { - writer.AddAttribute(HtmlTextWriterAttribute.Class, this.CssClass); - } - - writer.AddAttribute(HtmlTextWriterAttribute.Id, this.ClientID); - writer.AddAttribute(HtmlTextWriterAttribute.Name, HttpUtility.HtmlEncode(this.Name)); - writer.AddAttribute(HtmlTextWriterAttribute.Type, "text"); - writer.AddAttribute(HtmlTextWriterAttribute.Size, this.Columns.ToString(CultureInfo.InvariantCulture)); - writer.AddAttribute(HtmlTextWriterAttribute.Value, HttpUtility.HtmlEncode(this.Text)); - writer.AddAttribute(HtmlTextWriterAttribute.Tabindex, this.TabIndex.ToString(CultureInfo.CurrentCulture)); - - writer.RenderBeginTag(HtmlTextWriterTag.Input); - writer.RenderEndTag(); - } - - /// <summary> - /// When implemented by a class, processes postback data for an ASP.NET server control. - /// </summary> - /// <param name="postDataKey">The key identifier for the control.</param> - /// <param name="postCollection">The collection of all incoming name values.</param> - /// <returns> - /// true if the server control's state changes as a result of the postback; otherwise, false. - /// </returns> - protected virtual bool LoadPostData(string postDataKey, NameValueCollection postCollection) { - Contract.Assume(postCollection != null, "Missing contract"); - - // If the control was temporarily hidden, it won't be in the Form data, - // and we'll just implicitly keep the last Text setting. - if (postCollection[this.Name] != null) { - this.Text = postCollection[this.Name]; - return true; - } - - return false; - } - - /// <summary> - /// When implemented by a class, signals the server control to notify the ASP.NET application that the state of the control has changed. - /// </summary> - [SuppressMessage("Microsoft.Design", "CA1030:UseEventsWhereAppropriate", Justification = "Preserve signature of interface we're implementing.")] - protected virtual void RaisePostDataChangedEvent() { - this.OnTextChanged(); - } - - /// <summary> - /// Called on a postback when the Text property has changed. - /// </summary> - protected virtual void OnTextChanged() { - EventHandler textChanged = this.TextChanged; - if (textChanged != null) { - textChanged(this, EventArgs.Empty); - } - } - - /// <summary> - /// Creates the authentication requests for a given user-supplied Identifier. - /// </summary> - /// <param name="requests">The authentication requests to prepare.</param> - /// <returns> - /// A sequence of authentication requests, any one of which may be - /// used to determine the user's control of the <see cref="IAuthenticationRequest.ClaimedIdentifier"/>. - /// </returns> - private IEnumerable<IAuthenticationRequest> CreateRequestsCore(IEnumerable<IAuthenticationRequest> requests) { - Contract.Requires(requests != null); - - foreach (var request in requests) { - if (this.EnableRequestProfile) { - this.AddProfileArgs(request); - } - - yield return request; - } - } - - /// <summary> - /// Adds extensions to a given authentication request to ask the Provider - /// for user profile data. - /// </summary> - /// <param name="request">The authentication request to add the extensions to.</param> - private void AddProfileArgs(IAuthenticationRequest request) { - Contract.Requires<ArgumentNullException>(request != null); - - var sreg = new ClaimsRequest() { - Nickname = this.RequestNickname, - Email = this.RequestEmail, - FullName = this.RequestFullName, - BirthDate = this.RequestBirthDate, - Gender = this.RequestGender, - PostalCode = this.RequestPostalCode, - Country = this.RequestCountry, - Language = this.RequestLanguage, - TimeZone = this.RequestTimeZone, - PolicyUrl = string.IsNullOrEmpty(this.PolicyUrl) ? - null : new Uri(this.RelyingParty.Channel.GetRequestFromContext().UrlBeforeRewriting, this.Page.ResolveUrl(this.PolicyUrl)), - }; - - // Only actually add the extension request if fields are actually being requested. - if (!sreg.Equals(EmptyClaimsRequest)) { - request.AddExtension(sreg); - } - } - } -} diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/PositiveAnonymousResponse.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/PositiveAnonymousResponse.cs deleted file mode 100644 index fc334b0..0000000 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/PositiveAnonymousResponse.cs +++ /dev/null @@ -1,347 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="PositiveAnonymousResponse.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OpenId.RelyingParty { - using System; - using System.Collections.Generic; - using System.Diagnostics.Contracts; - using System.Linq; - using System.Web; - using DotNetOpenAuth.Messaging; - using DotNetOpenAuth.OpenId.Messages; - - /// <summary> - /// Wraps an extension-only response from the OP in an <see cref="IAuthenticationResponse"/> instance - /// for public consumption by the host web site. - /// </summary> - internal class PositiveAnonymousResponse : IAuthenticationResponse { - /// <summary> - /// Backin field for the <see cref="Response"/> property. - /// </summary> - private readonly IndirectSignedResponse response; - - /// <summary> - /// Information about the OP endpoint that issued this assertion. - /// </summary> - private readonly IProviderEndpoint provider; - - /// <summary> - /// Initializes a new instance of the <see cref="PositiveAnonymousResponse"/> class. - /// </summary> - /// <param name="response">The response message.</param> - protected internal PositiveAnonymousResponse(IndirectSignedResponse response) { - Contract.Requires<ArgumentNullException>(response != null); - - this.response = response; - if (response.ProviderEndpoint != null && response.Version != null) { - this.provider = new ProviderEndpointDescription(response.ProviderEndpoint, response.Version); - } - - // Derived types of this are responsible to log an appropriate message for themselves. - if (Logger.OpenId.IsInfoEnabled && this.GetType() == typeof(PositiveAnonymousResponse)) { - Logger.OpenId.Info("Received anonymous (identity-less) positive assertion."); - } - - if (response.ProviderEndpoint != null) { - Reporting.RecordEventOccurrence(this, response.ProviderEndpoint.AbsoluteUri); - } - } - - #region IAuthenticationResponse Properties - - /// <summary> - /// Gets the Identifier that the end user claims to own. For use with user database storage and lookup. - /// May be null for some failed authentications (i.e. failed directed identity authentications). - /// </summary> - /// <remarks> - /// <para> - /// This is the secure identifier that should be used for database storage and lookup. - /// It is not always friendly (i.e. =Arnott becomes =!9B72.7DD1.50A9.5CCD), but it protects - /// user identities against spoofing and other attacks. - /// </para> - /// <para> - /// For user-friendly identifiers to display, use the - /// <see cref="FriendlyIdentifierForDisplay"/> property. - /// </para> - /// </remarks> - public virtual Identifier ClaimedIdentifier { - get { return null; } - } - - /// <summary> - /// Gets a user-friendly OpenID Identifier for display purposes ONLY. - /// </summary> - /// <value></value> - /// <remarks> - /// <para> - /// This <i>should</i> be put through <see cref="HttpUtility.HtmlEncode(string)"/> before - /// sending to a browser to secure against javascript injection attacks. - /// </para> - /// <para> - /// This property retains some aspects of the user-supplied identifier that get lost - /// in the <see cref="ClaimedIdentifier"/>. For example, XRIs used as user-supplied - /// identifiers (i.e. =Arnott) become unfriendly unique strings (i.e. =!9B72.7DD1.50A9.5CCD). - /// For display purposes, such as text on a web page that says "You're logged in as ...", - /// this property serves to provide the =Arnott string, or whatever else is the most friendly - /// string close to what the user originally typed in. - /// </para> - /// <para> - /// If the user-supplied identifier is a URI, this property will be the URI after all - /// redirects, and with the protocol and fragment trimmed off. - /// If the user-supplied identifier is an XRI, this property will be the original XRI. - /// If the user-supplied identifier is an OpenID Provider identifier (i.e. yahoo.com), - /// this property will be the Claimed Identifier, with the protocol stripped if it is a URI. - /// </para> - /// <para> - /// It is <b>very</b> important that this property <i>never</i> be used for database storage - /// or lookup to avoid identity spoofing and other security risks. For database storage - /// and lookup please use the <see cref="ClaimedIdentifier"/> property. - /// </para> - /// </remarks> - public virtual string FriendlyIdentifierForDisplay { - get { return null; } - } - - /// <summary> - /// Gets the detailed success or failure status of the authentication attempt. - /// </summary> - public virtual AuthenticationStatus Status { - get { return AuthenticationStatus.ExtensionsOnly; } - } - - /// <summary> - /// Gets information about the OpenId Provider, as advertised by the - /// OpenID discovery documents found at the <see cref="ClaimedIdentifier"/> - /// location. - /// </summary> - /// <value> - /// The Provider endpoint that issued the positive assertion; - /// or <c>null</c> if information about the Provider is unavailable. - /// </value> - public IProviderEndpoint Provider { - get { return this.provider; } - } - - /// <summary> - /// Gets the details regarding a failed authentication attempt, if available. - /// This will be set if and only if <see cref="Status"/> is <see cref="AuthenticationStatus.Failed"/>. - /// </summary> - /// <value></value> - public Exception Exception { - get { return null; } - } - - #endregion - - /// <summary> - /// Gets a value indicating whether trusted callback arguments are available. - /// </summary> - /// <remarks> - /// We use this internally to avoid logging a warning during a standard snapshot creation. - /// </remarks> - internal bool TrustedCallbackArgumentsAvailable { - get { return this.response.ReturnToParametersSignatureValidated; } - } - - /// <summary> - /// Gets the positive extension-only message the Relying Party received that this instance wraps. - /// </summary> - protected internal IndirectSignedResponse Response { - get { return this.response; } - } - - #region IAuthenticationResponse methods - - /// <summary> - /// Gets a callback argument's value that was previously added using - /// <see cref="IAuthenticationRequest.AddCallbackArguments(string, string)"/>. - /// </summary> - /// <param name="key">The name of the parameter whose value is sought.</param> - /// <returns> - /// The value of the argument, or null if the named parameter could not be found. - /// </returns> - /// <remarks> - /// Callback parameters are only available if they are complete and untampered with - /// since the original request message (as proven by a signature). - /// If the relying party is operating in stateless mode <c>null</c> is always - /// returned since the callback arguments could not be signed to protect against - /// tampering. - /// </remarks> - public string GetCallbackArgument(string key) { - if (this.response.ReturnToParametersSignatureValidated) { - return this.GetUntrustedCallbackArgument(key); - } else { - Logger.OpenId.WarnFormat(OpenIdStrings.CallbackArgumentsRequireSecretStore, typeof(IRelyingPartyAssociationStore).Name, typeof(OpenIdRelyingParty).Name); - return null; - } - } - - /// <summary> - /// Gets a callback argument's value that was previously added using - /// <see cref="IAuthenticationRequest.AddCallbackArguments(string, string)"/>. - /// </summary> - /// <param name="key">The name of the parameter whose value is sought.</param> - /// <returns> - /// The value of the argument, or null if the named parameter could not be found. - /// </returns> - /// <remarks> - /// Callback parameters are only available even if the RP is in stateless mode, - /// or the callback parameters are otherwise unverifiable as untampered with. - /// Therefore, use this method only when the callback argument is not to be - /// used to make a security-sensitive decision. - /// </remarks> - public string GetUntrustedCallbackArgument(string key) { - return this.response.GetReturnToArgument(key); - } - - /// <summary> - /// Gets all the callback arguments that were previously added using - /// <see cref="IAuthenticationRequest.AddCallbackArguments(string, string)"/> or as a natural part - /// of the return_to URL. - /// </summary> - /// <returns>A name-value dictionary. Never null.</returns> - /// <remarks> - /// Callback parameters are only available if they are complete and untampered with - /// since the original request message (as proven by a signature). - /// If the relying party is operating in stateless mode an empty dictionary is always - /// returned since the callback arguments could not be signed to protect against - /// tampering. - /// </remarks> - public IDictionary<string, string> GetCallbackArguments() { - if (this.response.ReturnToParametersSignatureValidated) { - return this.GetUntrustedCallbackArguments(); - } else { - Logger.OpenId.WarnFormat(OpenIdStrings.CallbackArgumentsRequireSecretStore, typeof(IRelyingPartyAssociationStore).Name, typeof(OpenIdRelyingParty).Name); - return EmptyDictionary<string, string>.Instance; - } - } - - /// <summary> - /// Gets all the callback arguments that were previously added using - /// <see cref="IAuthenticationRequest.AddCallbackArguments(string, string)"/> or as a natural part - /// of the return_to URL. - /// </summary> - /// <returns>A name-value dictionary. Never null.</returns> - /// <remarks> - /// Callback parameters are only available if they are complete and untampered with - /// since the original request message (as proven by a signature). - /// If the relying party is operating in stateless mode an empty dictionary is always - /// returned since the callback arguments could not be signed to protect against - /// tampering. - /// </remarks> - public IDictionary<string, string> GetUntrustedCallbackArguments() { - var args = new Dictionary<string, string>(); - - // Return all the return_to arguments, except for the OpenID-supporting ones. - // The only arguments that should be returned here are the ones that the host - // web site adds explicitly. - foreach (string key in this.response.GetReturnToParameterNames().Where(key => !OpenIdRelyingParty.IsOpenIdSupportingParameter(key))) { - args[key] = this.response.GetReturnToArgument(key); - } - - return args; - } - - /// <summary> - /// Tries to get an OpenID extension that may be present in the response. - /// </summary> - /// <typeparam name="T">The type of extension to look for in the response message.</typeparam> - /// <returns> - /// The extension, if it is found. Null otherwise. - /// </returns> - /// <remarks> - /// <para>Extensions are returned only if the Provider signed them. - /// Relying parties that do not care if the values were modified in - /// transit should use the <see cref="GetUntrustedExtension<T>"/> method - /// in order to allow the Provider to not sign the extension. </para> - /// <para>Unsigned extensions are completely unreliable and should be - /// used only to prefill user forms since the user or any other third - /// party may have tampered with the data carried by the extension.</para> - /// <para>Signed extensions are only reliable if the relying party - /// trusts the OpenID Provider that signed them. Signing does not mean - /// the relying party can trust the values -- it only means that the values - /// have not been tampered with since the Provider sent the message.</para> - /// </remarks> - public T GetExtension<T>() where T : IOpenIdMessageExtension { - return this.response.SignedExtensions.OfType<T>().FirstOrDefault(); - } - - /// <summary> - /// Tries to get an OpenID extension that may be present in the response. - /// </summary> - /// <param name="extensionType">Type of the extension to look for in the response.</param> - /// <returns> - /// The extension, if it is found. Null otherwise. - /// </returns> - /// <remarks> - /// <para>Extensions are returned only if the Provider signed them. - /// Relying parties that do not care if the values were modified in - /// transit should use the <see cref="GetUntrustedExtension"/> method - /// in order to allow the Provider to not sign the extension. </para> - /// <para>Unsigned extensions are completely unreliable and should be - /// used only to prefill user forms since the user or any other third - /// party may have tampered with the data carried by the extension.</para> - /// <para>Signed extensions are only reliable if the relying party - /// trusts the OpenID Provider that signed them. Signing does not mean - /// the relying party can trust the values -- it only means that the values - /// have not been tampered with since the Provider sent the message.</para> - /// </remarks> - public IOpenIdMessageExtension GetExtension(Type extensionType) { - return this.response.SignedExtensions.OfType<IOpenIdMessageExtension>().Where(ext => extensionType.IsInstanceOfType(ext)).FirstOrDefault(); - } - - /// <summary> - /// Tries to get an OpenID extension that may be present in the response, without - /// requiring it to be signed by the Provider. - /// </summary> - /// <typeparam name="T">The type of extension to look for in the response message.</typeparam> - /// <returns> - /// The extension, if it is found. Null otherwise. - /// </returns> - /// <remarks> - /// <para>Extensions are returned whether they are signed or not. - /// Use the <see cref="GetExtension<T>"/> method to retrieve - /// extension responses only if they are signed by the Provider to - /// protect against tampering. </para> - /// <para>Unsigned extensions are completely unreliable and should be - /// used only to prefill user forms since the user or any other third - /// party may have tampered with the data carried by the extension.</para> - /// <para>Signed extensions are only reliable if the relying party - /// trusts the OpenID Provider that signed them. Signing does not mean - /// the relying party can trust the values -- it only means that the values - /// have not been tampered with since the Provider sent the message.</para> - /// </remarks> - public T GetUntrustedExtension<T>() where T : IOpenIdMessageExtension { - return this.response.Extensions.OfType<T>().FirstOrDefault(); - } - - /// <summary> - /// Tries to get an OpenID extension that may be present in the response. - /// </summary> - /// <param name="extensionType">Type of the extension to look for in the response.</param> - /// <returns> - /// The extension, if it is found. Null otherwise. - /// </returns> - /// <remarks> - /// <para>Extensions are returned whether they are signed or not. - /// Use the <see cref="GetExtension"/> method to retrieve - /// extension responses only if they are signed by the Provider to - /// protect against tampering. </para> - /// <para>Unsigned extensions are completely unreliable and should be - /// used only to prefill user forms since the user or any other third - /// party may have tampered with the data carried by the extension.</para> - /// <para>Signed extensions are only reliable if the relying party - /// trusts the OpenID Provider that signed them. Signing does not mean - /// the relying party can trust the values -- it only means that the values - /// have not been tampered with since the Provider sent the message.</para> - /// </remarks> - public IOpenIdMessageExtension GetUntrustedExtension(Type extensionType) { - return this.response.Extensions.OfType<IOpenIdMessageExtension>().Where(ext => extensionType.IsInstanceOfType(ext)).FirstOrDefault(); - } - - #endregion - } -} diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/PositiveAuthenticationResponse.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/PositiveAuthenticationResponse.cs deleted file mode 100644 index 3e2298c..0000000 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/PositiveAuthenticationResponse.cs +++ /dev/null @@ -1,174 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="PositiveAuthenticationResponse.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OpenId.RelyingParty { - using System; - using System.Diagnostics; - using System.Diagnostics.Contracts; - using System.Linq; - using System.Web; - using DotNetOpenAuth.Messaging; - using DotNetOpenAuth.OpenId.Messages; - - /// <summary> - /// Wraps a positive assertion response in an <see cref="IAuthenticationResponse"/> instance - /// for public consumption by the host web site. - /// </summary> - [DebuggerDisplay("Status: {Status}, ClaimedIdentifier: {ClaimedIdentifier}")] - internal class PositiveAuthenticationResponse : PositiveAnonymousResponse { - /// <summary> - /// Initializes a new instance of the <see cref="PositiveAuthenticationResponse"/> class. - /// </summary> - /// <param name="response">The positive assertion response that was just received by the Relying Party.</param> - /// <param name="relyingParty">The relying party.</param> - internal PositiveAuthenticationResponse(PositiveAssertionResponse response, OpenIdRelyingParty relyingParty) - : base(response) { - Contract.Requires<ArgumentNullException>(relyingParty != null); - - this.Endpoint = IdentifierDiscoveryResult.CreateForClaimedIdentifier( - this.Response.ClaimedIdentifier, - this.Response.GetReturnToArgument(AuthenticationRequest.UserSuppliedIdentifierParameterName), - this.Response.LocalIdentifier, - new ProviderEndpointDescription(this.Response.ProviderEndpoint, this.Response.Version), - null, - null); - - this.VerifyDiscoveryMatchesAssertion(relyingParty); - - Logger.OpenId.InfoFormat("Received identity assertion for {0} via {1}.", this.Response.ClaimedIdentifier, this.Provider.Uri); - } - - #region IAuthenticationResponse Properties - - /// <summary> - /// Gets the Identifier that the end user claims to own. For use with user database storage and lookup. - /// May be null for some failed authentications (i.e. failed directed identity authentications). - /// </summary> - /// <value></value> - /// <remarks> - /// <para> - /// This is the secure identifier that should be used for database storage and lookup. - /// It is not always friendly (i.e. =Arnott becomes =!9B72.7DD1.50A9.5CCD), but it protects - /// user identities against spoofing and other attacks. - /// </para> - /// <para> - /// For user-friendly identifiers to display, use the - /// <see cref="FriendlyIdentifierForDisplay"/> property. - /// </para> - /// </remarks> - public override Identifier ClaimedIdentifier { - get { return this.Endpoint.ClaimedIdentifier; } - } - - /// <summary> - /// Gets a user-friendly OpenID Identifier for display purposes ONLY. - /// </summary> - /// <remarks> - /// <para> - /// This <i>should</i> be put through <see cref="HttpUtility.HtmlEncode(string)"/> before - /// sending to a browser to secure against javascript injection attacks. - /// </para> - /// <para> - /// This property retains some aspects of the user-supplied identifier that get lost - /// in the <see cref="ClaimedIdentifier"/>. For example, XRIs used as user-supplied - /// identifiers (i.e. =Arnott) become unfriendly unique strings (i.e. =!9B72.7DD1.50A9.5CCD). - /// For display purposes, such as text on a web page that says "You're logged in as ...", - /// this property serves to provide the =Arnott string, or whatever else is the most friendly - /// string close to what the user originally typed in. - /// </para> - /// <para> - /// If the user-supplied identifier is a URI, this property will be the URI after all - /// redirects, and with the protocol and fragment trimmed off. - /// If the user-supplied identifier is an XRI, this property will be the original XRI. - /// If the user-supplied identifier is an OpenID Provider identifier (i.e. yahoo.com), - /// this property will be the Claimed Identifier, with the protocol stripped if it is a URI. - /// </para> - /// <para> - /// It is <b>very</b> important that this property <i>never</i> be used for database storage - /// or lookup to avoid identity spoofing and other security risks. For database storage - /// and lookup please use the <see cref="ClaimedIdentifier"/> property. - /// </para> - /// </remarks> - public override string FriendlyIdentifierForDisplay { - get { return this.Endpoint.FriendlyIdentifierForDisplay; } - } - - /// <summary> - /// Gets the detailed success or failure status of the authentication attempt. - /// </summary> - public override AuthenticationStatus Status { - get { return AuthenticationStatus.Authenticated; } - } - - #endregion - - /// <summary> - /// Gets the OpenID service endpoint reconstructed from the assertion message. - /// </summary> - /// <remarks> - /// This information is straight from the Provider, and therefore must not - /// be trusted until verified as matching the discovery information for - /// the claimed identifier to avoid a Provider asserting an Identifier - /// for which it has no authority. - /// </remarks> - internal IdentifierDiscoveryResult Endpoint { get; private set; } - - /// <summary> - /// Gets the positive assertion response message. - /// </summary> - protected internal new PositiveAssertionResponse Response { - get { return (PositiveAssertionResponse)base.Response; } - } - - /// <summary> - /// Verifies that the positive assertion data matches the results of - /// discovery on the Claimed Identifier. - /// </summary> - /// <param name="relyingParty">The relying party.</param> - /// <exception cref="ProtocolException"> - /// Thrown when the Provider is asserting that a user controls an Identifier - /// when discovery on that Identifier contradicts what the Provider says. - /// This would be an indication of either a misconfigured Provider or - /// an attempt by someone to spoof another user's identity with a rogue Provider. - /// </exception> - private void VerifyDiscoveryMatchesAssertion(OpenIdRelyingParty relyingParty) { - Logger.OpenId.Debug("Verifying assertion matches identifier discovery results..."); - - // Ensure that we abide by the RP's rules regarding RequireSsl for this discovery step. - Identifier claimedId = this.Response.ClaimedIdentifier; - if (relyingParty.SecuritySettings.RequireSsl) { - if (!claimedId.TryRequireSsl(out claimedId)) { - Logger.OpenId.ErrorFormat("This site is configured to accept only SSL-protected OpenIDs, but {0} was asserted and must be rejected.", this.Response.ClaimedIdentifier); - ErrorUtilities.ThrowProtocol(OpenIdStrings.RequireSslNotSatisfiedByAssertedClaimedId, this.Response.ClaimedIdentifier); - } - } - - // Check whether this particular identifier presents a problem with HTTP discovery - // due to limitations in the .NET Uri class. - UriIdentifier claimedIdUri = claimedId as UriIdentifier; - if (claimedIdUri != null && claimedIdUri.ProblematicNormalization) { - ErrorUtilities.VerifyProtocol(relyingParty.SecuritySettings.AllowApproximateIdentifierDiscovery, OpenIdStrings.ClaimedIdentifierDefiesDotNetNormalization); - Logger.OpenId.WarnFormat("Positive assertion for claimed identifier {0} cannot be precisely verified under partial trust hosting due to .NET limitation. An approximate verification will be attempted.", claimedId); - } - - // While it LOOKS like we're performing discovery over HTTP again - // Yadis.IdentifierDiscoveryCachePolicy is set to HttpRequestCacheLevel.CacheIfAvailable - // which means that the .NET runtime is caching our discoveries for us. This turns out - // to be very fast and keeps our code clean and easily verifiable as correct and secure. - // CAUTION: if this discovery is ever made to be skipped based on previous discovery - // data that was saved to the return_to URL, be careful to verify that that information - // is signed by the RP before it's considered reliable. In 1.x stateless mode, this RP - // doesn't (and can't) sign its own return_to URL, so its cached discovery information - // is merely a hint that must be verified by performing discovery again here. - var discoveryResults = relyingParty.Discover(claimedId); - ErrorUtilities.VerifyProtocol( - discoveryResults.Contains(this.Endpoint), - OpenIdStrings.IssuedAssertionFailsIdentifierDiscovery, - this.Endpoint, - discoveryResults.ToStringDeferred(true)); - } - } -} diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/PositiveAuthenticationResponseSnapshot.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/PositiveAuthenticationResponseSnapshot.cs deleted file mode 100644 index 80b424a..0000000 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/PositiveAuthenticationResponseSnapshot.cs +++ /dev/null @@ -1,304 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="PositiveAuthenticationResponseSnapshot.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OpenId.RelyingParty { - using System; - using System.Collections.Generic; - using System.Diagnostics.Contracts; - using System.Text; - using System.Web; - using DotNetOpenAuth.Messaging; - using DotNetOpenAuth.OpenId.Messages; - - /// <summary> - /// A serializable snapshot of a verified authentication message. - /// </summary> - [Serializable] - internal class PositiveAuthenticationResponseSnapshot : IAuthenticationResponse { - /// <summary> - /// The callback arguments that came with the authentication response. - /// </summary> - private IDictionary<string, string> callbackArguments; - - /// <summary> - /// The untrusted callback arguments that came with the authentication response. - /// </summary> - private IDictionary<string, string> untrustedCallbackArguments; - - /// <summary> - /// Initializes a new instance of the <see cref="PositiveAuthenticationResponseSnapshot"/> class. - /// </summary> - /// <param name="copyFrom">The authentication response to copy from.</param> - internal PositiveAuthenticationResponseSnapshot(IAuthenticationResponse copyFrom) { - Contract.Requires<ArgumentNullException>(copyFrom != null); - - this.ClaimedIdentifier = copyFrom.ClaimedIdentifier; - this.FriendlyIdentifierForDisplay = copyFrom.FriendlyIdentifierForDisplay; - this.Status = copyFrom.Status; - this.Provider = copyFrom.Provider; - this.untrustedCallbackArguments = copyFrom.GetUntrustedCallbackArguments(); - - // Do this special check to avoid logging a warning for trying to clone a dictionary. - var anonResponse = copyFrom as PositiveAnonymousResponse; - if (anonResponse == null || anonResponse.TrustedCallbackArgumentsAvailable) { - this.callbackArguments = copyFrom.GetCallbackArguments(); - } else { - this.callbackArguments = EmptyDictionary<string, string>.Instance; - } - } - - #region IAuthenticationResponse Members - - /// <summary> - /// Gets the Identifier that the end user claims to own. For use with user database storage and lookup. - /// May be null for some failed authentications (i.e. failed directed identity authentications). - /// </summary> - /// <value></value> - /// <remarks> - /// <para> - /// This is the secure identifier that should be used for database storage and lookup. - /// It is not always friendly (i.e. =Arnott becomes =!9B72.7DD1.50A9.5CCD), but it protects - /// user identities against spoofing and other attacks. - /// </para> - /// <para> - /// For user-friendly identifiers to display, use the - /// <see cref="FriendlyIdentifierForDisplay"/> property. - /// </para> - /// </remarks> - public Identifier ClaimedIdentifier { get; private set; } - - /// <summary> - /// Gets a user-friendly OpenID Identifier for display purposes ONLY. - /// </summary> - /// <value></value> - /// <remarks> - /// <para> - /// This <i>should</i> be put through <see cref="HttpUtility.HtmlEncode(string)"/> before - /// sending to a browser to secure against javascript injection attacks. - /// </para> - /// <para> - /// This property retains some aspects of the user-supplied identifier that get lost - /// in the <see cref="ClaimedIdentifier"/>. For example, XRIs used as user-supplied - /// identifiers (i.e. =Arnott) become unfriendly unique strings (i.e. =!9B72.7DD1.50A9.5CCD). - /// For display purposes, such as text on a web page that says "You're logged in as ...", - /// this property serves to provide the =Arnott string, or whatever else is the most friendly - /// string close to what the user originally typed in. - /// </para> - /// <para> - /// If the user-supplied identifier is a URI, this property will be the URI after all - /// redirects, and with the protocol and fragment trimmed off. - /// If the user-supplied identifier is an XRI, this property will be the original XRI. - /// If the user-supplied identifier is an OpenID Provider identifier (i.e. yahoo.com), - /// this property will be the Claimed Identifier, with the protocol stripped if it is a URI. - /// </para> - /// <para> - /// It is <b>very</b> important that this property <i>never</i> be used for database storage - /// or lookup to avoid identity spoofing and other security risks. For database storage - /// and lookup please use the <see cref="ClaimedIdentifier"/> property. - /// </para> - /// </remarks> - public string FriendlyIdentifierForDisplay { get; private set; } - - /// <summary> - /// Gets the detailed success or failure status of the authentication attempt. - /// </summary> - /// <value></value> - public AuthenticationStatus Status { get; private set; } - - /// <summary> - /// Gets information about the OpenId Provider, as advertised by the - /// OpenID discovery documents found at the <see cref="ClaimedIdentifier"/> - /// location. - /// </summary> - /// <value> - /// The Provider endpoint that issued the positive assertion; - /// or <c>null</c> if information about the Provider is unavailable. - /// </value> - public IProviderEndpoint Provider { get; private set; } - - /// <summary> - /// Gets the details regarding a failed authentication attempt, if available. - /// This will be set if and only if <see cref="Status"/> is <see cref="AuthenticationStatus.Failed"/>. - /// </summary> - /// <value></value> - public Exception Exception { - get { throw new NotSupportedException(OpenIdStrings.NotSupportedByAuthenticationSnapshot); } - } - - /// <summary> - /// Tries to get an OpenID extension that may be present in the response. - /// </summary> - /// <typeparam name="T">The type of extension to look for in the response message.</typeparam> - /// <returns> - /// The extension, if it is found. Null otherwise. - /// </returns> - /// <remarks> - /// <para>Extensions are returned only if the Provider signed them. - /// Relying parties that do not care if the values were modified in - /// transit should use the <see cref="GetUntrustedExtension<T>"/> method - /// in order to allow the Provider to not sign the extension. </para> - /// <para>Unsigned extensions are completely unreliable and should be - /// used only to prefill user forms since the user or any other third - /// party may have tampered with the data carried by the extension.</para> - /// <para>Signed extensions are only reliable if the relying party - /// trusts the OpenID Provider that signed them. Signing does not mean - /// the relying party can trust the values -- it only means that the values - /// have not been tampered with since the Provider sent the message.</para> - /// </remarks> - public T GetExtension<T>() where T : IOpenIdMessageExtension { - throw new NotSupportedException(OpenIdStrings.NotSupportedByAuthenticationSnapshot); - } - - /// <summary> - /// Tries to get an OpenID extension that may be present in the response. - /// </summary> - /// <param name="extensionType">Type of the extension to look for in the response.</param> - /// <returns> - /// The extension, if it is found. Null otherwise. - /// </returns> - /// <remarks> - /// <para>Extensions are returned only if the Provider signed them. - /// Relying parties that do not care if the values were modified in - /// transit should use the <see cref="GetUntrustedExtension"/> method - /// in order to allow the Provider to not sign the extension. </para> - /// <para>Unsigned extensions are completely unreliable and should be - /// used only to prefill user forms since the user or any other third - /// party may have tampered with the data carried by the extension.</para> - /// <para>Signed extensions are only reliable if the relying party - /// trusts the OpenID Provider that signed them. Signing does not mean - /// the relying party can trust the values -- it only means that the values - /// have not been tampered with since the Provider sent the message.</para> - /// </remarks> - public IOpenIdMessageExtension GetExtension(Type extensionType) { - throw new NotSupportedException(OpenIdStrings.NotSupportedByAuthenticationSnapshot); - } - - /// <summary> - /// Tries to get an OpenID extension that may be present in the response, without - /// requiring it to be signed by the Provider. - /// </summary> - /// <typeparam name="T">The type of extension to look for in the response message.</typeparam> - /// <returns> - /// The extension, if it is found. Null otherwise. - /// </returns> - /// <remarks> - /// <para>Extensions are returned whether they are signed or not. - /// Use the <see cref="GetExtension<T>"/> method to retrieve - /// extension responses only if they are signed by the Provider to - /// protect against tampering. </para> - /// <para>Unsigned extensions are completely unreliable and should be - /// used only to prefill user forms since the user or any other third - /// party may have tampered with the data carried by the extension.</para> - /// <para>Signed extensions are only reliable if the relying party - /// trusts the OpenID Provider that signed them. Signing does not mean - /// the relying party can trust the values -- it only means that the values - /// have not been tampered with since the Provider sent the message.</para> - /// </remarks> - public T GetUntrustedExtension<T>() where T : IOpenIdMessageExtension { - throw new NotSupportedException(OpenIdStrings.NotSupportedByAuthenticationSnapshot); - } - - /// <summary> - /// Tries to get an OpenID extension that may be present in the response. - /// </summary> - /// <param name="extensionType">Type of the extension to look for in the response.</param> - /// <returns> - /// The extension, if it is found. Null otherwise. - /// </returns> - /// <remarks> - /// <para>Extensions are returned whether they are signed or not. - /// Use the <see cref="GetExtension"/> method to retrieve - /// extension responses only if they are signed by the Provider to - /// protect against tampering. </para> - /// <para>Unsigned extensions are completely unreliable and should be - /// used only to prefill user forms since the user or any other third - /// party may have tampered with the data carried by the extension.</para> - /// <para>Signed extensions are only reliable if the relying party - /// trusts the OpenID Provider that signed them. Signing does not mean - /// the relying party can trust the values -- it only means that the values - /// have not been tampered with since the Provider sent the message.</para> - /// </remarks> - public IOpenIdMessageExtension GetUntrustedExtension(Type extensionType) { - throw new NotSupportedException(OpenIdStrings.NotSupportedByAuthenticationSnapshot); - } - - /// <summary> - /// Gets all the callback arguments that were previously added using - /// <see cref="IAuthenticationRequest.AddCallbackArguments(string, string)"/> or as a natural part - /// of the return_to URL. - /// </summary> - /// <returns>A name-value dictionary. Never null.</returns> - /// <remarks> - /// <para>This MAY return any argument on the querystring that came with the authentication response, - /// which may include parameters not explicitly added using - /// <see cref="IAuthenticationRequest.AddCallbackArguments(string, string)"/>.</para> - /// <para>Note that these values are NOT protected against tampering in transit.</para> - /// </remarks> - public IDictionary<string, string> GetCallbackArguments() { - // Return a copy so that the caller cannot change the contents. - return new Dictionary<string, string>(this.callbackArguments); - } - - /// <summary> - /// Gets all the callback arguments that were previously added using - /// <see cref="IAuthenticationRequest.AddCallbackArguments(string, string)"/> or as a natural part - /// of the return_to URL. - /// </summary> - /// <returns>A name-value dictionary. Never null.</returns> - /// <remarks> - /// Callback parameters are only available even if the RP is in stateless mode, - /// or the callback parameters are otherwise unverifiable as untampered with. - /// Therefore, use this method only when the callback argument is not to be - /// used to make a security-sensitive decision. - /// </remarks> - public IDictionary<string, string> GetUntrustedCallbackArguments() { - // Return a copy so that the caller cannot change the contents. - return new Dictionary<string, string>(this.untrustedCallbackArguments); - } - - /// <summary> - /// Gets a callback argument's value that was previously added using - /// <see cref="IAuthenticationRequest.AddCallbackArguments(string, string)"/>. - /// </summary> - /// <param name="key">The name of the parameter whose value is sought.</param> - /// <returns> - /// The value of the argument, or null if the named parameter could not be found. - /// </returns> - /// <remarks> - /// <para>This may return any argument on the querystring that came with the authentication response, - /// which may include parameters not explicitly added using - /// <see cref="IAuthenticationRequest.AddCallbackArguments(string, string)"/>.</para> - /// <para>Note that these values are NOT protected against tampering in transit.</para> - /// </remarks> - public string GetCallbackArgument(string key) { - string value; - this.callbackArguments.TryGetValue(key, out value); - return value; - } - - /// <summary> - /// Gets a callback argument's value that was previously added using - /// <see cref="IAuthenticationRequest.AddCallbackArguments(string, string)"/>. - /// </summary> - /// <param name="key">The name of the parameter whose value is sought.</param> - /// <returns> - /// The value of the argument, or null if the named parameter could not be found. - /// </returns> - /// <remarks> - /// Callback parameters are only available even if the RP is in stateless mode, - /// or the callback parameters are otherwise unverifiable as untampered with. - /// Therefore, use this method only when the callback argument is not to be - /// used to make a security-sensitive decision. - /// </remarks> - public string GetUntrustedCallbackArgument(string key) { - string value; - this.untrustedCallbackArguments.TryGetValue(key, out value); - return value; - } - - #endregion - } -} diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/SelectorButtonContract.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/SelectorButtonContract.cs deleted file mode 100644 index c70218a..0000000 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/SelectorButtonContract.cs +++ /dev/null @@ -1,46 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="SelectorButtonContract.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OpenId.RelyingParty { - using System; - using System.Diagnostics.Contracts; - using System.Web.UI; - - /// <summary> - /// The contract class for the <see cref="SelectorButton"/> class. - /// </summary> - [ContractClassFor(typeof(SelectorButton))] - internal abstract class SelectorButtonContract : SelectorButton { - /// <summary> - /// Ensures that this button has been initialized to a valid state. - /// </summary> - /// <remarks> - /// This is "internal" -- NOT "protected internal" deliberately. It makes it impossible - /// to derive from this class outside the assembly, which suits our purposes since the - /// <see cref="OpenIdSelector"/> control is not designed for an extensible set of button types. - /// </remarks> - internal override void EnsureValid() { - } - - /// <summary> - /// Renders the leading attributes for the LI tag. - /// </summary> - /// <param name="writer">The writer.</param> - protected internal override void RenderLeadingAttributes(HtmlTextWriter writer) { - Contract.Requires<ArgumentNullException>(writer != null); - } - - /// <summary> - /// Renders the content of the button. - /// </summary> - /// <param name="writer">The writer.</param> - /// <param name="selector">The containing selector control.</param> - protected internal override void RenderButtonContent(HtmlTextWriter writer, OpenIdSelector selector) { - Contract.Requires<ArgumentNullException>(writer != null); - Contract.Requires<ArgumentNullException>(selector != null); - } - } -} diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/SelectorInfoCardButton.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/SelectorInfoCardButton.cs deleted file mode 100644 index c5dda1c..0000000 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/SelectorInfoCardButton.cs +++ /dev/null @@ -1,102 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="SelectorInfoCardButton.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OpenId.RelyingParty { - using System; - using System.Collections.ObjectModel; - using System.ComponentModel; - using System.Diagnostics.Contracts; - using System.Web.UI; - using DotNetOpenAuth.InfoCard; - - /// <summary> - /// A button that appears in the <see cref="OpenIdSelector"/> control that - /// activates the Information Card selector on the browser, if one is available. - /// </summary> - public class SelectorInfoCardButton : SelectorButton, IDisposable { - /// <summary> - /// The backing field for the <see cref="InfoCardSelector"/> property. - /// </summary> - private InfoCardSelector infoCardSelector; - - /// <summary> - /// Initializes a new instance of the <see cref="SelectorInfoCardButton"/> class. - /// </summary> - public SelectorInfoCardButton() { - Reporting.RecordFeatureUse(this); - } - - /// <summary> - /// Gets or sets the InfoCard selector which may be displayed alongside the OP buttons. - /// </summary> - [PersistenceMode(PersistenceMode.InnerProperty)] - public InfoCardSelector InfoCardSelector { - get { - if (this.infoCardSelector == null) { - this.infoCardSelector = new InfoCardSelector(); - } - - return this.infoCardSelector; - } - - set { - Contract.Requires<ArgumentNullException>(value != null); - if (this.infoCardSelector != null) { - Logger.Library.WarnFormat("{0}.InfoCardSelector property is being set multiple times.", GetType().Name); - } - - this.infoCardSelector = value; - } - } - - #region IDisposable Members - - /// <summary> - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. - /// </summary> - public void Dispose() { - this.Dispose(true); - GC.SuppressFinalize(this); - } - - #endregion - - /// <summary> - /// Ensures that this button has been initialized to a valid state. - /// </summary> - internal override void EnsureValid() { - } - - /// <summary> - /// Renders the leading attributes for the LI tag. - /// </summary> - /// <param name="writer">The writer.</param> - protected internal override void RenderLeadingAttributes(HtmlTextWriter writer) { - writer.AddAttribute(HtmlTextWriterAttribute.Class, "infocard"); - } - - /// <summary> - /// Renders the content of the button. - /// </summary> - /// <param name="writer">The writer.</param> - /// <param name="selector">The containing selector control.</param> - protected internal override void RenderButtonContent(HtmlTextWriter writer, OpenIdSelector selector) { - this.InfoCardSelector.RenderControl(writer); - } - - /// <summary> - /// Releases unmanaged and - optionally - managed resources - /// </summary> - /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param> - protected virtual void Dispose(bool disposing) { - if (disposing) { - if (this.infoCardSelector != null) { - this.infoCardSelector.Dispose(); - } - } - } - } -} diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/SelectorOpenIdButton.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/SelectorOpenIdButton.cs deleted file mode 100644 index ac4dcbf..0000000 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/SelectorOpenIdButton.cs +++ /dev/null @@ -1,82 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="SelectorOpenIdButton.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OpenId.RelyingParty { - using System; - using System.ComponentModel; - using System.Diagnostics.Contracts; - using System.Drawing.Design; - using System.Web.UI; - using DotNetOpenAuth.Messaging; - - /// <summary> - /// A button that appears in the <see cref="OpenIdSelector"/> control that - /// allows the user to type in a user-supplied identifier. - /// </summary> - public class SelectorOpenIdButton : SelectorButton { - /// <summary> - /// Initializes a new instance of the <see cref="SelectorOpenIdButton"/> class. - /// </summary> - public SelectorOpenIdButton() { - Reporting.RecordFeatureUse(this); - } - - /// <summary> - /// Initializes a new instance of the <see cref="SelectorOpenIdButton"/> class. - /// </summary> - /// <param name="imageUrl">The image to display on the button.</param> - public SelectorOpenIdButton(string imageUrl) - : this() { - Contract.Requires<ArgumentException>(!string.IsNullOrEmpty(imageUrl)); - - this.Image = imageUrl; - } - - /// <summary> - /// Gets or sets the path to the image to display on the button's surface. - /// </summary> - /// <value>The virtual path to the image.</value> - [Editor("System.Web.UI.Design.ImageUrlEditor, System.Design, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", typeof(UITypeEditor))] - [UrlProperty] - public string Image { get; set; } - - /// <summary> - /// Ensures that this button has been initialized to a valid state. - /// </summary> - internal override void EnsureValid() { - Contract.Ensures(!string.IsNullOrEmpty(this.Image)); - - // Every button must have an image. - ErrorUtilities.VerifyOperation(!string.IsNullOrEmpty(this.Image), OpenIdStrings.PropertyNotSet, "SelectorButton.Image"); - } - - /// <summary> - /// Renders the leading attributes for the LI tag. - /// </summary> - /// <param name="writer">The writer.</param> - protected internal override void RenderLeadingAttributes(HtmlTextWriter writer) { - writer.AddAttribute(HtmlTextWriterAttribute.Id, "OpenIDButton"); - writer.AddAttribute(HtmlTextWriterAttribute.Class, "OpenIDButton"); - } - - /// <summary> - /// Renders the content of the button. - /// </summary> - /// <param name="writer">The writer.</param> - /// <param name="selector">The containing selector control.</param> - protected internal override void RenderButtonContent(HtmlTextWriter writer, OpenIdSelector selector) { - writer.AddAttribute(HtmlTextWriterAttribute.Src, selector.Page.ResolveUrl(this.Image)); - writer.RenderBeginTag(HtmlTextWriterTag.Img); - writer.RenderEndTag(); - - writer.AddAttribute(HtmlTextWriterAttribute.Src, selector.Page.ClientScript.GetWebResourceUrl(typeof(OpenIdAjaxTextBox), OpenIdAjaxTextBox.EmbeddedLoginSuccessResourceName)); - writer.AddAttribute(HtmlTextWriterAttribute.Class, "loginSuccess"); - writer.AddAttribute(HtmlTextWriterAttribute.Title, selector.AuthenticatedAsToolTip); - writer.RenderBeginTag(HtmlTextWriterTag.Img); - writer.RenderEndTag(); - } - } -} diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/SelectorProviderButton.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/SelectorProviderButton.cs deleted file mode 100644 index 2195e73..0000000 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/SelectorProviderButton.cs +++ /dev/null @@ -1,113 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="SelectorProviderButton.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OpenId.RelyingParty { - using System; - using System.ComponentModel; - using System.Diagnostics.Contracts; - using System.Drawing.Design; - using System.Web.UI; - using DotNetOpenAuth.ComponentModel; - using DotNetOpenAuth.Messaging; - - /// <summary> - /// A button that appears in the <see cref="OpenIdSelector"/> control that - /// provides one-click access to a popular OpenID Provider. - /// </summary> - public class SelectorProviderButton : SelectorButton { - /// <summary> - /// Initializes a new instance of the <see cref="SelectorProviderButton"/> class. - /// </summary> - public SelectorProviderButton() { - Reporting.RecordFeatureUse(this); - } - - /// <summary> - /// Initializes a new instance of the <see cref="SelectorProviderButton"/> class. - /// </summary> - /// <param name="providerIdentifier">The OP Identifier.</param> - /// <param name="imageUrl">The image to display on the button.</param> - public SelectorProviderButton(Identifier providerIdentifier, string imageUrl) - : this() { - Contract.Requires<ArgumentNullException>(providerIdentifier != null); - Contract.Requires<ArgumentException>(!string.IsNullOrEmpty(imageUrl)); - - this.OPIdentifier = providerIdentifier; - this.Image = imageUrl; - } - - /// <summary> - /// Gets or sets the path to the image to display on the button's surface. - /// </summary> - /// <value>The virtual path to the image.</value> - [Editor("System.Web.UI.Design.ImageUrlEditor, System.Design, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", typeof(UITypeEditor))] - [UrlProperty] - public string Image { get; set; } - - /// <summary> - /// Gets or sets the OP Identifier represented by the button. - /// </summary> - /// <value> - /// The OP identifier, which may be provided in the easiest "user-supplied identifier" form, - /// but for security should be provided with a leading https:// if possible. - /// For example: "yahoo.com" or "https://me.yahoo.com/". - /// </value> - [TypeConverter(typeof(IdentifierConverter))] - public Identifier OPIdentifier { get; set; } - - /// <summary> - /// Gets or sets a value indicating whether this Provider doesn't handle - /// checkid_immediate messages correctly and background authentication - /// should not be attempted. - /// </summary> - public bool SkipBackgroundAuthentication { get; set; } - - /// <summary> - /// Ensures that this button has been initialized to a valid state. - /// </summary> - internal override void EnsureValid() { - Contract.Ensures(!string.IsNullOrEmpty(this.Image)); - Contract.Ensures(this.OPIdentifier != null); - - // Every button must have an image. - ErrorUtilities.VerifyOperation(!string.IsNullOrEmpty(this.Image), OpenIdStrings.PropertyNotSet, "SelectorButton.Image"); - - // Every button must have exactly one purpose. - ErrorUtilities.VerifyOperation(this.OPIdentifier != null, OpenIdStrings.PropertyNotSet, "SelectorButton.OPIdentifier"); - } - - /// <summary> - /// Renders the leading attributes for the LI tag. - /// </summary> - /// <param name="writer">The writer.</param> - protected internal override void RenderLeadingAttributes(HtmlTextWriter writer) { - writer.AddAttribute(HtmlTextWriterAttribute.Id, this.OPIdentifier); - - string style = "OPButton"; - if (this.SkipBackgroundAuthentication) { - style += " NoAsyncAuth"; - } - writer.AddAttribute(HtmlTextWriterAttribute.Class, style); - } - - /// <summary> - /// Renders the content of the button. - /// </summary> - /// <param name="writer">The writer.</param> - /// <param name="selector">The containing selector control.</param> - protected internal override void RenderButtonContent(HtmlTextWriter writer, OpenIdSelector selector) { - writer.AddAttribute(HtmlTextWriterAttribute.Src, selector.Page.ResolveUrl(this.Image)); - writer.RenderBeginTag(HtmlTextWriterTag.Img); - writer.RenderEndTag(); - - writer.AddAttribute(HtmlTextWriterAttribute.Src, selector.Page.ClientScript.GetWebResourceUrl(typeof(OpenIdAjaxTextBox), OpenIdAjaxTextBox.EmbeddedLoginSuccessResourceName)); - writer.AddAttribute(HtmlTextWriterAttribute.Class, "loginSuccess"); - writer.AddAttribute(HtmlTextWriterAttribute.Title, selector.AuthenticatedAsToolTip); - writer.RenderBeginTag(HtmlTextWriterTag.Img); - writer.RenderEndTag(); - } - } -} diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/StandardRelyingPartyApplicationStore.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/StandardRelyingPartyApplicationStore.cs deleted file mode 100644 index f17b260..0000000 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/StandardRelyingPartyApplicationStore.cs +++ /dev/null @@ -1,110 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="StandardRelyingPartyApplicationStore.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OpenId.RelyingParty { - using System; - using System.Collections.Generic; - using DotNetOpenAuth.Configuration; - using DotNetOpenAuth.Messaging.Bindings; - using DotNetOpenAuth.OpenId.ChannelElements; - - /// <summary> - /// An in-memory store for Relying Parties, suitable for single server, single process - /// ASP.NET web sites. - /// </summary> - public class StandardRelyingPartyApplicationStore : IOpenIdApplicationStore { - /// <summary> - /// The nonce store to use. - /// </summary> - private readonly INonceStore nonceStore; - - /// <summary> - /// The association store to use. - /// </summary> - private readonly ICryptoKeyStore keyStore; - - /// <summary> - /// Initializes a new instance of the <see cref="StandardRelyingPartyApplicationStore"/> class. - /// </summary> - public StandardRelyingPartyApplicationStore() { - this.nonceStore = new NonceMemoryStore(DotNetOpenAuthSection.Configuration.OpenId.MaxAuthenticationTime); - this.keyStore = new MemoryCryptoKeyStore(); - } - - #region ICryptoKeyStore Members - - /// <summary> - /// Gets the key in a given bucket and handle. - /// </summary> - /// <param name="bucket">The bucket name. Case sensitive.</param> - /// <param name="handle">The key handle. Case sensitive.</param> - /// <returns> - /// The cryptographic key, or <c>null</c> if no matching key was found. - /// </returns> - public CryptoKey GetKey(string bucket, string handle) { - return this.keyStore.GetKey(bucket, handle); - } - - /// <summary> - /// Gets a sequence of existing keys within a given bucket. - /// </summary> - /// <param name="bucket">The bucket name. Case sensitive.</param> - /// <returns> - /// A sequence of handles and keys, ordered by descending <see cref="CryptoKey.ExpiresUtc"/>. - /// </returns> - public IEnumerable<KeyValuePair<string, CryptoKey>> GetKeys(string bucket) { - return this.keyStore.GetKeys(bucket); - } - - /// <summary> - /// Stores a cryptographic key. - /// </summary> - /// <param name="bucket">The name of the bucket to store the key in. Case sensitive.</param> - /// <param name="handle">The handle to the key, unique within the bucket. Case sensitive.</param> - /// <param name="key">The key to store.</param> - /// <exception cref="CryptoKeyCollisionException">Thrown in the event of a conflict with an existing key in the same bucket and with the same handle.</exception> - public void StoreKey(string bucket, string handle, CryptoKey key) { - this.keyStore.StoreKey(bucket, handle, key); - } - - /// <summary> - /// Removes the key. - /// </summary> - /// <param name="bucket">The bucket name. Case sensitive.</param> - /// <param name="handle">The key handle. Case sensitive.</param> - public void RemoveKey(string bucket, string handle) { - this.keyStore.RemoveKey(bucket, handle); - } - - #endregion - - #region INonceStore Members - - /// <summary> - /// Stores a given nonce and timestamp. - /// </summary> - /// <param name="context">The context, or namespace, within which the <paramref name="nonce"/> must be unique.</param> - /// <param name="nonce">A series of random characters.</param> - /// <param name="timestampUtc">The timestamp that together with the nonce string make it unique. - /// The timestamp may also be used by the data store to clear out old nonces.</param> - /// <returns> - /// True if the nonce+timestamp (combination) was not previously in the database. - /// False if the nonce was stored previously with the same timestamp. - /// </returns> - /// <remarks> - /// The nonce must be stored for no less than the maximum time window a message may - /// be processed within before being discarded as an expired message. - /// If the binding element is applicable to your channel, this expiration window - /// is retrieved or set using the - /// <see cref="StandardExpirationBindingElement.MaximumMessageAge"/> property. - /// </remarks> - public bool StoreNonce(string context, string nonce, DateTime timestampUtc) { - return this.nonceStore.StoreNonce(context, nonce, timestampUtc); - } - - #endregion - } -} diff --git a/src/DotNetOpenAuth/OpenId/RelyingPartyDescription.cs b/src/DotNetOpenAuth/OpenId/RelyingPartyDescription.cs deleted file mode 100644 index 7926e8f..0000000 --- a/src/DotNetOpenAuth/OpenId/RelyingPartyDescription.cs +++ /dev/null @@ -1,63 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="RelyingPartyDescription.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OpenId { - using System; - using System.Collections.Generic; - using System.Diagnostics.CodeAnalysis; - using System.Diagnostics.Contracts; - using System.Linq; - using System.Text; - using DotNetOpenAuth.Messaging; - - /// <summary> - /// A description of some OpenID Relying Party endpoint. - /// </summary> - /// <remarks> - /// This is an immutable type. - /// </remarks> - internal class RelyingPartyEndpointDescription { - /// <summary> - /// Initializes a new instance of the <see cref="RelyingPartyEndpointDescription"/> class. - /// </summary> - /// <param name="returnTo">The return to.</param> - /// <param name="supportedServiceTypeUris"> - /// The Type URIs of supported services advertised on a relying party's XRDS document. - /// </param> - internal RelyingPartyEndpointDescription(Uri returnTo, string[] supportedServiceTypeUris) { - Contract.Requires<ArgumentNullException>(returnTo != null); - Contract.Requires<ArgumentNullException>(supportedServiceTypeUris != null); - - this.ReturnToEndpoint = returnTo; - this.Protocol = GetProtocolFromServices(supportedServiceTypeUris); - } - - /// <summary> - /// Gets the URL to the login page on the discovered relying party web site. - /// </summary> - public Uri ReturnToEndpoint { get; private set; } - - /// <summary> - /// Gets the OpenId protocol that the discovered relying party supports. - /// </summary> - public Protocol Protocol { get; private set; } - - /// <summary> - /// Derives the highest OpenID protocol that this library and the OpenID Provider have - /// in common. - /// </summary> - /// <param name="supportedServiceTypeUris">The supported service type URIs.</param> - /// <returns>The best OpenID protocol version to use when communicating with this Provider.</returns> - [SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "OpenID", Justification = "Spelling correct")] - private static Protocol GetProtocolFromServices(string[] supportedServiceTypeUris) { - Protocol protocol = Protocol.FindBestVersion(p => p.RPReturnToTypeURI, supportedServiceTypeUris); - if (protocol == null) { - throw new InvalidOperationException("Unable to determine the version of OpenID the Relying Party supports."); - } - return protocol; - } - } -} diff --git a/src/DotNetOpenAuth/OpenId/SecuritySettings.cs b/src/DotNetOpenAuth/OpenId/SecuritySettings.cs deleted file mode 100644 index 26f6d2a..0000000 --- a/src/DotNetOpenAuth/OpenId/SecuritySettings.cs +++ /dev/null @@ -1,95 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="SecuritySettings.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OpenId { - using System; - using System.Collections.Generic; - using System.Collections.Specialized; - using System.Diagnostics.Contracts; - using DotNetOpenAuth.Messaging; - - /// <summary> - /// Security settings that may be applicable to both relying parties and providers. - /// </summary> - [Serializable] - public abstract class SecuritySettings { - /// <summary> - /// Gets the default minimum hash bit length. - /// </summary> - internal const int MinimumHashBitLengthDefault = 160; - - /// <summary> - /// Gets the maximum hash bit length default for relying parties. - /// </summary> - internal const int MaximumHashBitLengthRPDefault = 256; - - /// <summary> - /// Gets the maximum hash bit length default for providers. - /// </summary> - internal const int MaximumHashBitLengthOPDefault = 512; - - /// <summary> - /// Initializes a new instance of the <see cref="SecuritySettings"/> class. - /// </summary> - /// <param name="isProvider">A value indicating whether this class is being instantiated for a Provider.</param> - protected SecuritySettings(bool isProvider) { - this.MaximumHashBitLength = isProvider ? MaximumHashBitLengthOPDefault : MaximumHashBitLengthRPDefault; - this.MinimumHashBitLength = MinimumHashBitLengthDefault; - } - - /// <summary> - /// Gets or sets the minimum hash length (in bits) allowed to be used in an <see cref="Association"/> - /// with the remote party. The default is 160. - /// </summary> - /// <remarks> - /// SHA-1 (160 bits) has been broken. The minimum secure hash length is now 256 bits. - /// The default is still a 160 bit minimum to allow interop with common remote parties, - /// such as Yahoo! that only supports 160 bits. - /// For sites that require high security such as to store bank account information and - /// health records, 256 is the recommended value. - /// </remarks> - public int MinimumHashBitLength { get; set; } - - /// <summary> - /// Gets or sets the maximum hash length (in bits) allowed to be used in an <see cref="Association"/> - /// with the remote party. The default is 256 for relying parties and 512 for providers. - /// </summary> - /// <remarks> - /// The longer the bit length, the more secure the identities of your visitors are. - /// Setting a value higher than 256 on a relying party site may reduce performance - /// as many association requests will be denied, causing secondary requests or even - /// authentication failures. - /// Setting a value higher than 256 on a provider increases security where possible - /// without these side-effects. - /// </remarks> - public int MaximumHashBitLength { get; set; } - - /// <summary> - /// Determines whether a named association fits the security requirements. - /// </summary> - /// <param name="protocol">The protocol carrying the association.</param> - /// <param name="associationType">The value of the openid.assoc_type parameter.</param> - /// <returns> - /// <c>true</c> if the association is permitted given the security requirements; otherwise, <c>false</c>. - /// </returns> - internal bool IsAssociationInPermittedRange(Protocol protocol, string associationType) { - int lengthInBits = HmacShaAssociation.GetSecretLength(protocol, associationType) * 8; - return lengthInBits >= this.MinimumHashBitLength && lengthInBits <= this.MaximumHashBitLength; - } - - /// <summary> - /// Determines whether a given association fits the security requirements. - /// </summary> - /// <param name="association">The association to check.</param> - /// <returns> - /// <c>true</c> if the association is permitted given the security requirements; otherwise, <c>false</c>. - /// </returns> - internal bool IsAssociationInPermittedRange(Association association) { - Contract.Requires<ArgumentNullException>(association != null); - return association.HashBitLength >= this.MinimumHashBitLength && association.HashBitLength <= this.MaximumHashBitLength; - } - } -} diff --git a/src/DotNetOpenAuth/OpenId/UriIdentifier.cs b/src/DotNetOpenAuth/OpenId/UriIdentifier.cs deleted file mode 100644 index 145a394..0000000 --- a/src/DotNetOpenAuth/OpenId/UriIdentifier.cs +++ /dev/null @@ -1,727 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="UriIdentifier.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OpenId { - using System; - using System.Collections.Generic; - using System.Diagnostics.CodeAnalysis; - using System.Diagnostics.Contracts; - using System.Linq; - using System.Reflection; - using System.Security; - using System.Text; - using System.Text.RegularExpressions; - using System.Web.UI.HtmlControls; - using System.Xml; - using DotNetOpenAuth.Messaging; - using DotNetOpenAuth.OpenId.RelyingParty; - using DotNetOpenAuth.Xrds; - using DotNetOpenAuth.Yadis; - - /// <summary> - /// A URI style of OpenID Identifier. - /// </summary> - [Serializable] - [Pure] - public sealed class UriIdentifier : Identifier { - /// <summary> - /// The allowed protocol schemes in a URI Identifier. - /// </summary> - private static readonly string[] allowedSchemes = { "http", "https" }; - - /// <summary> - /// The special scheme to use for HTTP URLs that should not have their paths compressed. - /// </summary> - private static NonPathCompressingUriParser roundTrippingHttpParser = new NonPathCompressingUriParser(Uri.UriSchemeHttp); - - /// <summary> - /// The special scheme to use for HTTPS URLs that should not have their paths compressed. - /// </summary> - private static NonPathCompressingUriParser roundTrippingHttpsParser = new NonPathCompressingUriParser(Uri.UriSchemeHttps); - - /// <summary> - /// The special scheme to use for HTTP URLs that should not have their paths compressed. - /// </summary> - private static NonPathCompressingUriParser publishableHttpParser = new NonPathCompressingUriParser(Uri.UriSchemeHttp); - - /// <summary> - /// The special scheme to use for HTTPS URLs that should not have their paths compressed. - /// </summary> - private static NonPathCompressingUriParser publishableHttpsParser = new NonPathCompressingUriParser(Uri.UriSchemeHttps); - - /// <summary> - /// A value indicating whether scheme substitution is being used to workaround - /// .NET path compression that invalidates some OpenIDs that have trailing periods - /// in one of their path segments. - /// </summary> - private static bool schemeSubstitution; - - /// <summary> - /// Initializes static members of the <see cref="UriIdentifier"/> class. - /// </summary> - /// <remarks> - /// This method attempts to workaround the .NET Uri class parsing bug described here: - /// https://connect.microsoft.com/VisualStudio/feedback/details/386695/system-uri-incorrectly-strips-trailing-dots?wa=wsignin1.0#tabs - /// since some identifiers (like some of the pseudonymous identifiers from Yahoo) include path segments - /// that end with periods, which the Uri class will typically trim off. - /// </remarks> - [SuppressMessage("Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline", Justification = "Some things just can't be done in a field initializer.")] - static UriIdentifier() { - // Our first attempt to handle trailing periods in path segments is to leverage - // full trust if it's available to rewrite the rules. - // In fact this is the ONLY way in .NET 3.5 (and arguably in .NET 4.0) to send - // outbound HTTP requests with trailing periods, so it's the only way to perform - // discovery on such an identifier. - try { - UriParser.Register(roundTrippingHttpParser, "dnoarthttp", 80); - UriParser.Register(roundTrippingHttpsParser, "dnoarthttps", 443); - UriParser.Register(publishableHttpParser, "dnoahttp", 80); - UriParser.Register(publishableHttpsParser, "dnoahttps", 443); - roundTrippingHttpParser.Initialize(false); - roundTrippingHttpsParser.Initialize(false); - publishableHttpParser.Initialize(true); - publishableHttpsParser.Initialize(true); - schemeSubstitution = true; - Logger.OpenId.Debug(".NET Uri class path compression overridden."); - Reporting.RecordFeatureUse("FullTrust"); - } catch (SecurityException) { - // We must be running in partial trust. Nothing more we can do. - Logger.OpenId.Warn("Unable to coerce .NET to stop compressing URI paths due to partial trust limitations. Some URL identifiers may be unable to complete login."); - Reporting.RecordFeatureUse("PartialTrust"); - } - } - - /// <summary> - /// Initializes a new instance of the <see cref="UriIdentifier"/> class. - /// </summary> - /// <param name="uri">The value this identifier will represent.</param> - internal UriIdentifier(string uri) - : this(uri, false) { - Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(uri)); - } - - /// <summary> - /// Initializes a new instance of the <see cref="UriIdentifier"/> class. - /// </summary> - /// <param name="uri">The value this identifier will represent.</param> - /// <param name="requireSslDiscovery">if set to <c>true</c> [require SSL discovery].</param> - internal UriIdentifier(string uri, bool requireSslDiscovery) - : base(uri, requireSslDiscovery) { - Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(uri)); - Uri canonicalUri; - bool schemePrepended; - if (!TryCanonicalize(uri, out canonicalUri, requireSslDiscovery, out schemePrepended)) { - throw new UriFormatException(); - } - if (requireSslDiscovery && canonicalUri.Scheme != Uri.UriSchemeHttps) { - throw new ArgumentException(OpenIdStrings.ExplicitHttpUriSuppliedWithSslRequirement); - } - this.Uri = canonicalUri; - this.SchemeImplicitlyPrepended = schemePrepended; - } - - /// <summary> - /// Initializes a new instance of the <see cref="UriIdentifier"/> class. - /// </summary> - /// <param name="uri">The value this identifier will represent.</param> - internal UriIdentifier(Uri uri) - : this(uri, false) { - } - - /// <summary> - /// Initializes a new instance of the <see cref="UriIdentifier"/> class. - /// </summary> - /// <param name="uri">The value this identifier will represent.</param> - /// <param name="requireSslDiscovery">if set to <c>true</c> [require SSL discovery].</param> - internal UriIdentifier(Uri uri, bool requireSslDiscovery) - : base(uri != null ? uri.OriginalString : null, requireSslDiscovery) { - Contract.Requires<ArgumentNullException>(uri != null); - - string uriAsString = uri.OriginalString; - if (schemeSubstitution) { - uriAsString = NormalSchemeToSpecialRoundTrippingScheme(uriAsString); - } - - if (!TryCanonicalize(uriAsString, out uri)) { - throw new UriFormatException(); - } - if (requireSslDiscovery && uri.Scheme != Uri.UriSchemeHttps) { - throw new ArgumentException(OpenIdStrings.ExplicitHttpUriSuppliedWithSslRequirement); - } - this.Uri = uri; - this.SchemeImplicitlyPrepended = false; - } - - /// <summary> - /// Gets or sets a value indicating whether scheme substitution is being used to workaround - /// .NET path compression that invalidates some OpenIDs that have trailing periods - /// in one of their path segments. - /// </summary> - internal static bool SchemeSubstitutionTestHook { - get { return schemeSubstitution; } - set { schemeSubstitution = value; } - } - - /// <summary> - /// Gets the URI this instance represents. - /// </summary> - internal Uri Uri { get; private set; } - - /// <summary> - /// Gets a value indicating whether the scheme was missing when this - /// Identifier was created and added automatically as part of the - /// normalization process. - /// </summary> - internal bool SchemeImplicitlyPrepended { get; private set; } - - /// <summary> - /// Gets a value indicating whether this Identifier has characters or patterns that - /// the <see cref="Uri"/> class normalizes away and invalidating the Identifier. - /// </summary> - internal bool ProblematicNormalization { - get { - if (schemeSubstitution) { - // With full trust, we have no problematic URIs - return false; - } - - var simpleUri = new SimpleUri(this.OriginalString); - if (simpleUri.Path.EndsWith(".", StringComparison.Ordinal) || simpleUri.Path.Contains("./")) { - return true; - } - - return false; - } - } - - /// <summary> - /// Converts a <see cref="UriIdentifier"/> instance to a <see cref="Uri"/> instance. - /// </summary> - /// <param name="identifier">The identifier to convert to an ordinary <see cref="Uri"/> instance.</param> - /// <returns>The result of the conversion.</returns> - public static implicit operator Uri(UriIdentifier identifier) { - if (identifier == null) { - return null; - } - return identifier.Uri; - } - - /// <summary> - /// Converts a <see cref="Uri"/> instance to a <see cref="UriIdentifier"/> instance. - /// </summary> - /// <param name="identifier">The <see cref="Uri"/> instance to turn into a <see cref="UriIdentifier"/>.</param> - /// <returns>The result of the conversion.</returns> - public static implicit operator UriIdentifier(Uri identifier) { - if (identifier == null) { - return null; - } - return new UriIdentifier(identifier); - } - - /// <summary> - /// Tests equality between this URI and another URI. - /// </summary> - /// <param name="obj">The <see cref="T:System.Object"/> to compare with the current <see cref="T:System.Object"/>.</param> - /// <returns> - /// true if the specified <see cref="T:System.Object"/> is equal to the current <see cref="T:System.Object"/>; otherwise, false. - /// </returns> - /// <exception cref="T:System.NullReferenceException"> - /// The <paramref name="obj"/> parameter is null. - /// </exception> - public override bool Equals(object obj) { - UriIdentifier other = obj as UriIdentifier; - if (obj != null && other == null && Identifier.EqualityOnStrings) { // test hook to enable MockIdentifier comparison - other = Identifier.Parse(obj.ToString()) as UriIdentifier; - } - if (other == null) { - return false; - } - - if (this.ProblematicNormalization || other.ProblematicNormalization) { - return new SimpleUri(this.OriginalString).Equals(new SimpleUri(other.OriginalString)); - } else { - return this.Uri == other.Uri; - } - } - - /// <summary> - /// Returns the hash code of this XRI. - /// </summary> - /// <returns> - /// A hash code for the current <see cref="T:System.Object"/>. - /// </returns> - public override int GetHashCode() { - return Uri.GetHashCode(); - } - - /// <summary> - /// Returns the string form of the URI. - /// </summary> - /// <returns> - /// A <see cref="T:System.String"/> that represents the current <see cref="T:System.Object"/>. - /// </returns> - public override string ToString() { - if (this.ProblematicNormalization) { - return new SimpleUri(this.OriginalString).ToString(); - } else { - return this.Uri.AbsoluteUri; - } - } - - /// <summary> - /// Determines whether a URI is a valid OpenID Identifier (of any kind). - /// </summary> - /// <param name="uri">The URI to test for OpenID validity.</param> - /// <returns> - /// <c>true</c> if the identifier is valid; otherwise, <c>false</c>. - /// </returns> - /// <remarks> - /// A valid URI is absolute (not relative) and uses an http(s) scheme. - /// </remarks> - internal static bool IsValidUri(string uri) { - Uri normalized; - bool schemePrepended; - return TryCanonicalize(uri, out normalized, false, out schemePrepended); - } - - /// <summary> - /// Determines whether a URI is a valid OpenID Identifier (of any kind). - /// </summary> - /// <param name="uri">The URI to test for OpenID validity.</param> - /// <returns> - /// <c>true</c> if the identifier is valid; otherwise, <c>false</c>. - /// </returns> - /// <remarks> - /// A valid URI is absolute (not relative) and uses an http(s) scheme. - /// </remarks> - internal static bool IsValidUri(Uri uri) { - if (uri == null) { - return false; - } - if (!uri.IsAbsoluteUri) { - return false; - } - if (!IsAllowedScheme(uri)) { - return false; - } - return true; - } - - /// <summary> - /// Returns an <see cref="Identifier"/> that has no URI fragment. - /// Quietly returns the original <see cref="Identifier"/> if it is not - /// a <see cref="UriIdentifier"/> or no fragment exists. - /// </summary> - /// <returns> - /// A new <see cref="Identifier"/> instance if there was a - /// fragment to remove, otherwise this same instance.. - /// </returns> - internal override Identifier TrimFragment() { - // If there is no fragment, we have no need to rebuild the Identifier. - if (Uri.Fragment == null || Uri.Fragment.Length == 0) { - return this; - } - - // Strip the fragment. - return new UriIdentifier(this.OriginalString.Substring(0, this.OriginalString.IndexOf('#'))); - } - - /// <summary> - /// Converts a given identifier to its secure equivalent. - /// UriIdentifiers originally created with an implied HTTP scheme change to HTTPS. - /// Discovery is made to require SSL for the entire resolution process. - /// </summary> - /// <param name="secureIdentifier">The newly created secure identifier. - /// If the conversion fails, <paramref name="secureIdentifier"/> retains - /// <i>this</i> identifiers identity, but will never discover any endpoints.</param> - /// <returns> - /// True if the secure conversion was successful. - /// False if the Identifier was originally created with an explicit HTTP scheme. - /// </returns> - internal override bool TryRequireSsl(out Identifier secureIdentifier) { - // If this Identifier is already secure, reuse it. - if (IsDiscoverySecureEndToEnd) { - secureIdentifier = this; - return true; - } - - // If this identifier already uses SSL for initial discovery, return one - // that guarantees it will be used throughout the discovery process. - if (String.Equals(Uri.Scheme, Uri.UriSchemeHttps, StringComparison.OrdinalIgnoreCase)) { - secureIdentifier = new UriIdentifier(this.Uri, true); - return true; - } - - // Otherwise, try to make this Identifier secure by normalizing to HTTPS instead of HTTP. - if (this.SchemeImplicitlyPrepended) { - UriBuilder newIdentifierUri = new UriBuilder(this.Uri); - newIdentifierUri.Scheme = Uri.UriSchemeHttps; - if (newIdentifierUri.Port == 80) { - newIdentifierUri.Port = 443; - } - secureIdentifier = new UriIdentifier(newIdentifierUri.Uri, true); - return true; - } - - // This identifier is explicitly NOT https, so we cannot change it. - secureIdentifier = new NoDiscoveryIdentifier(this, true); - return false; - } - - /// <summary> - /// Determines whether the given URI is using a scheme in the list of allowed schemes. - /// </summary> - /// <param name="uri">The URI whose scheme is to be checked.</param> - /// <returns> - /// <c>true</c> if the scheme is allowed; otherwise, <c>false</c>. - /// <c>false</c> is also returned if <paramref name="uri"/> is null. - /// </returns> - private static bool IsAllowedScheme(string uri) { - if (string.IsNullOrEmpty(uri)) { - return false; - } - return Array.FindIndex( - allowedSchemes, - s => uri.StartsWith(s + Uri.SchemeDelimiter, StringComparison.OrdinalIgnoreCase)) >= 0; - } - - /// <summary> - /// Determines whether the given URI is using a scheme in the list of allowed schemes. - /// </summary> - /// <param name="uri">The URI whose scheme is to be checked.</param> - /// <returns> - /// <c>true</c> if the scheme is allowed; otherwise, <c>false</c>. - /// <c>false</c> is also returned if <paramref name="uri"/> is null. - /// </returns> - private static bool IsAllowedScheme(Uri uri) { - if (uri == null) { - return false; - } - return Array.FindIndex( - allowedSchemes, - s => uri.Scheme.Equals(s, StringComparison.OrdinalIgnoreCase)) >= 0; - } - - /// <summary> - /// Tries to canonicalize a user-supplied identifier. - /// This does NOT convert a user-supplied identifier to a Claimed Identifier! - /// </summary> - /// <param name="uri">The user-supplied identifier.</param> - /// <param name="canonicalUri">The resulting canonical URI.</param> - /// <param name="forceHttpsDefaultScheme">If set to <c>true</c> and the user-supplied identifier lacks a scheme, the "https://" scheme will be prepended instead of the standard "http://" one.</param> - /// <param name="schemePrepended">if set to <c>true</c> [scheme prepended].</param> - /// <returns> - /// <c>true</c> if the identifier was valid and could be canonicalized. - /// <c>false</c> if the identifier is outside the scope of allowed inputs and should be rejected. - /// </returns> - /// <remarks> - /// Canonicalization is done by adding a scheme in front of an - /// identifier if it isn't already present. Other trivial changes that do not - /// require network access are also done, such as lower-casing the hostname in the URI. - /// </remarks> - private static bool TryCanonicalize(string uri, out Uri canonicalUri, bool forceHttpsDefaultScheme, out bool schemePrepended) { - Contract.Requires<ArgumentException>(!string.IsNullOrEmpty(uri)); - - canonicalUri = null; - try { - uri = DoSimpleCanonicalize(uri, forceHttpsDefaultScheme, out schemePrepended); - if (schemeSubstitution) { - uri = NormalSchemeToSpecialRoundTrippingScheme(uri); - } - - // Use a UriBuilder because it helps to normalize the URL as well. - return TryCanonicalize(uri, out canonicalUri); - } catch (UriFormatException) { - // We try not to land here with checks in the try block, but just in case. - schemePrepended = false; - return false; - } - } - - /// <summary> - /// Fixes up the scheme if appropriate. - /// </summary> - /// <param name="uri">The URI, already in legal form (with http(s):// prepended if necessary).</param> - /// <param name="canonicalUri">The resulting canonical URI.</param> - /// <returns><c>true</c> if the canonicalization was successful; <c>false</c> otherwise.</returns> - /// <remarks> - /// This does NOT standardize an OpenID URL for storage in a database, as - /// it does nothing to convert the URL to a Claimed Identifier, besides the fact - /// that it only deals with URLs whereas OpenID 2.0 supports XRIs. - /// For this, you should lookup the value stored in IAuthenticationResponse.ClaimedIdentifier. - /// </remarks> - [SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase", Justification = "The user will see the result of this operation and they want to see it in lower case.")] - private static bool TryCanonicalize(string uri, out Uri canonicalUri) { - Contract.Requires<ArgumentNullException>(uri != null); - - if (schemeSubstitution) { - UriBuilder uriBuilder = new UriBuilder(uri); - - // Swap out our round-trippable scheme for the publishable (hidden) scheme. - uriBuilder.Scheme = uriBuilder.Scheme == roundTrippingHttpParser.RegisteredScheme ? publishableHttpParser.RegisteredScheme : publishableHttpsParser.RegisteredScheme; - canonicalUri = uriBuilder.Uri; - } else { - canonicalUri = new Uri(uri); - } - - return true; - } - - /// <summary> - /// Gets the special non-compressing scheme or URL for a standard scheme or URL. - /// </summary> - /// <param name="normal">The ordinary URL or scheme name.</param> - /// <returns>The non-compressing equivalent scheme or URL for the given value.</returns> - private static string NormalSchemeToSpecialRoundTrippingScheme(string normal) { - Contract.Requires<ArgumentException>(!string.IsNullOrEmpty(normal)); - Contract.Requires<InternalErrorException>(schemeSubstitution); - Contract.Ensures(!string.IsNullOrEmpty(Contract.Result<string>())); - - int delimiterIndex = normal.IndexOf(Uri.SchemeDelimiter, StringComparison.Ordinal); - string normalScheme = delimiterIndex < 0 ? normal : normal.Substring(0, delimiterIndex); - string nonCompressingScheme; - if (string.Equals(normalScheme, Uri.UriSchemeHttp, StringComparison.OrdinalIgnoreCase) || - string.Equals(normalScheme, publishableHttpParser.RegisteredScheme, StringComparison.OrdinalIgnoreCase)) { - nonCompressingScheme = roundTrippingHttpParser.RegisteredScheme; - } else if (string.Equals(normalScheme, Uri.UriSchemeHttps, StringComparison.OrdinalIgnoreCase) || - string.Equals(normalScheme, publishableHttpsParser.RegisteredScheme, StringComparison.OrdinalIgnoreCase)) { - nonCompressingScheme = roundTrippingHttpsParser.RegisteredScheme; - } else { - throw new NotSupportedException(); - } - - return delimiterIndex < 0 ? nonCompressingScheme : nonCompressingScheme + normal.Substring(delimiterIndex); - } - - /// <summary> - /// Performs the minimal URL normalization to allow a string to be passed to the <see cref="Uri"/> constructor. - /// </summary> - /// <param name="uri">The user-supplied identifier URI to normalize.</param> - /// <param name="forceHttpsDefaultScheme">if set to <c>true</c>, a missing scheme should result in HTTPS being prepended instead of HTTP.</param> - /// <param name="schemePrepended">if set to <c>true</c>, the scheme was prepended during normalization.</param> - /// <returns>The somewhat normalized URL.</returns> - private static string DoSimpleCanonicalize(string uri, bool forceHttpsDefaultScheme, out bool schemePrepended) { - Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(uri)); - - schemePrepended = false; - uri = uri.Trim(); - - // Assume http:// scheme if an allowed scheme isn't given, and strip - // fragments off. Consistent with spec section 7.2#3 - if (!IsAllowedScheme(uri)) { - uri = (forceHttpsDefaultScheme ? Uri.UriSchemeHttps : Uri.UriSchemeHttp) + - Uri.SchemeDelimiter + uri; - schemePrepended = true; - } - - return uri; - } - -#if CONTRACTS_FULL - /// <summary> - /// Verifies conditions that should be true for any valid state of this object. - /// </summary> - [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Called by code contracts.")] - [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called by code contracts.")] - [ContractInvariantMethod] - private void ObjectInvariant() { - Contract.Invariant(this.Uri != null); - Contract.Invariant(this.Uri.AbsoluteUri != null); - } -#endif - - /// <summary> - /// A simple URI class that doesn't suffer from the parsing problems of the <see cref="Uri"/> class. - /// </summary> - internal class SimpleUri { - /// <summary> - /// URI characters that separate the URI Path from subsequent elements. - /// </summary> - private static readonly char[] PathEndingCharacters = new char[] { '?', '#' }; - - /// <summary> - /// Initializes a new instance of the <see cref="SimpleUri"/> class. - /// </summary> - /// <param name="value">The value.</param> - internal SimpleUri(string value) { - Contract.Requires<ArgumentException>(!string.IsNullOrEmpty(value)); - - bool schemePrepended; - value = DoSimpleCanonicalize(value, false, out schemePrepended); - - // Leverage the Uri class's parsing where we can. - Uri uri = new Uri(value); - this.Scheme = uri.Scheme; - this.Authority = uri.Authority; - this.Query = uri.Query; - this.Fragment = uri.Fragment; - - // Get the Path out ourselves, since the default Uri parser compresses it too much for OpenID. - int schemeLength = value.IndexOf(Uri.SchemeDelimiter, StringComparison.Ordinal); - Contract.Assume(schemeLength > 0); - int hostStart = schemeLength + Uri.SchemeDelimiter.Length; - int hostFinish = value.IndexOf('/', hostStart); - if (hostFinish < 0) { - this.Path = "/"; - } else { - int pathFinish = value.IndexOfAny(PathEndingCharacters, hostFinish); - Contract.Assume(pathFinish >= hostFinish || pathFinish < 0); - if (pathFinish < 0) { - this.Path = value.Substring(hostFinish); - } else { - this.Path = value.Substring(hostFinish, pathFinish - hostFinish); - } - } - - this.Path = NormalizePathEscaping(this.Path); - } - - /// <summary> - /// Gets the scheme. - /// </summary> - /// <value>The scheme.</value> - public string Scheme { get; private set; } - - /// <summary> - /// Gets the authority. - /// </summary> - /// <value>The authority.</value> - public string Authority { get; private set; } - - /// <summary> - /// Gets the path of the URI. - /// </summary> - /// <value>The path from the URI.</value> - public string Path { get; private set; } - - /// <summary> - /// Gets the query. - /// </summary> - /// <value>The query.</value> - public string Query { get; private set; } - - /// <summary> - /// Gets the fragment. - /// </summary> - /// <value>The fragment.</value> - public string Fragment { get; private set; } - - /// <summary> - /// Returns a <see cref="System.String"/> that represents this instance. - /// </summary> - /// <returns> - /// A <see cref="System.String"/> that represents this instance. - /// </returns> - public override string ToString() { - return this.Scheme + Uri.SchemeDelimiter + this.Authority + this.Path + this.Query + this.Fragment; - } - - /// <summary> - /// Determines whether the specified <see cref="System.Object"/> is equal to this instance. - /// </summary> - /// <param name="obj">The <see cref="System.Object"/> to compare with this instance.</param> - /// <returns> - /// <c>true</c> if the specified <see cref="System.Object"/> is equal to this instance; otherwise, <c>false</c>. - /// </returns> - /// <exception cref="T:System.NullReferenceException"> - /// The <paramref name="obj"/> parameter is null. - /// </exception> - public override bool Equals(object obj) { - SimpleUri other = obj as SimpleUri; - if (other == null) { - return false; - } - - // Note that this equality check is intentionally leaving off the Fragment part - // to match Uri behavior, and is intentionally being case sensitive and insensitive - // for different parts. - return string.Equals(this.Scheme, other.Scheme, StringComparison.OrdinalIgnoreCase) && - string.Equals(this.Authority, other.Authority, StringComparison.OrdinalIgnoreCase) && - string.Equals(this.Path, other.Path, StringComparison.Ordinal) && - string.Equals(this.Query, other.Query, StringComparison.Ordinal); - } - - /// <summary> - /// Returns a hash code for this instance. - /// </summary> - /// <returns> - /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. - /// </returns> - public override int GetHashCode() { - int hashCode = 0; - hashCode += StringComparer.OrdinalIgnoreCase.GetHashCode(this.Scheme); - hashCode += StringComparer.OrdinalIgnoreCase.GetHashCode(this.Authority); - hashCode += StringComparer.Ordinal.GetHashCode(this.Path); - hashCode += StringComparer.Ordinal.GetHashCode(this.Query); - return hashCode; - } - - /// <summary> - /// Normalizes the characters that are escaped in the given URI path. - /// </summary> - /// <param name="path">The path to normalize.</param> - /// <returns>The given path, with exactly those characters escaped which should be.</returns> - private static string NormalizePathEscaping(string path) { - Contract.Requires<ArgumentNullException>(path != null); - - string[] segments = path.Split('/'); - for (int i = 0; i < segments.Length; i++) { - segments[i] = Uri.EscapeDataString(Uri.UnescapeDataString(segments[i])); - } - - return string.Join("/", segments); - } - } - - /// <summary> - /// A URI parser that does not compress paths, such as trimming trailing periods from path segments. - /// </summary> - private class NonPathCompressingUriParser : GenericUriParser { - /// <summary> - /// The field that stores the scheme that this parser is registered under. - /// </summary> - private static FieldInfo schemeField; - - /// <summary> - /// The standard "http" or "https" scheme that this parser is subverting. - /// </summary> - private string standardScheme; - - /// <summary> - /// Initializes a new instance of the <see cref="NonPathCompressingUriParser"/> class. - /// </summary> - /// <param name="standardScheme">The standard scheme that this parser will be subverting.</param> - public NonPathCompressingUriParser(string standardScheme) - : base(GenericUriParserOptions.DontCompressPath | GenericUriParserOptions.IriParsing | GenericUriParserOptions.Idn) { - Contract.Requires<ArgumentException>(!string.IsNullOrEmpty(standardScheme)); - this.standardScheme = standardScheme; - } - - /// <summary> - /// Gets the scheme this parser is registered under. - /// </summary> - /// <value>The registered scheme.</value> - internal string RegisteredScheme { get; private set; } - - /// <summary> - /// Initializes this parser with the actual scheme it should appear to be. - /// </summary> - /// <param name="hideNonStandardScheme">if set to <c>true</c> Uris using this scheme will look like they're using the original standard scheme.</param> - [SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase", Justification = "Schemes are traditionally displayed in lowercase.")] - internal void Initialize(bool hideNonStandardScheme) { - if (schemeField == null) { - schemeField = typeof(UriParser).GetField("m_Scheme", BindingFlags.NonPublic | BindingFlags.Instance); - } - - this.RegisteredScheme = (string)schemeField.GetValue(this); - - if (hideNonStandardScheme) { - schemeField.SetValue(this, this.standardScheme.ToLowerInvariant()); - } - } - } - } -} diff --git a/src/DotNetOpenAuth/OpenId/XriDiscoveryProxyService.cs b/src/DotNetOpenAuth/OpenId/XriDiscoveryProxyService.cs deleted file mode 100644 index b1a3430..0000000 --- a/src/DotNetOpenAuth/OpenId/XriDiscoveryProxyService.cs +++ /dev/null @@ -1,108 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="XriDiscoveryProxyService.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OpenId { - using System; - using System.Collections.Generic; - using System.Diagnostics.CodeAnalysis; - using System.Diagnostics.Contracts; - using System.Globalization; - using System.Linq; - using System.Text; - using System.Xml; - using DotNetOpenAuth.Configuration; - using DotNetOpenAuth.Messaging; - using DotNetOpenAuth.OpenId.RelyingParty; - using DotNetOpenAuth.Xrds; - using DotNetOpenAuth.Yadis; - - /// <summary> - /// The discovery service for XRI identifiers that uses an XRI proxy resolver for discovery. - /// </summary> - [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Xri", Justification = "Acronym")] - public class XriDiscoveryProxyService : IIdentifierDiscoveryService { - /// <summary> - /// The magic URL that will provide us an XRDS document for a given XRI identifier. - /// </summary> - /// <remarks> - /// We use application/xrd+xml instead of application/xrds+xml because it gets - /// xri.net to automatically give us exactly the right XRD element for community i-names - /// automatically, saving us having to choose which one to use out of the result. - /// The ssl=true parameter tells the proxy resolver to accept only SSL connections - /// when resolving community i-names. - /// </remarks> - private const string XriResolverProxyTemplate = "https://{1}/{0}?_xrd_r=application/xrd%2Bxml;sep=false"; - - /// <summary> - /// Initializes a new instance of the <see cref="XriDiscoveryProxyService"/> class. - /// </summary> - public XriDiscoveryProxyService() { - } - - #region IDiscoveryService Members - - /// <summary> - /// Performs discovery on the specified identifier. - /// </summary> - /// <param name="identifier">The identifier to perform discovery on.</param> - /// <param name="requestHandler">The means to place outgoing HTTP requests.</param> - /// <param name="abortDiscoveryChain">if set to <c>true</c>, no further discovery services will be called for this identifier.</param> - /// <returns> - /// A sequence of service endpoints yielded by discovery. Must not be null, but may be empty. - /// </returns> - public IEnumerable<IdentifierDiscoveryResult> Discover(Identifier identifier, IDirectWebRequestHandler requestHandler, out bool abortDiscoveryChain) { - abortDiscoveryChain = false; - var xriIdentifier = identifier as XriIdentifier; - if (xriIdentifier == null) { - return Enumerable.Empty<IdentifierDiscoveryResult>(); - } - - return DownloadXrds(xriIdentifier, requestHandler).XrdElements.CreateServiceEndpoints(xriIdentifier); - } - - #endregion - - /// <summary> - /// Downloads the XRDS document for this XRI. - /// </summary> - /// <param name="identifier">The identifier.</param> - /// <param name="requestHandler">The request handler.</param> - /// <returns>The XRDS document.</returns> - private static XrdsDocument DownloadXrds(XriIdentifier identifier, IDirectWebRequestHandler requestHandler) { - Contract.Requires<ArgumentNullException>(identifier != null); - Contract.Requires<ArgumentNullException>(requestHandler != null); - Contract.Ensures(Contract.Result<XrdsDocument>() != null); - XrdsDocument doc; - using (var xrdsResponse = Yadis.Request(requestHandler, GetXrdsUrl(identifier), identifier.IsDiscoverySecureEndToEnd)) { - doc = new XrdsDocument(XmlReader.Create(xrdsResponse.ResponseStream)); - } - ErrorUtilities.VerifyProtocol(doc.IsXrdResolutionSuccessful, OpenIdStrings.XriResolutionFailed); - return doc; - } - - /// <summary> - /// Gets the URL from which this XRI's XRDS document may be downloaded. - /// </summary> - /// <param name="identifier">The identifier.</param> - /// <returns>The URI to HTTP GET from to get the services.</returns> - private static Uri GetXrdsUrl(XriIdentifier identifier) { - ErrorUtilities.VerifyProtocol(DotNetOpenAuthSection.Configuration.OpenId.XriResolver.Enabled, OpenIdStrings.XriResolutionDisabled); - string xriResolverProxy = XriResolverProxyTemplate; - if (identifier.IsDiscoverySecureEndToEnd) { - // Indicate to xri.net that we require SSL to be used for delegated resolution - // of community i-names. - xriResolverProxy += ";https=true"; - } - - return new Uri( - string.Format( - CultureInfo.InvariantCulture, - xriResolverProxy, - identifier, - DotNetOpenAuthSection.Configuration.OpenId.XriResolver.Proxy.Name)); - } - } -} diff --git a/src/DotNetOpenAuth/OpenId/XriIdentifier.cs b/src/DotNetOpenAuth/OpenId/XriIdentifier.cs deleted file mode 100644 index 729f603..0000000 --- a/src/DotNetOpenAuth/OpenId/XriIdentifier.cs +++ /dev/null @@ -1,208 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="XriIdentifier.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OpenId { - using System; - using System.Collections.Generic; - using System.Diagnostics.CodeAnalysis; - using System.Diagnostics.Contracts; - using System.Globalization; - using System.Xml; - using DotNetOpenAuth.Configuration; - using DotNetOpenAuth.Messaging; - using DotNetOpenAuth.OpenId.RelyingParty; - using DotNetOpenAuth.Xrds; - using DotNetOpenAuth.Yadis; - - /// <summary> - /// An XRI style of OpenID Identifier. - /// </summary> - [Serializable] - [ContractVerification(true)] - [Pure] - public sealed class XriIdentifier : Identifier { - /// <summary> - /// An XRI always starts with one of these symbols. - /// </summary> - internal static readonly char[] GlobalContextSymbols = { '=', '@', '+', '$', '!' }; - - /// <summary> - /// The scheme and separator "xri://" - /// </summary> - private const string XriScheme = "xri://"; - - /// <summary> - /// Backing store for the <see cref="CanonicalXri"/> property. - /// </summary> - private readonly string canonicalXri; - - /// <summary> - /// Initializes a new instance of the <see cref="XriIdentifier"/> class. - /// </summary> - /// <param name="xri">The string value of the XRI.</param> - internal XriIdentifier(string xri) - : this(xri, false) { - Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(xri)); - Contract.Requires<FormatException>(IsValidXri(xri), OpenIdStrings.InvalidXri); - } - - /// <summary> - /// Initializes a new instance of the <see cref="XriIdentifier"/> class. - /// </summary> - /// <param name="xri">The XRI that this Identifier will represent.</param> - /// <param name="requireSsl"> - /// If set to <c>true</c>, discovery and the initial authentication redirect will - /// only succeed if it can be done entirely using SSL. - /// </param> - internal XriIdentifier(string xri, bool requireSsl) - : base(xri, requireSsl) { - Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(xri)); - Contract.Requires<FormatException>(IsValidXri(xri), OpenIdStrings.InvalidXri); - Contract.Assume(xri != null); // Proven by IsValidXri - this.OriginalXri = xri; - this.canonicalXri = CanonicalizeXri(xri); - } - - /// <summary> - /// Gets the original XRI supplied to the constructor. - /// </summary> - internal string OriginalXri { get; private set; } - - /// <summary> - /// Gets the canonical form of the XRI string. - /// </summary> - internal string CanonicalXri { - get { - Contract.Ensures(Contract.Result<string>() != null); - return this.canonicalXri; - } - } - - /// <summary> - /// Tests equality between this XRI and another XRI. - /// </summary> - /// <param name="obj">The <see cref="T:System.Object"/> to compare with the current <see cref="T:System.Object"/>.</param> - /// <returns> - /// true if the specified <see cref="T:System.Object"/> is equal to the current <see cref="T:System.Object"/>; otherwise, false. - /// </returns> - /// <exception cref="T:System.NullReferenceException"> - /// The <paramref name="obj"/> parameter is null. - /// </exception> - public override bool Equals(object obj) { - XriIdentifier other = obj as XriIdentifier; - if (obj != null && other == null && Identifier.EqualityOnStrings) { // test hook to enable MockIdentifier comparison - string objString = obj.ToString(); - ErrorUtilities.VerifyInternal(!string.IsNullOrEmpty(objString), "Identifier.ToString() returned a null or empty string."); - other = Identifier.Parse(objString) as XriIdentifier; - } - if (other == null) { - return false; - } - return this.CanonicalXri == other.CanonicalXri; - } - - /// <summary> - /// Returns the hash code of this XRI. - /// </summary> - /// <returns> - /// A hash code for the current <see cref="T:System.Object"/>. - /// </returns> - public override int GetHashCode() { - return this.CanonicalXri.GetHashCode(); - } - - /// <summary> - /// Returns the canonical string form of the XRI. - /// </summary> - /// <returns> - /// A <see cref="T:System.String"/> that represents the current <see cref="T:System.Object"/>. - /// </returns> - public override string ToString() { - return this.CanonicalXri; - } - - /// <summary> - /// Tests whether a given string represents a valid XRI format. - /// </summary> - /// <param name="xri">The value to test for XRI validity.</param> - /// <returns> - /// <c>true</c> if the given string constitutes a valid XRI; otherwise, <c>false</c>. - /// </returns> - internal static bool IsValidXri(string xri) { - Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(xri)); - xri = xri.Trim(); - - // TODO: better validation code here - return xri.IndexOfAny(GlobalContextSymbols) == 0 - || xri.StartsWith("(", StringComparison.Ordinal) - || xri.StartsWith(XriScheme, StringComparison.OrdinalIgnoreCase); - } - - /// <summary> - /// Returns an <see cref="Identifier"/> that has no URI fragment. - /// Quietly returns the original <see cref="Identifier"/> if it is not - /// a <see cref="UriIdentifier"/> or no fragment exists. - /// </summary> - /// <returns> - /// A new <see cref="Identifier"/> instance if there was a - /// fragment to remove, otherwise this same instance.. - /// </returns> - /// <remarks> - /// XRI Identifiers never have a fragment part, and thus this method - /// always returns this same instance. - /// </remarks> - internal override Identifier TrimFragment() { - return this; - } - - /// <summary> - /// Converts a given identifier to its secure equivalent. - /// UriIdentifiers originally created with an implied HTTP scheme change to HTTPS. - /// Discovery is made to require SSL for the entire resolution process. - /// </summary> - /// <param name="secureIdentifier">The newly created secure identifier. - /// If the conversion fails, <paramref name="secureIdentifier"/> retains - /// <i>this</i> identifiers identity, but will never discover any endpoints.</param> - /// <returns> - /// True if the secure conversion was successful. - /// False if the Identifier was originally created with an explicit HTTP scheme. - /// </returns> - [ContractVerification(false)] // bugs/limitations in CC static analysis - internal override bool TryRequireSsl(out Identifier secureIdentifier) { - secureIdentifier = IsDiscoverySecureEndToEnd ? this : new XriIdentifier(this, true); - return true; - } - - /// <summary> - /// Takes any valid form of XRI string and returns the canonical form of the same XRI. - /// </summary> - /// <param name="xri">The xri to canonicalize.</param> - /// <returns>The canonicalized form of the XRI.</returns> - /// <remarks>The canonical form, per the OpenID spec, is no scheme and no whitespace on either end.</remarks> - private static string CanonicalizeXri(string xri) { - Contract.Requires<ArgumentNullException>(xri != null); - Contract.Ensures(Contract.Result<string>() != null); - xri = xri.Trim(); - if (xri.StartsWith(XriScheme, StringComparison.OrdinalIgnoreCase)) { - Contract.Assume(XriScheme.Length <= xri.Length); // should be implied by StartsWith - xri = xri.Substring(XriScheme.Length); - } - return xri; - } - -#if CONTRACTS_FULL - /// <summary> - /// Verifies conditions that should be true for any valid state of this object. - /// </summary> - [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Called by code contracts.")] - [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called by code contracts.")] - [ContractInvariantMethod] - private void ObjectInvariant() { - Contract.Invariant(this.canonicalXri != null); - } -#endif - } -} diff --git a/src/DotNetOpenAuth/Properties/AssemblyInfo.cs b/src/DotNetOpenAuth/Properties/AssemblyInfo.cs deleted file mode 100644 index a63c71e..0000000 --- a/src/DotNetOpenAuth/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,59 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="AssemblyInfo.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -// We DON'T put an AssemblyVersionAttribute in here because it is generated in the build. - -using System; -using System.Diagnostics.Contracts; -using System.Net; -using System.Reflection; -using System.Resources; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Security; -using System.Security.Permissions; -using System.Web.UI; - -[assembly: TagPrefix("DotNetOpenAuth", "dnoa")] -[assembly: TagPrefix("DotNetOpenAuth.InfoCard", "ic")] -[assembly: TagPrefix("DotNetOpenAuth.OAuth", "oauth")] -[assembly: TagPrefix("DotNetOpenAuth.OpenId", "openid")] -[assembly: TagPrefix("DotNetOpenAuth.OpenId.Provider", "op")] -[assembly: TagPrefix("DotNetOpenAuth.OpenId.RelyingParty", "rp")] - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("DotNetOpenAuth")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("DotNetOpenAuth")] -[assembly: AssemblyCopyright("Copyright © 2008")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] -[assembly: NeutralResourcesLanguage("en-US")] -[assembly: CLSCompliant(true)] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("7d73990c-47c0-4256-9f20-a893add9e289")] - -[assembly: ContractVerification(true)] - -#if StrongNameSigned -// See comment at top of this file. We need this so that strong-naming doesn't -// keep this assembly from being useful to shared host (medium trust) web sites. -[assembly: AllowPartiallyTrustedCallers] - -[assembly: InternalsVisibleTo("DotNetOpenAuth.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100AD093C3765257C89A7010E853F2C7C741FF92FA8ACE06D7B8254702CAD5CF99104447F63AB05F8BB6F51CE0D81C8C93D2FCE8C20AAFF7042E721CBA16EAAE98778611DED11C0ABC8900DC5667F99B50A9DADEC24DBD8F2C91E3E8AD300EF64F1B4B9536CEB16FB440AF939F57624A9B486F867807C649AE4830EAB88C6C03998")] -#else -[assembly: InternalsVisibleTo("DotNetOpenAuth.Test")] -#endif diff --git a/src/DotNetOpenAuth/Reporting.cs b/src/DotNetOpenAuth/Reporting.cs deleted file mode 100644 index 03565b8..0000000 --- a/src/DotNetOpenAuth/Reporting.cs +++ /dev/null @@ -1,930 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="Reporting.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth { - using System; - using System.Collections.Generic; - using System.Diagnostics; - using System.Diagnostics.CodeAnalysis; - using System.Diagnostics.Contracts; - using System.Globalization; - using System.IO; - using System.IO.IsolatedStorage; - using System.Linq; - using System.Net; - using System.Reflection; - using System.Security; - using System.Text; - using System.Threading; - using System.Web; - using DotNetOpenAuth.Configuration; - using DotNetOpenAuth.Messaging; - using DotNetOpenAuth.Messaging.Bindings; - using DotNetOpenAuth.OAuth; - using DotNetOpenAuth.OAuth.ChannelElements; - - /// <summary> - /// The statistical reporting mechanism used so this library's project authors - /// know what versions and features are in use. - /// </summary> - public static class Reporting { - /// <summary> - /// A UTF8 encoder that doesn't emit the preamble. Used for mid-stream writers. - /// </summary> - private static readonly Encoding Utf8NoPreamble = new UTF8Encoding(false); - - /// <summary> - /// A value indicating whether reporting is desirable or not. Must be logical-AND'd with !<see cref="broken"/>. - /// </summary> - private static bool enabled; - - /// <summary> - /// A value indicating whether reporting experienced an error and cannot be enabled. - /// </summary> - private static bool broken; - - /// <summary> - /// A value indicating whether the reporting class has been initialized or not. - /// </summary> - private static bool initialized; - - /// <summary> - /// The object to lock during initialization. - /// </summary> - private static object initializationSync = new object(); - - /// <summary> - /// The isolated storage to use for collecting data in between published reports. - /// </summary> - private static IsolatedStorageFile file; - - /// <summary> - /// The GUID that shows up at the top of all reports from this user/machine/domain. - /// </summary> - private static Guid reportOriginIdentity; - - /// <summary> - /// The recipient of collected reports. - /// </summary> - private static Uri wellKnownPostLocation = new Uri("https://reports.dotnetopenauth.net/ReportingPost.ashx"); - - /// <summary> - /// The outgoing HTTP request handler to use for publishing reports. - /// </summary> - private static IDirectWebRequestHandler webRequestHandler; - - /// <summary> - /// A few HTTP request hosts and paths we've seen. - /// </summary> - private static PersistentHashSet observedRequests; - - /// <summary> - /// Cultures that have come in via HTTP requests. - /// </summary> - private static PersistentHashSet observedCultures; - - /// <summary> - /// Features that have been used. - /// </summary> - private static PersistentHashSet observedFeatures; - - /// <summary> - /// A collection of all the observations to include in the report. - /// </summary> - private static List<PersistentHashSet> observations = new List<PersistentHashSet>(); - - /// <summary> - /// The named events that we have counters for. - /// </summary> - private static Dictionary<string, PersistentCounter> events = new Dictionary<string, PersistentCounter>(StringComparer.OrdinalIgnoreCase); - - /// <summary> - /// The lock acquired while considering whether to publish a report. - /// </summary> - private static object publishingConsiderationLock = new object(); - - /// <summary> - /// The time that we last published reports. - /// </summary> - private static DateTime lastPublished = DateTime.Now; - - /// <summary> - /// Initializes static members of the <see cref="Reporting"/> class. - /// </summary> - [SuppressMessage("Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline", Justification = "We do more than field initialization here.")] - [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Reporting MUST NOT cause unhandled exceptions.")] - static Reporting() { - Enabled = DotNetOpenAuthSection.Configuration.Reporting.Enabled; - } - - /// <summary> - /// Gets or sets a value indicating whether this reporting is enabled. - /// </summary> - /// <value><c>true</c> if enabled; otherwise, <c>false</c>.</value> - /// <remarks> - /// Setting this property to <c>true</c> <i>may</i> have no effect - /// if reporting has already experienced a failure of some kind. - /// </remarks> - public static bool Enabled { - get { - return enabled && !broken; - } - - set { - if (value) { - Initialize(); - } - - // Only set the static field here, so that other threads - // don't try to use reporting while we're initializing it. - enabled = value; - } - } - - /// <summary> - /// Gets the configuration to use for reporting. - /// </summary> - private static ReportingElement Configuration { - get { return DotNetOpenAuthSection.Configuration.Reporting; } - } - - /// <summary> - /// Records an event occurrence. - /// </summary> - /// <param name="eventName">Name of the event.</param> - /// <param name="category">The category within the event. Null and empty strings are allowed, but considered the same.</param> - [SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "PersistentCounter instances are stored in a table for later use.")] - internal static void RecordEventOccurrence(string eventName, string category) { - Contract.Requires(!String.IsNullOrEmpty(eventName)); - - // In release builds, just quietly return. - if (string.IsNullOrEmpty(eventName)) { - return; - } - - if (Enabled && Configuration.IncludeEventStatistics) { - PersistentCounter counter; - lock (events) { - if (!events.TryGetValue(eventName, out counter)) { - events[eventName] = counter = new PersistentCounter(file, "event-" + SanitizeFileName(eventName) + ".txt"); - } - } - - counter.Increment(category); - Touch(); - } - } - - /// <summary> - /// Records an event occurence. - /// </summary> - /// <param name="eventNameByObjectType">The object whose type name is the event name to record.</param> - /// <param name="category">The category within the event. Null and empty strings are allowed, but considered the same.</param> - internal static void RecordEventOccurrence(object eventNameByObjectType, string category) { - Contract.Requires(eventNameByObjectType != null); - - // In release builds, just quietly return. - if (eventNameByObjectType == null) { - return; - } - - if (Enabled && Configuration.IncludeEventStatistics) { - RecordEventOccurrence(eventNameByObjectType.GetType().Name, category); - } - } - - /// <summary> - /// Records the use of a feature by name. - /// </summary> - /// <param name="feature">The feature.</param> - internal static void RecordFeatureUse(string feature) { - Contract.Requires(!String.IsNullOrEmpty(feature)); - - // In release builds, just quietly return. - if (string.IsNullOrEmpty(feature)) { - return; - } - - if (Enabled && Configuration.IncludeFeatureUsage) { - observedFeatures.Add(feature); - Touch(); - } - } - - /// <summary> - /// Records the use of a feature by object type. - /// </summary> - /// <param name="value">The object whose type is the feature to set as used.</param> - internal static void RecordFeatureUse(object value) { - Contract.Requires(value != null); - - // In release builds, just quietly return. - if (value == null) { - return; - } - - if (Enabled && Configuration.IncludeFeatureUsage) { - observedFeatures.Add(value.GetType().Name); - Touch(); - } - } - - /// <summary> - /// Records the use of a feature by object type. - /// </summary> - /// <param name="value">The object whose type is the feature to set as used.</param> - /// <param name="dependency1">Some dependency used by <paramref name="value"/>.</param> - internal static void RecordFeatureAndDependencyUse(object value, object dependency1) { - Contract.Requires(value != null); - - // In release builds, just quietly return. - if (value == null) { - return; - } - - if (Enabled && Configuration.IncludeFeatureUsage) { - StringBuilder builder = new StringBuilder(); - builder.Append(value.GetType().Name); - builder.Append(" "); - builder.Append(dependency1 != null ? dependency1.GetType().Name : "(null)"); - observedFeatures.Add(builder.ToString()); - Touch(); - } - } - - /// <summary> - /// Records the use of a feature by object type. - /// </summary> - /// <param name="value">The object whose type is the feature to set as used.</param> - /// <param name="dependency1">Some dependency used by <paramref name="value"/>.</param> - /// <param name="dependency2">Some dependency used by <paramref name="value"/>.</param> - internal static void RecordFeatureAndDependencyUse(object value, object dependency1, object dependency2) { - Contract.Requires(value != null); - - // In release builds, just quietly return. - if (value == null) { - return; - } - - if (Enabled && Configuration.IncludeFeatureUsage) { - StringBuilder builder = new StringBuilder(); - builder.Append(value.GetType().Name); - builder.Append(" "); - builder.Append(dependency1 != null ? dependency1.GetType().Name : "(null)"); - builder.Append(" "); - builder.Append(dependency2 != null ? dependency2.GetType().Name : "(null)"); - observedFeatures.Add(builder.ToString()); - Touch(); - } - } - - /// <summary> - /// Records the feature and dependency use. - /// </summary> - /// <param name="value">The consumer or service provider.</param> - /// <param name="service">The service.</param> - /// <param name="tokenManager">The token manager.</param> - /// <param name="nonceStore">The nonce store.</param> - internal static void RecordFeatureAndDependencyUse(object value, ServiceProviderDescription service, ITokenManager tokenManager, INonceStore nonceStore) { - Contract.Requires(value != null); - Contract.Requires(service != null); - Contract.Requires(tokenManager != null); - - // In release builds, just quietly return. - if (value == null || service == null || tokenManager == null) { - return; - } - - if (Enabled && Configuration.IncludeFeatureUsage) { - StringBuilder builder = new StringBuilder(); - builder.Append(value.GetType().Name); - builder.Append(" "); - builder.Append(tokenManager.GetType().Name); - if (nonceStore != null) { - builder.Append(" "); - builder.Append(nonceStore.GetType().Name); - } - builder.Append(" "); - builder.Append(service.Version); - builder.Append(" "); - builder.Append(service.UserAuthorizationEndpoint); - observedFeatures.Add(builder.ToString()); - Touch(); - } - } - - /// <summary> - /// Records statistics collected from incoming requests. - /// </summary> - /// <param name="request">The request.</param> - internal static void RecordRequestStatistics(HttpRequestInfo request) { - Contract.Requires(request != null); - - // In release builds, just quietly return. - if (request == null) { - return; - } - - if (Enabled) { - if (Configuration.IncludeCultures) { - observedCultures.Add(Thread.CurrentThread.CurrentCulture.Name); - } - - if (Configuration.IncludeLocalRequestUris && !observedRequests.IsFull) { - var requestBuilder = new UriBuilder(request.UrlBeforeRewriting); - requestBuilder.Query = null; - requestBuilder.Fragment = null; - observedRequests.Add(requestBuilder.Uri.AbsoluteUri); - } - - Touch(); - } - } - - /// <summary> - /// Initializes Reporting if it has not been initialized yet. - /// </summary> - [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "This method must never throw.")] - private static void Initialize() { - lock (initializationSync) { - if (!broken && !initialized) { - try { - file = GetIsolatedStorage(); - reportOriginIdentity = GetOrCreateOriginIdentity(); - - webRequestHandler = new StandardWebRequestHandler(); - observations.Add(observedRequests = new PersistentHashSet(file, "requests.txt", 3)); - observations.Add(observedCultures = new PersistentHashSet(file, "cultures.txt", 20)); - observations.Add(observedFeatures = new PersistentHashSet(file, "features.txt", int.MaxValue)); - - // Record site-wide features in use. - if (HttpContext.Current != null && HttpContext.Current.ApplicationInstance != null) { - // MVC or web forms? - // front-end or back end web farm? - // url rewriting? - ////RecordFeatureUse(IsMVC ? "ASP.NET MVC" : "ASP.NET Web Forms"); - } - - initialized = true; - } catch (Exception e) { - // This is supposed to be as low-risk as possible, so if it fails, just disable reporting - // and avoid rethrowing. - broken = true; - Logger.Library.Error("Error while trying to initialize reporting.", e); - } - } - } - } - - /// <summary> - /// Assembles a report for submission. - /// </summary> - /// <returns>A stream that contains the report.</returns> - private static Stream GetReport() { - var stream = new MemoryStream(); - try { - var writer = new StreamWriter(stream, Encoding.UTF8); - writer.WriteLine(reportOriginIdentity.ToString("B")); - writer.WriteLine(Util.LibraryVersion); - writer.WriteLine(".NET Framework {0}", Environment.Version); - - foreach (var observation in observations) { - observation.Flush(); - writer.WriteLine("===================================="); - writer.WriteLine(observation.FileName); - try { - using (var fileStream = new IsolatedStorageFileStream(observation.FileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, file)) { - writer.Flush(); - fileStream.CopyTo(writer.BaseStream); - } - } catch (FileNotFoundException) { - writer.WriteLine("(missing)"); - } - } - - // Not all event counters may have even loaded in this app instance. - // We flush the ones in memory, and then read all of them off disk. - foreach (var counter in events.Values) { - counter.Flush(); - } - - foreach (string eventFile in file.GetFileNames("event-*.txt")) { - writer.WriteLine("===================================="); - writer.WriteLine(eventFile); - using (var fileStream = new IsolatedStorageFileStream(eventFile, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, file)) { - writer.Flush(); - fileStream.CopyTo(writer.BaseStream); - } - } - - // Make sure the stream is positioned at the beginning. - writer.Flush(); - stream.Position = 0; - return stream; - } catch { - stream.Dispose(); - throw; - } - } - - /// <summary> - /// Sends the usage reports to the library authors. - /// </summary> - /// <returns>A value indicating whether submitting the report was successful.</returns> - private static bool SendStats() { - try { - var request = (HttpWebRequest)WebRequest.Create(wellKnownPostLocation); - request.UserAgent = Util.LibraryVersion; - request.AllowAutoRedirect = false; - request.Method = "POST"; - request.ContentType = "text/dnoa-report1"; - Stream report = GetReport(); - request.ContentLength = report.Length; - using (var requestStream = webRequestHandler.GetRequestStream(request)) { - report.CopyTo(requestStream); - } - - using (var response = webRequestHandler.GetResponse(request)) { - Logger.Library.Info("Statistical report submitted successfully."); - - // The response stream may contain a message for the webmaster. - // Since as part of the report we submit the library version number, - // the report receiving service may have alerts such as: - // "You're using an obsolete version with exploitable security vulnerabilities." - using (var responseReader = response.GetResponseReader()) { - string line = responseReader.ReadLine(); - if (line != null) { - DemuxLogMessage(line); - } - } - } - - // Report submission was successful. Reset all counters. - lock (events) { - foreach (PersistentCounter counter in events.Values) { - counter.Reset(); - counter.Flush(); - } - - // We can just delete the files for counters that are not currently loaded. - foreach (string eventFile in file.GetFileNames("event-*.txt")) { - if (!events.Values.Any(e => string.Equals(e.FileName, eventFile, StringComparison.OrdinalIgnoreCase))) { - file.DeleteFile(eventFile); - } - } - } - - return true; - } catch (ProtocolException ex) { - Logger.Library.Error("Unable to submit statistical report due to an HTTP error.", ex); - } catch (FileNotFoundException ex) { - Logger.Library.Error("Unable to submit statistical report because the report file is missing.", ex); - } - - return false; - } - - /// <summary> - /// Interprets the reporting response as a log message if possible. - /// </summary> - /// <param name="line">The line from the HTTP response to interpret as a log message.</param> - private static void DemuxLogMessage(string line) { - if (line != null) { - string[] parts = line.Split(new char[] { ' ' }, 2); - if (parts.Length == 2) { - string level = parts[0]; - string message = parts[1]; - switch (level) { - case "INFO": - Logger.Library.Info(message); - break; - case "WARN": - Logger.Library.Warn(message); - break; - case "ERROR": - Logger.Library.Error(message); - break; - case "FATAL": - Logger.Library.Fatal(message); - break; - } - } - } - } - - /// <summary> - /// Called by every internal/public method on this class to give - /// periodic operations a chance to run. - /// </summary> - private static void Touch() { - // Publish stats if it's time to do so. - lock (publishingConsiderationLock) { - if (DateTime.Now - lastPublished > Configuration.MinimumReportingInterval) { - lastPublished = DateTime.Now; - SendStatsAsync(); - } - } - } - - /// <summary> - /// Sends the stats report asynchronously, and careful to not throw any unhandled exceptions. - /// </summary> - [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Unhandled exceptions MUST NOT be thrown from here.")] - private static void SendStatsAsync() { - // Do it on a background thread since it could take a while and we - // don't want to slow down this request we're borrowing. - ThreadPool.QueueUserWorkItem(state => { - try { - SendStats(); - } catch (Exception ex) { - // Something bad and unexpected happened. Just deactivate to avoid more trouble. - Logger.Library.Error("Error while trying to submit statistical report.", ex); - broken = true; - } - }); - } - - /// <summary> - /// Gets the isolated storage to use for reporting. - /// </summary> - /// <returns>An isolated storage location appropriate for our host.</returns> - private static IsolatedStorageFile GetIsolatedStorage() { - Contract.Ensures(Contract.Result<IsolatedStorageFile>() != null); - - IsolatedStorageFile result = null; - - // We'll try for whatever storage location we can get, - // and not catch exceptions from the last attempt so that - // the overall failure is caught by our caller. - try { - // This works on Personal Web Server - result = IsolatedStorageFile.GetUserStoreForDomain(); - } catch (SecurityException) { - } catch (IsolatedStorageException) { - } - - // This works on IIS when full trust is granted. - if (result == null) { - result = IsolatedStorageFile.GetMachineStoreForDomain(); - } - - Logger.Library.InfoFormat("Reporting will use isolated storage with scope: {0}", result.Scope); - return result; - } - - /// <summary> - /// Gets a unique, pseudonymous identifier for this particular web site or application. - /// </summary> - /// <returns>A GUID that will serve as the identifier.</returns> - /// <remarks> - /// The identifier is made persistent by storing the identifier in isolated storage. - /// If an existing identifier is not found, a new one is created, persisted, and returned. - /// </remarks> - private static Guid GetOrCreateOriginIdentity() { - Contract.Requires<InvalidOperationException>(file != null); - Contract.Ensures(Contract.Result<Guid>() != Guid.Empty); - - Guid identityGuid = Guid.Empty; - const int GuidLength = 16; - using (var identityFileStream = new IsolatedStorageFileStream("identity.txt", FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.Read, file)) { - if (identityFileStream.Length == GuidLength) { - byte[] guidBytes = new byte[GuidLength]; - if (identityFileStream.Read(guidBytes, 0, GuidLength) == GuidLength) { - identityGuid = new Guid(guidBytes); - } - } - - if (identityGuid == Guid.Empty) { - identityGuid = Guid.NewGuid(); - byte[] guidBytes = identityGuid.ToByteArray(); - identityFileStream.SetLength(0); - identityFileStream.Write(guidBytes, 0, guidBytes.Length); - } - - return identityGuid; - } - } - - /// <summary> - /// Sanitizes the name of the file so it only includes valid filename characters. - /// </summary> - /// <param name="fileName">The filename to sanitize.</param> - /// <returns>The filename, with any and all invalid filename characters replaced with the hyphen (-) character.</returns> - private static string SanitizeFileName(string fileName) { - Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(fileName)); - char[] invalidCharacters = Path.GetInvalidFileNameChars(); - if (fileName.IndexOfAny(invalidCharacters) < 0) { - return fileName; // nothing invalid about this filename. - } - - // Use a stringbuilder since we may be replacing several characters - // and we don't want to instantiate a new string buffer for each new version. - StringBuilder sanitized = new StringBuilder(fileName); - foreach (char invalidChar in invalidCharacters) { - sanitized.Replace(invalidChar, '-'); - } - - return sanitized.ToString(); - } - - /// <summary> - /// A set of values that persist the set to disk. - /// </summary> - private class PersistentHashSet : IDisposable { - /// <summary> - /// The isolated persistent storage. - /// </summary> - private readonly FileStream fileStream; - - /// <summary> - /// The persistent reader. - /// </summary> - private readonly StreamReader reader; - - /// <summary> - /// The persistent writer. - /// </summary> - private readonly StreamWriter writer; - - /// <summary> - /// The total set of elements. - /// </summary> - private readonly HashSet<string> memorySet = new HashSet<string>(StringComparer.OrdinalIgnoreCase); - - /// <summary> - /// The maximum number of elements to track before not storing new elements. - /// </summary> - private readonly int maximumElements; - - /// <summary> - /// The set of new elements added to the <see cref="memorySet"/> since the last flush. - /// </summary> - private List<string> newElements = new List<string>(); - - /// <summary> - /// The time the last flush occurred. - /// </summary> - private DateTime lastFlushed; - - /// <summary> - /// A flag indicating whether the set has changed since it was last flushed. - /// </summary> - private bool dirty; - - /// <summary> - /// Initializes a new instance of the <see cref="PersistentHashSet"/> class. - /// </summary> - /// <param name="storage">The storage location.</param> - /// <param name="fileName">Name of the file.</param> - /// <param name="maximumElements">The maximum number of elements to track.</param> - internal PersistentHashSet(IsolatedStorageFile storage, string fileName, int maximumElements) { - Contract.Requires<ArgumentNullException>(storage != null); - Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(fileName)); - this.FileName = fileName; - this.maximumElements = maximumElements; - - // Load the file into memory. - this.fileStream = new IsolatedStorageFileStream(fileName, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.Read, storage); - this.reader = new StreamReader(this.fileStream, Encoding.UTF8); - while (!this.reader.EndOfStream) { - this.memorySet.Add(this.reader.ReadLine()); - } - - this.writer = new StreamWriter(this.fileStream, Utf8NoPreamble); - this.lastFlushed = DateTime.Now; - } - - /// <summary> - /// Gets a value indicating whether the hashset has reached capacity and is not storing more elements. - /// </summary> - /// <value><c>true</c> if this instance is full; otherwise, <c>false</c>.</value> - internal bool IsFull { - get { - lock (this.memorySet) { - return this.memorySet.Count >= this.maximumElements; - } - } - } - - /// <summary> - /// Gets the name of the file. - /// </summary> - /// <value>The name of the file.</value> - internal string FileName { get; private set; } - - #region IDisposable Members - - /// <summary> - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. - /// </summary> - public void Dispose() { - this.Dispose(true); - GC.SuppressFinalize(this); - } - - #endregion - - /// <summary> - /// Adds a value to the set. - /// </summary> - /// <param name="value">The value.</param> - internal void Add(string value) { - lock (this.memorySet) { - if (!this.IsFull) { - if (this.memorySet.Add(value)) { - this.newElements.Add(value); - this.dirty = true; - - if (this.IsFull) { - this.Flush(); - } - } - - if (this.dirty && DateTime.Now - this.lastFlushed > Configuration.MinimumFlushInterval) { - this.Flush(); - } - } - } - } - - /// <summary> - /// Flushes any newly added values to disk. - /// </summary> - internal void Flush() { - lock (this.memorySet) { - foreach (string element in this.newElements) { - this.writer.WriteLine(element); - } - this.writer.Flush(); - - // Assign a whole new list since future lists might be smaller in order to - // decrease demand on memory. - this.newElements = new List<string>(); - this.dirty = false; - this.lastFlushed = DateTime.Now; - } - } - - /// <summary> - /// Releases unmanaged and - optionally - managed resources - /// </summary> - /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param> - protected virtual void Dispose(bool disposing) { - if (disposing) { - this.writer.Dispose(); - this.reader.Dispose(); - this.fileStream.Dispose(); - } - } - } - - /// <summary> - /// A feature usage counter. - /// </summary> - private class PersistentCounter : IDisposable { - /// <summary> - /// The separator to use between category names and their individual counters. - /// </summary> - private static readonly char[] separator = new char[] { '\t' }; - - /// <summary> - /// The isolated persistent storage. - /// </summary> - private readonly FileStream fileStream; - - /// <summary> - /// The persistent reader. - /// </summary> - private readonly StreamReader reader; - - /// <summary> - /// The persistent writer. - /// </summary> - private readonly StreamWriter writer; - - /// <summary> - /// The time the last flush occurred. - /// </summary> - private DateTime lastFlushed; - - /// <summary> - /// The in-memory copy of the counter. - /// </summary> - private Dictionary<string, int> counters = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase); - - /// <summary> - /// A flag indicating whether the set has changed since it was last flushed. - /// </summary> - private bool dirty; - - /// <summary> - /// Initializes a new instance of the <see cref="PersistentCounter"/> class. - /// </summary> - /// <param name="storage">The storage location.</param> - /// <param name="fileName">Name of the file.</param> - internal PersistentCounter(IsolatedStorageFile storage, string fileName) { - Contract.Requires<ArgumentNullException>(storage != null); - Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(fileName)); - this.FileName = fileName; - - // Load the file into memory. - this.fileStream = new IsolatedStorageFileStream(fileName, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.Read, storage); - this.reader = new StreamReader(this.fileStream, Encoding.UTF8); - while (!this.reader.EndOfStream) { - string line = this.reader.ReadLine(); - string[] parts = line.Split(separator, 2); - int counter; - if (int.TryParse(parts[0], out counter)) { - string category = string.Empty; - if (parts.Length > 1) { - category = parts[1]; - } - this.counters[category] = counter; - } - } - - this.writer = new StreamWriter(this.fileStream, Utf8NoPreamble); - this.lastFlushed = DateTime.Now; - } - - /// <summary> - /// Gets the name of the file. - /// </summary> - /// <value>The name of the file.</value> - internal string FileName { get; private set; } - - #region IDisposable Members - - /// <summary> - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. - /// </summary> - public void Dispose() { - this.Dispose(true); - GC.SuppressFinalize(this); - } - - #endregion - - /// <summary> - /// Increments the counter. - /// </summary> - /// <param name="category">The category within the event. Null and empty strings are allowed, but considered the same.</param> - internal void Increment(string category) { - if (category == null) { - category = string.Empty; - } - lock (this) { - int counter; - this.counters.TryGetValue(category, out counter); - this.counters[category] = counter + 1; - this.dirty = true; - if (this.dirty && DateTime.Now - this.lastFlushed > Configuration.MinimumFlushInterval) { - this.Flush(); - } - } - } - - /// <summary> - /// Flushes any newly added values to disk. - /// </summary> - internal void Flush() { - lock (this) { - this.writer.BaseStream.Position = 0; - this.writer.BaseStream.SetLength(0); // truncate file - foreach (var pair in this.counters) { - this.writer.Write(pair.Value); - this.writer.Write(separator[0]); - this.writer.WriteLine(pair.Key); - } - this.writer.Flush(); - this.dirty = false; - this.lastFlushed = DateTime.Now; - } - } - - /// <summary> - /// Resets all counters. - /// </summary> - internal void Reset() { - lock (this) { - this.counters.Clear(); - } - } - - /// <summary> - /// Releases unmanaged and - optionally - managed resources - /// </summary> - /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param> - protected virtual void Dispose(bool disposing) { - if (disposing) { - this.writer.Dispose(); - this.reader.Dispose(); - this.fileStream.Dispose(); - } - } - } - } -} diff --git a/src/DotNetOpenAuth/Strings.Designer.cs b/src/DotNetOpenAuth/Strings.Designer.cs deleted file mode 100644 index 1461f83..0000000 --- a/src/DotNetOpenAuth/Strings.Designer.cs +++ /dev/null @@ -1,99 +0,0 @@ -//------------------------------------------------------------------------------ -// <auto-generated> -// This code was generated by a tool. -// Runtime Version:4.0.30319.1 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// </auto-generated> -//------------------------------------------------------------------------------ - -namespace DotNetOpenAuth { - 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 Strings { - - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal Strings() { - } - - /// <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.Strings", typeof(Strings).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 configuration-specified type {0} must be public, and is not.. - /// </summary> - internal static string ConfigurationTypeMustBePublic { - get { - return ResourceManager.GetString("ConfigurationTypeMustBePublic", resourceCulture); - } - } - - /// <summary> - /// Looks up a localized string similar to The configuration XAML reference to {0} requires a current HttpContext to resolve.. - /// </summary> - internal static string ConfigurationXamlReferenceRequiresHttpContext { - get { - return ResourceManager.GetString("ConfigurationXamlReferenceRequiresHttpContext", resourceCulture); - } - } - - /// <summary> - /// Looks up a localized string similar to The current IHttpHandler is not one of types: {0}. An embedded resource URL provider must be set in your .config file.. - /// </summary> - internal static string EmbeddedResourceUrlProviderRequired { - get { - return ResourceManager.GetString("EmbeddedResourceUrlProviderRequired", resourceCulture); - } - } - - /// <summary> - /// Looks up a localized string similar to No current HttpContext was detected, so an {0} instance must be explicitly provided or specified in the .config file. Call the constructor overload that takes an {0}.. - /// </summary> - internal static string StoreRequiredWhenNoHttpContextAvailable { - get { - return ResourceManager.GetString("StoreRequiredWhenNoHttpContextAvailable", resourceCulture); - } - } - } -} diff --git a/src/DotNetOpenAuth/Strings.resx b/src/DotNetOpenAuth/Strings.resx deleted file mode 100644 index 4b78664..0000000 --- a/src/DotNetOpenAuth/Strings.resx +++ /dev/null @@ -1,132 +0,0 @@ -<?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="ConfigurationTypeMustBePublic" xml:space="preserve"> - <value>The configuration-specified type {0} must be public, and is not.</value> - </data> - <data name="StoreRequiredWhenNoHttpContextAvailable" xml:space="preserve"> - <value>No current HttpContext was detected, so an {0} instance must be explicitly provided or specified in the .config file. Call the constructor overload that takes an {0}.</value> - </data> - <data name="ConfigurationXamlReferenceRequiresHttpContext" xml:space="preserve"> - <value>The configuration XAML reference to {0} requires a current HttpContext to resolve.</value> - </data> - <data name="EmbeddedResourceUrlProviderRequired" xml:space="preserve"> - <value>The current IHttpHandler is not one of types: {0}. An embedded resource URL provider must be set in your .config file.</value> - </data> -</root>
\ No newline at end of file diff --git a/src/DotNetOpenAuth/UriUtil.cs b/src/DotNetOpenAuth/UriUtil.cs deleted file mode 100644 index 819c406..0000000 --- a/src/DotNetOpenAuth/UriUtil.cs +++ /dev/null @@ -1,117 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="UriUtil.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth { - using System; - using System.Collections.Specialized; - using System.Diagnostics.CodeAnalysis; - using System.Diagnostics.Contracts; - using System.Linq; - using System.Text.RegularExpressions; - using System.Web; - using System.Web.UI; - using DotNetOpenAuth.Messaging; - - /// <summary> - /// Utility methods for working with URIs. - /// </summary> - [ContractVerification(true)] - internal static class UriUtil { - /// <summary> - /// Tests a URI for the presence of an OAuth payload. - /// </summary> - /// <param name="uri">The URI to test.</param> - /// <param name="prefix">The prefix.</param> - /// <returns> - /// True if the URI contains an OAuth message. - /// </returns> - [ContractVerification(false)] // bugs/limitations in CC static analysis - internal static bool QueryStringContainPrefixedParameters(this Uri uri, string prefix) { - Contract.Requires<ArgumentException>(!string.IsNullOrEmpty(prefix)); - if (uri == null) { - return false; - } - - NameValueCollection nvc = HttpUtility.ParseQueryString(uri.Query); - Contract.Assume(nvc != null); // BCL - return nvc.Keys.OfType<string>().Any(key => key.StartsWith(prefix, StringComparison.Ordinal)); - } - - /// <summary> - /// Determines whether some <see cref="Uri"/> is using HTTPS. - /// </summary> - /// <param name="uri">The Uri being tested for security.</param> - /// <returns> - /// <c>true</c> if the URI represents an encrypted request; otherwise, <c>false</c>. - /// </returns> - internal static bool IsTransportSecure(this Uri uri) { - Contract.Requires<ArgumentNullException>(uri != null); - return string.Equals(uri.Scheme, "https", StringComparison.OrdinalIgnoreCase); - } - - /// <summary> - /// Equivalent to UriBuilder.ToString() but omits port # if it may be implied. - /// Equivalent to UriBuilder.Uri.ToString(), but doesn't throw an exception if the Host has a wildcard. - /// </summary> - /// <param name="builder">The UriBuilder to render as a string.</param> - /// <returns>The string version of the Uri.</returns> - internal static string ToStringWithImpliedPorts(this UriBuilder builder) { - Contract.Requires<ArgumentNullException>(builder != null); - Contract.Ensures(Contract.Result<string>() != null); - - // We only check for implied ports on HTTP and HTTPS schemes since those - // are the only ones supported by OpenID anyway. - if ((builder.Port == 80 && string.Equals(builder.Scheme, "http", StringComparison.OrdinalIgnoreCase)) || - (builder.Port == 443 && string.Equals(builder.Scheme, "https", StringComparison.OrdinalIgnoreCase))) { - // An implied port may be removed. - string url = builder.ToString(); - - // Be really careful to only remove the first :80 or :443 so we are guaranteed - // we're removing only the port (and not something in the query string that - // looks like a port. - string result = Regex.Replace(url, @"^(https?://[^:]+):\d+", m => m.Groups[1].Value, RegexOptions.IgnoreCase); - Contract.Assume(result != null); // Regex.Replace never returns null - return result; - } else { - // The port must be explicitly given anyway. - return builder.ToString(); - } - } - - /// <summary> - /// Validates that a URL will be resolvable at runtime. - /// </summary> - /// <param name="page">The page hosting the control that receives this URL as a property.</param> - /// <param name="designMode">If set to <c>true</c> the page is in design-time mode rather than runtime mode.</param> - /// <param name="value">The URI to check.</param> - /// <exception cref="UriFormatException">Thrown if the given URL is not a valid, resolvable URI.</exception> - [SuppressMessage("Microsoft.Usage", "CA1806:DoNotIgnoreMethodResults", MessageId = "System.Uri", Justification = "Just to throw an exception on invalid input.")] - internal static void ValidateResolvableUrl(Page page, bool designMode, string value) { - if (string.IsNullOrEmpty(value)) { - return; - } - - if (page != null && !designMode) { - Contract.Assume(page.Request != null); - - // Validate new value by trying to construct a Realm object based on it. - string relativeUrl = page.ResolveUrl(value); - Contract.Assume(page.Request.Url != null); - Contract.Assume(relativeUrl != null); - new Uri(page.Request.Url, relativeUrl); // throws an exception on failure. - } else { - // We can't fully test it, but it should start with either ~/ or a protocol. - if (Regex.IsMatch(value, @"^https?://")) { - new Uri(value); // make sure it's fully-qualified, but ignore wildcards - } else if (value.StartsWith("~/", StringComparison.Ordinal)) { - // this is valid too - } else { - throw new UriFormatException(); - } - } - } - } -} diff --git a/src/DotNetOpenAuth/Util.cs b/src/DotNetOpenAuth/Util.cs deleted file mode 100644 index 0317c4d..0000000 --- a/src/DotNetOpenAuth/Util.cs +++ /dev/null @@ -1,229 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="Util.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- -namespace DotNetOpenAuth { - using System; - using System.Collections.Generic; - using System.Diagnostics.Contracts; - using System.Globalization; - using System.Net; - using System.Reflection; - using System.Text; - using System.Web; - using System.Web.UI; - - using DotNetOpenAuth.Configuration; - using DotNetOpenAuth.Messaging; - - /// <summary> - /// A grab-bag utility class. - /// </summary> - [ContractVerification(true)] - internal static class Util { - /// <summary> - /// The base namespace for this library from which all other namespaces derive. - /// </summary> - internal const string DefaultNamespace = "DotNetOpenAuth"; - - /// <summary> - /// The web.config file-specified provider of web resource URLs. - /// </summary> - private static IEmbeddedResourceRetrieval embeddedResourceRetrieval = DotNetOpenAuthSection.Configuration.EmbeddedResourceRetrievalProvider.CreateInstance(null, false); - - /// <summary> - /// Gets a human-readable description of the library name and version, including - /// whether the build is an official or private one. - /// </summary> - public static string LibraryVersion { - get { - string assemblyFullName = Assembly.GetExecutingAssembly().FullName; - bool official = assemblyFullName.Contains("PublicKeyToken=2780ccd10d57b246"); - - // We use InvariantCulture since this is used for logging. - return string.Format(CultureInfo.InvariantCulture, "{0} ({1})", assemblyFullName, official ? "official" : "private"); - } - } - - /// <summary> - /// Tests for equality between two objects. Safely handles the case where one or both are null. - /// </summary> - /// <typeparam name="T">The type of objects been checked for equality.</typeparam> - /// <param name="first">The first object.</param> - /// <param name="second">The second object.</param> - /// <returns><c>true</c> if the two objects are equal; <c>false</c> otherwise.</returns> - internal static bool EqualsNullSafe<T>(this T first, T second) where T : class { - // If one is null and the other is not... - if (object.ReferenceEquals(first, null) ^ object.ReferenceEquals(second, null)) { - return false; - } - - // If both are null... (we only check one because we already know both are either null or non-null) - if (object.ReferenceEquals(first, null)) { - return true; - } - - // Neither are null. Delegate to the Equals method. - return first.Equals(second); - } - - /// <summary> - /// Prepares a dictionary for printing as a string. - /// </summary> - /// <typeparam name="K">The type of the key.</typeparam> - /// <typeparam name="V">The type of the value.</typeparam> - /// <param name="pairs">The dictionary or sequence of name-value pairs.</param> - /// <returns>An object whose ToString method will perform the actual work of generating the string.</returns> - /// <remarks> - /// The work isn't done until (and if) the - /// <see cref="Object.ToString"/> method is actually called, which makes it great - /// for logging complex objects without being in a conditional block. - /// </remarks> - internal static object ToStringDeferred<K, V>(this IEnumerable<KeyValuePair<K, V>> pairs) { - return new DelayedToString<IEnumerable<KeyValuePair<K, V>>>( - pairs, - p => { - ////Contract.Requires(pairs != null); // CC: anonymous method can't handle it - ErrorUtilities.VerifyArgumentNotNull(pairs, "pairs"); - var dictionary = pairs as IDictionary<K, V>; - StringBuilder sb = new StringBuilder(dictionary != null ? dictionary.Count * 40 : 200); - foreach (var pair in pairs) { - sb.AppendFormat("\t{0}: {1}{2}", pair.Key, pair.Value, Environment.NewLine); - } - return sb.ToString(); - }); - } - - /// <summary> - /// Offers deferred ToString processing for a list of elements, that are assumed - /// to generate just a single-line string. - /// </summary> - /// <typeparam name="T">The type of elements contained in the list.</typeparam> - /// <param name="list">The list of elements.</param> - /// <returns>An object whose ToString method will perform the actual work of generating the string.</returns> - internal static object ToStringDeferred<T>(this IEnumerable<T> list) { - return ToStringDeferred<T>(list, false); - } - - /// <summary> - /// Offers deferred ToString processing for a list of elements. - /// </summary> - /// <typeparam name="T">The type of elements contained in the list.</typeparam> - /// <param name="list">The list of elements.</param> - /// <param name="multiLineElements">if set to <c>true</c>, special formatting will be applied to the output to make it clear where one element ends and the next begins.</param> - /// <returns>An object whose ToString method will perform the actual work of generating the string.</returns> - [ContractVerification(false)] - internal static object ToStringDeferred<T>(this IEnumerable<T> list, bool multiLineElements) { - return new DelayedToString<IEnumerable<T>>( - list, - l => { - // Code contracts not allowed in generator methods. - ErrorUtilities.VerifyArgumentNotNull(l, "l"); - - string newLine = Environment.NewLine; - ////Contract.Assume(newLine != null && newLine.Length > 0); - StringBuilder sb = new StringBuilder(); - if (multiLineElements) { - sb.AppendLine("[{"); - foreach (T obj in l) { - // Prepare the string repersentation of the object - string objString = obj != null ? obj.ToString() : "<NULL>"; - - // Indent every line printed - objString = objString.Replace(newLine, Environment.NewLine + "\t"); - sb.Append("\t"); - sb.Append(objString); - - if (!objString.EndsWith(Environment.NewLine, StringComparison.Ordinal)) { - sb.AppendLine(); - } - sb.AppendLine("}, {"); - } - if (sb.Length > 2 + Environment.NewLine.Length) { // if anything was in the enumeration - sb.Length -= 2 + Environment.NewLine.Length; // trim off the last ", {\r\n" - } else { - sb.Length -= 1 + Environment.NewLine.Length; // trim off the opening { - } - sb.Append("]"); - return sb.ToString(); - } else { - sb.Append("{"); - foreach (T obj in l) { - sb.Append(obj != null ? obj.ToString() : "<NULL>"); - sb.AppendLine(","); - } - if (sb.Length > 1) { - sb.Length -= 1; - } - sb.Append("}"); - return sb.ToString(); - } - }); - } - - /// <summary> - /// Gets the web resource URL from a Page or <see cref="IEmbeddedResourceRetrieval"/> object. - /// </summary> - /// <param name="someTypeInResourceAssembly">Some type in resource assembly.</param> - /// <param name="manifestResourceName">Name of the manifest resource.</param> - /// <returns>An absolute URL</returns> - internal static string GetWebResourceUrl(Type someTypeInResourceAssembly, string manifestResourceName) { - Page page; - IEmbeddedResourceRetrieval retrieval; - - if (embeddedResourceRetrieval != null) { - Uri url = embeddedResourceRetrieval.GetWebResourceUrl(someTypeInResourceAssembly, manifestResourceName); - return url != null ? url.AbsoluteUri : null; - } else if ((page = HttpContext.Current.CurrentHandler as Page) != null) { - return page.ClientScript.GetWebResourceUrl(someTypeInResourceAssembly, manifestResourceName); - } else if ((retrieval = HttpContext.Current.CurrentHandler as IEmbeddedResourceRetrieval) != null) { - return retrieval.GetWebResourceUrl(someTypeInResourceAssembly, manifestResourceName).AbsoluteUri; - } else { - throw new InvalidOperationException( - string.Format( - CultureInfo.CurrentCulture, - Strings.EmbeddedResourceUrlProviderRequired, - string.Join(", ", new string[] { typeof(Page).FullName, typeof(IEmbeddedResourceRetrieval).FullName }))); - } - } - - /// <summary> - /// Manages an individual deferred ToString call. - /// </summary> - /// <typeparam name="T">The type of object to be serialized as a string.</typeparam> - private class DelayedToString<T> { - /// <summary> - /// The object that will be serialized if called upon. - /// </summary> - private readonly T obj; - - /// <summary> - /// The method used to serialize <see cref="obj"/> to string form. - /// </summary> - private readonly Func<T, string> toString; - - /// <summary> - /// Initializes a new instance of the DelayedToString class. - /// </summary> - /// <param name="obj">The object that may be serialized to string form.</param> - /// <param name="toString">The method that will serialize the object if called upon.</param> - public DelayedToString(T obj, Func<T, string> toString) { - Contract.Requires<ArgumentNullException>(toString != null); - - this.obj = obj; - this.toString = toString; - } - - /// <summary> - /// Returns a <see cref="T:System.String"/> that represents the current <see cref="T:System.Object"/>. - /// </summary> - /// <returns> - /// A <see cref="T:System.String"/> that represents the current <see cref="T:System.Object"/>. - /// </returns> - public override string ToString() { - return this.toString(this.obj) ?? string.Empty; - } - } - } -} diff --git a/src/DotNetOpenAuth/Xrds/TypeElement.cs b/src/DotNetOpenAuth/Xrds/TypeElement.cs deleted file mode 100644 index c413629..0000000 --- a/src/DotNetOpenAuth/Xrds/TypeElement.cs +++ /dev/null @@ -1,34 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="TypeElement.cs" company="Andrew Arnott, Scott Hanselman"> -// Copyright (c) Andrew Arnott, Scott Hanselman. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.Xrds { - using System; - using System.Diagnostics.Contracts; - using System.Xml.XPath; - - /// <summary> - /// The Type element in an XRDS document. - /// </summary> - internal class TypeElement : XrdsNode { - /// <summary> - /// Initializes a new instance of the <see cref="TypeElement"/> class. - /// </summary> - /// <param name="typeElement">The type element.</param> - /// <param name="parent">The parent.</param> - public TypeElement(XPathNavigator typeElement, ServiceElement parent) : - base(typeElement, parent) { - Contract.Requires<ArgumentNullException>(typeElement != null); - Contract.Requires<ArgumentNullException>(parent != null); - } - - /// <summary> - /// Gets the URI. - /// </summary> - public string Uri { - get { return Node.Value; } - } - } -} diff --git a/src/DotNetOpenAuth/Xrds/XrdsNode.cs b/src/DotNetOpenAuth/Xrds/XrdsNode.cs deleted file mode 100644 index 39bd9b9..0000000 --- a/src/DotNetOpenAuth/Xrds/XrdsNode.cs +++ /dev/null @@ -1,69 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="XrdsNode.cs" company="Andrew Arnott, Scott Hanselman"> -// Copyright (c) Andrew Arnott, Scott Hanselman. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.Xrds { - using System; - using System.Diagnostics.Contracts; - using System.Xml; - using System.Xml.XPath; - using DotNetOpenAuth.Messaging; - - /// <summary> - /// A node in an XRDS document. - /// </summary> - internal class XrdsNode { - /// <summary> - /// The XRD namespace xri://$xrd*($v*2.0) - /// </summary> - internal const string XrdNamespace = "xri://$xrd*($v*2.0)"; - - /// <summary> - /// The XRDS namespace xri://$xrds - /// </summary> - internal const string XrdsNamespace = "xri://$xrds"; - - /// <summary> - /// Initializes a new instance of the <see cref="XrdsNode"/> class. - /// </summary> - /// <param name="node">The node represented by this instance.</param> - /// <param name="parentNode">The parent node.</param> - protected XrdsNode(XPathNavigator node, XrdsNode parentNode) { - Contract.Requires<ArgumentNullException>(node != null); - Contract.Requires<ArgumentNullException>(parentNode != null); - - this.Node = node; - this.ParentNode = parentNode; - this.XmlNamespaceResolver = this.ParentNode.XmlNamespaceResolver; - } - - /// <summary> - /// Initializes a new instance of the <see cref="XrdsNode"/> class. - /// </summary> - /// <param name="document">The document's root node, which this instance represents.</param> - protected XrdsNode(XPathNavigator document) { - Contract.Requires<ArgumentNullException>(document != null); - Contract.Requires<ArgumentException>(document.NameTable != null); - - this.Node = document; - this.XmlNamespaceResolver = new XmlNamespaceManager(document.NameTable); - } - - /// <summary> - /// Gets the node. - /// </summary> - internal XPathNavigator Node { get; private set; } - - /// <summary> - /// Gets the parent node, or null if this is the root node. - /// </summary> - protected internal XrdsNode ParentNode { get; private set; } - - /// <summary> - /// Gets the XML namespace resolver to use in XPath expressions. - /// </summary> - protected internal XmlNamespaceManager XmlNamespaceResolver { get; private set; } - } -} diff --git a/src/DotNetOpenAuth/Yadis/HtmlParser.cs b/src/DotNetOpenAuth/Yadis/HtmlParser.cs deleted file mode 100644 index a6b64c1..0000000 --- a/src/DotNetOpenAuth/Yadis/HtmlParser.cs +++ /dev/null @@ -1,142 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="HtmlParser.cs" company="Andrew Arnott, Scott Hanselman, Jason Alexander"> -// Copyright (c) Andrew Arnott, Scott Hanselman, Jason Alexander. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.Yadis { - using System; - using System.Collections.Generic; - using System.Diagnostics.Contracts; - using System.Globalization; - using System.Linq; - using System.Text; - using System.Text.RegularExpressions; - using System.Web; - using System.Web.UI.HtmlControls; - - /// <summary> - /// An HTML HEAD tag parser. - /// </summary> - internal static class HtmlParser { - /// <summary> - /// Common flags to use on regex tests. - /// </summary> - private const RegexOptions Flags = RegexOptions.IgnorePatternWhitespace | RegexOptions.Singleline | RegexOptions.Compiled | RegexOptions.IgnoreCase; - - /// <summary> - /// A regular expression designed to select tags (?) - /// </summary> - private const string TagExpr = "\n# Starts with the tag name at a word boundary, where the tag name is\n# not a namespace\n<{0}\\b(?!:)\n \n# All of the stuff up to a \">\", hopefully attributes.\n(?<attrs>[^>]*?)\n \n(?: # Match a short tag\n />\n \n| # Match a full tag\n >\n \n (?<contents>.*?)\n \n # Closed by\n (?: # One of the specified close tags\n </?{1}\\s*>\n \n # End of the string\n | \\Z\n \n )\n \n)\n "; - - /// <summary> - /// A regular expression designed to select start tags (?) - /// </summary> - private const string StartTagExpr = "\n# Starts with the tag name at a word boundary, where the tag name is\n# not a namespace\n<{0}\\b(?!:)\n \n# All of the stuff up to a \">\", hopefully attributes.\n(?<attrs>[^>]*?)\n \n(?: # Match a short tag\n />\n \n| # Match a full tag\n >\n )\n "; - - /// <summary> - /// A regular expression designed to select attributes within a tag. - /// </summary> - private static readonly Regex attrRe = new Regex("\n# Must start with a sequence of word-characters, followed by an equals sign\n(?<attrname>(\\w|-)+)=\n\n# Then either a quoted or unquoted attribute\n(?:\n\n # Match everything that's between matching quote marks\n (?<qopen>[\"\\'])(?<attrval>.*?)\\k<qopen>\n|\n\n # If the value is not quoted, match up to whitespace\n (?<attrval>(?:[^\\s<>/]|/(?!>))+)\n)\n\n|\n\n(?<endtag>[<>])\n ", Flags); - - /// <summary> - /// A regular expression designed to select the HEAD tag. - /// </summary> - private static readonly Regex headRe = TagMatcher("head", new[] { "body" }); - - /// <summary> - /// A regular expression designed to select the HTML tag. - /// </summary> - private static readonly Regex htmlRe = TagMatcher("html", new string[0]); - - /// <summary> - /// A regular expression designed to remove all comments and scripts from a string. - /// </summary> - private static readonly Regex removedRe = new Regex(@"<!--.*?-->|<!\[CDATA\[.*?\]\]>|<script\b[^>]*>.*?</script>", Flags); - - /// <summary> - /// Finds all the HTML HEAD tag child elements that match the tag name of a given type. - /// </summary> - /// <typeparam name="T">The HTML tag of interest.</typeparam> - /// <param name="html">The HTML to scan.</param> - /// <returns>A sequence of the matching elements.</returns> - public static IEnumerable<T> HeadTags<T>(string html) where T : HtmlControl, new() { - html = removedRe.Replace(html, string.Empty); - Match match = htmlRe.Match(html); - string tagName = (new T()).TagName; - if (match.Success) { - Match match2 = headRe.Match(html, match.Index, match.Length); - if (match2.Success) { - string text = null; - string text2 = null; - Regex regex = StartTagMatcher(tagName); - for (Match match3 = regex.Match(html, match2.Index, match2.Length); match3.Success; match3 = match3.NextMatch()) { - int beginning = (match3.Index + tagName.Length) + 1; - int length = (match3.Index + match3.Length) - beginning; - Match match4 = attrRe.Match(html, beginning, length); - var headTag = new T(); - while (match4.Success) { - if (match4.Groups["endtag"].Success) { - break; - } - text = match4.Groups["attrname"].Value; - text2 = HttpUtility.HtmlDecode(match4.Groups["attrval"].Value); - headTag.Attributes.Add(text, text2); - match4 = match4.NextMatch(); - } - yield return headTag; - } - } - } - } - - /// <summary> - /// Filters a list of controls based on presence of an attribute. - /// </summary> - /// <typeparam name="T">The type of HTML controls being filtered.</typeparam> - /// <param name="sequence">The sequence.</param> - /// <param name="attribute">The attribute.</param> - /// <returns>A filtered sequence of attributes.</returns> - internal static IEnumerable<T> WithAttribute<T>(this IEnumerable<T> sequence, string attribute) where T : HtmlControl { - Contract.Requires<ArgumentNullException>(sequence != null); - Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(attribute)); - return sequence.Where(tag => tag.Attributes[attribute] != null); - } - - /// <summary> - /// Generates a regular expression that will find a given HTML tag. - /// </summary> - /// <param name="tagName">Name of the tag.</param> - /// <param name="closeTags">The close tags (?).</param> - /// <returns>The created regular expression.</returns> - private static Regex TagMatcher(string tagName, params string[] closeTags) { - string text2; - if (closeTags.Length > 0) { - StringBuilder builder = new StringBuilder(); - builder.AppendFormat("(?:{0}", tagName); - int index = 0; - string[] textArray = closeTags; - int length = textArray.Length; - while (index < length) { - string text = textArray[index]; - index++; - builder.AppendFormat("|{0}", text); - } - builder.Append(")"); - text2 = builder.ToString(); - } else { - text2 = tagName; - } - return new Regex(string.Format(CultureInfo.InvariantCulture, TagExpr, tagName, text2), Flags); - } - - /// <summary> - /// Generates a regular expression designed to find a given tag. - /// </summary> - /// <param name="tagName">The tag to find.</param> - /// <returns>The created regular expression.</returns> - private static Regex StartTagMatcher(string tagName) { - return new Regex(string.Format(CultureInfo.InvariantCulture, StartTagExpr, tagName), Flags); - } - } -} diff --git a/src/DotNetOpenAuth/Yadis/Yadis.cs b/src/DotNetOpenAuth/Yadis/Yadis.cs deleted file mode 100644 index 357dd8d..0000000 --- a/src/DotNetOpenAuth/Yadis/Yadis.cs +++ /dev/null @@ -1,205 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="Yadis.cs" company="Andrew Arnott, Scott Hanselman"> -// Copyright (c) Andrew Arnott, Scott Hanselman. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.Yadis { - using System; - using System.Diagnostics.Contracts; - using System.IO; - using System.Net; - using System.Net.Cache; - using System.Web.UI.HtmlControls; - using System.Xml; - using DotNetOpenAuth.Configuration; - using DotNetOpenAuth.Messaging; - using DotNetOpenAuth.OpenId; - using DotNetOpenAuth.Xrds; - - /// <summary> - /// YADIS discovery manager. - /// </summary> - internal class Yadis { - /// <summary> - /// The HTTP header to look for in responses to declare where the XRDS document should be found. - /// </summary> - internal const string HeaderName = "X-XRDS-Location"; - - /// <summary> - /// Gets or sets the cache that can be used for HTTP requests made during identifier discovery. - /// </summary> -#if DEBUG - internal static readonly RequestCachePolicy IdentifierDiscoveryCachePolicy = new HttpRequestCachePolicy(HttpRequestCacheLevel.BypassCache); -#else - internal static readonly RequestCachePolicy IdentifierDiscoveryCachePolicy = new HttpRequestCachePolicy(DotNetOpenAuthSection.Configuration.OpenId.CacheDiscovery ? HttpRequestCacheLevel.CacheIfAvailable : HttpRequestCacheLevel.BypassCache); -#endif - - /// <summary> - /// The maximum number of bytes to read from an HTTP response - /// in searching for a link to a YADIS document. - /// </summary> - internal const int MaximumResultToScan = 1024 * 1024; - - /// <summary> - /// Performs YADIS discovery on some identifier. - /// </summary> - /// <param name="requestHandler">The mechanism to use for sending HTTP requests.</param> - /// <param name="uri">The URI to perform discovery on.</param> - /// <param name="requireSsl">Whether discovery should fail if any step of it is not encrypted.</param> - /// <returns> - /// The result of discovery on the given URL. - /// Null may be returned if an error occurs, - /// or if <paramref name="requireSsl"/> is true but part of discovery - /// is not protected by SSL. - /// </returns> - public static DiscoveryResult Discover(IDirectWebRequestHandler requestHandler, UriIdentifier uri, bool requireSsl) { - CachedDirectWebResponse response; - try { - if (requireSsl && !string.Equals(uri.Uri.Scheme, Uri.UriSchemeHttps, StringComparison.OrdinalIgnoreCase)) { - Logger.Yadis.WarnFormat("Discovery on insecure identifier '{0}' aborted.", uri); - return null; - } - response = Request(requestHandler, uri, requireSsl, ContentTypes.Html, ContentTypes.XHtml, ContentTypes.Xrds).GetSnapshot(MaximumResultToScan); - if (response.Status != System.Net.HttpStatusCode.OK) { - Logger.Yadis.ErrorFormat("HTTP error {0} {1} while performing discovery on {2}.", (int)response.Status, response.Status, uri); - return null; - } - } catch (ArgumentException ex) { - // Unsafe URLs generate this - Logger.Yadis.WarnFormat("Unsafe OpenId URL detected ({0}). Request aborted. {1}", uri, ex); - return null; - } - CachedDirectWebResponse response2 = null; - if (IsXrdsDocument(response)) { - Logger.Yadis.Debug("An XRDS response was received from GET at user-supplied identifier."); - Reporting.RecordEventOccurrence("Yadis", "XRDS in initial response"); - response2 = response; - } else { - string uriString = response.Headers.Get(HeaderName); - Uri url = null; - if (uriString != null) { - if (Uri.TryCreate(uriString, UriKind.Absolute, out url)) { - Logger.Yadis.DebugFormat("{0} found in HTTP header. Preparing to pull XRDS from {1}", HeaderName, url); - Reporting.RecordEventOccurrence("Yadis", "XRDS referenced in HTTP header"); - } - } - if (url == null && response.ContentType != null && (response.ContentType.MediaType == ContentTypes.Html || response.ContentType.MediaType == ContentTypes.XHtml)) { - url = FindYadisDocumentLocationInHtmlMetaTags(response.GetResponseString()); - if (url != null) { - Logger.Yadis.DebugFormat("{0} found in HTML Http-Equiv tag. Preparing to pull XRDS from {1}", HeaderName, url); - Reporting.RecordEventOccurrence("Yadis", "XRDS referenced in HTML"); - } - } - if (url != null) { - if (!requireSsl || string.Equals(url.Scheme, Uri.UriSchemeHttps, StringComparison.OrdinalIgnoreCase)) { - response2 = Request(requestHandler, url, requireSsl, ContentTypes.Xrds).GetSnapshot(MaximumResultToScan); - if (response2.Status != HttpStatusCode.OK) { - Logger.Yadis.ErrorFormat("HTTP error {0} {1} while performing discovery on {2}.", (int)response2.Status, response2.Status, uri); - } - } else { - Logger.Yadis.WarnFormat("XRDS document at insecure location '{0}'. Aborting YADIS discovery.", url); - } - } - } - return new DiscoveryResult(uri, response, response2); - } - - /// <summary> - /// Searches an HTML document for a - /// <meta http-equiv="X-XRDS-Location" content="{YadisURL}"> - /// tag and returns the content of YadisURL. - /// </summary> - /// <param name="html">The HTML to search.</param> - /// <returns>The URI of the XRDS document if found; otherwise <c>null</c>.</returns> - public static Uri FindYadisDocumentLocationInHtmlMetaTags(string html) { - foreach (var metaTag in HtmlParser.HeadTags<HtmlMeta>(html)) { - if (HeaderName.Equals(metaTag.HttpEquiv, StringComparison.OrdinalIgnoreCase)) { - if (metaTag.Content != null) { - Uri uri; - if (Uri.TryCreate(metaTag.Content, UriKind.Absolute, out uri)) { - return uri; - } - } - } - } - return null; - } - - /// <summary> - /// Sends a YADIS HTTP request as part of identifier discovery. - /// </summary> - /// <param name="requestHandler">The request handler to use to actually submit the request.</param> - /// <param name="uri">The URI to GET.</param> - /// <param name="requireSsl">Whether only HTTPS URLs should ever be retrieved.</param> - /// <param name="acceptTypes">The value of the Accept HTTP header to include in the request.</param> - /// <returns>The HTTP response retrieved from the request.</returns> - internal static IncomingWebResponse Request(IDirectWebRequestHandler requestHandler, Uri uri, bool requireSsl, params string[] acceptTypes) { - Contract.Requires<ArgumentNullException>(requestHandler != null); - Contract.Requires<ArgumentNullException>(uri != null); - Contract.Ensures(Contract.Result<IncomingWebResponse>() != null); - Contract.Ensures(Contract.Result<IncomingWebResponse>().ResponseStream != null); - - HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uri); - request.CachePolicy = IdentifierDiscoveryCachePolicy; - if (acceptTypes != null) { - request.Accept = string.Join(",", acceptTypes); - } - - DirectWebRequestOptions options = DirectWebRequestOptions.None; - if (requireSsl) { - options |= DirectWebRequestOptions.RequireSsl; - } - - try { - return requestHandler.GetResponse(request, options); - } catch (ProtocolException ex) { - var webException = ex.InnerException as WebException; - if (webException != null) { - var response = webException.Response as HttpWebResponse; - if (response != null && response.IsFromCache) { - // We don't want to report error responses from the cache, since the server may have fixed - // whatever was causing the problem. So try again with cache disabled. - Logger.Messaging.Error("An HTTP error response was obtained from the cache. Retrying with cache disabled.", ex); - var nonCachingRequest = request.Clone(); - nonCachingRequest.CachePolicy = new HttpRequestCachePolicy(HttpRequestCacheLevel.Reload); - return requestHandler.GetResponse(nonCachingRequest, options); - } - } - - throw; - } - } - - /// <summary> - /// Determines whether a given HTTP response constitutes an XRDS document. - /// </summary> - /// <param name="response">The response to test.</param> - /// <returns> - /// <c>true</c> if the response constains an XRDS document; otherwise, <c>false</c>. - /// </returns> - private static bool IsXrdsDocument(CachedDirectWebResponse response) { - if (response.ContentType == null) { - return false; - } - - if (response.ContentType.MediaType == ContentTypes.Xrds) { - return true; - } - - if (response.ContentType.MediaType == ContentTypes.Xml) { - // This COULD be an XRDS document with an imprecise content-type. - response.ResponseStream.Seek(0, SeekOrigin.Begin); - XmlReader reader = XmlReader.Create(response.ResponseStream); - while (reader.Read() && reader.NodeType != XmlNodeType.Element) { - // intentionally blank - } - if (reader.NamespaceURI == XrdsNode.XrdsNamespace && reader.Name == "XRDS") { - return true; - } - } - - return false; - } - } -} diff --git a/src/DotNetOpenAuth/OpenId/DiffieHellman/mono/BigInteger.cs b/src/Mono.Math/BigInteger.cs index 33df8df..33df8df 100644 --- a/src/DotNetOpenAuth/OpenId/DiffieHellman/mono/BigInteger.cs +++ b/src/Mono.Math/BigInteger.cs diff --git a/src/DotNetOpenAuth/OpenId/DiffieHellman/mono/ConfidenceFactor.cs b/src/Mono.Math/ConfidenceFactor.cs index fd0747d..fd0747d 100644 --- a/src/DotNetOpenAuth/OpenId/DiffieHellman/mono/ConfidenceFactor.cs +++ b/src/Mono.Math/ConfidenceFactor.cs diff --git a/src/Mono.Math/Mono.Math.csproj b/src/Mono.Math/Mono.Math.csproj new file mode 100644 index 0000000..c0d66b6 --- /dev/null +++ b/src/Mono.Math/Mono.Math.csproj @@ -0,0 +1,46 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), EnlistmentInfo.props))\EnlistmentInfo.props" Condition=" '$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), EnlistmentInfo.props))' != '' " /> + <PropertyGroup> + <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> + <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> + </PropertyGroup> + <Import Project="$(ProjectRoot)tools\DotNetOpenAuth.props" /> + <PropertyGroup> + <SchemaVersion>2.0</SchemaVersion> + <ProjectGuid>{F4CD3C04-6037-4946-B7A5-34BFC96A75D2}</ProjectGuid> + <OutputType>Library</OutputType> + <AppDesignerFolder>Properties</AppDesignerFolder> + <RootNamespace>Mono.Math</RootNamespace> + <AssemblyName>Mono.Math</AssemblyName> + </PropertyGroup> + <Import Project="$(ProjectRoot)tools\DotNetOpenAuth.Product.props" /> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> + </PropertyGroup> + <ItemGroup> + <Compile Include="BigInteger.cs"> + <SubType>Code</SubType> + </Compile> + <Compile Include="ConfidenceFactor.cs"> + <SubType>Code</SubType> + </Compile> + <Compile Include="NextPrimeFinder.cs"> + <SubType>Code</SubType> + </Compile> + <Compile Include="PrimalityTests.cs"> + <SubType>Code</SubType> + </Compile> + <Compile Include="PrimeGeneratorBase.cs"> + <SubType>Code</SubType> + </Compile> + <Compile Include="Properties\AssemblyInfo.cs" /> + <Compile Include="SequentialSearchPrimeGeneratorBase.cs"> + <SubType>Code</SubType> + </Compile> + </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))' != '' " /> +</Project>
\ No newline at end of file diff --git a/src/DotNetOpenAuth/OpenId/DiffieHellman/mono/NextPrimeFinder.cs b/src/Mono.Math/NextPrimeFinder.cs index 19433f2..19433f2 100644 --- a/src/DotNetOpenAuth/OpenId/DiffieHellman/mono/NextPrimeFinder.cs +++ b/src/Mono.Math/NextPrimeFinder.cs diff --git a/src/DotNetOpenAuth/OpenId/DiffieHellman/mono/PrimalityTests.cs b/src/Mono.Math/PrimalityTests.cs index b2ddc74..b2ddc74 100644 --- a/src/DotNetOpenAuth/OpenId/DiffieHellman/mono/PrimalityTests.cs +++ b/src/Mono.Math/PrimalityTests.cs diff --git a/src/DotNetOpenAuth/OpenId/DiffieHellman/mono/PrimeGeneratorBase.cs b/src/Mono.Math/PrimeGeneratorBase.cs index 12b6a69..12b6a69 100644 --- a/src/DotNetOpenAuth/OpenId/DiffieHellman/mono/PrimeGeneratorBase.cs +++ b/src/Mono.Math/PrimeGeneratorBase.cs diff --git a/src/Mono.Math/Properties/AssemblyInfo.cs b/src/Mono.Math/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..cea21c0 --- /dev/null +++ b/src/Mono.Math/Properties/AssemblyInfo.cs @@ -0,0 +1,52 @@ +//----------------------------------------------------------------------- +// <copyright file="AssemblyInfo.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +// We DON'T put an AssemblyVersionAttribute in here because it is generated in the build. + +using System; +using System.Diagnostics.Contracts; +using System.Net; +using System.Reflection; +using System.Resources; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Security; +using System.Security.Permissions; +using System.Web.UI; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Mono Math")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("DotNetOpenAuth")] +[assembly: AssemblyCopyright("Copyright © 2008")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: NeutralResourcesLanguage("en-US")] +[assembly: CLSCompliant(true)] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("7d73990c-47c0-4256-9f20-a893add9e289")] + +[assembly: ContractVerification(true)] + +#if StrongNameSigned +// See comment at top of this file. We need this so that strong-naming doesn't +// keep this assembly from being useful to shared host (medium trust) web sites. +[assembly: AllowPartiallyTrustedCallers] + +[assembly: InternalsVisibleTo("Org.Mentalis.Security.Cryptography, PublicKey=0024000004800000940000000602000000240000525341310004000001000100AD093C3765257C89A7010E853F2C7C741FF92FA8ACE06D7B8254702CAD5CF99104447F63AB05F8BB6F51CE0D81C8C93D2FCE8C20AAFF7042E721CBA16EAAE98778611DED11C0ABC8900DC5667F99B50A9DADEC24DBD8F2C91E3E8AD300EF64F1B4B9536CEB16FB440AF939F57624A9B486F867807C649AE4830EAB88C6C03998")] +#else +[assembly: InternalsVisibleTo("Org.Mentalis.Security.Cryptography")] +#endif diff --git a/src/DotNetOpenAuth/OpenId/DiffieHellman/mono/SequentialSearchPrimeGeneratorBase.cs b/src/Mono.Math/SequentialSearchPrimeGeneratorBase.cs index a017ee4..a017ee4 100644 --- a/src/DotNetOpenAuth/OpenId/DiffieHellman/mono/SequentialSearchPrimeGeneratorBase.cs +++ b/src/Mono.Math/SequentialSearchPrimeGeneratorBase.cs diff --git a/src/DotNetOpenAuth/OpenId/DiffieHellman/DHKeyGeneration.cs b/src/Org.Mentalis.Security.Cryptography/DHKeyGeneration.cs index 6eca6a0..6eca6a0 100644 --- a/src/DotNetOpenAuth/OpenId/DiffieHellman/DHKeyGeneration.cs +++ b/src/Org.Mentalis.Security.Cryptography/DHKeyGeneration.cs diff --git a/src/DotNetOpenAuth/OpenId/DiffieHellman/DHParameters.cs b/src/Org.Mentalis.Security.Cryptography/DHParameters.cs index 8105125..8105125 100644 --- a/src/DotNetOpenAuth/OpenId/DiffieHellman/DHParameters.cs +++ b/src/Org.Mentalis.Security.Cryptography/DHParameters.cs diff --git a/src/DotNetOpenAuth/OpenId/DiffieHellman/DiffieHellman.cs b/src/Org.Mentalis.Security.Cryptography/DiffieHellman.cs index 5019f70..5019f70 100644 --- a/src/DotNetOpenAuth/OpenId/DiffieHellman/DiffieHellman.cs +++ b/src/Org.Mentalis.Security.Cryptography/DiffieHellman.cs diff --git a/src/DotNetOpenAuth/OpenId/DiffieHellman/DiffieHellmanManaged.cs b/src/Org.Mentalis.Security.Cryptography/DiffieHellmanManaged.cs index 6ac28df..6ac28df 100644 --- a/src/DotNetOpenAuth/OpenId/DiffieHellman/DiffieHellmanManaged.cs +++ b/src/Org.Mentalis.Security.Cryptography/DiffieHellmanManaged.cs diff --git a/src/Org.Mentalis.Security.Cryptography/Org.Mentalis.Security.Cryptography.csproj b/src/Org.Mentalis.Security.Cryptography/Org.Mentalis.Security.Cryptography.csproj new file mode 100644 index 0000000..c2b1055 --- /dev/null +++ b/src/Org.Mentalis.Security.Cryptography/Org.Mentalis.Security.Cryptography.csproj @@ -0,0 +1,38 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), EnlistmentInfo.props))\EnlistmentInfo.props" Condition=" '$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), EnlistmentInfo.props))' != '' " /> + <PropertyGroup> + <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> + <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> + </PropertyGroup> + <Import Project="$(ProjectRoot)tools\DotNetOpenAuth.props" /> + <PropertyGroup> + <SchemaVersion>2.0</SchemaVersion> + <ProjectGuid>{26DC877F-5987-48DD-9DDB-E62F2DE0E150}</ProjectGuid> + <OutputType>Library</OutputType> + <AppDesignerFolder>Properties</AppDesignerFolder> + <RootNamespace>Org.Mentalis.Security.Cryptography</RootNamespace> + <AssemblyName>Org.Mentalis.Security.Cryptography</AssemblyName> + </PropertyGroup> + <Import Project="$(ProjectRoot)tools\DotNetOpenAuth.Product.props" /> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> + </PropertyGroup> + <ItemGroup> + <Compile Include="DHKeyGeneration.cs" /> + <Compile Include="DHParameters.cs" /> + <Compile Include="DiffieHellman.cs" /> + <Compile Include="DiffieHellmanManaged.cs" /> + <Compile Include="Properties\AssemblyInfo.cs" /> + </ItemGroup> + <ItemGroup> + <ProjectReference Include="..\Mono.Math\Mono.Math.csproj"> + <Project>{F4CD3C04-6037-4946-B7A5-34BFC96A75D2}</Project> + <Name>Mono.Math</Name> + </ProjectReference> + </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))' != '' " /> +</Project>
\ No newline at end of file diff --git a/src/Org.Mentalis.Security.Cryptography/Properties/AssemblyInfo.cs b/src/Org.Mentalis.Security.Cryptography/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..45a2fda --- /dev/null +++ b/src/Org.Mentalis.Security.Cryptography/Properties/AssemblyInfo.cs @@ -0,0 +1,60 @@ +//----------------------------------------------------------------------- +// <copyright file="AssemblyInfo.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +// We DON'T put an AssemblyVersionAttribute in here because it is generated in the build. + +using System; +using System.Diagnostics.Contracts; +using System.Net; +using System.Reflection; +using System.Resources; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Security; +using System.Security.Permissions; +using System.Web.UI; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Org.Mentalis.Security.Cryptography")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("DotNetOpenAuth")] +[assembly: AssemblyCopyright("Copyright © 2008")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: NeutralResourcesLanguage("en-US")] +[assembly: CLSCompliant(true)] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("7d73990c-47c0-4256-9f20-a893add9e289")] + +[assembly: ContractVerification(true)] + +#if StrongNameSigned +// See comment at top of this file. We need this so that strong-naming doesn't +// keep this assembly from being useful to shared host (medium trust) web sites. +[assembly: AllowPartiallyTrustedCallers] + +[assembly: InternalsVisibleTo("DotNetOpenAuth.OpenId, PublicKey=0024000004800000940000000602000000240000525341310004000001000100AD093C3765257C89A7010E853F2C7C741FF92FA8ACE06D7B8254702CAD5CF99104447F63AB05F8BB6F51CE0D81C8C93D2FCE8C20AAFF7042E721CBA16EAAE98778611DED11C0ABC8900DC5667F99B50A9DADEC24DBD8F2C91E3E8AD300EF64F1B4B9536CEB16FB440AF939F57624A9B486F867807C649AE4830EAB88C6C03998")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.OpenId.Provider, PublicKey=0024000004800000940000000602000000240000525341310004000001000100AD093C3765257C89A7010E853F2C7C741FF92FA8ACE06D7B8254702CAD5CF99104447F63AB05F8BB6F51CE0D81C8C93D2FCE8C20AAFF7042E721CBA16EAAE98778611DED11C0ABC8900DC5667F99B50A9DADEC24DBD8F2C91E3E8AD300EF64F1B4B9536CEB16FB440AF939F57624A9B486F867807C649AE4830EAB88C6C03998")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.OpenId.RelyingParty, PublicKey=0024000004800000940000000602000000240000525341310004000001000100AD093C3765257C89A7010E853F2C7C741FF92FA8ACE06D7B8254702CAD5CF99104447F63AB05F8BB6F51CE0D81C8C93D2FCE8C20AAFF7042E721CBA16EAAE98778611DED11C0ABC8900DC5667F99B50A9DADEC24DBD8F2C91E3E8AD300EF64F1B4B9536CEB16FB440AF939F57624A9B486F867807C649AE4830EAB88C6C03998")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100AD093C3765257C89A7010E853F2C7C741FF92FA8ACE06D7B8254702CAD5CF99104447F63AB05F8BB6F51CE0D81C8C93D2FCE8C20AAFF7042E721CBA16EAAE98778611DED11C0ABC8900DC5667F99B50A9DADEC24DBD8F2C91E3E8AD300EF64F1B4B9536CEB16FB440AF939F57624A9B486F867807C649AE4830EAB88C6C03998")] +[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")] +#else +[assembly: InternalsVisibleTo("DotNetOpenAuth.OpenId")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.OpenId.Provider")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.OpenId.RelyingParty")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.Test")] +[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] +#endif diff --git a/tools/Documentation.targets b/tools/Documentation.targets index faf1f75..74efe3d 100644 --- a/tools/Documentation.targets +++ b/tools/Documentation.targets @@ -4,10 +4,16 @@ <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> <ProjectRoot Condition="'$(ProjectRoot)' == ''">$(MSBuildProjectDirectory)\..\..\</ProjectRoot> <OutputAssembly>DotNetOpenAuth</OutputAssembly> + <OutputAssemblyFile>$(ILMergeOutputAssembly)</OutputAssemblyFile> <DocOutputPath>$(ProjectRoot)doc\</DocOutputPath> - <DocumentationFile>$(OutputPath)$(OutputAssembly).xml</DocumentationFile> + <DocumentationFile>$(ILMergeOutputXmlDocs)</DocumentationFile> </PropertyGroup> + <ItemGroup> + <MRefDependencies Include="$(ProjectRoot)lib\log4net.dll" /> + <MRefDependencies Include="$(ProjectRoot)lib\net-v3.5\System.Web.Mvc.dll" /> + </ItemGroup> + <Import Project="$(ProjectRoot)Tools\sandcastle.targets" /> </Project> diff --git a/tools/DotNetOpenAuth.Product.props b/tools/DotNetOpenAuth.Product.props new file mode 100644 index 0000000..971ed7b --- /dev/null +++ b/tools/DotNetOpenAuth.Product.props @@ -0,0 +1,157 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <PropertyGroup> + <MSBuildAllProjects>$(MSBuildAllProjects);$(MSBuildThisFile)</MSBuildAllProjects> + </PropertyGroup> + <PropertyGroup> + <OutputType>Library</OutputType> + <RootNamespace>DotNetOpenAuth</RootNamespace> + <FileAlignment>512</FileAlignment> + <TargetFrameworkProfile /> + <DocumentationFile>$(OutputPath)$(AssemblyName).xml</DocumentationFile> + <StandardCopyright> +Copyright (c) 2011, Andrew Arnott. All rights reserved. +Code licensed under the Ms-PL License: +http://opensource.org/licenses/ms-pl.html + </StandardCopyright> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + + <CodeAnalysisRules /> + <CodeAnalysisRuleSet>Migrated rules for DotNetOpenAuth.ruleset</CodeAnalysisRuleSet> + <CodeAnalysisModuleSuppressionsFile>GlobalSuppressions.cs</CodeAnalysisModuleSuppressionsFile> + <CodeAnalysisUseTypeNameInSuppression>true</CodeAnalysisUseTypeNameInSuppression> + + <CodeContractsCustomRewriterAssembly /> + <CodeContractsCustomRewriterClass /> + <CodeContractsRunCodeAnalysis>False</CodeContractsRunCodeAnalysis> + <CodeContractsBuildReferenceAssembly>True</CodeContractsBuildReferenceAssembly> + <CodeContractsNonNullObligations>True</CodeContractsNonNullObligations> + <CodeContractsBoundsObligations>True</CodeContractsBoundsObligations> + <CodeContractsLibPaths /> + <CodeContractsPlatformPath /> + <CodeContractsExtraAnalysisOptions /> + <CodeContractsBaseLineFile /> + <CodeContractsUseBaseLine>False</CodeContractsUseBaseLine> + <CodeContractsRunInBackground>True</CodeContractsRunInBackground> + <CodeContractsShowSquigglies>True</CodeContractsShowSquigglies> + <CodeContractsArithmeticObligations>False</CodeContractsArithmeticObligations> + <CodeContractsRuntimeOnlyPublicSurface>False</CodeContractsRuntimeOnlyPublicSurface> + <CodeContractsRuntimeThrowOnFailure>True</CodeContractsRuntimeThrowOnFailure> + <CodeContractsRuntimeCallSiteRequires>False</CodeContractsRuntimeCallSiteRequires> + <CodeContractsEmitXMLDocs>True</CodeContractsEmitXMLDocs> + <CodeContractsRedundantAssumptions>False</CodeContractsRedundantAssumptions> + <CodeContractsExtraRewriteOptions /> + + <CodeContractsReferenceAssembly Condition=" '$(BuildCodeContractsReferenceAssemblies)' == 'true' ">Build</CodeContractsReferenceAssembly> + <CodeContractsReferenceAssembly Condition=" '$(BuildCodeContractsReferenceAssemblies)' != 'true' ">DoNotBuild</CodeContractsReferenceAssembly> + </PropertyGroup> + + <PropertyGroup Condition=" '$(CodeContractsRewritingEnabled)' != 'true' "> + <CodeContractsAssemblyMode>0</CodeContractsAssemblyMode> + <CodeContractsEnableRuntimeChecking>False</CodeContractsEnableRuntimeChecking> + </PropertyGroup> + + <PropertyGroup Condition=" '$(CodeContractsRewritingEnabled)' == 'true' "> + <CodeContractsAssemblyMode>1</CodeContractsAssemblyMode> + <CodeContractsEnableRuntimeChecking>True</CodeContractsEnableRuntimeChecking> + </PropertyGroup> + + <PropertyGroup Condition=" '$(Configuration)' == 'Debug' "> + <DebugSymbols>true</DebugSymbols> + <DebugType>full</DebugType> + <Optimize>false</Optimize> + <DefineConstants>DEBUG;TRACE</DefineConstants> + <AllowUnsafeBlocks>false</AllowUnsafeBlocks> + <RunCodeAnalysis>false</RunCodeAnalysis> + + <CodeContractsRuntimeCheckingLevel>Full</CodeContractsRuntimeCheckingLevel> + </PropertyGroup> + + <PropertyGroup Condition=" '$(Configuration)' == 'Release' "> + <DebugType>pdbonly</DebugType> + <Optimize>true</Optimize> + <DefineConstants>TRACE</DefineConstants> + + <CodeContractsRuntimeCheckingLevel>ReleaseRequires</CodeContractsRuntimeCheckingLevel> + </PropertyGroup> + + <PropertyGroup Condition=" '$(Configuration)' == 'CodeAnalysis' "> + <RunCodeAnalysis>true</RunCodeAnalysis> + </PropertyGroup> + + <ItemGroup> + <DelaySignedAssemblies Include="$(OutputPath)CodeContracts\$(AssemblyName).Contracts.dll" + Condition=" '$(BuildCodeContractsReferenceAssemblies)' == 'true' and '$(AddContractsAssemblyForDelaySigning)' != 'false' "/> + </ItemGroup> + + <ItemGroup> + <Reference Include="log4net, Version=1.2.10.0, Culture=neutral, PublicKeyToken=1b44e1d426115821, processorArchitecture=MSIL"> + <SpecificVersion>False</SpecificVersion> + </Reference> + <Reference Include="System" /> + <Reference Include="System.Security" /> + <Reference Include="System.configuration" /> + <Reference Include="System.Core"> + <RequiredTargetFramework>3.5</RequiredTargetFramework> + </Reference> + <Reference Include="System.Data" /> + <Reference Include="System.Drawing" /> + <Reference Include="System.IdentityModel"> + <RequiredTargetFramework>3.0</RequiredTargetFramework> + </Reference> + <Reference Include="System.IdentityModel.Selectors"> + <RequiredTargetFramework>3.0</RequiredTargetFramework> + </Reference> + <Reference Include="System.Runtime.Serialization"> + <RequiredTargetFramework>3.0</RequiredTargetFramework> + </Reference> + <Reference Include="System.ServiceModel"> + <RequiredTargetFramework>3.0</RequiredTargetFramework> + </Reference> + <Reference Include="System.ServiceModel.Web"> + <RequiredTargetFramework>3.5</RequiredTargetFramework> + </Reference> + <Reference Include="System.Web" /> + <Reference Include="System.Web.Abstractions"> + <RequiredTargetFramework>3.5</RequiredTargetFramework> + </Reference> + <Reference Include="System.Web.Extensions"> + <RequiredTargetFramework>3.5</RequiredTargetFramework> + </Reference> + <Reference Include="System.Web.Extensions.Design"> + <RequiredTargetFramework>3.5</RequiredTargetFramework> + </Reference> + <Reference Include="System.Web.Routing"> + <RequiredTargetFramework>3.5</RequiredTargetFramework> + </Reference> + <Reference Include="System.Windows.Forms" /> + <Reference Include="System.XML" /> + <Reference Include="System.Xml.Linq"> + <RequiredTargetFramework>3.5</RequiredTargetFramework> + </Reference> + <Reference Include="System.ComponentModel.DataAnnotations"> + <RequiredTargetFramework>3.5</RequiredTargetFramework> + </Reference> + </ItemGroup> + + <ItemGroup Condition=" '$(ClrVersion)' == '4' "> + <Reference Include="System.Web.Mvc, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL" /> + <Reference Include="System.Xaml" /> + </ItemGroup> + <ItemGroup Condition=" '$(ClrVersion)' != '4' "> + <!-- MVC 2 can run on CLR 2 (it doesn't require CLR 4) but since MVC 2 apps tend to use type forwarding, + it's a more broadly consumable idea to bind against MVC 1 for the library unless we're building on CLR 4, + which will definitely have MVC 2 available. --> + <Reference Include="System.Web.Mvc, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL" /> + <Reference Include="System.Web.Mobile" /> + <Reference Include="PresentationFramework"> + <!-- For XamlReader, but we use System.Xaml.dll in .NET 4.0 --> + <RequiredTargetFramework>3.0</RequiredTargetFramework> + </Reference> + <Reference Include="WindowsBase"> + <!-- ObservableCollection<T>, moved to System.dll in .NET 4.0 --> + <RequiredTargetFramework>3.0</RequiredTargetFramework> + </Reference> + </ItemGroup> +</Project> diff --git a/tools/DotNetOpenAuth.Versioning.targets b/tools/DotNetOpenAuth.Versioning.targets index cbcb76d..7ea18b1 100644 --- a/tools/DotNetOpenAuth.Versioning.targets +++ b/tools/DotNetOpenAuth.Versioning.targets @@ -6,6 +6,9 @@ <PropertyGroup> <VersionCsFile>$(IntermediatePath)\$(AssemblyName).Version.cs</VersionCsFile> <NoWarn>$(NoWarn);1607</NoWarn> + + <!-- PrereleaseVersion can be any alphanumeric identifier with a preceding hyphen, or blank. --> + <PrereleaseVersion>-beta</PrereleaseVersion> </PropertyGroup> <UsingTask AssemblyFile="$(ProjectRoot)lib\MSBuild.Community.Tasks.dll" TaskName="AssemblyInfo"/> @@ -16,15 +19,25 @@ GitRepoRoot="$(ProjectRoot)"> <Output TaskParameter="Version" PropertyName="BuildVersion" /> <Output TaskParameter="SimpleVersion" PropertyName="BuildVersionSimple" /> - <Output TaskParameter="GitCommitId" PropertyName="AssemblyInformationalVersion" /> + <Output TaskParameter="GitCommitId" PropertyName="GitCommitId" /> + <Output TaskParameter="BuildNumber" PropertyName="BuildNumber" /> </GetBuildVersion> <PropertyGroup> <!-- In TeamCity, the build agent doesn't get the .git directory, but the commit id is available by other means. --> - <AssemblyInformationalVersion Condition=" '$(AssemblyInformationalVersion)' == '' ">$(BUILD_VCS_NUMBER)</AssemblyInformationalVersion> + <GitCommitId Condition=" '$(GitCommitId)' == '' ">$(BUILD_VCS_NUMBER)</GitCommitId> + + <SemVerBuildSuffix>+build.$(BuildNumber).$(GitCommitId.Substring(0,10))</SemVerBuildSuffix> + <AssemblyInformationalVersion>$(BuildVersionSimple)$(PrereleaseVersion)$(SemVerBuildSuffix)</AssemblyInformationalVersion> + + <!-- When NuGet supports SemVer 2.0, we can set NuGetPackageVersion to be the same as $(AssemblyInformationalVersion) --> + <NuGetPackageVersion Condition=" '$(PrereleaseVersion)' == '' ">$(BuildVersion)</NuGetPackageVersion> + <NuGetPackageVersion Condition=" '$(PrereleaseVersion)' != '' ">$(BuildVersionSimple)$(PrereleaseVersion)</NuGetPackageVersion> </PropertyGroup> <Warning Condition=" '$(AssemblyInformationalVersion)' == '' " Text="Unable to determine the git HEAD commit ID to use for informational version number." /> - <Message Condition=" '$(AssemblyInformationalVersion)' != '' " Text="Building version $(BuildVersion) from commit $(AssemblyInformationalVersion)"/> + <Message Condition=" '$(AssemblyInformationalVersion)' != '' " Text="Building version $(BuildVersion) from commit $(GitCommitId)"/> <Message Condition=" '$(AssemblyInformationalVersion)' == '' " Text="Building version $(BuildVersion)"/> + <Message Importance="low" Text="AssemblyInformationalVersion: $(AssemblyInformationalVersion)" /> + <Message Importance="low" Text="NuGetPackageVersion: $(NuGetPackageVersion)" /> </Target> <Target Name="BeforeBuild" DependsOnTargets="GetBuildVersion"> diff --git a/tools/DotNetOpenAuth.automated.targets b/tools/DotNetOpenAuth.automated.targets index 5333b34..d9a5e45 100644 --- a/tools/DotNetOpenAuth.automated.targets +++ b/tools/DotNetOpenAuth.automated.targets @@ -15,7 +15,7 @@ <Delete Files="@(FilesToClean)" /> <RemoveDir Directories="@(DirectoriesToClean)" /> - <MSBuild Projects="@(ProjectsToClean)" Targets="%(ProjectsToClean.Targets)" BuildInParallel="$(BuildInParallel)" /> + <MSBuild Projects="@(ProjectsToClean)" Targets="%(ProjectsToClean.Targets)" BuildInParallel="$(BuildInParallel)" Properties="%(ProjectsToClean.Properties)" /> </Target> <Target Name="_SetDropProperties" DependsOnTargets="GetBuildVersion"> @@ -34,12 +34,12 @@ <Target Name="BuildProduct" DependsOnTargets="SkipVerification"> <MSBuild BuildInParallel="$(BuildInParallel)" - Projects="$(ProjectRoot)src\$(ProductName)\$(ProductName).csproj" /> + Projects="$(ProjectRoot)src\$(ProductName)\$(ProductName).proj" /> </Target> <Target Name="BuildUnifiedProduct" DependsOnTargets="BuildProduct"> <MSBuild BuildInParallel="$(BuildInParallel)" - Projects="$(ProjectRoot)src\$(ProductName)\$(ProductName).csproj" + Projects="$(ProjectRoot)src\$(ProductName)\$(ProductName).proj" Targets="BuildUnifiedProduct" /> </Target> </Project> diff --git a/tools/DotNetOpenAuth.props b/tools/DotNetOpenAuth.props index 8364393..1455d68 100644 --- a/tools/DotNetOpenAuth.props +++ b/tools/DotNetOpenAuth.props @@ -15,8 +15,9 @@ <Zip7ToolPath>$(ToolsDir)7-Zip.x86\</Zip7ToolPath> <NuGetToolPath>$(ToolsDir)NuGet\</NuGetToolPath> <ZipFormat Condition=" '$(ZipFormat)' == '' ">.7z</ZipFormat> - <ClrVersion Condition=" '$(TargetFrameworkVersion)' == 'v4.0' ">4</ClrVersion> - <ClrVersion Condition=" '$(TargetFrameworkVersion)' != 'v4.0' ">2</ClrVersion> + <ClrVersion Condition=" '$(TargetFrameworkVersion)' == 'v4.0' or '$(TargetFrameworkVersion)' == 'v4.5' ">4</ClrVersion> + <ClrVersion Condition=" '$(ClrVersion)' == '' ">2</ClrVersion> + <BuildCodeContractsReferenceAssemblies>false</BuildCodeContractsReferenceAssemblies> <SignAssembly>true</SignAssembly> <PublicKeyFile Condition="'$(PublicKeyFile)' == ''">$(ProjectRoot)src\official-build-key.pub</PublicKeyFile> @@ -28,6 +29,9 @@ <ILMergeOutputAssemblyDirectory>$(OutputPath)unified\</ILMergeOutputAssemblyDirectory> <ILMergeOutputAssembly>$(ILMergeOutputAssemblyDirectory)$(ProductName).dll</ILMergeOutputAssembly> + <ILMergeOutputXmlDocs>$(ILMergeOutputAssemblyDirectory)$(ProductName).xml</ILMergeOutputXmlDocs> + <ILMergeOutputContractAssemblyDirectory>$(ILMergeOutputAssemblyDirectory)CodeContracts\</ILMergeOutputContractAssemblyDirectory> + <ILMergeOutputContractAssembly>$(ILMergeOutputContractAssemblyDirectory)$(ProductName).Contracts.dll</ILMergeOutputContractAssembly> <!-- Always use our own toolset's copy of Code Contracts for reliably reproducible builds. Suppress the installed code contracts from importing itself. --> @@ -35,9 +39,54 @@ <ImportCodeContractsFromToolset>true</ImportCodeContractsFromToolset> </PropertyGroup> + <PropertyGroup Condition=" '$(ClrVersion)' == '4' "> + <ILMergeTargetPlatformDirectory>$(MSBuildProgramFiles32)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.0</ILMergeTargetPlatformDirectory> + </PropertyGroup> + <ItemGroup Condition=" '$(ClrVersion)' == '4' "> + <ILMergeSearchDirectories Include="$(ILMergeTargetPlatformDirectory)" /> + </ItemGroup> + + <ItemGroup Condition=" '$(ClrVersion)' != '4' "> + <ILMergeSearchDirectories Include=" + $(MSBuildProgramFiles32)\Reference Assemblies\Microsoft\Framework\v3.0; + $(MSBuildProgramFiles32)\Reference Assemblies\Microsoft\Framework\v3.5; + " /> + </ItemGroup> + <ItemGroup> - <SignDependsOn Include="Build" /> - <ILMergeInputAssemblies Include="$(OutputPath)$(ProductName).dll" /> + <ProductProjectNames Include=" + DotNetOpenAuth.Core; + DotNetOpenAuth.Core.UI; + Mono.Math; + Org.Mentalis.Security.Cryptography; + DotNetOpenAuth.OpenId; + DotNetOpenAuth.OpenId.UI; + DotNetOpenAuth.OpenId.Provider; + DotNetOpenAuth.OpenId.Provider.UI; + DotNetOpenAuth.OpenId.RelyingParty; + DotNetOpenAuth.OpenId.RelyingParty.UI; + DotNetOpenAuth.OAuth; + DotNetOpenAuth.OAuth.Consumer; + DotNetOpenAuth.OAuth.ServiceProvider; + DotNetOpenAuth.InfoCard; + DotNetOpenAuth.InfoCard.UI; + DotNetOpenAuth.OpenIdInfoCard.UI; + " /> + <ProductProjectNames Include=" + DotNetOpenAuth.OAuth2; + DotNetOpenAuth.OAuth2.AuthorizationServer; + DotNetOpenAuth.OAuth2.Client; + DotNetOpenAuth.OAuth2.Client.UI; + DotNetOpenAuth.OAuth2.ResourceServer; + "> + <MergeIntoUnifiedAssembly Condition=" '$(IncludeOAuth2)' != 'true' ">false</MergeIntoUnifiedAssembly> + </ProductProjectNames> + <ProductProjects Include="@(ProductProjectNames->'$(ProjectRoot)src\%(Identity)\%(Identity).csproj')"/> + + <ProjectReferencesToRemove Include="@(ProductProjectNames->'..\..\src\%(Identity)\%(Identity).csproj')" /> + <AssemblyReferencesToReplaceWith Include="@(ProjectReferencesToRemove->'..\..\Bin\$(ProductName).dll')" /> + + <SignDependsOn Include="Build" Condition=" '$(SuppressBuildTarget)' != 'true' " /> <ILMergeInputAssemblies Condition=" '$(ClrVersion)' == '2' " Include="$(ProjectRoot)lib\Microsoft.Contracts.dll" /> </ItemGroup> @@ -50,4 +99,4 @@ </CheckAdminRights> <Message Text="IsElevated = $(IsElevated)" /> </Target> -</Project>
\ No newline at end of file +</Project> diff --git a/tools/DotNetOpenAuth.targets b/tools/DotNetOpenAuth.targets index 06745c7..3765455 100644 --- a/tools/DotNetOpenAuth.targets +++ b/tools/DotNetOpenAuth.targets @@ -18,6 +18,7 @@ <CodeContractsInstallDir>$(ProjectRoot)tools\Contracts\</CodeContractsInstallDir> <DefineConstants Condition=" '$(SignAssembly)' == 'true' ">$(DefineConstants);StrongNameSigned</DefineConstants> <DefineConstants Condition=" '$(ClrVersion)' == '4' ">$(DefineConstants);CLR4</DefineConstants> + <DefineConstants Condition=" '$(ExcludeDiffieHellman)' == 'true' ">$(DefineConstants);ExcludeDiffieHellman</DefineConstants> <AssemblySearchPaths>$(ProjectRoot)lib;$(AssemblySearchPaths)</AssemblySearchPaths> <AssemblySearchPaths Condition="Exists('$(ProjectRoot)lib\net-$(TargetFrameworkVersion)')">$(ProjectRoot)lib\net-$(TargetFrameworkVersion);$(AssemblySearchPaths)</AssemblySearchPaths> </PropertyGroup> @@ -111,7 +112,8 @@ <Target Name="ResignDelaySignedAssemblies" Outputs="@(ResignedAssembliesOutputs)"> <ItemGroup> <DelaySignedProjects Include=" - $(ProjectRoot)src\dotnetopenauth\dotnetopenauth.csproj; + @(ProductProjects); + $(ProjectRoot)src\dotnetopenauth\dotnetopenauth.proj; $(ProjectRoot)src\dotnetopenauth.test\dotnetopenauth.test.csproj; $(ProjectRoot)samples\openidofflineprovider\openidofflineprovider.csproj; " /> @@ -121,5 +123,7 @@ </MSBuild> </Target> + <Target Name="GetOutputPath" Outputs="$(OutputPath)" /> + <Import Condition="'$(CodeContractsImported)' != 'true' AND ('$(DontImportCodeContracts)' != 'true' or '$(ImportCodeContractsFromToolset)' == 'true')" Project="$(CodeContractsInstallDir)\MsBuild\v4.0\Microsoft.CodeContracts.targets"/> </Project> diff --git a/tools/NuGet/NuGet.exe b/tools/NuGet/NuGet.exe Binary files differindex 907d24d..74d0d57 100644 --- a/tools/NuGet/NuGet.exe +++ b/tools/NuGet/NuGet.exe diff --git a/tools/drop.proj b/tools/drop.proj index 21baae0..69a636c 100644 --- a/tools/drop.proj +++ b/tools/drop.proj @@ -47,10 +47,10 @@ <DropBinSourceFiles Include=" $(ILMergeOutputAssemblyDirectory)$(SignedSubPath)$(ProductName).dll; $(ILMergeOutputAssemblyDirectory)$(ProductName).pdb; - $(OutputPath)$(ProductName).xml; + $(ILMergeOutputAssemblyDirectory)$(ProductName).xml; $(OutputPath)CodeContracts\$(SignedSubPath)$(ProductName).Contracts.???; $(ProjectRoot)Doc\README.Bin.html; - $(ProjectRoot)src\$(ProductName)\Configuration\$(ProductName).xsd; + $(ProjectRoot)src\$(ProductName).Core\Configuration\$(ProductName).xsd; " /> <DropSatelliteSourceFiles Include="$(OutputPath)**\$(SignedSubPath)$(ProductName).resources.dll" /> <DropSatelliteSourceFiles> @@ -74,14 +74,12 @@ $(ProjectRoot)**\Debug\**; $(ProjectRoot)**\StyleCop.Cache; $(ProjectRoot)Samples\_ReSharper.*\**; - $(ProjectRoot)Samples\**\DotNetOpenAuth.???; - $(ProjectRoot)Samples\**\DotNetOpenAuth.resources.???; - $(ProjectRoot)Samples\**\log4net.???; - $(ProjectRoot)Samples\**\PresentationCore.dll; - $(ProjectRoot)Samples\**\System.Printing.dll; + $(ProjectRoot)Samples\**\bin\**; + $(ProjectRoot)Samples\**\obj\**; $(ProjectRoot)Samples\**\*.refresh_; $(ProjectRoot)Samples\*.proj; " /> + <DropSamplesSourceFiles Include="$(ProjectRoot)Samples\**\Bin\*.refresh" /> <!-- Some .refresh files are only applicable to drop builds, so we rename them from *.refresh_ --> <DropSamplesRefreshSourceFiles Include="$(ProjectRoot)Samples\**\*.refresh_" /> <DropSpecsSourceFiles Include="$(ProjectRoot)Doc\specs\*.htm*" /> @@ -137,7 +135,22 @@ RemoveImportsStartingWith="%24(ProjectRoot)tools\" AddReferences="Microsoft.Contracts"/> <ChangeProjectReferenceToAssemblyReference Projects="@(SampleProjectTargets)" - ProjectReferences="..\..\src\$(ProductName)\$(ProductName).csproj" References="..\..\Bin\$(ProductName).dll" /> + ProjectReferences="@(ProjectReferencesToRemove)" References="@(AssemblyReferencesToReplaceWith)" /> + <RegexFileReplace + Files="@(DropSamplesFiles)" + Pattern='<%@ Register Assembly="DotNetOpenAuth[^"]+"' + Replacement='<%@ Register Assembly="DotNetOpenAuth"' + Condition=" '%(Extension)' == '.aspx' " /> + <RegexFileReplace + Files="@(DropSamplesFiles)" + Pattern='xmlns\:(.+)assembly=DotNetOpenAuth([^;"]+)' + Replacement='xmlns:$1assembly=DotNetOpenAuth' + Condition=" '%(Extension)' == '.xaml' " /> + <RegexFileReplace + Files="@(DropSamplesFiles)" + Pattern='type="DotNetOpenAuth([^,]+), DotNetOpenAuth([^"]+)"' + Replacement='type="DotNetOpenAuth$1, DotNetOpenAuth"' + Condition=" '%(Extension)' == '.config' " /> <DowngradeProjects Projects="@(SampleProjectTargets);@(SampleSolutionTargets)" DowngradeMvc2ToMvc1="$(DowngradeMvc2ToMvc1)" diff --git a/tools/sandcastle.targets b/tools/sandcastle.targets index 65511f8..c7860b7 100644 --- a/tools/sandcastle.targets +++ b/tools/sandcastle.targets @@ -11,7 +11,7 @@ <Presentation>$(DxRoot)Presentation\$(PresentationStyle)\</Presentation> <HHC>$(PROGRAMFILES)\Html Help Workshop\hhc.exe</HHC> - <OutputAssemblyFile>$(OutputPath)$(OutputAssembly).dll</OutputAssemblyFile> + <OutputAssemblyFile Condition=" '$(OutputAssemblyFile)' == '' ">$(OutputPath)$(OutputAssembly).dll</OutputAssemblyFile> <DocOutputApiPath>$(DocOutputPath)api\</DocOutputApiPath> <DocIntermediatePath>$(ProjectRoot)obj\Doc\$(Configuration)\</DocIntermediatePath> @@ -114,9 +114,15 @@ <MakeDir Directories="$(DocOutputApiPath)html;$(DocOutputApiPath)media;$(DocOutputApiPath)intellisense"/> </Target> - <Target Name="ReflectionBase" Inputs="$(OutputAssemblyFile)" Outputs="$(ReflectionBaseFile)" + <Target Name="ReflectionBase" Inputs="$(OutputAssemblyFile)" Outputs="$(ReflectionBaseFile)" DependsOnTargets="SetEnvironmentVars;CreateIntermediatePath;ProductionTools"> - <Exec Command='"$(ProductionTools)MRefBuilder.exe" "$(OutputAssemblyFile)" /out:"$(ReflectionBaseFile)"' /> + <ItemGroup> + <MRefDependenciesSwitch Include="@(MRefDependencies->'/dep:"%(Identity)"')" /> + </ItemGroup> + <PropertyGroup> + <MRefDependenciesSwitch>@(MRefDependenciesSwitch,' ')</MRefDependenciesSwitch> + </PropertyGroup> + <Exec Command='"$(ProductionTools)MRefBuilder.exe" "$(OutputAssemblyFile)" /out:"$(ReflectionBaseFile)" $(MRefDependenciesSwitch)' /> </Target> <Target Name="ReflectionData" DependsOnTargets="FxReflection;ReflectionBase" Inputs="$(ReflectionBaseFile)" Outputs="$(ReflectionFile)"> |