diff options
184 files changed, 4246 insertions, 2018 deletions
diff --git a/Performance.psess b/Performance.psess new file mode 100644 index 0000000..519b31c --- /dev/null +++ b/Performance.psess @@ -0,0 +1,91 @@ +<?xml version="1.0" encoding="UTF-8"?>
+<VSPerformanceSession Version="1.00">
+ <Options>
+ <Solution>C:\git\dotnetopenid\src\DotNetOpenId.sln</Solution>
+ <CollectionMethod>Instrumentation</CollectionMethod>
+ <AllocationMethod>None</AllocationMethod>
+ <LaunchRuntimeControl>true</LaunchRuntimeControl>
+ <AddReport>true</AddReport>
+ <UniqueReport>Timestamp</UniqueReport>
+ <SamplingMethod>Cycles</SamplingMethod>
+ <CycleCount>10000000</CycleCount>
+ <PageFaultCount>10</PageFaultCount>
+ <SysCallCount>10</SysCallCount>
+ <SamplingCounter Name="" ReloadValue="00000000000f4240" DisplayName="" />
+ <RelocateBinaries>false</RelocateBinaries>
+ <HardwareCounters EnableHWCounters="false" />
+ <EtwSettings />
+ <PdhSettings>
+ <PdhCountersEnabled>false</PdhCountersEnabled>
+ <PdhCountersRate>500</PdhCountersRate>
+ <PdhCounters>
+ <PdhCounter>\Memory\Pages/sec</PdhCounter>
+ <PdhCounter>\PhysicalDisk(_Total)\Avg. Disk Queue Length</PdhCounter>
+ <PdhCounter>\Processor(_Total)\% Processor Time</PdhCounter>
+ </PdhCounters>
+ </PdhSettings>
+ </Options>
+ <ExcludeSmallFuncs>true</ExcludeSmallFuncs>
+ <PreinstrumentEvent>
+ <InstrEventExclude>false</InstrEventExclude>
+ </PreinstrumentEvent>
+ <PostinstrumentEvent>
+ <InstrEventExclude>false</InstrEventExclude>
+ </PostinstrumentEvent>
+ <Binaries>
+ <ProjBinary>
+ <Path>C:\git\dotnetopenid\src\DotNetOpenId\obj\Debug\DotNetOpenId.dll</Path>
+ <ArgumentTimestamp>01/01/0001 00:00:00</ArgumentTimestamp>
+ <Instrument>true</Instrument>
+ <Sample>true</Sample>
+ <LaunchProject>false</LaunchProject>
+ <LaunchMethod>Executable</LaunchMethod>
+ <ExecutablePath>C:\git\dotnetopenid\tools\NUnit\bin\nunit-console.exe</ExecutablePath>
+ <StartupDirectory>C:\git\dotnetopenid\</StartupDirectory>
+ <Arguments>C:\git\dotnetopenid\bin\Debug\dotnetopenid.test.dll</Arguments>
+ <NetAppHost>IIS</NetAppHost>
+ <NetBrowser>InternetExplorer</NetBrowser>
+ <ExcludeSmallFuncs>false</ExcludeSmallFuncs>
+ <PreinstrumentEvent>
+ <InstrEventProgram />
+ <InstrEventArguments />
+ <InstrEventDescription />
+ <InstrEventExclude>false</InstrEventExclude>
+ </PreinstrumentEvent>
+ <PostinstrumentEvent>
+ <InstrEventProgram />
+ <InstrEventArguments />
+ <InstrEventDescription />
+ <InstrEventExclude>false</InstrEventExclude>
+ </PostinstrumentEvent>
+ <ProjRef>{5D6EDC86-F5B2-4786-8376-4E7C24C63D39}|DotNetOpenId\DotNetOpenId.csproj</ProjRef>
+ <ProjPath>C:\git\dotnetopenid\src\DotNetOpenId\DotNetOpenId.csproj</ProjPath>
+ <ProjName>DotNetOpenId</ProjName>
+ </ProjBinary>
+ </Binaries>
+ <Reports>
+ <Report>
+ <Path>C:\git\dotnetopenid\src\nunit-console080803.vsp</Path>
+ </Report>
+ <Report>
+ <Path>C:\git\dotnetopenid\src\nunit-console080803(1).vsp</Path>
+ </Report>
+ </Reports>
+ <Launches>
+ <ProjBinary>
+ <Path>:PB:{5D6EDC86-F5B2-4786-8376-4E7C24C63D39}|DotNetOpenId\DotNetOpenId.csproj</Path>
+ </ProjBinary>
+ </Launches>
+ <RuntimeMarks>
+ <MarkName>Mark One</MarkName>
+ <MarkName>Mark Two</MarkName>
+ <MarkName>Mark Three</MarkName>
+ <MarkName>Mark Four</MarkName>
+ <MarkName>Mark Five</MarkName>
+ <MarkName>Mark Six</MarkName>
+ <MarkName>Mark Seven</MarkName>
+ <MarkName>Mark Eight</MarkName>
+ <MarkName>Mark Nine</MarkName>
+ <MarkName>Mark Ten</MarkName>
+ </RuntimeMarks>
+</VSPerformanceSession>
\ No newline at end of file @@ -15,9 +15,7 @@ <ItemGroup>
<SampleDirectories Include="
- $(ProjectRoot)\samples\ProviderCustomStore;
$(ProjectRoot)\samples\ProviderPortal;
- $(ProjectRoot)\samples\RelyingPartyCustomStore;
$(ProjectRoot)\samples\RelyingPartyMvc;
$(ProjectRoot)\samples\RelyingPartyPortal;
" />
diff --git a/doc/Configuration.htm b/doc/Configuration.htm new file mode 100644 index 0000000..164ba05 --- /dev/null +++ b/doc/Configuration.htm @@ -0,0 +1,167 @@ +<!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>
+ <title>Web.config file configuration of DotNetOpenId</title>
+<style>
+#id_text_to_colorize{width:600px;height:120px}
+.linenos {padding-right: 5px;background: #ccc}
+.code {padding-left: 5px;}
+.highlight { background: #ffffff; }
+.highlight .c { color: #408080; font-style: italic } /* Comment */
+.highlight .err { border: 1px solid #FF0000 } /* Error */
+.highlight .k { color: #008000; font-weight: bold } /* Keyword */
+.highlight .o { color: #666666 } /* Operator */
+.highlight .cm { color: #408080; font-style: italic } /* Comment.Multiline */
+.highlight .cp { color: #BC7A00 } /* Comment.Preproc */
+.highlight .c1 { color: #408080; font-style: italic } /* Comment.Single */
+.highlight .cs { color: #408080; font-style: italic } /* Comment.Special */
+.highlight .gd { color: #A00000 } /* Generic.Deleted */
+.highlight .ge { font-style: italic } /* Generic.Emph */
+.highlight .gr { color: #FF0000 } /* Generic.Error */
+.highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */
+.highlight .gi { color: #00A000 } /* Generic.Inserted */
+.highlight .go { color: #808080 } /* Generic.Output */
+.highlight .gp { color: #000080; font-weight: bold } /* Generic.Prompt */
+.highlight .gs { font-weight: bold } /* Generic.Strong */
+.highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */
+.highlight .gt { color: #0040D0 } /* Generic.Traceback */
+.highlight .kc { color: #008000; font-weight: bold } /* Keyword.Constant */
+.highlight .kd { color: #008000; font-weight: bold } /* Keyword.Declaration */
+.highlight .kp { color: #008000 } /* Keyword.Pseudo */
+.highlight .kr { color: #008000; font-weight: bold } /* Keyword.Reserved */
+.highlight .kt { color: #B00040 } /* Keyword.Type */
+.highlight .m { color: #666666 } /* Literal.Number */
+.highlight .s { color: #BA2121 } /* Literal.String */
+.highlight .na { color: #7D9029 } /* Name.Attribute */
+.highlight .nb { color: #008000 } /* Name.Builtin */
+.highlight .nc { color: #0000FF; font-weight: bold } /* Name.Class */
+.highlight .no { color: #880000 } /* Name.Constant */
+.highlight .nd { color: #AA22FF } /* Name.Decorator */
+.highlight .ni { color: #999999; font-weight: bold } /* Name.Entity */
+.highlight .ne { color: #D2413A; font-weight: bold } /* Name.Exception */
+.highlight .nf { color: #0000FF } /* Name.Function */
+.highlight .nl { color: #A0A000 } /* Name.Label */
+.highlight .nn { color: #0000FF; font-weight: bold } /* Name.Namespace */
+.highlight .nt { color: #008000; font-weight: bold } /* Name.Tag */
+.highlight .nv { color: #19177C } /* Name.Variable */
+.highlight .ow { color: #AA22FF; font-weight: bold } /* Operator.Word */
+.highlight .w { color: #bbbbbb } /* Text.Whitespace */
+.highlight .mf { color: #666666 } /* Literal.Number.Float */
+.highlight .mh { color: #666666 } /* Literal.Number.Hex */
+.highlight .mi { color: #666666 } /* Literal.Number.Integer */
+.highlight .mo { color: #666666 } /* Literal.Number.Oct */
+.highlight .sb { color: #BA2121 } /* Literal.String.Backtick */
+.highlight .sc { color: #BA2121 } /* Literal.String.Char */
+.highlight .sd { color: #BA2121; font-style: italic } /* Literal.String.Doc */
+.highlight .s2 { color: #BA2121 } /* Literal.String.Double */
+.highlight .se { color: #BB6622; font-weight: bold } /* Literal.String.Escape */
+.highlight .sh { color: #BA2121 } /* Literal.String.Heredoc */
+.highlight .si { color: #BB6688; font-weight: bold } /* Literal.String.Interpol */
+.highlight .sx { color: #008000 } /* Literal.String.Other */
+.highlight .sr { color: #BB6688 } /* Literal.String.Regex */
+.highlight .s1 { color: #BA2121 } /* Literal.String.Single */
+.highlight .ss { color: #19177C } /* Literal.String.Symbol */
+.highlight .bp { color: #008000 } /* Name.Builtin.Pseudo */
+.highlight .vc { color: #19177C } /* Name.Variable.Class */
+.highlight .vg { color: #19177C } /* Name.Variable.Global */
+.highlight .vi { color: #19177C } /* Name.Variable.Instance */
+.highlight .il { color: #666666 } /* Literal.Number.Integer.Long */
+</style>
+</head>
+<body>
+
+ <p>DotNetOpenId can be configured in some aspects inside your web project's
+ web.config file. To do this, add the below <sectionGroup> within the
+ <configSections> of your Web.config file:</p>
+ <div class="highlight"><pre><span class="nt"><configSections></span>
+ <span class="nt"><sectionGroup</span> <span class="na">name=</span><span
+ class="s">"dotNetOpenId"</span><span class="nt">></span>
+ <span class="nt"><section</span> <span class="na">name=</span><span
+ class="s">"relyingParty"</span> <span class="na">type=</span><span
+ class="s">"DotNetOpenId.Configuration.RelyingPartySection"</span> <span
+ class="na">requirePermission=</span><span class="s">"false"</span> <span
+ class="na">allowLocation=</span><span class="s">"true"</span><span
+ class="nt">/></span>
+ <span class="nt"><section</span> <span class="na">name=</span><span
+ class="s">"provider"</span> <span class="na">type=</span><span class="s">"DotNetOpenId.Configuration.ProviderSection"</span> <span
+ class="na">requirePermission=</span><span class="s">"false"</span> <span
+ class="na">allowLocation=</span><span class="s">"true"</span><span
+ class="nt">/></span>
+ <span class="nt"><section</span> <span class="na">name=</span><span
+ class="s">"untrustedWebRequest"</span> <span class="na">type=</span><span
+ class="s">"DotNetOpenId.Configuration.UntrustedWebRequestSection"</span> <span
+ class="na">requirePermission=</span><span class="s">"false"</span> <span
+ class="na">allowLocation=</span><span class="s">"false"</span><span
+ class="nt">/></span>
+ <span class="nt"></sectionGroup></span>
+<span class="nt"></configSections></span></pre></div>
+ <p>If you do not already have a configSections element in your Web.config file, add
+ it at the very top, as the first child of the root <configuration> tag.</p>
+ <p>Following is an example of every possible configuration setting, where each
+ demonstrate value happens to be the default that would be used if it wasn't set
+ in the .config file. Keep in mind that every setting below is optional, so
+ you need only include those elements that you wish to change in your own copy of
+ Web.config. The <dotNetOpenId> node below should show up as a peer node to
+ system.web in your Web.config file.</p>
+ <div class="highlight"><pre><span class="nt"><dotNetOpenId></span>
+ <span class="nt"><relyingParty></span>
+ <span class="nt"><security</span> <span class="na">minimumHashBitLength=</span><span
+ class="s">"160"</span> <span class="na">maximumHashBitLength=</span><span
+ class="s">"256"</span>
+ <span class="na">requireSsl=</span><span class="s">"false"</span> <span
+ class="na">minimumRequiredOpenIdVersion=</span><span class="s">"V10"</span> <span
+ class="nt">/></span>
+ <span class="nt"><store</span> <span class="na">type=</span><span class="s">"SomeSite.CustomRPStore, SomeSite"</span> <span
+ class="nt">/></span>
+ <span class="nt"></relyingParty></span>
+ <span class="nt"><provider></span>
+ <span class="nt"><security</span> <span class="na">minimumHashBitLength=</span><span
+ class="s">"160"</span> <span class="na">maximumHashBitLength=</span><span
+ class="s">"256"</span> <span class="nt">/></span>
+ <span class="nt"><store</span> <span class="na">type=</span><span class="s">"SomeSite.CustomProviderStore, SomeSite"</span> <span
+ class="nt">/></span>
+ <span class="nt"></provider></span>
+ <span class="nt"><untrustedWebRequest</span> <span class="na">readWriteTimeout=</span><span
+ class="s">"00:00:00.800"</span> <span class="na">timeout=</span><span
+ class="s">"00:00:10"</span> <span class="na">maximumBytesToRead=</span><span
+ class="s">"1048576"</span> <span class="na">maximumRedirections=</span><span
+ class="s">"10"</span><span class="nt">></span>
+ <span class="nt"><whitelistHosts></span>
+ <span class="nt"><add</span> <span class="na">name=</span><span
+ class="s">"localhost"</span> <span class="nt">/></span>
+ <span class="nt"><add</span> <span class="na">name=</span><span
+ class="s">"127.0.0.1"</span> <span class="nt">/></span>
+ <span class="nt"></whitelistHosts></span>
+ <span class="nt"><whitelistHostsRegex></span>
+ <span class="nt"><add</span> <span class="na">name=</span><span
+ class="s">"^(.*\.)?goodsite.com"</span> <span class="nt">/></span>
+ <span class="nt"></whitelistHostsRegex></span>
+ <span class="nt"><blacklistHosts></span>
+ <span class="nt"><add</span> <span class="na">name=</span><span
+ class="s">"internalfinancialserver"</span> <span class="nt">/></span>
+ <span class="nt"><add</span> <span class="na">name=</span><span
+ class="s">"www.evilsite.com"</span> <span class="nt">/></span>
+ <span class="nt"></blacklistHosts></span>
+ <span class="nt"><blacklistHostsRegex></span>
+ <span class="nt"><add</span> <span class="na">name=</span><span
+ class="s">"^(.*\.)?evilsite.com"</span> <span class="nt">/></span>
+ <span class="nt"></blacklistHostsRegex></span>
+ <span class="nt"></untrustedWebRequest></span>
+<span class="nt"></dotNetOpenId></span>
+</pre></div>
+
+ <p>All these configuration values are also configurable at runtime using the object
+ model of the library. Using the Web.config file allows changes to be made
+ without recompiling the web site. In the case of OpenIdRelyingParty and
+ OpenIdProvider, it also allows you to setup your configuration just once, in
+ your .config file, and have it apply to every instance of OpenIdRelyingParty or
+ OpenIdProvider instead of you having to set up that configuration everywhere you
+ instantiate these types.</p>
+ <p>By using the ASP.NET <location> element, you can set some configuration settings
+ for OpenIdRelyingParty or OpenIdProvider based on which directory or web page in
+ your project is instantiating them. This would allow you to, for example,
+ use enhanced SSL security requirements at just the administrator log in screen
+ while allowing non-SSL OpenIDs for ordinary users.</p>
+
+</body>
+</html>
diff --git a/samples/DotNetOpenId.Samples.sln b/samples/DotNetOpenId.Samples.sln index 5bc28b4..86e8db0 100644 --- a/samples/DotNetOpenId.Samples.sln +++ b/samples/DotNetOpenId.Samples.sln @@ -5,10 +5,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RelyingPartyPortal", "Relyi EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ProviderPortal", "ProviderPortal\ProviderPortal.csproj", "{2A59DE0A-B76A-4B42-9A33-04D34548353D}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ProviderCustomStore", "ProviderCustomStore\ProviderCustomStore.csproj", "{2D0B2C39-3F90-484E-848B-F3EF956835C3}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RelyingPartyCustomStore", "RelyingPartyCustomStore\RelyingPartyCustomStore.csproj", "{DB54DC19-BA56-4C22-A8A0-C49289EA4F53}"
-EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RelyingPartyMvc", "RelyingPartyMvc\RelyingPartyMvc.csproj", "{07B193F1-68AD-4E9C-98AF-BEFB5E9403CB}"
EndProject
Global
@@ -25,14 +21,6 @@ Global {2A59DE0A-B76A-4B42-9A33-04D34548353D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2A59DE0A-B76A-4B42-9A33-04D34548353D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2A59DE0A-B76A-4B42-9A33-04D34548353D}.Release|Any CPU.Build.0 = Release|Any CPU
- {2D0B2C39-3F90-484E-848B-F3EF956835C3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {2D0B2C39-3F90-484E-848B-F3EF956835C3}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {2D0B2C39-3F90-484E-848B-F3EF956835C3}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {2D0B2C39-3F90-484E-848B-F3EF956835C3}.Release|Any CPU.Build.0 = Release|Any CPU
- {DB54DC19-BA56-4C22-A8A0-C49289EA4F53}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {DB54DC19-BA56-4C22-A8A0-C49289EA4F53}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {DB54DC19-BA56-4C22-A8A0-C49289EA4F53}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {DB54DC19-BA56-4C22-A8A0-C49289EA4F53}.Release|Any CPU.Build.0 = Release|Any CPU
{07B193F1-68AD-4E9C-98AF-BEFB5E9403CB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{07B193F1-68AD-4E9C-98AF-BEFB5E9403CB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{07B193F1-68AD-4E9C-98AF-BEFB5E9403CB}.Release|Any CPU.ActiveCfg = Release|Any CPU
diff --git a/samples/ProviderCustomStore/.gitignore b/samples/ProviderCustomStore/.gitignore deleted file mode 100644 index f4e2383..0000000 --- a/samples/ProviderCustomStore/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -Bin -obj -*.user -*Trace.txt diff --git a/samples/ProviderCustomStore/Default.aspx b/samples/ProviderCustomStore/Default.aspx deleted file mode 100644 index 375b3cb..0000000 --- a/samples/ProviderCustomStore/Default.aspx +++ /dev/null @@ -1,29 +0,0 @@ -<%@ Page Language="C#" AutoEventWireup="true" %>
-
-<%@ Register Assembly="DotNetOpenId" Namespace="DotNetOpenId" 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">
- <openid:XrdsPublisher runat="server" XrdsUrl="~/op_xrds.aspx" />
- <title>OpenID Provider, by DotNetOpenId</title>
-</head>
-<body>
- <form id="form1" runat="server">
- <h1>
- OpenID Provider, with custom store
- </h1>
- <h2>
- Provided by <a href="http://dotnetopenid.googlecode.com">DotNetOpenId</a>
- </h2>
- <p>
- This sample implements a custom store for associations, which can be useful when
- deploying an OpenId provider site on a web farm.
- </p>
- <p>
- This is a very stripped-down sample. No login is required on this site as it automatically
- responds affirmatively to any OpenId request sent to it. Start the authentication
- process on the Relying Party sample site.
- </p>
- </form>
-</body>
-</html>
diff --git a/samples/ProviderCustomStore/Global.asax b/samples/ProviderCustomStore/Global.asax deleted file mode 100644 index 3fe35c6..0000000 --- a/samples/ProviderCustomStore/Global.asax +++ /dev/null @@ -1 +0,0 @@ -<%@ Application Codebehind="Global.asax.cs" Inherits="ProviderCustomStore.Global" Language="C#" %>
diff --git a/samples/ProviderCustomStore/Global.asax.cs b/samples/ProviderCustomStore/Global.asax.cs deleted file mode 100644 index ad31f4f..0000000 --- a/samples/ProviderCustomStore/Global.asax.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System;
-using ProviderPortal;
-
-namespace ProviderCustomStore {
- public class Global : System.Web.HttpApplication {
- public Global() {
- // since this is a sample, and will often be used with localhost
- DotNetOpenId.UntrustedWebRequest.WhitelistHosts.Add("localhost");
- }
-
- protected void Application_BeginRequest(object sender, EventArgs e) {
- URLRewriter.Process();
- }
- }
-}
\ No newline at end of file diff --git a/samples/ProviderCustomStore/Properties/AssemblyInfo.cs b/samples/ProviderCustomStore/Properties/AssemblyInfo.cs deleted file mode 100644 index 166993e..0000000 --- a/samples/ProviderCustomStore/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System.Reflection;
-using System.Runtime.CompilerServices;
-using System.Runtime.InteropServices;
-
-// 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("ProviderCustomStore")]
-[assembly: AssemblyDescription("")]
-[assembly: AssemblyConfiguration("")]
-[assembly: AssemblyCompany("")]
-[assembly: AssemblyProduct("ProviderCustomStore")]
-[assembly: AssemblyCopyright("Copyright © 2008")]
-[assembly: AssemblyTrademark("")]
-[assembly: AssemblyCulture("")]
-
-// 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("3d5900ae-111a-45be-96b3-d9e4606ca793")]
-
-// Version information for an assembly consists of the following four values:
-//
-// Major Version
-// Minor Version
-// Build Number
-// Revision
-//
-// You can specify all the values or you can default the Revision and Build Numbers
-// by using the '*' as shown below:
-[assembly: AssemblyVersion("1.0.0.0")]
-[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/samples/ProviderCustomStore/ProviderCustomStore.csproj b/samples/ProviderCustomStore/ProviderCustomStore.csproj deleted file mode 100644 index 32ebced..0000000 --- a/samples/ProviderCustomStore/ProviderCustomStore.csproj +++ /dev/null @@ -1,122 +0,0 @@ -<Project ToolsVersion="3.5" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
- <PropertyGroup>
- <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
- <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
- <ProductVersion>9.0.21022</ProductVersion>
- <SchemaVersion>2.0</SchemaVersion>
- <ProjectGuid>{2D0B2C39-3F90-484E-848B-F3EF956835C3}</ProjectGuid>
- <ProjectTypeGuids>{349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc}</ProjectTypeGuids>
- <OutputType>Library</OutputType>
- <AppDesignerFolder>Properties</AppDesignerFolder>
- <RootNamespace>ProviderCustomStore</RootNamespace>
- <AssemblyName>ProviderCustomStore</AssemblyName>
- <TargetFrameworkVersion>v2.0</TargetFrameworkVersion>
- </PropertyGroup>
- <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
- <DebugSymbols>true</DebugSymbols>
- <DebugType>full</DebugType>
- <Optimize>false</Optimize>
- <OutputPath>bin\</OutputPath>
- <DefineConstants>DEBUG;TRACE</DefineConstants>
- <ErrorReport>prompt</ErrorReport>
- <WarningLevel>4</WarningLevel>
- </PropertyGroup>
- <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
- <DebugType>pdbonly</DebugType>
- <Optimize>true</Optimize>
- <OutputPath>bin\</OutputPath>
- <DefineConstants>TRACE</DefineConstants>
- <ErrorReport>prompt</ErrorReport>
- <WarningLevel>4</WarningLevel>
- </PropertyGroup>
- <ItemGroup>
- <Reference Include="log4net, Version=1.2.10.0, Culture=neutral, PublicKeyToken=1b44e1d426115821, processorArchitecture=MSIL">
- <SpecificVersion>False</SpecificVersion>
- <HintPath>..\..\lib\log4net.dll</HintPath>
- </Reference>
- <Reference Include="System" />
- <Reference Include="System.Data" />
- <Reference Include="System.Drawing" />
- <Reference Include="System.Web" />
- <Reference Include="System.Xml" />
- <Reference Include="System.Configuration" />
- <Reference Include="System.Web.Services" />
- <Reference Include="System.EnterpriseServices" />
- <Reference Include="System.Web.Mobile" />
- </ItemGroup>
- <ItemGroup>
- <Content Include="Default.aspx" />
- <Content Include="Global.asax" />
- <Content Include="op_xrds.aspx" />
- <Content Include="Server.aspx" />
- <Content Include="user.aspx" />
- <Content Include="user_xrds.aspx" />
- <Content Include="Web.config" />
- </ItemGroup>
- <ItemGroup>
- <Compile Include="..\ProviderPortal\Code\URLRewriter.cs">
- <Link>URLRewriter.cs</Link>
- </Compile>
- <Compile Include="CustomStore.cs" />
- <Compile Include="CustomStoreDataSet.Designer.cs">
- <DependentUpon>CustomStoreDataSet.xsd</DependentUpon>
- <AutoGen>True</AutoGen>
- <DesignTime>True</DesignTime>
- </Compile>
- <Compile Include="Global.asax.cs">
- <DependentUpon>Global.asax</DependentUpon>
- </Compile>
- <Compile Include="Properties\AssemblyInfo.cs" />
- <Compile Include="Server.aspx.cs">
- <DependentUpon>Server.aspx</DependentUpon>
- <SubType>ASPXCodeBehind</SubType>
- </Compile>
- <Compile Include="Server.aspx.designer.cs">
- <DependentUpon>Server.aspx</DependentUpon>
- </Compile>
- </ItemGroup>
- <ItemGroup>
- <ProjectReference Include="..\..\src\DotNetOpenId\DotNetOpenId.csproj">
- <Project>{5D6EDC86-F5B2-4786-8376-4E7C24C63D39}</Project>
- <Name>DotNetOpenId</Name>
- </ProjectReference>
- </ItemGroup>
- <ItemGroup>
- <None Include="CustomStoreDataSet.xsc">
- <DependentUpon>CustomStoreDataSet.xsd</DependentUpon>
- </None>
- <None Include="CustomStoreDataSet.xsd">
- <Generator>MSDataSetGenerator</Generator>
- <LastGenOutput>CustomStoreDataSet.Designer.cs</LastGenOutput>
- <SubType>Designer</SubType>
- </None>
- <None Include="CustomStoreDataSet.xss">
- <DependentUpon>CustomStoreDataSet.xsd</DependentUpon>
- </None>
- </ItemGroup>
- <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
- <Import Project="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v9.0\WebApplications\Microsoft.WebApplication.targets" />
- <!-- To modify your build process, add your task inside one of the targets below and uncomment it.
- Other similar extension points exist, see Microsoft.Common.targets.
- <Target Name="BeforeBuild">
- </Target>
- <Target Name="AfterBuild">
- </Target>
- -->
- <ProjectExtensions>
- <VisualStudio>
- <FlavorProperties GUID="{349c5851-65df-11da-9384-00065b846f21}">
- <WebProjectProperties>
- <UseIIS>False</UseIIS>
- <AutoAssignPort>True</AutoAssignPort>
- <DevelopmentServerPort>1230</DevelopmentServerPort>
- <DevelopmentServerVPath>/</DevelopmentServerVPath>
- <IISUrl>
- </IISUrl>
- <NTLMAuthentication>False</NTLMAuthentication>
- <SaveServerSettingsInUserFile>False</SaveServerSettingsInUserFile>
- </WebProjectProperties>
- </FlavorProperties>
- </VisualStudio>
- </ProjectExtensions>
-</Project>
\ No newline at end of file diff --git a/samples/ProviderCustomStore/Server.aspx b/samples/ProviderCustomStore/Server.aspx deleted file mode 100644 index 8b487a5..0000000 --- a/samples/ProviderCustomStore/Server.aspx +++ /dev/null @@ -1,44 +0,0 @@ -<%@ Page Language="C#" AutoEventWireup="true" Inherits="Server" CodeBehind="server.aspx.cs" ValidateRequest="false" %>
-
-<%@ Register Assembly="DotNetOpenId" Namespace="DotNetOpenId.Provider" TagPrefix="openid" %>
-<html>
-<head>
- <title>This is an OpenID server</title>
-</head>
-<body>
- <form id="Form1" runat='server'>
- <p>
- This is an OpenID server endpoint.
- </p>
- <p>
- For more information about OpenID, see:
- </p>
- <table>
- <tr>
- <td>
- <a href="http://dotnetopenid.googlecode.com/">http://dotnetopenid.googlecode.com/</a>
- </td>
- <td>
- Home of this library
- </td>
- </tr>
- <tr>
- <td>
- <a href="http://www.openid.net/">http://www.openid.net/</a>
- </td>
- <td>
- The official OpenID Web site
- </td>
- </tr>
- <tr>
- <td>
- <a href="http://www.openidenabled.com/">http://www.openidenabled.com/</a>
- </td>
- <td>
- An OpenID community Web site
- </td>
- </tr>
- </table>
- </form>
-</body>
-</html>
diff --git a/samples/ProviderCustomStore/Server.aspx.cs b/samples/ProviderCustomStore/Server.aspx.cs deleted file mode 100644 index 9d4a9f6..0000000 --- a/samples/ProviderCustomStore/Server.aspx.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System;
-using System.Collections.Specialized;
-using System.Diagnostics;
-using DotNetOpenId.Provider;
-using ProviderCustomStore;
-
-public partial class Server : System.Web.UI.Page {
- protected void Page_Load(object sender, EventArgs e) {
- var builder = new UriBuilder(Request.Url);
- builder.Query = null;
- builder.Fragment = null;
- Uri providerEndpoint = builder.Uri;
- NameValueCollection query = Request.RequestType == "GET" ? Request.QueryString : Request.Form;
- OpenIdProvider op = new OpenIdProvider(CustomStore.Instance, providerEndpoint, Request.Url, query);
- if (op.Request != null) {
- if (!op.Request.IsResponseReady) {
- var request = (IAuthenticationRequest)op.Request;
- if (request.IsDirectedIdentity) throw new NotSupportedException("This sample does not implement directed identity support.");
- request.IsAuthenticated = true;
- }
- Debug.Assert(op.Request.IsResponseReady);
- op.Request.Response.Send();
- Response.End();
- }
- }
-}
diff --git a/samples/ProviderCustomStore/Server.aspx.designer.cs b/samples/ProviderCustomStore/Server.aspx.designer.cs deleted file mode 100644 index 6a53f1f..0000000 --- a/samples/ProviderCustomStore/Server.aspx.designer.cs +++ /dev/null @@ -1,23 +0,0 @@ -//------------------------------------------------------------------------------
-// <auto-generated>
-// This code was generated by a tool.
-// Runtime Version:2.0.50727.1434
-//
-// Changes to this file may cause incorrect behavior and will be lost if
-// the code is regenerated.
-// </auto-generated>
-//------------------------------------------------------------------------------
-
-
-
-public partial class Server {
-
- /// <summary>
- /// Form1 control.
- /// </summary>
- /// <remarks>
- /// Auto-generated field.
- /// To modify move field declaration from designer file to code-behind file.
- /// </remarks>
- protected global::System.Web.UI.HtmlControls.HtmlForm Form1;
-}
diff --git a/samples/ProviderCustomStore/Web.config b/samples/ProviderCustomStore/Web.config deleted file mode 100644 index 1d4b0d4..0000000 --- a/samples/ProviderCustomStore/Web.config +++ /dev/null @@ -1,64 +0,0 @@ -<?xml version="1.0"?>
-<!--
- Note: As an alternative to hand editing this file you can use the
- web admin tool to configure settings for your application. Use
- the Website->Asp.Net Configuration option in Visual Studio.
- A full list of settings and comments can be found in
- machine.config.comments usually located in
- \Windows\Microsoft.Net\Framework\v2.x\Config
--->
-<configuration>
- <configSections>
- <section name="urlrewrites" type="ProviderPortal.URLRewriter"/>
- </configSections>
- <connectionStrings/>
- <!--
- Original version created by Richard Birkby (2002-02-22, http://www.codeproject.com/aspnet/URLRewriter.asp)
- Maps from old website to new website using Regular Expressions
- rule/url - old website url (Regular Expression)
- rule/rewrite - new website replacement expression
- Of two or more rules which match a given request, the first will always take precedance.
- -->
- <urlrewrites>
- <rule>
- <!-- This rewrites urls like: user/john ->user.aspx?username=john-->
- <url>/user/(.*)</url>
- <rewrite>/user.aspx?username=$1</rewrite>
- </rule>
- </urlrewrites>
- <system.web>
- <compilation debug="true" />
- <sessionState mode="InProc" cookieless="false"/>
- <membership>
- <providers>
- <clear/>
- <add
- name="AspNetSqlMembershipProvider"
- type="System.Web.Security.SqlMembershipProvider"
- connectionStringName="LocalSqlServer"
- enablePasswordRetrieval="false"
- enablePasswordReset="true"
- requiresQuestionAndAnswer="false"
- applicationName="/"
- requiresUniqueEmail="false"
- passwordFormat="Hashed"
- maxInvalidPasswordAttempts="5"
- minRequiredPasswordLength="1"
- minRequiredNonalphanumericCharacters="0"
- passwordAttemptWindow="10"
- passwordStrengthRegularExpression=""
- />
- </providers>
- </membership>
- <authentication mode="Forms">
- <forms name="ProviderCustomStoreSession"/> <!-- named cookie prevents conflicts with other samples -->
- </authentication>
- <customErrors mode="RemoteOnly"/>
- <!-- Trust level discussion:
- Full: everything works
- High: TRACE compilation symbol must NOT be defined
- Medium/Low: doesn't work on default machine.config. ConfigurationPermission is denied (why is it needed?)
- -->
- <trust level="Full" originUrl=""/>
- </system.web>
-</configuration>
diff --git a/samples/ProviderCustomStore/op_xrds.aspx b/samples/ProviderCustomStore/op_xrds.aspx deleted file mode 100644 index b906bfe..0000000 --- a/samples/ProviderCustomStore/op_xrds.aspx +++ /dev/null @@ -1,19 +0,0 @@ -<%@ Page Language="C#" AutoEventWireup="true" ContentType="application/xrds+xml" %><?xml version="1.0" encoding="UTF-8"?> -<%-- -This page is a required as part of the service discovery phase of the openid
-protocol (step 1). It simply renders the xml for doing service discovery of
-server.aspx using the xrds mechanism.
-This XRDS doc is discovered via the user.aspx page.
---%> -<xrds:XRDS - xmlns:xrds="xri://$xrds" - xmlns:openid="http://openid.net/xmlns/1.0" - xmlns="xri://$xrd*($v*2.0)"> - <XRD> - <Service priority="10"> - <Type>http://specs.openid.net/auth/2.0/server</Type> - <Type>http://openid.net/extensions/sreg/1.1</Type> - <URI><%=new Uri(Request.Url, Response.ApplyAppPathModifier("~/server.aspx"))%></URI> - </Service> - </XRD> -</xrds:XRDS> diff --git a/samples/ProviderCustomStore/user.aspx b/samples/ProviderCustomStore/user.aspx deleted file mode 100644 index 033af02..0000000 --- a/samples/ProviderCustomStore/user.aspx +++ /dev/null @@ -1,17 +0,0 @@ -<%@ Page Language="C#" AutoEventWireup="true" %>
-
-<%@ Register Assembly="DotNetOpenId" Namespace="DotNetOpenId.Provider" TagPrefix="openid" %>
-<html>
-<head>
- <openid:IdentityEndpoint ID="IdentityEndpoint20" runat="server" ProviderEndpointUrl="~/Server.aspx"
- XrdsUrl="~/user_xrds.aspx" ProviderVersion="V20" />
- <!-- and for backward compatibility with OpenID 1.x RPs... -->
- <openid:IdentityEndpoint ID="IdentityEndpoint11" runat="server" ProviderEndpointUrl="~/Server.aspx"
- ProviderVersion="V11" />
-</head>
-<body>
- <p>
- OpenID identity page for <%=Request.QueryString["username"]%>
- </p>
-</body>
-</html>
diff --git a/samples/ProviderCustomStore/user_xrds.aspx b/samples/ProviderCustomStore/user_xrds.aspx deleted file mode 100644 index 56b1244..0000000 --- a/samples/ProviderCustomStore/user_xrds.aspx +++ /dev/null @@ -1,24 +0,0 @@ -<%@ Page Language="C#" AutoEventWireup="true" ContentType="application/xrds+xml" %><?xml version="1.0" encoding="UTF-8"?> -<%-- -This page is a required as part of the service discovery phase of the openid
-protocol (step 1). It simply renders the xml for doing service discovery of
-server.aspx using the xrds mechanism.
-This XRDS doc is discovered via the user.aspx page.
---%> -<xrds:XRDS - xmlns:xrds="xri://$xrds" - xmlns:openid="http://openid.net/xmlns/1.0" - xmlns="xri://$xrd*($v*2.0)"> - <XRD> - <Service priority="10"> - <Type>http://specs.openid.net/auth/2.0/signon</Type> - <Type>http://openid.net/extensions/sreg/1.1</Type> - <URI><%=new Uri(Request.Url, Response.ApplyAppPathModifier("~/server.aspx"))%></URI> - </Service> - <Service priority="20"> - <Type>http://openid.net/signon/1.0</Type> - <Type>http://openid.net/extensions/sreg/1.1</Type> - <URI><%=new Uri(Request.Url, Response.ApplyAppPathModifier("~/server.aspx"))%></URI> - </Service> - </XRD> -</xrds:XRDS> diff --git a/samples/ProviderCustomStore/CustomStore.cs b/samples/ProviderPortal/Code/CustomStore.cs index 5b25ba4..de7043f 100644 --- a/samples/ProviderCustomStore/CustomStore.cs +++ b/samples/ProviderPortal/Code/CustomStore.cs @@ -6,7 +6,7 @@ using DotNetOpenId; using DotNetOpenId.RelyingParty;
using IProviderAssociationStore = DotNetOpenId.IAssociationStore<DotNetOpenId.AssociationRelyingPartyType>;
-namespace ProviderCustomStore {
+namespace ProviderPortal.Code {
/// <summary>
/// This custom store serializes all elements to demonstrate peristent and/or shared storage.
/// This is common in a web farm, for example.
@@ -18,8 +18,7 @@ namespace ProviderCustomStore { /// that using a database is possible.
/// </remarks>
public class CustomStore : IProviderAssociationStore {
- public static CustomStore Instance = new CustomStore();
- public CustomStoreDataSet dataSet = new CustomStoreDataSet();
+ static CustomStoreDataSet dataSet = new CustomStoreDataSet();
#region IAssociationStore<AssociationRelyingPartyType> Members
diff --git a/samples/ProviderCustomStore/CustomStoreDataSet.Designer.cs b/samples/ProviderPortal/Code/CustomStoreDataSet.Designer.cs index 87e5c13..6fbe114 100644 --- a/samples/ProviderCustomStore/CustomStoreDataSet.Designer.cs +++ b/samples/ProviderPortal/Code/CustomStoreDataSet.Designer.cs @@ -1,7 +1,7 @@ //------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
-// Runtime Version:2.0.50727.1434
+// Runtime Version:2.0.50727.3053
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
@@ -10,7 +10,7 @@ #pragma warning disable 1591
-namespace ProviderCustomStore {
+namespace ProviderPortal.Code {
/// <summary>
diff --git a/samples/ProviderCustomStore/CustomStoreDataSet.xsc b/samples/ProviderPortal/Code/CustomStoreDataSet.xsc index 551fc56..551fc56 100644 --- a/samples/ProviderCustomStore/CustomStoreDataSet.xsc +++ b/samples/ProviderPortal/Code/CustomStoreDataSet.xsc diff --git a/samples/ProviderCustomStore/CustomStoreDataSet.xsd b/samples/ProviderPortal/Code/CustomStoreDataSet.xsd index 47f68e8..47f68e8 100644 --- a/samples/ProviderCustomStore/CustomStoreDataSet.xsd +++ b/samples/ProviderPortal/Code/CustomStoreDataSet.xsd diff --git a/samples/ProviderCustomStore/CustomStoreDataSet.xss b/samples/ProviderPortal/Code/CustomStoreDataSet.xss index d097e67..d097e67 100644 --- a/samples/ProviderCustomStore/CustomStoreDataSet.xss +++ b/samples/ProviderPortal/Code/CustomStoreDataSet.xss diff --git a/samples/ProviderPortal/Code/Util.cs b/samples/ProviderPortal/Code/Util.cs index 982748c..f864972 100644 --- a/samples/ProviderPortal/Code/Util.cs +++ b/samples/ProviderPortal/Code/Util.cs @@ -19,6 +19,29 @@ public class Util { return ExtractUserName(new Uri(identifier.ToString()));
}
public static Identifier BuildIdentityUrl() {
- return new Uri(HttpContext.Current.Request.Url, "/user/" + HttpContext.Current.User.Identity.Name);
+ string username = HttpContext.Current.User.Identity.Name;
+ // be sure to normalize case the way the user's identity page does.
+ username = username.Substring(0, 1).ToUpperInvariant() + username.Substring(1).ToLowerInvariant();
+ return new Uri(HttpContext.Current.Request.Url, "/user/" + username);
+ }
+ internal static void ProcessAuthenticationChallenge(IAuthenticationRequest idrequest) {
+ if (idrequest.Immediate) {
+ if (idrequest.IsDirectedIdentity) {
+ if (HttpContext.Current.User.Identity.IsAuthenticated) {
+ idrequest.LocalIdentifier = Util.BuildIdentityUrl();
+ idrequest.IsAuthenticated = true;
+ } else {
+ idrequest.IsAuthenticated = false;
+ }
+ } else {
+ string userOwningOpenIdUrl = Util.ExtractUserName(idrequest.LocalIdentifier);
+ // NOTE: in a production provider site, you may want to only
+ // respond affirmatively if the user has already authorized this consumer
+ // to know the answer.
+ idrequest.IsAuthenticated = userOwningOpenIdUrl == HttpContext.Current.User.Identity.Name;
+ }
+ } else {
+ HttpContext.Current.Response.Redirect("~/decide.aspx", true);
+ }
}
}
diff --git a/samples/ProviderPortal/Global.asax.cs b/samples/ProviderPortal/Global.asax.cs index 599bae2..091ea56 100644 --- a/samples/ProviderPortal/Global.asax.cs +++ b/samples/ProviderPortal/Global.asax.cs @@ -8,11 +8,6 @@ namespace ProviderPortal { public class Global : System.Web.HttpApplication {
internal static StringBuilder LogMessages = new StringBuilder();
- public Global() {
- // since this is a sample, and will often be used with localhost
- DotNetOpenId.UntrustedWebRequest.WhitelistHosts.Add("localhost");
- }
-
public static log4net.ILog Logger = log4net.LogManager.GetLogger(typeof(Global));
protected void Application_Start(object sender, EventArgs e) {
diff --git a/samples/ProviderPortal/Provider.ashx b/samples/ProviderPortal/Provider.ashx new file mode 100644 index 0000000..27475cc --- /dev/null +++ b/samples/ProviderPortal/Provider.ashx @@ -0,0 +1 @@ +<%@ WebHandler Language="C#" CodeBehind="Provider.ashx.cs" Class="ProviderPortal.Provider" %>
diff --git a/samples/ProviderPortal/Provider.ashx.cs b/samples/ProviderPortal/Provider.ashx.cs new file mode 100644 index 0000000..7e2e749 --- /dev/null +++ b/samples/ProviderPortal/Provider.ashx.cs @@ -0,0 +1,62 @@ +using System.Web;
+using System.Web.SessionState;
+using DotNetOpenId.Provider;
+
+namespace ProviderPortal {
+ /// <summary>
+ /// A fast OpenID message handler that responds to OpenID messages
+ /// directed at the Provider.
+ /// </summary>
+ /// <remarks>
+ /// This performs the same function as server.aspx, which uses the ProviderEndpoint
+ /// control to reduce the amount of source code in the web site. A typical Provider
+ /// site will have EITHER this .ashx handler OR the .aspx page -- NOT both.
+ /// </remarks>
+ public class Provider : IHttpHandler, IRequiresSessionState {
+ const string pendingAuthenticationRequestKey = "pendingAuthenticationRequestKey";
+ internal static IAuthenticationRequest PendingAuthenticationRequest {
+ get { return HttpContext.Current.Session[pendingAuthenticationRequestKey] as IAuthenticationRequest; }
+ set { HttpContext.Current.Session[pendingAuthenticationRequestKey] = value; }
+ }
+
+ public void ProcessRequest(HttpContext context) {
+ OpenIdProvider provider = new OpenIdProvider();
+ if (provider.Request != null) {
+ // Some OpenID requests are automatable and can be responded to immediately.
+ if (!provider.Request.IsResponseReady) {
+ // But authentication requests cannot be responded to until something on
+ // this site decides whether to approve or disapprove the authentication.
+ var idrequest = (IAuthenticationRequest)provider.Request;
+ // We store the authentication request in the user's session so that
+ // redirects and user prompts can appear and eventually some page can decide
+ // to respond to the OpenID authentication request either affirmatively or
+ // negatively.
+ PendingAuthenticationRequest = idrequest;
+ // We delegate that approval process to our utility method that we share
+ // with our other Provider sample page server.aspx.
+ Util.ProcessAuthenticationChallenge(idrequest);
+ // As part of authentication approval, the user may need to authenticate
+ // to this Provider and/or decide whether to allow the requesting RP site
+ // to log this user in. If any UI needs to be presented to the user,
+ // the previous call to ProcessAuthenticationChallenge MAY not return
+ // due to a redirect to some ASPX page.
+ } else {
+ // Some other automatable OpenID request is coming down, so clear
+ // any previously session stored authentication request that might be
+ // stored for this user.
+ PendingAuthenticationRequest = null;
+ }
+ // Whether this was an automated message or an authentication message,
+ // if there is a response ready to send back immediately, do so.
+ if (provider.Request.IsResponseReady) {
+ provider.Request.Response.Send();
+ PendingAuthenticationRequest = null;
+ }
+ }
+ }
+
+ public bool IsReusable {
+ get { return true; }
+ }
+ }
+}
diff --git a/samples/ProviderPortal/ProviderPortal.csproj b/samples/ProviderPortal/ProviderPortal.csproj index cd8e45b..f39b097 100644 --- a/samples/ProviderPortal/ProviderPortal.csproj +++ b/samples/ProviderPortal/ProviderPortal.csproj @@ -58,6 +58,12 @@ <Content Include="user_xrds.aspx" />
</ItemGroup>
<ItemGroup>
+ <Compile Include="Code\CustomStore.cs" />
+ <Compile Include="Code\CustomStoreDataSet.Designer.cs">
+ <DependentUpon>CustomStoreDataSet.xsd</DependentUpon>
+ <AutoGen>True</AutoGen>
+ <DesignTime>True</DesignTime>
+ </Compile>
<Compile Include="Code\ReadOnlyXmlMembershipProvider.cs" />
<Compile Include="Code\TracePageAppender.cs" />
<Compile Include="Code\URLRewriter.cs" />
@@ -87,6 +93,9 @@ <DependentUpon>ProfileFields.ascx</DependentUpon>
</Compile>
<Compile Include="Properties\AssemblyInfo.cs" />
+ <Compile Include="Provider.ashx.cs">
+ <DependentUpon>Provider.ashx</DependentUpon>
+ </Compile>
<Compile Include="server.aspx.cs">
<DependentUpon>server.aspx</DependentUpon>
<SubType>ASPXCodeBehind</SubType>
@@ -122,6 +131,20 @@ <Content Include="styles.css" />
<Content Include="TracePage.aspx" />
</ItemGroup>
+ <ItemGroup>
+ <None Include="Code\CustomStoreDataSet.xsc">
+ <DependentUpon>CustomStoreDataSet.xsd</DependentUpon>
+ </None>
+ <None Include="Code\CustomStoreDataSet.xsd">
+ <Generator>MSDataSetGenerator</Generator>
+ <LastGenOutput>CustomStoreDataSet.Designer.cs</LastGenOutput>
+ <SubType>Designer</SubType>
+ </None>
+ <None Include="Code\CustomStoreDataSet.xss">
+ <DependentUpon>CustomStoreDataSet.xsd</DependentUpon>
+ </None>
+ <Content Include="Provider.ashx" />
+ </ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<Import Project="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v9.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/ProviderPortal/Web.config b/samples/ProviderPortal/Web.config index a667724..c27f8bc 100644 --- a/samples/ProviderPortal/Web.config +++ b/samples/ProviderPortal/Web.config @@ -11,7 +11,24 @@ <configSections>
<section name="urlrewrites" type="ProviderPortal.URLRewriter" requirePermission="false"/>
<section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler" requirePermission="false" />
+ <sectionGroup name="dotNetOpenId">
+ <section name="relyingParty" type="DotNetOpenId.Configuration.RelyingPartySection" requirePermission="false" allowLocation="true"/>
+ <section name="provider" type="DotNetOpenId.Configuration.ProviderSection" requirePermission="false" allowLocation="true"/>
+ <section name="untrustedWebRequest" type="DotNetOpenId.Configuration.UntrustedWebRequestSection" requirePermission="false" allowLocation="false"/>
+ </sectionGroup>
</configSections>
+ <dotNetOpenId>
+ <provider>
+ <!-- Uncomment the following to activate the sample custom store. -->
+ <!--<store type="ProviderPortal.Code.CustomStore, ProviderPortal" />-->
+ </provider>
+ <untrustedWebRequest>
+ <whitelistHosts>
+ <!-- since this is a sample, and will often be used with localhost -->
+ <add name="localhost" />
+ </whitelistHosts>
+ </untrustedWebRequest>
+ </dotNetOpenId>
<connectionStrings/>
<!--
Original version created by Richard Birkby (2002-02-22, http://www.codeproject.com/aspnet/URLRewriter.asp)
diff --git a/samples/ProviderPortal/decide.aspx.cs b/samples/ProviderPortal/decide.aspx.cs index 1e1f95d..db381ac 100644 --- a/samples/ProviderPortal/decide.aspx.cs +++ b/samples/ProviderPortal/decide.aspx.cs @@ -23,7 +23,7 @@ public partial class decide : Page { realmLabel.Text = ProviderEndpoint.PendingAuthenticationRequest.Realm.ToString();
// check that the logged in user is the same as the user requesting authentication to the consumer. If not, then log them out.
- if (User.Identity.Name == Util.ExtractUserName(ProviderEndpoint.PendingAuthenticationRequest.LocalIdentifier)) {
+ if (String.Equals(User.Identity.Name, Util.ExtractUserName(ProviderEndpoint.PendingAuthenticationRequest.LocalIdentifier), StringComparison.OrdinalIgnoreCase)) {
// if simple registration fields were used, then prompt the user for them
var requestedFields = ProviderEndpoint.PendingAuthenticationRequest.GetExtension<ClaimsRequest>();
if (requestedFields != null) {
@@ -43,9 +43,14 @@ public partial class decide : Page { protected void Yes_Click(Object sender, EventArgs e) {
var sregRequest = ProviderEndpoint.PendingAuthenticationRequest.GetExtension<ClaimsRequest>();
- var sregResponse = profileFields.GetOpenIdProfileFields(sregRequest);
+ ClaimsResponse sregResponse = null;
+ if (sregRequest != null) {
+ sregResponse = profileFields.GetOpenIdProfileFields(sregRequest);
+ }
ProviderEndpoint.PendingAuthenticationRequest.IsAuthenticated = true;
- ProviderEndpoint.PendingAuthenticationRequest.AddResponseExtension(sregResponse);
+ if (sregResponse != null) {
+ ProviderEndpoint.PendingAuthenticationRequest.AddResponseExtension(sregResponse);
+ }
Debug.Assert(ProviderEndpoint.PendingAuthenticationRequest.IsResponseReady);
ProviderEndpoint.PendingAuthenticationRequest.Response.Send();
ProviderEndpoint.PendingAuthenticationRequest = null;
diff --git a/samples/ProviderPortal/server.aspx b/samples/ProviderPortal/server.aspx index 318df4a..e67a7ba 100644 --- a/samples/ProviderPortal/server.aspx +++ b/samples/ProviderPortal/server.aspx @@ -7,6 +7,13 @@ </head>
<body>
<form runat='server'>
+ <%-- This page provides an example of how to use the ProviderEndpoint control on an ASPX page
+ to host an OpenID Provider. Alternatively for greater performance an .ashx file can be used.
+ See Provider.ashx for an example. A typical web site will NOT use both .ashx and .aspx
+ provider endpoints.
+ This server.aspx page is the default provider endpoint to use. To switch to the .ashx handler,
+ change the user_xrds.aspx and op_xrds.aspx files to point to provider.ashx instead of server.aspx.
+ --%>
<openid:ProviderEndpoint runat="server" OnAuthenticationChallenge="provider_AuthenticationChallenge" />
<p>
<asp:Label ID="serverEndpointUrl" runat="server" EnableViewState="false" />
diff --git a/samples/ProviderPortal/server.aspx.cs b/samples/ProviderPortal/server.aspx.cs index 8589f40..fbc9a6c 100644 --- a/samples/ProviderPortal/server.aspx.cs +++ b/samples/ProviderPortal/server.aspx.cs @@ -10,24 +10,6 @@ public partial class server : System.Web.UI.Page { serverEndpointUrl.Text = Request.Url.ToString();
}
protected void provider_AuthenticationChallenge(object sender, AuthenticationChallengeEventArgs e) {
- var idrequest = e.Request;
- if (idrequest.Immediate) {
- if (idrequest.IsDirectedIdentity) {
- if (User.Identity.IsAuthenticated) {
- idrequest.LocalIdentifier = Util.BuildIdentityUrl();
- idrequest.IsAuthenticated = true;
- } else {
- idrequest.IsAuthenticated = false;
- }
- } else {
- string userOwningOpenIdUrl = Util.ExtractUserName(idrequest.LocalIdentifier);
- // NOTE: in a production provider site, you may want to only
- // respond affirmatively if the user has already authorized this consumer
- // to know the answer.
- idrequest.IsAuthenticated = userOwningOpenIdUrl == User.Identity.Name;
- }
- } else {
- Response.Redirect("~/decide.aspx", true); // This ends processing on this page.
- }
+ Util.ProcessAuthenticationChallenge(e.Request);
}
}
\ No newline at end of file diff --git a/samples/ProviderPortal/server.aspx.designer.cs b/samples/ProviderPortal/server.aspx.designer.cs index 16dd628..90203b1 100644 --- a/samples/ProviderPortal/server.aspx.designer.cs +++ b/samples/ProviderPortal/server.aspx.designer.cs @@ -1,7 +1,7 @@ //------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
-// Runtime Version:2.0.50727.1434
+// Runtime Version:2.0.50727.3053
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
diff --git a/samples/ProviderPortal/user.aspx b/samples/ProviderPortal/user.aspx index 68bccbc..fc3716f 100644 --- a/samples/ProviderPortal/user.aspx +++ b/samples/ProviderPortal/user.aspx @@ -1,9 +1,10 @@ <%@ Page Language="C#" AutoEventWireup="true" Inherits="user" CodeBehind="user.aspx.cs" MasterPageFile="~/Site.Master" %>
<%@ Register Assembly="DotNetOpenId" Namespace="DotNetOpenId.Provider" TagPrefix="openid" %>
-<asp:Content ID="Content2" runat="server" ContentPlaceHolderID="Head">
+<asp:Content ID="Content2" runat="server" ContentPlaceHolderID="head">
<openid:IdentityEndpoint ID="IdentityEndpoint20" runat="server" ProviderEndpointUrl="~/Server.aspx"
- XrdsUrl="~/user_xrds.aspx" ProviderVersion="V20" />
+ XrdsUrl="~/user_xrds.aspx" ProviderVersion="V20"
+ AutoNormalizeRequest="true" OnNormalizeUri="IdentityEndpoint20_NormalizeUri" />
<!-- and for backward compatibility with OpenID 1.x RPs... -->
<openid:IdentityEndpoint ID="IdentityEndpoint11" runat="server" ProviderEndpointUrl="~/Server.aspx"
ProviderVersion="V11" />
diff --git a/samples/ProviderPortal/user.aspx.cs b/samples/ProviderPortal/user.aspx.cs index 5400200..744cae5 100644 --- a/samples/ProviderPortal/user.aspx.cs +++ b/samples/ProviderPortal/user.aspx.cs @@ -1,5 +1,5 @@ using System;
-using System.Configuration;
+using DotNetOpenId.Provider;
/// <summary>
/// This page is a required as part of the service discovery phase of the openid protocol (step 1).
@@ -18,4 +18,15 @@ public partial class user : System.Web.UI.Page { protected void Page_Load(object sender, EventArgs e) {
usernameLabel.Text = Request.QueryString["username"];
}
+
+ protected void IdentityEndpoint20_NormalizeUri(object sender, IdentityEndpointNormalizationEventArgs e) {
+ // This sample Provider has a custom policy for normalizing URIs, which is that the whole
+ // path of the URI be lowercase except for the first letter of the username.
+ UriBuilder normalized = new UriBuilder(e.UserSuppliedIdentifier);
+ string username = Request.QueryString["username"].TrimEnd('/').ToLowerInvariant();
+ username = username.Substring(0, 1).ToUpperInvariant() + username.Substring(1);
+ normalized.Path = "/user/" + username;
+ normalized.Scheme = "http"; // for a real Provider, this should be HTTPS if supported.
+ e.NormalizedIdentifier = normalized.Uri;
+ }
}
diff --git a/samples/RelyingPartyCustomStore/.gitignore b/samples/RelyingPartyCustomStore/.gitignore deleted file mode 100644 index 0986274..0000000 --- a/samples/RelyingPartyCustomStore/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -Bin -obj -*Trace.txt -*.user diff --git a/samples/RelyingPartyCustomStore/Default.aspx b/samples/RelyingPartyCustomStore/Default.aspx deleted file mode 100644 index 445b3a9..0000000 --- a/samples/RelyingPartyCustomStore/Default.aspx +++ /dev/null @@ -1,30 +0,0 @@ -<%@ Page Language="C#" AutoEventWireup="true" %>
-
-<%@ Register Assembly="DotNetOpenId" Namespace="DotNetOpenId" 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 id="Head1" runat="server">
- <title>OpenID Relying Party, by DotNetOpenId</title>
- <openid:XrdsPublisher runat="server" XrdsUrl="~/xrds.aspx" />
-</head>
-<body>
- <form id="form1" runat="server">
- <h1>
- OpenID Relying Party, with custom store
- </h1>
- <h2>
- Provided by <a href="http://dotnetopenid.googlecode.com">DotNetOpenId</a>
- </h2>
- <p>
- This sample implements a custom store for associations and nonces, which can be useful
- when deploying a relying party site on a web farm.
- </p>
- <p>
- Visit the
- <asp:HyperLink ID="HyperLink1" runat="server" NavigateUrl="~/MembersOnly/Default.aspx"
- Text="Members Only" />
- area. (This will trigger a login demo).
- </p>
- </form>
-</body>
-</html>
diff --git a/samples/RelyingPartyCustomStore/Global.asax b/samples/RelyingPartyCustomStore/Global.asax deleted file mode 100644 index a00a077..0000000 --- a/samples/RelyingPartyCustomStore/Global.asax +++ /dev/null @@ -1 +0,0 @@ -<%@ Application Codebehind="Global.asax.cs" Inherits="RelyingPartyCustomStore.Global" Language="C#" %>
diff --git a/samples/RelyingPartyCustomStore/Global.asax.cs b/samples/RelyingPartyCustomStore/Global.asax.cs deleted file mode 100644 index b8b7593..0000000 --- a/samples/RelyingPartyCustomStore/Global.asax.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System;
-using System.Collections;
-using System.Configuration;
-using System.Data;
-using System.Web;
-using System.Web.Security;
-using System.Web.SessionState;
-
-namespace RelyingPartyCustomStore {
- public class Global : System.Web.HttpApplication {
- public Global() {
- // since this is a sample, and will often be used with localhost
- DotNetOpenId.UntrustedWebRequest.WhitelistHosts.Add("localhost");
- }
- }
-}
\ No newline at end of file diff --git a/samples/RelyingPartyCustomStore/MembersOnly/Default.aspx b/samples/RelyingPartyCustomStore/MembersOnly/Default.aspx deleted file mode 100644 index ae974cc..0000000 --- a/samples/RelyingPartyCustomStore/MembersOnly/Default.aspx +++ /dev/null @@ -1,21 +0,0 @@ -<%@ Page Language="C#" AutoEventWireup="true" %>
-
-<!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">
- <title>Welcome OpenID User!</title>
-</head>
-<body>
- <form id="form1" runat="server">
- <h1>
- Members Only Area
- </h1>
- <p>
- Congratulations, <b>
- <asp:LoginName ID="LoginName1" runat="server" />
- </b>. You have completed the OpenID login process.
- </p>
- <asp:LoginStatus ID="LoginStatus1" runat="server" />
- </form>
-</body>
-</html>
diff --git a/samples/RelyingPartyCustomStore/MembersOnly/Web.config b/samples/RelyingPartyCustomStore/MembersOnly/Web.config deleted file mode 100644 index 7fbfa59..0000000 --- a/samples/RelyingPartyCustomStore/MembersOnly/Web.config +++ /dev/null @@ -1,18 +0,0 @@ -<?xml version="1.0"?>
-<!--
- Note: As an alternative to hand editing this file you can use the
- web admin tool to configure settings for your application. Use
- the Website->Asp.Net Configuration option in Visual Studio.
- A full list of settings and comments can be found in
- machine.config.comments usually located in
- \Windows\Microsoft.Net\Framework\v2.x\Config
--->
-<configuration>
- <appSettings/>
- <connectionStrings/>
- <system.web>
- <authorization>
- <deny users="?"/>
- </authorization>
- </system.web>
-</configuration>
diff --git a/samples/RelyingPartyCustomStore/Properties/AssemblyInfo.cs b/samples/RelyingPartyCustomStore/Properties/AssemblyInfo.cs deleted file mode 100644 index 7ff8c9b..0000000 --- a/samples/RelyingPartyCustomStore/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System.Reflection;
-using System.Runtime.CompilerServices;
-using System.Runtime.InteropServices;
-
-// 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("RelyingPartyCustomStore")]
-[assembly: AssemblyDescription("")]
-[assembly: AssemblyConfiguration("")]
-[assembly: AssemblyCompany("")]
-[assembly: AssemblyProduct("RelyingPartyCustomStore")]
-[assembly: AssemblyCopyright("Copyright © 2008")]
-[assembly: AssemblyTrademark("")]
-[assembly: AssemblyCulture("")]
-
-// 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("3d5900ae-111a-45be-96b3-d9e4606ca793")]
-
-// Version information for an assembly consists of the following four values:
-//
-// Major Version
-// Minor Version
-// Build Number
-// Revision
-//
-// You can specify all the values or you can default the Revision and Build Numbers
-// by using the '*' as shown below:
-[assembly: AssemblyVersion("1.0.0.0")]
-[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/samples/RelyingPartyCustomStore/RelyingPartyCustomStore.csproj b/samples/RelyingPartyCustomStore/RelyingPartyCustomStore.csproj deleted file mode 100644 index 2519ec1..0000000 --- a/samples/RelyingPartyCustomStore/RelyingPartyCustomStore.csproj +++ /dev/null @@ -1,116 +0,0 @@ -<Project ToolsVersion="3.5" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
- <PropertyGroup>
- <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
- <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
- <ProductVersion>9.0.21022</ProductVersion>
- <SchemaVersion>2.0</SchemaVersion>
- <ProjectGuid>{DB54DC19-BA56-4C22-A8A0-C49289EA4F53}</ProjectGuid>
- <ProjectTypeGuids>{349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc}</ProjectTypeGuids>
- <OutputType>Library</OutputType>
- <AppDesignerFolder>Properties</AppDesignerFolder>
- <RootNamespace>RelyingPartyCustomStore</RootNamespace>
- <AssemblyName>RelyingPartyCustomStore</AssemblyName>
- <TargetFrameworkVersion>v2.0</TargetFrameworkVersion>
- </PropertyGroup>
- <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
- <DebugSymbols>true</DebugSymbols>
- <DebugType>full</DebugType>
- <Optimize>false</Optimize>
- <OutputPath>bin\</OutputPath>
- <DefineConstants>DEBUG;TRACE</DefineConstants>
- <ErrorReport>prompt</ErrorReport>
- <WarningLevel>4</WarningLevel>
- </PropertyGroup>
- <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
- <DebugType>pdbonly</DebugType>
- <Optimize>true</Optimize>
- <OutputPath>bin\</OutputPath>
- <DefineConstants>TRACE</DefineConstants>
- <ErrorReport>prompt</ErrorReport>
- <WarningLevel>4</WarningLevel>
- </PropertyGroup>
- <ItemGroup>
- <Reference Include="System" />
- <Reference Include="System.Data" />
- <Reference Include="System.Drawing" />
- <Reference Include="System.Web" />
- <Reference Include="System.Xml" />
- <Reference Include="System.Configuration" />
- <Reference Include="System.Web.Services" />
- <Reference Include="System.EnterpriseServices" />
- <Reference Include="System.Web.Mobile" />
- </ItemGroup>
- <ItemGroup>
- <Content Include="Default.aspx" />
- <Content Include="login.aspx" />
- <Content Include="MembersOnly\Default.aspx" />
- <Content Include="Web.config" />
- </ItemGroup>
- <ItemGroup>
- <Compile Include="CustomStore.cs" />
- <Compile Include="CustomStoreDataSet.Designer.cs">
- <AutoGen>True</AutoGen>
- <DesignTime>True</DesignTime>
- <DependentUpon>CustomStoreDataSet.xsd</DependentUpon>
- </Compile>
- <Compile Include="Global.asax.cs">
- <DependentUpon>Global.asax</DependentUpon>
- </Compile>
- <Compile Include="login.aspx.cs">
- <DependentUpon>login.aspx</DependentUpon>
- <SubType>ASPXCodeBehind</SubType>
- </Compile>
- <Compile Include="login.aspx.designer.cs">
- <DependentUpon>login.aspx</DependentUpon>
- </Compile>
- <Compile Include="Properties\AssemblyInfo.cs" />
- </ItemGroup>
- <ItemGroup>
- <ProjectReference Include="..\..\src\DotNetOpenId\DotNetOpenId.csproj">
- <Project>{5D6EDC86-F5B2-4786-8376-4E7C24C63D39}</Project>
- <Name>DotNetOpenId</Name>
- </ProjectReference>
- </ItemGroup>
- <ItemGroup>
- <Content Include="Global.asax" />
- <Content Include="MembersOnly\Web.config" />
- </ItemGroup>
- <ItemGroup>
- <None Include="CustomStoreDataSet.xsc">
- <DependentUpon>CustomStoreDataSet.xsd</DependentUpon>
- </None>
- <None Include="CustomStoreDataSet.xsd">
- <SubType>Designer</SubType>
- <Generator>MSDataSetGenerator</Generator>
- <LastGenOutput>CustomStoreDataSet.Designer.cs</LastGenOutput>
- </None>
- <None Include="CustomStoreDataSet.xss">
- <DependentUpon>CustomStoreDataSet.xsd</DependentUpon>
- </None>
- </ItemGroup>
- <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
- <Import Project="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v9.0\WebApplications\Microsoft.WebApplication.targets" />
- <!-- To modify your build process, add your task inside one of the targets below and uncomment it.
- Other similar extension points exist, see Microsoft.Common.targets.
- <Target Name="BeforeBuild">
- </Target>
- <Target Name="AfterBuild">
- </Target>
- -->
- <ProjectExtensions>
- <VisualStudio>
- <FlavorProperties GUID="{349c5851-65df-11da-9384-00065b846f21}">
- <WebProjectProperties>
- <UseIIS>False</UseIIS>
- <AutoAssignPort>True</AutoAssignPort>
- <DevelopmentServerPort>6945</DevelopmentServerPort>
- <DevelopmentServerVPath>/</DevelopmentServerVPath>
- <IISUrl>
- </IISUrl>
- <NTLMAuthentication>False</NTLMAuthentication>
- <SaveServerSettingsInUserFile>False</SaveServerSettingsInUserFile>
- </WebProjectProperties>
- </FlavorProperties>
- </VisualStudio>
- </ProjectExtensions>
-</Project>
\ No newline at end of file diff --git a/samples/RelyingPartyCustomStore/Web.config b/samples/RelyingPartyCustomStore/Web.config deleted file mode 100644 index d5574f5..0000000 --- a/samples/RelyingPartyCustomStore/Web.config +++ /dev/null @@ -1,29 +0,0 @@ -<?xml version="1.0"?>
-<configuration>
- <system.web>
- <compilation debug="true"/>
- <customErrors mode="RemoteOnly"/>
- <authentication mode="Forms">
- <forms name="RelyingPartyCustomStoreSession"/> <!-- named cookie prevents conflicts with other samples -->
- </authentication>
- <trace enabled="false" writeToDiagnosticsTrace="true" />
- <!-- Trust level discussion:
- Full: everything works
- High: TRACE compilation symbol must NOT be defined
- Medium/Low: doesn't work on default machine.config, because WebPermission.Connect is denied.
- -->
- <trust level="Full" originUrl=""/>
- </system.web>
- <system.diagnostics>
- <switches>
- <!-- Disabled tracing because it breaks WebResource.axd, which downloads the OpenID logo. -->
- <!--<add name="OpenID" value="4"/>-->
- </switches>
- <trace autoflush="true" indentsize="4">
- <listeners>
- <add name="fileLogger" type="System.Diagnostics.TextWriterTraceListener"
- initializeData="openidConsumerTrace.txt" traceOutputOptions="None"/>
- </listeners>
- </trace>
- </system.diagnostics>
-</configuration>
diff --git a/samples/RelyingPartyCustomStore/login.aspx b/samples/RelyingPartyCustomStore/login.aspx deleted file mode 100644 index d04ec0a..0000000 --- a/samples/RelyingPartyCustomStore/login.aspx +++ /dev/null @@ -1,24 +0,0 @@ -<%@ Page Language="C#" AutoEventWireup="True" CodeBehind="login.aspx.cs" Inherits="login" ValidateRequest="false" %>
-
-<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
-<html xmlns="http://www.w3.org/1999/xhtml">
-<head>
- <title>Login</title>
-</head>
-<body>
- <form id="Form1" runat="server">
- <h2>
- Login Page
- </h2>
- <asp:Label ID="Label1" runat="server" Text="OpenID Login" />
- <asp:TextBox ID="openIdBox" runat="server" />
- <asp:Button ID="loginButton" runat="server" Text="Login"
- onclick="loginButton_Click" />
- <br />
- <asp:Label ID="loginFailedLabel" runat="server" EnableViewState="False" Text="Login failed"
- Visible="False" />
- <asp:Label ID="loginCanceledLabel" runat="server" EnableViewState="False" Text="Login canceled"
- Visible="False" />
- </form>
-</body>
-</html>
diff --git a/samples/RelyingPartyCustomStore/login.aspx.cs b/samples/RelyingPartyCustomStore/login.aspx.cs deleted file mode 100644 index f7b1088..0000000 --- a/samples/RelyingPartyCustomStore/login.aspx.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System;
-using System.Web.UI;
-using DotNetOpenId.RelyingParty;
-using System.Web.Security;
-using RelyingPartyCustomStore;
-
-public partial class login : System.Web.UI.Page {
- protected void Page_Load(object sender, EventArgs e) {
- openIdBox.Focus();
-
- OpenIdRelyingParty rp = new OpenIdRelyingParty(CustomStore.Instance, Request.Url,
- Request.HttpMethod == "GET" ? Request.QueryString : Request.Form);
- if (rp.Response != null) {
- switch (rp.Response.Status) {
- case AuthenticationStatus.Authenticated:
- FormsAuthentication.RedirectFromLoginPage(rp.Response.ClaimedIdentifier, false);
- break;
- case AuthenticationStatus.Canceled:
- loginCanceledLabel.Visible = true;
- break;
- case AuthenticationStatus.Failed:
- loginFailedLabel.Visible = true;
- break;
- }
- }
- }
-
- protected void loginButton_Click(object sender, EventArgs e) {
- OpenIdRelyingParty rp = new OpenIdRelyingParty(CustomStore.Instance, Request.Url,
- Request.HttpMethod == "GET" ? Request.QueryString : Request.Form);
- rp.CreateRequest(openIdBox.Text).RedirectToProvider();
- }
-}
diff --git a/samples/RelyingPartyCustomStore/login.aspx.designer.cs b/samples/RelyingPartyCustomStore/login.aspx.designer.cs deleted file mode 100644 index b5a6e1f..0000000 --- a/samples/RelyingPartyCustomStore/login.aspx.designer.cs +++ /dev/null @@ -1,68 +0,0 @@ -//------------------------------------------------------------------------------
-// <auto-generated>
-// This code was generated by a tool.
-// Runtime Version:2.0.50727.1434
-//
-// Changes to this file may cause incorrect behavior and will be lost if
-// the code is regenerated.
-// </auto-generated>
-//------------------------------------------------------------------------------
-
-
-
-public partial class login {
-
- /// <summary>
- /// Form1 control.
- /// </summary>
- /// <remarks>
- /// Auto-generated field.
- /// To modify move field declaration from designer file to code-behind file.
- /// </remarks>
- protected global::System.Web.UI.HtmlControls.HtmlForm Form1;
-
- /// <summary>
- /// Label1 control.
- /// </summary>
- /// <remarks>
- /// Auto-generated field.
- /// To modify move field declaration from designer file to code-behind file.
- /// </remarks>
- protected global::System.Web.UI.WebControls.Label Label1;
-
- /// <summary>
- /// openIdBox control.
- /// </summary>
- /// <remarks>
- /// Auto-generated field.
- /// To modify move field declaration from designer file to code-behind file.
- /// </remarks>
- protected global::System.Web.UI.WebControls.TextBox openIdBox;
-
- /// <summary>
- /// loginButton control.
- /// </summary>
- /// <remarks>
- /// Auto-generated field.
- /// To modify move field declaration from designer file to code-behind file.
- /// </remarks>
- protected global::System.Web.UI.WebControls.Button loginButton;
-
- /// <summary>
- /// loginFailedLabel control.
- /// </summary>
- /// <remarks>
- /// Auto-generated field.
- /// To modify move field declaration from designer file to code-behind file.
- /// </remarks>
- protected global::System.Web.UI.WebControls.Label loginFailedLabel;
-
- /// <summary>
- /// loginCanceledLabel control.
- /// </summary>
- /// <remarks>
- /// Auto-generated field.
- /// To modify move field declaration from designer file to code-behind file.
- /// </remarks>
- protected global::System.Web.UI.WebControls.Label loginCanceledLabel;
-}
diff --git a/samples/RelyingPartyMvc/Global.asax.cs b/samples/RelyingPartyMvc/Global.asax.cs index 0f0b4ef..0659169 100644 --- a/samples/RelyingPartyMvc/Global.asax.cs +++ b/samples/RelyingPartyMvc/Global.asax.cs @@ -7,11 +7,6 @@ using System.Web.Routing; namespace RelyingPartyMvc {
public class GlobalApplication : System.Web.HttpApplication {
- public GlobalApplication() {
- // since this is a sample, and will often be used with localhost
- DotNetOpenId.UntrustedWebRequest.WhitelistHosts.Add("localhost");
- }
-
public static void RegisterRoutes(RouteCollection routes) {
// Note: Change the URL to "{controller}.mvc/{action}/{id}" to enable
// automatic support on IIS6 and IIS7 classic mode
diff --git a/samples/RelyingPartyMvc/Web.config b/samples/RelyingPartyMvc/Web.config index fc256b8..d72528a 100644 --- a/samples/RelyingPartyMvc/Web.config +++ b/samples/RelyingPartyMvc/Web.config @@ -9,6 +9,11 @@ -->
<configuration>
<configSections>
+ <sectionGroup name="dotNetOpenId">
+ <section name="relyingParty" type="DotNetOpenId.Configuration.RelyingPartySection" requirePermission="false" allowLocation="true"/>
+ <section name="provider" type="DotNetOpenId.Configuration.ProviderSection" requirePermission="false" allowLocation="true"/>
+ <section name="untrustedWebRequest" type="DotNetOpenId.Configuration.UntrustedWebRequestSection" requirePermission="false" allowLocation="false"/>
+ </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"/>
@@ -21,6 +26,14 @@ </sectionGroup>
</sectionGroup>
</configSections>
+ <dotNetOpenId>
+ <untrustedWebRequest>
+ <whitelistHosts>
+ <!-- since this is a sample, and will often be used with localhost -->
+ <add name="localhost" />
+ </whitelistHosts>
+ </untrustedWebRequest>
+ </dotNetOpenId>
<appSettings/>
<connectionStrings/>
<system.web>
diff --git a/samples/RelyingPartyCustomStore/CustomStore.cs b/samples/RelyingPartyPortal/Code/CustomStore.cs index b0e3700..0083152 100644 --- a/samples/RelyingPartyCustomStore/CustomStore.cs +++ b/samples/RelyingPartyPortal/Code/CustomStore.cs @@ -5,7 +5,7 @@ using System.Security.Cryptography; using DotNetOpenId;
using DotNetOpenId.RelyingParty;
-namespace RelyingPartyCustomStore {
+namespace ConsumerPortal.Code {
/// <summary>
/// This custom store serializes all elements to demonstrate peristent and/or shared storage.
/// This is common in a web farm, for example.
@@ -17,8 +17,7 @@ namespace RelyingPartyCustomStore { /// that using a database is possible.
/// </remarks>
public class CustomStore : IRelyingPartyApplicationStore {
- public static CustomStore Instance = new CustomStore();
- public CustomStoreDataSet dataSet = new CustomStoreDataSet();
+ static CustomStoreDataSet dataSet = new CustomStoreDataSet();
#region IAssociationStore<Uri> Members
@@ -66,7 +65,7 @@ namespace RelyingPartyCustomStore { #region INonceStore Members
- byte[] secretSigningKey;
+ static byte[] secretSigningKey;
public byte[] SecretSigningKey {
get {
if (secretSigningKey == null) {
@@ -75,7 +74,7 @@ namespace RelyingPartyCustomStore { // initialize in a local variable before setting in field for thread safety.
byte[] auth_key = new byte[64];
new RNGCryptoServiceProvider().GetBytes(auth_key);
- this.secretSigningKey = auth_key;
+ CustomStore.secretSigningKey = auth_key;
}
}
}
@@ -115,6 +114,5 @@ namespace RelyingPartyCustomStore { for (int i = view.Count - 1; i >= 0; i--)
view.Delete(i);
}
-
}
}
diff --git a/samples/RelyingPartyCustomStore/CustomStoreDataSet.Designer.cs b/samples/RelyingPartyPortal/Code/CustomStoreDataSet.Designer.cs index b757e71..5b7c03e 100644 --- a/samples/RelyingPartyCustomStore/CustomStoreDataSet.Designer.cs +++ b/samples/RelyingPartyPortal/Code/CustomStoreDataSet.Designer.cs @@ -1,7 +1,7 @@ //------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
-// Runtime Version:2.0.50727.1434
+// Runtime Version:2.0.50727.3053
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
@@ -10,7 +10,7 @@ #pragma warning disable 1591
-namespace RelyingPartyCustomStore {
+namespace ConsumerPortal.Code {
/// <summary>
diff --git a/samples/RelyingPartyPortal/Code/CustomStoreDataSet.cs b/samples/RelyingPartyPortal/Code/CustomStoreDataSet.cs new file mode 100644 index 0000000..d53f870 --- /dev/null +++ b/samples/RelyingPartyPortal/Code/CustomStoreDataSet.cs @@ -0,0 +1,6 @@ +namespace ConsumerPortal.Code {
+
+
+ public partial class CustomStoreDataSet {
+ }
+}
diff --git a/samples/RelyingPartyCustomStore/CustomStoreDataSet.xsc b/samples/RelyingPartyPortal/Code/CustomStoreDataSet.xsc index 551fc56..551fc56 100644 --- a/samples/RelyingPartyCustomStore/CustomStoreDataSet.xsc +++ b/samples/RelyingPartyPortal/Code/CustomStoreDataSet.xsc diff --git a/samples/RelyingPartyCustomStore/CustomStoreDataSet.xsd b/samples/RelyingPartyPortal/Code/CustomStoreDataSet.xsd index 76b77aa..76b77aa 100644 --- a/samples/RelyingPartyCustomStore/CustomStoreDataSet.xsd +++ b/samples/RelyingPartyPortal/Code/CustomStoreDataSet.xsd diff --git a/samples/RelyingPartyCustomStore/CustomStoreDataSet.xss b/samples/RelyingPartyPortal/Code/CustomStoreDataSet.xss index fbe7113..fbe7113 100644 --- a/samples/RelyingPartyCustomStore/CustomStoreDataSet.xss +++ b/samples/RelyingPartyPortal/Code/CustomStoreDataSet.xss diff --git a/samples/RelyingPartyPortal/Global.asax.cs b/samples/RelyingPartyPortal/Global.asax.cs index 259512f..eb61da9 100644 --- a/samples/RelyingPartyPortal/Global.asax.cs +++ b/samples/RelyingPartyPortal/Global.asax.cs @@ -8,11 +8,6 @@ namespace ConsumerPortal { public class Global : System.Web.HttpApplication {
internal static StringBuilder LogMessages = new StringBuilder();
- public Global() {
- // since this is a sample, and will often be used with localhost
- DotNetOpenId.UntrustedWebRequest.WhitelistHosts.Add("localhost");
- }
-
public static log4net.ILog Logger = log4net.LogManager.GetLogger(typeof(Global));
protected void Application_Start(object sender, EventArgs e) {
diff --git a/samples/RelyingPartyPortal/RelyingPartyPortal.csproj b/samples/RelyingPartyPortal/RelyingPartyPortal.csproj index 9799cac..24fd010 100644 --- a/samples/RelyingPartyPortal/RelyingPartyPortal.csproj +++ b/samples/RelyingPartyPortal/RelyingPartyPortal.csproj @@ -2,7 +2,7 @@ <PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
- <ProductVersion>9.0.21022</ProductVersion>
+ <ProductVersion>9.0.30729</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>{51BCD5E9-E17A-4FB2-BAC8-C156DD7A1CA4}</ProjectGuid>
<ProjectTypeGuids>{349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc}</ProjectTypeGuids>
@@ -54,6 +54,16 @@ <Content Include="Web.config" />
</ItemGroup>
<ItemGroup>
+ <Compile Include="Code\CustomStore.cs" />
+ <Compile Include="Code\CustomStoreDataSet.cs">
+ <DependentUpon>CustomStoreDataSet.xsd</DependentUpon>
+ <SubType>Component</SubType>
+ </Compile>
+ <Compile Include="Code\CustomStoreDataSet.Designer.cs">
+ <DependentUpon>CustomStoreDataSet.xsd</DependentUpon>
+ <AutoGen>True</AutoGen>
+ <DesignTime>True</DesignTime>
+ </Compile>
<Compile Include="Code\State.cs" />
<Compile Include="Code\TracePageAppender.cs" />
<Compile Include="Global.asax.cs">
@@ -112,6 +122,19 @@ <Content Include="Site.Master" />
<Content Include="TracePage.aspx" />
</ItemGroup>
+ <ItemGroup>
+ <None Include="Code\CustomStoreDataSet.xsc">
+ <DependentUpon>CustomStoreDataSet.xsd</DependentUpon>
+ </None>
+ <None Include="Code\CustomStoreDataSet.xsd">
+ <Generator>MSDataSetGenerator</Generator>
+ <LastGenOutput>CustomStoreDataSet.Designer.cs</LastGenOutput>
+ <SubType>Designer</SubType>
+ </None>
+ <None Include="Code\CustomStoreDataSet.xss">
+ <DependentUpon>CustomStoreDataSet.xsd</DependentUpon>
+ </None>
+ </ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<Import Project="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v9.0\WebApplications\Microsoft.WebApplication.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
@@ -132,6 +155,9 @@ <IISUrl>
</IISUrl>
<NTLMAuthentication>False</NTLMAuthentication>
+ <UseCustomServer>False</UseCustomServer>
+ <CustomServerUrl>
+ </CustomServerUrl>
<SaveServerSettingsInUserFile>False</SaveServerSettingsInUserFile>
</WebProjectProperties>
</FlavorProperties>
diff --git a/samples/RelyingPartyPortal/Web.config b/samples/RelyingPartyPortal/Web.config index c7a8349..3c68b88 100644 --- a/samples/RelyingPartyPortal/Web.config +++ b/samples/RelyingPartyPortal/Web.config @@ -2,7 +2,25 @@ <configuration>
<configSections>
<section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler" requirePermission="false" />
+ <sectionGroup name="dotNetOpenId">
+ <section name="relyingParty" type="DotNetOpenId.Configuration.RelyingPartySection" requirePermission="false" allowLocation="true"/>
+ <section name="provider" type="DotNetOpenId.Configuration.ProviderSection" requirePermission="false" allowLocation="true"/>
+ <section name="untrustedWebRequest" type="DotNetOpenId.Configuration.UntrustedWebRequestSection" requirePermission="false" allowLocation="false"/>
+ </sectionGroup>
</configSections>
+ <dotNetOpenId>
+ <relyingParty>
+ <security requireSsl="false" />
+ <!-- Uncomment the following to activate the sample custom store. -->
+ <!--<store type="ConsumerPortal.Code.CustomStore, ConsumerPortal" />-->
+ </relyingParty>
+ <untrustedWebRequest>
+ <whitelistHosts>
+ <!-- since this is a sample, and will often be used with localhost -->
+ <add name="localhost" />
+ </whitelistHosts>
+ </untrustedWebRequest>
+ </dotNetOpenId>
<system.web>
<!--<sessionState cookieless="true" />-->
<compilation debug="true"/>
@@ -46,4 +64,5 @@ <level value="ALL" />
</logger>
</log4net>
+
</configuration>
diff --git a/samples/RelyingPartyPortal/loginProgrammatic.aspx.cs b/samples/RelyingPartyPortal/loginProgrammatic.aspx.cs index 1abd587..39a6f9b 100644 --- a/samples/RelyingPartyPortal/loginProgrammatic.aspx.cs +++ b/samples/RelyingPartyPortal/loginProgrammatic.aspx.cs @@ -1,4 +1,5 @@ using System;
+using System.Net;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
@@ -11,9 +12,28 @@ public partial class loginProgrammatic : System.Web.UI.Page { args.IsValid = Identifier.IsValid(args.Value);
}
+ OpenIdRelyingParty createRelyingParty() {
+ OpenIdRelyingParty openid = new OpenIdRelyingParty();
+ int minsha, maxsha, minversion;
+ if (int.TryParse(Request.QueryString["minsha"], out minsha)) {
+ openid.Settings.MinimumHashBitLength = minsha;
+ }
+ if (int.TryParse(Request.QueryString["maxsha"], out maxsha)) {
+ openid.Settings.MaximumHashBitLength = maxsha;
+ }
+ if (int.TryParse(Request.QueryString["minversion"], out minversion)) {
+ switch (minversion) {
+ case 1: openid.Settings.MinimumRequiredOpenIdVersion = ProtocolVersion.V10; break;
+ case 2: openid.Settings.MinimumRequiredOpenIdVersion = ProtocolVersion.V20; break;
+ default: throw new ArgumentOutOfRangeException("minversion");
+ }
+ }
+ return openid;
+ }
+
protected void loginButton_Click(object sender, EventArgs e) {
if (!Page.IsValid) return; // don't login if custom validation failed.
- OpenIdRelyingParty openid = new OpenIdRelyingParty();
+ OpenIdRelyingParty openid = createRelyingParty();
try {
IAuthenticationRequest request = openid.CreateRequest(openIdBox.Text);
// This is where you would add any OpenID extensions you wanted
@@ -27,13 +47,28 @@ public partial class loginProgrammatic : System.Web.UI.Page { // was not a valid OpenID endpoint.
openidValidator.Text = ex.Message;
openidValidator.IsValid = false;
+ } catch (WebException ex) {
+ // The user probably entered an Identifier that
+ // was not a valid OpenID endpoint.
+ openidValidator.Text = ex.Message;
+ openidValidator.IsValid = false;
}
}
protected void Page_Load(object sender, EventArgs e) {
openIdBox.Focus();
+ // For debugging/testing, we allow remote clearing of all associations...
+ // NOT a good idea on a production site.
+ if (Request.QueryString["clearAssociations"] == "1") {
+ Application.Remove("DotNetOpenId.RelyingParty.RelyingParty.AssociationStore");
+ // Force a redirect now to prevent the user from logging in while associations
+ // are constantly being cleared.
+ UriBuilder builder = new UriBuilder(Request.Url);
+ builder.Query = null;
+ Response.Redirect(builder.Uri.AbsoluteUri);
+ }
- OpenIdRelyingParty openid = new OpenIdRelyingParty();
+ OpenIdRelyingParty openid = createRelyingParty();
if (openid.Response != null) {
switch (openid.Response.Status) {
case AuthenticationStatus.Authenticated:
diff --git a/src/DotNetOpenId.Test/AssociationTestSuite.cs b/src/DotNetOpenId.Test/AssociationTestSuite.cs index 327d174..3333f3a 100644 --- a/src/DotNetOpenId.Test/AssociationTestSuite.cs +++ b/src/DotNetOpenId.Test/AssociationTestSuite.cs @@ -1,15 +1,15 @@ using System;
using System.Collections.Generic;
-using System.Text;
+using System.Security.Cryptography;
using NUnit.Framework;
-namespace DotNetOpenId.Test
-{
+namespace DotNetOpenId.Test {
[TestFixture]
public class AssociationTestSuite {
static readonly TimeSpan deltaDateTime = TimeSpan.FromSeconds(2);
- byte[] sha1Secret = new byte[CryptUtil.Sha1.HashSize / 8];
- byte[] sha1Secret2 = new byte[CryptUtil.Sha1.HashSize / 8];
+ static HashAlgorithm sha1 = DiffieHellmanUtil.Lookup(Protocol.Default, Protocol.Default.Args.SessionType.DH_SHA1);
+ byte[] sha1Secret = new byte[sha1.HashSize / 8];
+ byte[] sha1Secret2 = new byte[sha1.HashSize / 8];
public AssociationTestSuite() {
// just a little something to make it at all interesting.
@@ -24,7 +24,8 @@ namespace DotNetOpenId.Test public void Properties() {
string handle = "somehandle";
TimeSpan lifetime = TimeSpan.FromMinutes(2);
- Association assoc = new HmacSha1Association(handle, sha1Secret, lifetime);
+ Association assoc = HmacShaAssociation.Create(Protocol.Default, Protocol.Default.Args.SignatureAlgorithm.HMAC_SHA1,
+ handle, sha1Secret, lifetime);
Assert.IsFalse(assoc.IsExpired);
Assert.IsTrue(Math.Abs((DateTime.Now - assoc.Issued.ToLocalTime()).TotalSeconds) < deltaDateTime.TotalSeconds);
Assert.IsTrue(Math.Abs((DateTime.Now.ToLocalTime() + lifetime - assoc.Expires.ToLocalTime()).TotalSeconds) < deltaDateTime.TotalSeconds);
@@ -36,8 +37,10 @@ namespace DotNetOpenId.Test [Test]
public void Sign() {
- Association assoc1 = new HmacSha1Association("h1", sha1Secret, TimeSpan.FromMinutes(2));
- Association assoc2 = new HmacSha1Association("h2", sha1Secret2, TimeSpan.FromMinutes(2));
+ Association assoc1 = HmacShaAssociation.Create(Protocol.Default, Protocol.Default.Args.SignatureAlgorithm.HMAC_SHA1,
+ "h1", sha1Secret, TimeSpan.FromMinutes(2));
+ Association assoc2 = HmacShaAssociation.Create(Protocol.Default, Protocol.Default.Args.SignatureAlgorithm.HMAC_SHA1,
+ "h2", sha1Secret2, TimeSpan.FromMinutes(2));
var dict = new Dictionary<string, string>();
dict.Add("a", "b");
@@ -77,7 +80,8 @@ namespace DotNetOpenId.Test [Test]
public void SignSome() {
- Association assoc = new HmacSha1Association("h1", sha1Secret, TimeSpan.FromMinutes(2));
+ Association assoc = HmacShaAssociation.Create(Protocol.Default, Protocol.Default.Args.SignatureAlgorithm.HMAC_SHA1,
+ "h1", sha1Secret, TimeSpan.FromMinutes(2));
const string prefix = "q.";
var dict = new Dictionary<string, string>();
@@ -85,7 +89,7 @@ namespace DotNetOpenId.Test dict.Add("q.c", "d");
dict.Add("q.e", "f");
- var signKeys = new List<string> {"a", "c"}; // don't sign e
+ var signKeys = new List<string> { "a", "c" }; // don't sign e
byte[] sig1 = assoc.Sign(dict, signKeys, prefix);
diff --git a/src/DotNetOpenId.Test/AssociationsTest.cs b/src/DotNetOpenId.Test/AssociationsTest.cs index 7f4ec26..01dad58 100644 --- a/src/DotNetOpenId.Test/AssociationsTest.cs +++ b/src/DotNetOpenId.Test/AssociationsTest.cs @@ -1,13 +1,12 @@ using System;
-using System.Collections.Generic;
-using System.Text;
+using System.Security.Cryptography;
using NUnit.Framework;
-using System.Threading;
namespace DotNetOpenId.Test {
[TestFixture]
public class AssociationsTest {
- byte[] sha1Secret = new byte[CryptUtil.Sha1.HashSize / 8];
+ static HashAlgorithm sha1 = DiffieHellmanUtil.Lookup(Protocol.Default, Protocol.Default.Args.SessionType.DH_SHA1);
+ byte[] sha1Secret = new byte[sha1.HashSize / 8];
Associations assocs;
[SetUp]
@@ -27,7 +26,8 @@ namespace DotNetOpenId.Test { [Test]
public void HandleLifecycle() {
- Association a = new HmacSha1Association("somehandle", sha1Secret, TimeSpan.FromDays(1));
+ Association a = HmacShaAssociation.Create(Protocol.Default, Protocol.Default.Args.SignatureAlgorithm.HMAC_SHA1,
+ "somehandle", sha1Secret, TimeSpan.FromDays(1));
assocs.Set(a);
Assert.AreSame(a, assocs.Get(a.Handle));
Assert.IsTrue(assocs.Remove(a.Handle));
@@ -37,8 +37,10 @@ namespace DotNetOpenId.Test { [Test]
public void Best() {
- Association a = new HmacSha1Association("h1", sha1Secret, TimeSpan.FromHours(1));
- Association b = new HmacSha1Association("h2", sha1Secret, TimeSpan.FromHours(1));
+ Association a = HmacShaAssociation.Create(Protocol.Default, Protocol.Default.Args.SignatureAlgorithm.HMAC_SHA1,
+ "h1", sha1Secret, TimeSpan.FromHours(1));
+ Association b = HmacShaAssociation.Create(Protocol.Default, Protocol.Default.Args.SignatureAlgorithm.HMAC_SHA1,
+ "h2", sha1Secret, TimeSpan.FromHours(1));
assocs.Set(a);
assocs.Set(b);
diff --git a/src/DotNetOpenId.Test/DiffieHellmanTestSuite.cs b/src/DotNetOpenId.Test/DiffieHellmanUtilTests.cs index 52d9b4c..9b2fba9 100644 --- a/src/DotNetOpenId.Test/DiffieHellmanTestSuite.cs +++ b/src/DotNetOpenId.Test/DiffieHellmanUtilTests.cs @@ -8,8 +8,8 @@ using NUnit.Framework; namespace DotNetOpenId.Test {
public static class DHTestUtil {
public static string Test1() {
- DiffieHellman dh1 = CryptUtil.CreateDiffieHellman();
- DiffieHellman dh2 = CryptUtil.CreateDiffieHellman();
+ DiffieHellman dh1 = DiffieHellmanUtil.CreateDiffieHellman();
+ DiffieHellman dh2 = DiffieHellmanUtil.CreateDiffieHellman();
string secret1 = Convert.ToBase64String(dh1.DecryptKeyExchange(dh2.CreateKeyExchange()));
string secret2 = Convert.ToBase64String(dh2.DecryptKeyExchange(dh1.CreateKeyExchange()));
@@ -21,7 +21,7 @@ namespace DotNetOpenId.Test { }
[TestFixture]
- public class DiffieHellmanTestSuite {
+ public class DiffieHellmanUtilTests {
[Test]
public void Test() {
@@ -40,14 +40,14 @@ namespace DotNetOpenId.Test { while ((line = sr.ReadLine()) != null) {
string[] parts = line.Trim().Split(' ');
byte[] x = Convert.FromBase64String(parts[0]);
- DiffieHellmanManaged dh = new DiffieHellmanManaged(CryptUtil.DEFAULT_MOD, CryptUtil.DEFAULT_GEN, x);
+ DiffieHellmanManaged dh = new DiffieHellmanManaged(DiffieHellmanUtil.DEFAULT_MOD, DiffieHellmanUtil.DEFAULT_GEN, x);
byte[] pub = dh.CreateKeyExchange();
byte[] y = Convert.FromBase64String(parts[1]);
if (y[0] == 0 && y[1] <= 127)
y.CopyTo(y, 1);
- Assert.AreEqual(y, Convert.FromBase64String(CryptUtil.UnsignedToBase64(pub)), line);
+ Assert.AreEqual(y, Convert.FromBase64String(DiffieHellmanUtil.UnsignedToBase64(pub)), line);
}
} finally {
sr.Close();
diff --git a/src/DotNetOpenId.TestWeb/htmldiscovery/html1020.aspx b/src/DotNetOpenId.Test/Discovery/htmldiscovery/html1020.html index 7c6c15e..7c6c15e 100644 --- a/src/DotNetOpenId.TestWeb/htmldiscovery/html1020.aspx +++ b/src/DotNetOpenId.Test/Discovery/htmldiscovery/html1020.html diff --git a/src/DotNetOpenId.TestWeb/htmldiscovery/html10both.aspx b/src/DotNetOpenId.Test/Discovery/htmldiscovery/html10both.html index e97803e..e97803e 100644 --- a/src/DotNetOpenId.TestWeb/htmldiscovery/html10both.aspx +++ b/src/DotNetOpenId.Test/Discovery/htmldiscovery/html10both.html diff --git a/src/DotNetOpenId.TestWeb/htmldiscovery/html10del.aspx b/src/DotNetOpenId.Test/Discovery/htmldiscovery/html10del.html index ddf121a..ddf121a 100644 --- a/src/DotNetOpenId.TestWeb/htmldiscovery/html10del.aspx +++ b/src/DotNetOpenId.Test/Discovery/htmldiscovery/html10del.html diff --git a/src/DotNetOpenId.TestWeb/htmldiscovery/html10prov.aspx b/src/DotNetOpenId.Test/Discovery/htmldiscovery/html10prov.html index 1b198f9..1b198f9 100644 --- a/src/DotNetOpenId.TestWeb/htmldiscovery/html10prov.aspx +++ b/src/DotNetOpenId.Test/Discovery/htmldiscovery/html10prov.html diff --git a/src/DotNetOpenId.TestWeb/htmldiscovery/html2010.aspx b/src/DotNetOpenId.Test/Discovery/htmldiscovery/html2010.html index 9fa3738..9fa3738 100644 --- a/src/DotNetOpenId.TestWeb/htmldiscovery/html2010.aspx +++ b/src/DotNetOpenId.Test/Discovery/htmldiscovery/html2010.html diff --git a/src/DotNetOpenId.TestWeb/htmldiscovery/html2010combinedA.aspx b/src/DotNetOpenId.Test/Discovery/htmldiscovery/html2010combinedA.html index c057b67..c057b67 100644 --- a/src/DotNetOpenId.TestWeb/htmldiscovery/html2010combinedA.aspx +++ b/src/DotNetOpenId.Test/Discovery/htmldiscovery/html2010combinedA.html diff --git a/src/DotNetOpenId.TestWeb/htmldiscovery/html2010combinedB.aspx b/src/DotNetOpenId.Test/Discovery/htmldiscovery/html2010combinedB.html index 3a86d4c..3a86d4c 100644 --- a/src/DotNetOpenId.TestWeb/htmldiscovery/html2010combinedB.aspx +++ b/src/DotNetOpenId.Test/Discovery/htmldiscovery/html2010combinedB.html diff --git a/src/DotNetOpenId.TestWeb/htmldiscovery/html2010combinedC.aspx b/src/DotNetOpenId.Test/Discovery/htmldiscovery/html2010combinedC.html index 13a3185..13a3185 100644 --- a/src/DotNetOpenId.TestWeb/htmldiscovery/html2010combinedC.aspx +++ b/src/DotNetOpenId.Test/Discovery/htmldiscovery/html2010combinedC.html diff --git a/src/DotNetOpenId.TestWeb/htmldiscovery/html20both.aspx b/src/DotNetOpenId.Test/Discovery/htmldiscovery/html20both.html index f41e66a..f41e66a 100644 --- a/src/DotNetOpenId.TestWeb/htmldiscovery/html20both.aspx +++ b/src/DotNetOpenId.Test/Discovery/htmldiscovery/html20both.html diff --git a/src/DotNetOpenId.TestWeb/htmldiscovery/html20del.aspx b/src/DotNetOpenId.Test/Discovery/htmldiscovery/html20del.html index 20852c0..20852c0 100644 --- a/src/DotNetOpenId.TestWeb/htmldiscovery/html20del.aspx +++ b/src/DotNetOpenId.Test/Discovery/htmldiscovery/html20del.html diff --git a/src/DotNetOpenId.TestWeb/htmldiscovery/html20prov.aspx b/src/DotNetOpenId.Test/Discovery/htmldiscovery/html20prov.html index f0e673e..f0e673e 100644 --- a/src/DotNetOpenId.TestWeb/htmldiscovery/html20prov.aspx +++ b/src/DotNetOpenId.Test/Discovery/htmldiscovery/html20prov.html diff --git a/src/DotNetOpenId.TestWeb/htmldiscovery/html20relative.aspx b/src/DotNetOpenId.Test/Discovery/htmldiscovery/html20relative.html index b13520c..b13520c 100644 --- a/src/DotNetOpenId.TestWeb/htmldiscovery/html20relative.aspx +++ b/src/DotNetOpenId.Test/Discovery/htmldiscovery/html20relative.html diff --git a/src/DotNetOpenId.Test/Discovery/xrdsdiscovery/XrdsReferencedInHead.html b/src/DotNetOpenId.Test/Discovery/xrdsdiscovery/XrdsReferencedInHead.html new file mode 100644 index 0000000..615038c --- /dev/null +++ b/src/DotNetOpenId.Test/Discovery/xrdsdiscovery/XrdsReferencedInHead.html @@ -0,0 +1,12 @@ +<!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">
+ <title>Untitled Page</title>
+ <meta http-equiv="X-XRDS-Location" content="http://localhost/xrds1020.xml"/>
+</head>
+<body>
+ <form id="form1" runat="server">
+ </form>
+</body>
+</html>
diff --git a/src/DotNetOpenId.Test/Discovery/xrdsdiscovery/XrdsReferencedInHttpHeader.html b/src/DotNetOpenId.Test/Discovery/xrdsdiscovery/XrdsReferencedInHttpHeader.html new file mode 100644 index 0000000..f0842db --- /dev/null +++ b/src/DotNetOpenId.Test/Discovery/xrdsdiscovery/XrdsReferencedInHttpHeader.html @@ -0,0 +1,9 @@ +<html xmlns="http://www.w3.org/1999/xhtml">
+<head runat="server">
+ <title>Untitled Page</title>
+</head>
+<body>
+ <form id="form1" runat="server">
+ </form>
+</body>
+</html>
diff --git a/src/DotNetOpenId.TestWeb/xrdsdiscovery/xrds-irrelevant.aspx b/src/DotNetOpenId.Test/Discovery/xrdsdiscovery/xrds-irrelevant.xml index 3cdebab..5116c0b 100644 --- a/src/DotNetOpenId.TestWeb/xrdsdiscovery/xrds-irrelevant.aspx +++ b/src/DotNetOpenId.Test/Discovery/xrdsdiscovery/xrds-irrelevant.xml @@ -1,4 +1,4 @@ -<%@ Page Language="C#" AutoEventWireup="true" ContentType="application/xrds+xml" %><?xml version="1.0" encoding="UTF-8"?> +<?xml version="1.0" encoding="UTF-8"?> <xrds:XRDS xmlns:xrds="xri://$xrds" xmlns:openid="http://openid.net/xmlns/1.0" diff --git a/src/DotNetOpenId.TestWeb/xrdsdiscovery/xrds10.aspx b/src/DotNetOpenId.Test/Discovery/xrdsdiscovery/xrds10.xml index 89cbd88..c67b36e 100644 --- a/src/DotNetOpenId.TestWeb/xrdsdiscovery/xrds10.aspx +++ b/src/DotNetOpenId.Test/Discovery/xrdsdiscovery/xrds10.xml @@ -1,4 +1,4 @@ -<%@ Page Language="C#" AutoEventWireup="true" ContentType="application/xrds+xml" %><?xml version="1.0" encoding="UTF-8"?> +<?xml version="1.0" encoding="UTF-8"?> <xrds:XRDS xmlns:xrds="xri://$xrds" xmlns:openid="http://openid.net/xmlns/1.0" diff --git a/src/DotNetOpenId.TestWeb/xrdsdiscovery/xrds1020.aspx b/src/DotNetOpenId.Test/Discovery/xrdsdiscovery/xrds1020.xml index acd982a..1b56f0f 100644 --- a/src/DotNetOpenId.TestWeb/xrdsdiscovery/xrds1020.aspx +++ b/src/DotNetOpenId.Test/Discovery/xrdsdiscovery/xrds1020.xml @@ -1,4 +1,4 @@ -<%@ Page Language="C#" AutoEventWireup="true" ContentType="application/xrds+xml" %><?xml version="1.0" encoding="UTF-8"?> +<?xml version="1.0" encoding="UTF-8"?> <xrds:XRDS xmlns:xrds="xri://$xrds" xmlns:openid="http://openid.net/xmlns/1.0" diff --git a/src/DotNetOpenId.TestWeb/xrdsdiscovery/xrds11.aspx b/src/DotNetOpenId.Test/Discovery/xrdsdiscovery/xrds11.xml index 7a757b2..2d6a7aa 100644 --- a/src/DotNetOpenId.TestWeb/xrdsdiscovery/xrds11.aspx +++ b/src/DotNetOpenId.Test/Discovery/xrdsdiscovery/xrds11.xml @@ -1,4 +1,4 @@ -<%@ Page Language="C#" AutoEventWireup="true" ContentType="application/xrds+xml" %><?xml version="1.0" encoding="UTF-8"?> +<?xml version="1.0" encoding="UTF-8"?> <xrds:XRDS xmlns:xrds="xri://$xrds" xmlns:openid="http://openid.net/xmlns/1.0" diff --git a/src/DotNetOpenId.TestWeb/xrdsdiscovery/xrds20.aspx b/src/DotNetOpenId.Test/Discovery/xrdsdiscovery/xrds20.xml index 24a832b..870b540 100644 --- a/src/DotNetOpenId.TestWeb/xrdsdiscovery/xrds20.aspx +++ b/src/DotNetOpenId.Test/Discovery/xrdsdiscovery/xrds20.xml @@ -1,4 +1,4 @@ -<%@ Page Language="C#" AutoEventWireup="true" ContentType="application/xrds+xml" %><?xml version="1.0" encoding="UTF-8"?> +<?xml version="1.0" encoding="UTF-8"?> <xrds:XRDS xmlns:xrds="xri://$xrds" xmlns:openid="http://openid.net/xmlns/1.0" diff --git a/src/DotNetOpenId.TestWeb/xrdsdiscovery/xrds2010b.aspx b/src/DotNetOpenId.Test/Discovery/xrdsdiscovery/xrds2010a.xml index aa8d20a..4f72ac2 100644 --- a/src/DotNetOpenId.TestWeb/xrdsdiscovery/xrds2010b.aspx +++ b/src/DotNetOpenId.Test/Discovery/xrdsdiscovery/xrds2010a.xml @@ -1,18 +1,18 @@ -<%@ Page Language="C#" AutoEventWireup="true" ContentType="application/xrds+xml" %><?xml version="1.0" encoding="UTF-8"?> +<?xml version="1.0" encoding="UTF-8"?> <xrds:XRDS xmlns:xrds="xri://$xrds" xmlns:openid="http://openid.net/xmlns/1.0" xmlns="xri://$xrd*($v*2.0)"> <XRD> - <Service priority="20"> - <Type>http://openid.net/signon/1.0</Type> - <Type>http://openid.net/extensions/sreg/1.1</Type> - <URI>http://c/d</URI> - </Service> <Service priority="10"> <Type>http://specs.openid.net/auth/2.0/signon</Type> <Type>http://openid.net/extensions/sreg/1.1</Type> <URI>http://a/b</URI> </Service> + <Service priority="20"> + <Type>http://openid.net/signon/1.0</Type> + <Type>http://openid.net/extensions/sreg/1.1</Type> + <URI>http://c/d</URI> + </Service> </XRD> </xrds:XRDS> diff --git a/src/DotNetOpenId.TestWeb/xrdsdiscovery/xrds2010a.aspx b/src/DotNetOpenId.Test/Discovery/xrdsdiscovery/xrds2010b.xml index 35f4de7..8ad468f 100644 --- a/src/DotNetOpenId.TestWeb/xrdsdiscovery/xrds2010a.aspx +++ b/src/DotNetOpenId.Test/Discovery/xrdsdiscovery/xrds2010b.xml @@ -1,18 +1,18 @@ -<%@ Page Language="C#" AutoEventWireup="true" ContentType="application/xrds+xml" %><?xml version="1.0" encoding="UTF-8"?> +<?xml version="1.0" encoding="UTF-8"?> <xrds:XRDS xmlns:xrds="xri://$xrds" xmlns:openid="http://openid.net/xmlns/1.0" xmlns="xri://$xrd*($v*2.0)"> <XRD> - <Service priority="10"> - <Type>http://specs.openid.net/auth/2.0/signon</Type> - <Type>http://openid.net/extensions/sreg/1.1</Type> - <URI>http://a/b</URI> - </Service> <Service priority="20"> <Type>http://openid.net/signon/1.0</Type> <Type>http://openid.net/extensions/sreg/1.1</Type> <URI>http://c/d</URI> </Service> + <Service priority="10"> + <Type>http://specs.openid.net/auth/2.0/signon</Type> + <Type>http://openid.net/extensions/sreg/1.1</Type> + <URI>http://a/b</URI> + </Service> </XRD> </xrds:XRDS> diff --git a/src/DotNetOpenId.Test/DotNetOpenId.Test.csproj b/src/DotNetOpenId.Test/DotNetOpenId.Test.csproj index 451f497..851b7e2 100644 --- a/src/DotNetOpenId.Test/DotNetOpenId.Test.csproj +++ b/src/DotNetOpenId.Test/DotNetOpenId.Test.csproj @@ -2,7 +2,7 @@ <PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
- <ProductVersion>9.0.21022</ProductVersion>
+ <ProductVersion>9.0.30729</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>{CDEE655B-3902-420E-ADED-F4B6F666FB03}</ProjectGuid>
<OutputType>Library</OutputType>
@@ -33,6 +33,10 @@ <WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
+ <Reference Include="log4net, Version=1.2.10.0, Culture=neutral, PublicKeyToken=1b44e1d426115821, processorArchitecture=MSIL">
+ <SpecificVersion>False</SpecificVersion>
+ <HintPath>..\..\lib\log4net.dll</HintPath>
+ </Reference>
<Reference Include="nunit.framework, Version=2.2.9.0, Culture=neutral, PublicKeyToken=96d09a1eb7f44a77, processorArchitecture=MSIL">
<HintPath>../../tools/NUnit/bin/nunit.framework.dll</HintPath>
<SpecificVersion>False</SpecificVersion>
@@ -48,6 +52,8 @@ <ItemGroup>
<Compile Include="AssociationsTest.cs" />
<Compile Include="AssociationTestSuite.cs" />
+ <Compile Include="Mocks\DirectMessageSniffWrapper.cs" />
+ <Compile Include="Mocks\DirectMessageTestRedirector.cs" />
<Compile Include="ExtensionsArgumentsManagerTests.cs" />
<Compile Include="Extensions\AttributeExchangeFetchRequestTests.cs" />
<Compile Include="Extensions\AttributeExchangeFetchResponseTests.cs" />
@@ -59,33 +65,36 @@ <Compile Include="Extensions\PolicyRequestTests.cs" />
<Compile Include="Extensions\PolicyResponseTests.cs" />
<Compile Include="Extensions\SimpleRegistrationTests.cs" />
- <Compile Include="Hosting\EncodingInterceptor.cs" />
<Compile Include="IdentifierTests.cs" />
+ <Compile Include="Mocks\MockHttpRequest.cs" />
+ <Compile Include="Mocks\MockIdentifier.cs" />
<Compile Include="NonceTest.cs" />
<Compile Include="PiecewiseJointTesting.cs" />
<Compile Include="Provider\IAuthenticationRequestTest.cs" />
<Compile Include="RelyingParty\AuthenticationRequestTests.cs" />
<Compile Include="RelyingParty\AuthenticationResponseTests.cs" />
<Compile Include="RelyingParty\IProviderEndpointTests.cs" />
- <Compile Include="RelyingParty\OpenIdMobileTextBoxTest.cs" />
+ <Compile Include="UI\OpenIdMobileTextBoxTest.cs" />
<Compile Include="RelyingParty\OpenIdRelyingPartyTest.cs" />
- <Compile Include="RelyingParty\OpenIdTextBoxTest.cs" />
- <Compile Include="DiffieHellmanTestSuite.cs" />
+ <Compile Include="UI\OpenIdTextBoxTest.cs" />
+ <Compile Include="DiffieHellmanUtilTests.cs" />
<Compile Include="EndToEndTesting.cs" />
<Compile Include="KeyValueFormEncodingTests.cs" />
<Compile Include="RelyingParty\ServiceEndpointTests.cs" />
- <Compile Include="TestSupportSanityTest.cs" />
+ <Compile Include="UI\TestSupportSanityTest.cs" />
<Compile Include="Hosting\AspNetHost.cs" />
<Compile Include="Hosting\HttpHost.cs" />
<Compile Include="TestSupport.cs" />
<Compile Include="Hosting\TestingWorkerRequest.cs" />
<Compile Include="Extensions\ClaimsResponseTests.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
- <Compile Include="Provider\IdentityEndpointTest.cs" />
+ <Compile Include="UI\IdentityEndpointTest.cs" />
<Compile Include="Provider\OpenIdProviderTest.cs" />
- <Compile Include="Provider\ProviderEndpointTest.cs" />
+ <Compile Include="UI\ProviderEndpointTest.cs" />
<Compile Include="RelyingParty\TokenTest.cs" />
<Compile Include="RealmTestSuite.cs" />
+ <Compile Include="UI\UITestSupport.cs" />
+ <Compile Include="UI\WebControlTesting.cs" />
<Compile Include="UntrustedWebRequestTests.cs" />
<Compile Include="UriIdentifierTests.cs" />
<Compile Include="UriUtilTest.cs" />
@@ -94,6 +103,28 @@ </ItemGroup>
<ItemGroup>
<Content Include="dhpriv.txt" />
+ <EmbeddedResource Include="Logging.config" />
+ <EmbeddedResource Include="Discovery\htmldiscovery\html1020.html" />
+ <EmbeddedResource Include="Discovery\htmldiscovery\html10both.html" />
+ <EmbeddedResource Include="Discovery\htmldiscovery\html10del.html" />
+ <EmbeddedResource Include="Discovery\htmldiscovery\html10prov.html" />
+ <EmbeddedResource Include="Discovery\htmldiscovery\html2010.html" />
+ <EmbeddedResource Include="Discovery\htmldiscovery\html2010combinedA.html" />
+ <EmbeddedResource Include="Discovery\htmldiscovery\html2010combinedB.html" />
+ <EmbeddedResource Include="Discovery\htmldiscovery\html2010combinedC.html" />
+ <EmbeddedResource Include="Discovery\htmldiscovery\html20both.html" />
+ <EmbeddedResource Include="Discovery\htmldiscovery\html20del.html" />
+ <EmbeddedResource Include="Discovery\htmldiscovery\html20prov.html" />
+ <EmbeddedResource Include="Discovery\htmldiscovery\html20relative.html" />
+ <EmbeddedResource Include="Discovery\xrdsdiscovery\xrds-irrelevant.xml" />
+ <EmbeddedResource Include="Discovery\xrdsdiscovery\xrds10.xml" />
+ <EmbeddedResource Include="Discovery\xrdsdiscovery\xrds1020.xml" />
+ <EmbeddedResource Include="Discovery\xrdsdiscovery\xrds11.xml" />
+ <EmbeddedResource Include="Discovery\xrdsdiscovery\xrds20.xml" />
+ <EmbeddedResource Include="Discovery\xrdsdiscovery\xrds2010a.xml" />
+ <EmbeddedResource Include="Discovery\xrdsdiscovery\xrds2010b.xml" />
+ <EmbeddedResource Include="Discovery\xrdsdiscovery\XrdsReferencedInHead.html" />
+ <EmbeddedResource Include="Discovery\xrdsdiscovery\XrdsReferencedInHttpHeader.html" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\DotNetOpenId\DotNetOpenId.csproj">
@@ -102,5 +133,5 @@ </ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
- <Import Project="..\..\tools\DotNetOpenId.Versioning.targets" />
-</Project>
+ <Import Project="..\..\tools\DotNetOpenId.Versioning.targets" />
+</Project>
\ No newline at end of file diff --git a/src/DotNetOpenId.Test/EndToEndTesting.cs b/src/DotNetOpenId.Test/EndToEndTesting.cs index 1997638..349286a 100644 --- a/src/DotNetOpenId.Test/EndToEndTesting.cs +++ b/src/DotNetOpenId.Test/EndToEndTesting.cs @@ -1,313 +1,194 @@ using System;
-using System.Collections.Generic;
-using System.Text;
-using NUnit.Framework;
-using DotNetOpenId.RelyingParty;
using System.Collections.Specialized;
-using System.Web;
-using System.Net;
using System.Diagnostics;
using System.IO;
+using System.Net;
using System.Text.RegularExpressions;
+using DotNetOpenId.RelyingParty;
+using DotNetOpenId.Test.Mocks;
+using NUnit.Framework;
namespace DotNetOpenId.Test {
[TestFixture]
public class EndToEndTesting {
- IRelyingPartyApplicationStore appStore;
[SetUp]
public void Setup() {
- appStore = new ApplicationMemoryStore();
if (!UntrustedWebRequest.WhitelistHosts.Contains("localhost"))
UntrustedWebRequest.WhitelistHosts.Add("localhost");
}
- void parameterizedTest(UriIdentifier identityUrl,
- AuthenticationRequestMode requestMode, AuthenticationStatus expectedResult,
- bool tryReplayAttack, bool provideStore) {
- parameterizedProgrammaticTest(identityUrl, identityUrl, requestMode, expectedResult, tryReplayAttack, provideStore);
- parameterizedWebClientTest(identityUrl, requestMode, expectedResult, tryReplayAttack, provideStore);
+ [TearDown]
+ public void TearDown() {
+ MockHttpRequest.Reset();
}
- void parameterizedTest(UriIdentifier opIdentifier, UriIdentifier claimedIdentifier,
- AuthenticationRequestMode requestMode, AuthenticationStatus expectedResult,
- bool tryReplayAttack, bool provideStore) {
- parameterizedProgrammaticTest(opIdentifier, claimedIdentifier, requestMode, expectedResult, tryReplayAttack, provideStore);
- parameterizedWebClientTest(opIdentifier, requestMode, expectedResult, tryReplayAttack, provideStore);
- }
- void parameterizedProgrammaticTest(UriIdentifier identityUrl, UriIdentifier claimedUrl,
- AuthenticationRequestMode requestMode, AuthenticationStatus expectedResult,
- bool tryReplayAttack, bool provideStore) {
- var store = provideStore ? appStore : null;
-
- Uri redirectToProviderUrl;
- var returnTo = TestSupport.GetFullUrl(TestSupport.ConsumerPage);
- var realm = new Realm(TestSupport.GetFullUrl(TestSupport.ConsumerPage).AbsoluteUri);
- var consumer = new OpenIdRelyingParty(store, null, null);
- Assert.IsNull(consumer.Response);
- var request = consumer.CreateRequest(identityUrl, realm, returnTo);
- Protocol protocol = Protocol.Lookup(request.Provider.Version);
- // Test properties and defaults
- Assert.AreEqual(AuthenticationRequestMode.Setup, request.Mode);
- Assert.AreEqual(returnTo, request.ReturnToUrl);
- Assert.AreEqual(realm, request.Realm);
+ void parameterizedTest(TestSupport.Scenarios scenario, ProtocolVersion version,
+ AuthenticationRequestMode requestMode, AuthenticationStatus expectedResult) {
+ Identifier claimedId = TestSupport.GetMockIdentifier(scenario, version);
+ parameterizedProgrammaticTest(scenario, version, claimedId, requestMode, expectedResult, true);
+ parameterizedProgrammaticTest(scenario, version, claimedId, requestMode, expectedResult, false);
+ }
+ void parameterizedOPIdentifierTest(TestSupport.Scenarios scenario,
+ AuthenticationRequestMode requestMode, AuthenticationStatus expectedResult) {
+ ProtocolVersion version = ProtocolVersion.V20; // only this version supports directed identity
+ UriIdentifier claimedIdentifier = TestSupport.GetDirectedIdentityUrl(TestSupport.Scenarios.ApproveOnSetup, version);
+ Identifier opIdentifier = TestSupport.GetMockOPIdentifier(TestSupport.Scenarios.ApproveOnSetup, claimedIdentifier);
+ parameterizedProgrammaticOPIdentifierTest(opIdentifier, version, claimedIdentifier, requestMode, expectedResult, true);
+ parameterizedProgrammaticOPIdentifierTest(opIdentifier, version, claimedIdentifier, requestMode, expectedResult, false);
+ }
+ void parameterizedProgrammaticTest(TestSupport.Scenarios scenario, ProtocolVersion version,
+ Identifier claimedUrl, AuthenticationRequestMode requestMode,
+ AuthenticationStatus expectedResult, bool provideStore) {
+ var request = TestSupport.CreateRelyingPartyRequest(!provideStore, scenario, version);
request.Mode = requestMode;
- // Verify the redirect URL
- Assert.IsNotNull(request.RedirectingResponse);
- var consumerToProviderQuery = HttpUtility.ParseQueryString(request.RedirectingResponse.ExtractUrl().Query);
- Assert.IsTrue(consumerToProviderQuery[protocol.openid.return_to].StartsWith(returnTo.AbsoluteUri, StringComparison.Ordinal));
- Assert.AreEqual(realm.ToString(), consumerToProviderQuery[protocol.openid.Realm]);
- redirectToProviderUrl = request.RedirectingResponse.ExtractUrl();
-
- HttpWebRequest providerRequest = (HttpWebRequest)WebRequest.Create(redirectToProviderUrl);
- providerRequest.AllowAutoRedirect = false;
- Uri redirectUrl;
- try {
- using (HttpWebResponse providerResponse = (HttpWebResponse)providerRequest.GetResponse()) {
- Assert.AreEqual(HttpStatusCode.Redirect, providerResponse.StatusCode);
- redirectUrl = new Uri(providerResponse.Headers[HttpResponseHeader.Location]);
- }
- } catch (WebException ex) {
- Trace.WriteLine(ex);
- if (ex.Response != null) {
- using (StreamReader sr = new StreamReader(ex.Response.GetResponseStream())) {
- Trace.WriteLine(sr.ReadToEnd());
- }
- }
- throw;
- }
- consumer = new OpenIdRelyingParty(store, redirectUrl, HttpUtility.ParseQueryString(redirectUrl.Query));
- Assert.AreEqual(expectedResult, consumer.Response.Status);
- Assert.AreEqual(claimedUrl, consumer.Response.ClaimedIdentifier);
-
- // Try replay attack
- if (tryReplayAttack) {
- // This simulates a network sniffing user who caught the
- // authenticating query en route to either the user agent or
- // the consumer, and tries the same query to the consumer in an
- // attempt to spoof the identity of the authenticating user.
- try {
- var replayAttackConsumer = new OpenIdRelyingParty(store, redirectUrl, HttpUtility.ParseQueryString(redirectUrl.Query));
- Assert.AreNotEqual(AuthenticationStatus.Authenticated, replayAttackConsumer.Response.Status, "Replay attack");
- } catch (OpenIdException) { // nonce already used
- // another way to pass
- }
- }
+ var rpResponse = TestSupport.CreateRelyingPartyResponseThroughProvider(request,
+ opReq => opReq.IsAuthenticated = expectedResult == AuthenticationStatus.Authenticated);
+ Assert.AreEqual(expectedResult, rpResponse.Status);
+ Assert.AreEqual(claimedUrl, rpResponse.ClaimedIdentifier);
}
- void parameterizedWebClientTest(UriIdentifier identityUrl,
- AuthenticationRequestMode requestMode, AuthenticationStatus expectedResult,
- bool tryReplayAttack, bool provideStore) {
- var store = provideStore ? appStore : null;
+ void parameterizedProgrammaticOPIdentifierTest(Identifier opIdentifier, ProtocolVersion version,
+ Identifier claimedUrl, AuthenticationRequestMode requestMode,
+ AuthenticationStatus expectedResult, bool provideStore) {
- Uri redirectToProviderUrl;
- HttpWebRequest rpRequest = (HttpWebRequest)WebRequest.Create(TestSupport.GetFullUrl(TestSupport.ConsumerPage));
- NameValueCollection query = new NameValueCollection();
- using (HttpWebResponse response = (HttpWebResponse)rpRequest.GetResponse()) {
- using (StreamReader sr = new StreamReader(response.GetResponseStream())) {
- Regex regex = new Regex(@"\<input\b.*\bname=""(\w+)"".*\bvalue=""([^""]+)""", RegexOptions.IgnoreCase);
- while (!sr.EndOfStream) {
- string line = sr.ReadLine();
- Match m = regex.Match(line);
- if (m.Success) {
- query[m.Groups[1].Value] = m.Groups[2].Value;
- }
- }
- }
- }
- query["OpenIdTextBox1$wrappedTextBox"] = identityUrl;
- rpRequest = (HttpWebRequest)WebRequest.Create(TestSupport.GetFullUrl(TestSupport.ConsumerPage));
- rpRequest.Method = "POST";
- rpRequest.AllowAutoRedirect = false;
- string queryString = UriUtil.CreateQueryString(query);
- rpRequest.ContentLength = queryString.Length;
- rpRequest.ContentType = "application/x-www-form-urlencoded";
- using (StreamWriter sw = new StreamWriter(rpRequest.GetRequestStream())) {
- sw.Write(queryString);
- }
- using (HttpWebResponse response = (HttpWebResponse)rpRequest.GetResponse()) {
- using (StreamReader sr = new StreamReader(response.GetResponseStream())) {
- string doc = sr.ReadToEnd();
- Debug.WriteLine(doc);
- }
- redirectToProviderUrl = new Uri(response.Headers[HttpResponseHeader.Location]);
- }
+ var rp = TestSupport.CreateRelyingParty(provideStore ? TestSupport.RelyingPartyStore : null, null, null);
- HttpWebRequest providerRequest = (HttpWebRequest)WebRequest.Create(redirectToProviderUrl);
- providerRequest.AllowAutoRedirect = false;
- Uri redirectUrl;
- try {
- using (HttpWebResponse providerResponse = (HttpWebResponse)providerRequest.GetResponse()) {
- Assert.AreEqual(HttpStatusCode.Redirect, providerResponse.StatusCode);
- redirectUrl = new Uri(providerResponse.Headers[HttpResponseHeader.Location]);
- }
- } catch (WebException ex) {
- Trace.WriteLine(ex);
- if (ex.Response != null) {
- using (StreamReader sr = new StreamReader(ex.Response.GetResponseStream())) {
- Trace.WriteLine(sr.ReadToEnd());
- }
- }
- throw;
- }
- rpRequest = (HttpWebRequest)WebRequest.Create(redirectUrl);
- rpRequest.AllowAutoRedirect = false;
- using (HttpWebResponse response = (HttpWebResponse)rpRequest.GetResponse()) {
- Assert.AreEqual(HttpStatusCode.Redirect, response.StatusCode); // redirect on login
- }
+ var returnTo = TestSupport.GetFullUrl(TestSupport.ConsumerPage);
+ var realm = new Realm(TestSupport.GetFullUrl(TestSupport.ConsumerPage).AbsoluteUri);
+ var request = rp.CreateRequest(opIdentifier, realm, returnTo);
+ request.Mode = requestMode;
- // Try replay attack
- if (tryReplayAttack) {
- // This simulates a network sniffing user who caught the
- // authenticating query en route to either the user agent or
- // the consumer, and tries the same query to the consumer in an
- // attempt to spoof the identity of the authenticating user.
- rpRequest = (HttpWebRequest)WebRequest.Create(redirectUrl);
- rpRequest.AllowAutoRedirect = false;
- using (HttpWebResponse response = (HttpWebResponse)rpRequest.GetResponse()) {
- Assert.AreEqual(HttpStatusCode.OK, response.StatusCode); // error message
- }
+ var rpResponse = TestSupport.CreateRelyingPartyResponseThroughProvider(request,
+ opReq => {
+ opReq.IsAuthenticated = expectedResult == AuthenticationStatus.Authenticated;
+ if (opReq.IsAuthenticated.Value) {
+ opReq.ClaimedIdentifier = claimedUrl;
+ }
+ });
+ Assert.AreEqual(expectedResult, rpResponse.Status);
+ if (rpResponse.Status == AuthenticationStatus.Authenticated) {
+ Assert.AreEqual(claimedUrl, rpResponse.ClaimedIdentifier);
+ } else if (rpResponse.Status == AuthenticationStatus.SetupRequired) {
+ Assert.IsNull(rpResponse.ClaimedIdentifier);
+ Assert.IsNull(rpResponse.FriendlyIdentifierForDisplay);
+ Assert.IsNull(rpResponse.Exception);
+ Assert.IsInstanceOfType(typeof(ISetupRequiredAuthenticationResponse), rpResponse);
+ Assert.AreEqual(opIdentifier.ToString(), ((ISetupRequiredAuthenticationResponse)rpResponse).ClaimedOrProviderIdentifier.ToString());
}
}
[Test]
public void Pass_Setup_AutoApproval_11() {
parameterizedTest(
- TestSupport.GetIdentityUrl(TestSupport.Scenarios.AutoApproval, ProtocolVersion.V11),
+ TestSupport.Scenarios.AutoApproval, ProtocolVersion.V11,
AuthenticationRequestMode.Setup,
- AuthenticationStatus.Authenticated,
- true,
- true
+ AuthenticationStatus.Authenticated
);
}
[Test]
public void Pass_Setup_AutoApproval_20() {
parameterizedTest(
- TestSupport.GetIdentityUrl(TestSupport.Scenarios.AutoApproval, ProtocolVersion.V20),
+ TestSupport.Scenarios.AutoApproval, ProtocolVersion.V20,
AuthenticationRequestMode.Setup,
- AuthenticationStatus.Authenticated,
- true,
- true
+ AuthenticationStatus.Authenticated
);
}
[Test]
public void Pass_Immediate_AutoApproval_11() {
parameterizedTest(
- TestSupport.GetIdentityUrl(TestSupport.Scenarios.AutoApproval, ProtocolVersion.V11),
+ TestSupport.Scenarios.AutoApproval, ProtocolVersion.V11,
AuthenticationRequestMode.Immediate,
- AuthenticationStatus.Authenticated,
- true,
- true
+ AuthenticationStatus.Authenticated
);
}
[Test]
public void Pass_Immediate_AutoApproval_20() {
parameterizedTest(
- TestSupport.GetIdentityUrl(TestSupport.Scenarios.AutoApproval, ProtocolVersion.V20),
+ TestSupport.Scenarios.AutoApproval, ProtocolVersion.V20,
AuthenticationRequestMode.Immediate,
- AuthenticationStatus.Authenticated,
- true,
- true
+ AuthenticationStatus.Authenticated
);
}
[Test]
public void Fail_Immediate_ApproveOnSetup_11() {
parameterizedTest(
- TestSupport.GetIdentityUrl(TestSupport.Scenarios.ApproveOnSetup, ProtocolVersion.V11),
+ TestSupport.Scenarios.ApproveOnSetup, ProtocolVersion.V11,
AuthenticationRequestMode.Immediate,
- AuthenticationStatus.SetupRequired,
- false,
- true
+ AuthenticationStatus.SetupRequired
);
}
[Test]
public void Fail_Immediate_ApproveOnSetup_20() {
parameterizedTest(
- TestSupport.GetIdentityUrl(TestSupport.Scenarios.ApproveOnSetup, ProtocolVersion.V20),
+ TestSupport.Scenarios.ApproveOnSetup, ProtocolVersion.V20,
AuthenticationRequestMode.Immediate,
- AuthenticationStatus.SetupRequired,
- false,
- true
+ AuthenticationStatus.SetupRequired
);
}
[Test]
public void Pass_Setup_ApproveOnSetup_11() {
parameterizedTest(
- TestSupport.GetIdentityUrl(TestSupport.Scenarios.ApproveOnSetup, ProtocolVersion.V11),
+ TestSupport.Scenarios.ApproveOnSetup, ProtocolVersion.V11,
AuthenticationRequestMode.Setup,
- AuthenticationStatus.Authenticated,
- true,
- true
+ AuthenticationStatus.Authenticated
);
}
[Test]
public void Pass_Setup_ApproveOnSetup_20() {
parameterizedTest(
- TestSupport.GetIdentityUrl(TestSupport.Scenarios.ApproveOnSetup, ProtocolVersion.V20),
+ TestSupport.Scenarios.ApproveOnSetup, ProtocolVersion.V20,
AuthenticationRequestMode.Setup,
- AuthenticationStatus.Authenticated,
- true,
- true
+ AuthenticationStatus.Authenticated
);
}
[Test]
- public void Pass_NoStore_AutoApproval_11() {
- parameterizedTest(
- TestSupport.GetIdentityUrl(TestSupport.Scenarios.ApproveOnSetup, ProtocolVersion.V11),
- AuthenticationRequestMode.Setup,
- AuthenticationStatus.Authenticated,
- true,
- false
- );
- }
- [Test]
- public void Pass_NoStore_AutoApproval_20() {
- parameterizedTest(
- TestSupport.GetIdentityUrl(TestSupport.Scenarios.ApproveOnSetup, ProtocolVersion.V20),
- AuthenticationRequestMode.Setup,
- AuthenticationStatus.Authenticated,
- true,
- false
- );
+ public void Pass_Immediate_AutoApproval_DirectedIdentity_20() {
+ parameterizedOPIdentifierTest(
+ TestSupport.Scenarios.AutoApproval,
+ AuthenticationRequestMode.Immediate,
+ AuthenticationStatus.Authenticated);
}
+
[Test]
public void Pass_Setup_ApproveOnSetup_DirectedIdentity_20() {
- parameterizedTest(
- TestSupport.GetOPIdentityUrl(TestSupport.Scenarios.ApproveOnSetup),
- TestSupport.GetDirectedIdentityUrl(TestSupport.Scenarios.ApproveOnSetup, ProtocolVersion.V20),
+ parameterizedOPIdentifierTest(
+ TestSupport.Scenarios.ApproveOnSetup,
AuthenticationRequestMode.Setup,
- AuthenticationStatus.Authenticated,
- true,
- true);
+ AuthenticationStatus.Authenticated);
}
+
[Test]
- public void Pass_NoStore_ApproveOnSetup_DirectedIdentity_20() {
- parameterizedTest(
- TestSupport.GetOPIdentityUrl(TestSupport.Scenarios.ApproveOnSetup),
- TestSupport.GetDirectedIdentityUrl(TestSupport.Scenarios.ApproveOnSetup, ProtocolVersion.V20),
- AuthenticationRequestMode.Setup,
- AuthenticationStatus.Authenticated,
- true,
- false);
+ public void Fail_Immediate_ApproveOnSetup_DirectedIdentity_20() {
+ parameterizedOPIdentifierTest(
+ TestSupport.Scenarios.ApproveOnSetup,
+ AuthenticationRequestMode.Immediate,
+ AuthenticationStatus.SetupRequired);
}
[Test]
public void ProviderAddedFragmentRemainsInClaimedIdentifier() {
- Uri userSuppliedIdentifier = TestSupport.GetIdentityUrl(TestSupport.Scenarios.AutoApprovalAddFragment, ProtocolVersion.V20);
+ Identifier userSuppliedIdentifier = TestSupport.GetMockIdentifier(TestSupport.Scenarios.AutoApprovalAddFragment, ProtocolVersion.V20);
UriBuilder claimedIdentifier = new UriBuilder(userSuppliedIdentifier);
claimedIdentifier.Fragment = "frag";
parameterizedProgrammaticTest(
- userSuppliedIdentifier,
+ TestSupport.Scenarios.AutoApprovalAddFragment, ProtocolVersion.V20,
claimedIdentifier.Uri,
AuthenticationRequestMode.Setup,
AuthenticationStatus.Authenticated,
- false,
true
);
}
+
+ [Test]
+ public void SampleScriptedTest() {
+ var rpReq = TestSupport.CreateRelyingPartyRequest(false, TestSupport.Scenarios.AutoApproval, ProtocolVersion.V20);
+ var rpResp = TestSupport.CreateRelyingPartyResponseThroughProvider(rpReq, opReq => opReq.IsAuthenticated = true);
+ Assert.AreEqual(AuthenticationStatus.Authenticated, rpResp.Status);
+ }
}
}
diff --git a/src/DotNetOpenId.Test/Extensions/AttributeExchangeTests.cs b/src/DotNetOpenId.Test/Extensions/AttributeExchangeTests.cs index 62efbc2..abf8e32 100644 --- a/src/DotNetOpenId.Test/Extensions/AttributeExchangeTests.cs +++ b/src/DotNetOpenId.Test/Extensions/AttributeExchangeTests.cs @@ -17,10 +17,10 @@ namespace DotNetOpenId.Test.Extensions { [Test]
public void None() {
var fetchResponse = ParameterizedTest<FetchResponse>(
- TestSupport.GetIdentityUrl(TestSupport.Scenarios.ExtensionFullCooperation, Version), null);
+ TestSupport.Scenarios.ExtensionFullCooperation, Version, null);
Assert.IsNull(fetchResponse);
var storeResponse = ParameterizedTest<StoreResponse>(
- TestSupport.GetIdentityUrl(TestSupport.Scenarios.ExtensionFullCooperation, Version), null);
+ TestSupport.Scenarios.ExtensionFullCooperation, Version, null);
Assert.IsNull(storeResponse);
}
@@ -30,7 +30,7 @@ namespace DotNetOpenId.Test.Extensions { request.AddAttribute(new AttributeRequest(nicknameTypeUri));
request.AddAttribute(new AttributeRequest(emailTypeUri, false, int.MaxValue));
var response = ParameterizedTest<FetchResponse>(
- TestSupport.GetIdentityUrl(TestSupport.Scenarios.ExtensionFullCooperation, Version), request);
+ TestSupport.Scenarios.ExtensionFullCooperation, Version, request);
Assert.IsNotNull(response);
var att = response.GetAttribute(nicknameTypeUri);
Assert.IsNotNull(att);
@@ -50,7 +50,7 @@ namespace DotNetOpenId.Test.Extensions { var request = new FetchRequest();
request.AddAttribute(new AttributeRequest { TypeUri = emailTypeUri, Count = 1 });
var response = ParameterizedTest<FetchResponse>(
- TestSupport.GetIdentityUrl(TestSupport.Scenarios.ExtensionFullCooperation, Version), request);
+ TestSupport.Scenarios.ExtensionFullCooperation, Version, request);
Assert.IsNotNull(response);
var att = response.GetAttribute(emailTypeUri);
Assert.IsNotNull(att);
@@ -69,7 +69,7 @@ namespace DotNetOpenId.Test.Extensions { request.AddAttribute(newAttribute);
var response = ParameterizedTest<StoreResponse>(
- TestSupport.GetIdentityUrl(TestSupport.Scenarios.ExtensionFullCooperation, Version), request);
+ TestSupport.Scenarios.ExtensionFullCooperation, Version, request);
Assert.IsNotNull(response);
Assert.IsTrue(response.Succeeded);
Assert.IsNull(response.FailureReason);
@@ -77,7 +77,7 @@ namespace DotNetOpenId.Test.Extensions { var fetchRequest = new FetchRequest();
fetchRequest.AddAttribute(new AttributeRequest { TypeUri = incrementingAttribute });
var fetchResponse = ParameterizedTest<FetchResponse>(
- TestSupport.GetIdentityUrl(TestSupport.Scenarios.ExtensionFullCooperation, Version), fetchRequest);
+ TestSupport.Scenarios.ExtensionFullCooperation, Version, fetchRequest);
Assert.IsNotNull(fetchResponse);
var att = fetchResponse.GetAttribute(incrementingAttribute);
Assert.IsNotNull(att);
@@ -92,11 +92,7 @@ namespace DotNetOpenId.Test.Extensions { /// </summary>
[Test, ExpectedException(typeof(OpenIdException))]
public void FetchAndStore() {
- var identityUrl = TestSupport.GetIdentityUrl(TestSupport.Scenarios.ExtensionFullCooperation, Version);
- var returnTo = TestSupport.GetFullUrl(TestSupport.ConsumerPage);
- var realm = new Realm(TestSupport.GetFullUrl(TestSupport.ConsumerPage).AbsoluteUri);
- var consumer = new OpenIdRelyingParty(AppStore, null, null);
- var request = consumer.CreateRequest(identityUrl, realm, returnTo);
+ var request = TestSupport.CreateRelyingPartyRequest(false, TestSupport.Scenarios.ExtensionFullCooperation, Version);
request.AddExtension(new FetchRequest());
request.AddExtension(new StoreRequest());
}
diff --git a/src/DotNetOpenId.Test/Extensions/ExtensionTestBase.cs b/src/DotNetOpenId.Test/Extensions/ExtensionTestBase.cs index 825ef58..cfa9cca 100644 --- a/src/DotNetOpenId.Test/Extensions/ExtensionTestBase.cs +++ b/src/DotNetOpenId.Test/Extensions/ExtensionTestBase.cs @@ -1,56 +1,128 @@ using System;
using System.Collections.Generic;
-using System.Linq;
-using System.Text;
+using DotNetOpenId.Extensions;
+using DotNetOpenId.Extensions.AttributeExchange;
+using DotNetOpenId.Extensions.ProviderAuthenticationPolicy;
+using DotNetOpenId.Extensions.SimpleRegistration;
using DotNetOpenId.RelyingParty;
using NUnit.Framework;
-using System.Net;
-using DotNetOpenId.Extensions;
-using System.IO;
-using System.Diagnostics;
-using System.Web;
+using OPRequest = DotNetOpenId.Provider.IAuthenticationRequest;
+using SregDemandLevel = DotNetOpenId.Extensions.SimpleRegistration.DemandLevel;
+using PapeConstants = DotNetOpenId.Extensions.ProviderAuthenticationPolicy.Constants;
namespace DotNetOpenId.Test.Extensions {
public class ExtensionTestBase {
- protected IRelyingPartyApplicationStore AppStore;
protected const ProtocolVersion Version = ProtocolVersion.V20;
+ Dictionary<string, AttributeValues> storedAttributes;
[SetUp]
public virtual void Setup() {
- AppStore = new ApplicationMemoryStore();
+ storedAttributes = new Dictionary<string, AttributeValues>();
+ }
+
+ [TearDown]
+ public virtual void TearDown() {
+ Mocks.MockHttpRequest.Reset();
}
- protected T ParameterizedTest<T>(Identifier identityUrl, IExtensionRequest extension)
+ protected T ParameterizedTest<T>(TestSupport.Scenarios scenario, ProtocolVersion version, IExtensionRequest extension)
where T : IExtensionResponse, new() {
- Debug.Assert(identityUrl != null);
- var returnTo = TestSupport.GetFullUrl(TestSupport.ConsumerPage);
- var realm = new Realm(TestSupport.GetFullUrl(TestSupport.ConsumerPage).AbsoluteUri);
- var consumer = new OpenIdRelyingParty(AppStore, null, null);
- var request = consumer.CreateRequest(identityUrl, realm, returnTo);
+ var rpRequest = TestSupport.CreateRelyingPartyRequest(false, scenario, version);
if (extension != null)
- request.AddExtension(extension);
-
- HttpWebRequest providerRequest = (HttpWebRequest)WebRequest.Create(request.RedirectingResponse.ExtractUrl());
- providerRequest.AllowAutoRedirect = false;
- Uri redirectUrl;
- try {
- using (HttpWebResponse providerResponse = (HttpWebResponse)providerRequest.GetResponse()) {
- Assert.AreEqual(HttpStatusCode.Redirect, providerResponse.StatusCode);
- redirectUrl = new Uri(providerResponse.Headers[HttpResponseHeader.Location]);
- }
- } catch (WebException ex) {
- Trace.WriteLine(ex);
- if (ex.Response != null) {
- using (StreamReader sr = new StreamReader(ex.Response.GetResponseStream())) {
- Trace.WriteLine(sr.ReadToEnd());
+ rpRequest.AddExtension(extension);
+
+ var response = TestSupport.CreateRelyingPartyResponseThroughProvider(rpRequest, request => {
+ TestSupport.SetAuthenticationFromScenario(scenario, request);
+ ExtensionsResponder(request);
+ });
+ Assert.AreEqual(AuthenticationStatus.Authenticated, response.Status);
+ return response.GetExtension<T>();
+ }
+
+ const string nicknameTypeUri = WellKnownAttributes.Name.Alias;
+ const string emailTypeUri = WellKnownAttributes.Contact.Email;
+
+ private void ExtensionsResponder(OPRequest request) {
+ var sregRequest = request.GetExtension<ClaimsRequest>();
+ var sregResponse = sregRequest != null ? sregRequest.CreateResponse() : null;
+ var aeFetchRequest = request.GetExtension<FetchRequest>();
+ var aeFetchResponse = new FetchResponse();
+ var aeStoreRequest = request.GetExtension<StoreRequest>();
+ var aeStoreResponse = new StoreResponse();
+ var papeRequest = request.GetExtension<PolicyRequest>();
+ var papeResponse = new PolicyResponse();
+
+ TestSupport.Scenarios scenario = (TestSupport.Scenarios)Enum.Parse(typeof(TestSupport.Scenarios),
+ new Uri(request.LocalIdentifier).AbsolutePath.TrimStart('/'));
+ switch (scenario) {
+ case TestSupport.Scenarios.ExtensionFullCooperation:
+ if (sregRequest != null) {
+ if (sregRequest.FullName != SregDemandLevel.NoRequest)
+ sregResponse.FullName = "Andrew Arnott";
+ if (sregRequest.Email != SregDemandLevel.NoRequest)
+ sregResponse.Email = "andrewarnott@gmail.com";
+ }
+ if (aeFetchRequest != null) {
+ var att = aeFetchRequest.GetAttribute(nicknameTypeUri);
+ if (att != null)
+ aeFetchResponse.AddAttribute(att.Respond("Andrew"));
+ att = aeFetchRequest.GetAttribute(emailTypeUri);
+ if (att != null) {
+ string[] emails = new[] { "a@a.com", "b@b.com" };
+ string[] subset = new string[Math.Min(emails.Length, att.Count)];
+ Array.Copy(emails, subset, subset.Length);
+ aeFetchResponse.AddAttribute(att.Respond(subset));
+ }
+ foreach (var att2 in aeFetchRequest.Attributes) {
+ if (storedAttributes.ContainsKey(att2.TypeUri))
+ aeFetchResponse.AddAttribute(storedAttributes[att2.TypeUri]);
+ }
+ }
+ if (papeRequest != null) {
+ if (papeRequest.MaximumAuthenticationAge.HasValue) {
+ papeResponse.AuthenticationTimeUtc = DateTime.UtcNow - (papeRequest.MaximumAuthenticationAge.Value - TimeSpan.FromSeconds(30));
+ }
+ if (papeRequest.PreferredAuthLevelTypes.Contains(PapeConstants.AuthenticationLevels.NistTypeUri)) {
+ papeResponse.NistAssuranceLevel = NistAssuranceLevel.Level1;
+ }
}
+ break;
+ case TestSupport.Scenarios.ExtensionPartialCooperation:
+ if (sregRequest != null) {
+ if (sregRequest.FullName == SregDemandLevel.Require)
+ sregResponse.FullName = "Andrew Arnott";
+ if (sregRequest.Email == SregDemandLevel.Require)
+ sregResponse.Email = "andrewarnott@gmail.com";
+ }
+ if (aeFetchRequest != null) {
+ var att = aeFetchRequest.GetAttribute(nicknameTypeUri);
+ if (att != null && att.IsRequired)
+ aeFetchResponse.AddAttribute(att.Respond("Andrew"));
+ att = aeFetchRequest.GetAttribute(emailTypeUri);
+ if (att != null && att.IsRequired) {
+ string[] emails = new[] { "a@a.com", "b@b.com" };
+ string[] subset = new string[Math.Min(emails.Length, att.Count)];
+ Array.Copy(emails, subset, subset.Length);
+ aeFetchResponse.AddAttribute(att.Respond(subset));
+ }
+ foreach (var att2 in aeFetchRequest.Attributes) {
+ if (att2.IsRequired && storedAttributes.ContainsKey(att2.TypeUri))
+ aeFetchResponse.AddAttribute(storedAttributes[att2.TypeUri]);
+ }
+ }
+ break;
+ }
+ if (aeStoreRequest != null) {
+ foreach (var att in aeStoreRequest.Attributes) {
+ storedAttributes[att.TypeUri] = att;
}
- throw;
+ aeStoreResponse.Succeeded = true;
}
- consumer = new OpenIdRelyingParty(AppStore, redirectUrl, HttpUtility.ParseQueryString(redirectUrl.Query));
- Assert.AreEqual(AuthenticationStatus.Authenticated, consumer.Response.Status);
- Assert.AreEqual(identityUrl, consumer.Response.ClaimedIdentifier);
- return consumer.Response.GetExtension<T>();
+
+ if (sregRequest != null) request.AddResponseExtension(sregResponse);
+ if (aeFetchRequest != null) request.AddResponseExtension(aeFetchResponse);
+ if (aeStoreRequest != null) request.AddResponseExtension(aeStoreResponse);
+ if (papeRequest != null) request.AddResponseExtension(papeResponse);
}
}
}
diff --git a/src/DotNetOpenId.Test/Extensions/PapeTests.cs b/src/DotNetOpenId.Test/Extensions/PapeTests.cs index 914089e..6fb693d 100644 --- a/src/DotNetOpenId.Test/Extensions/PapeTests.cs +++ b/src/DotNetOpenId.Test/Extensions/PapeTests.cs @@ -11,7 +11,7 @@ namespace DotNetOpenId.Test.Extensions { [Test]
public void None() {
var response = ParameterizedTest<PolicyResponse>(
- TestSupport.GetIdentityUrl(TestSupport.Scenarios.ExtensionFullCooperation, Version), null);
+ TestSupport.Scenarios.ExtensionFullCooperation, Version, null);
Assert.IsNull(response);
}
@@ -21,7 +21,7 @@ namespace DotNetOpenId.Test.Extensions { request.MaximumAuthenticationAge = TimeSpan.FromMinutes(10);
request.PreferredAuthLevelTypes.Add(Constants.AuthenticationLevels.NistTypeUri);
var response = ParameterizedTest<PolicyResponse>(
- TestSupport.GetIdentityUrl(TestSupport.Scenarios.ExtensionFullCooperation, Version), request);
+ TestSupport.Scenarios.ExtensionFullCooperation, Version, request);
Assert.IsNotNull(response);
Assert.IsNotNull(response.AuthenticationTimeUtc);
Assert.IsTrue(response.AuthenticationTimeUtc.Value > DateTime.UtcNow - request.MaximumAuthenticationAge);
diff --git a/src/DotNetOpenId.Test/Extensions/SimpleRegistrationTests.cs b/src/DotNetOpenId.Test/Extensions/SimpleRegistrationTests.cs index 69942a5..9437f27 100644 --- a/src/DotNetOpenId.Test/Extensions/SimpleRegistrationTests.cs +++ b/src/DotNetOpenId.Test/Extensions/SimpleRegistrationTests.cs @@ -11,7 +11,7 @@ namespace DotNetOpenId.Test.Extensions { [Test]
public void None() {
var response = ParameterizedTest<ClaimsResponse>(
- TestSupport.GetIdentityUrl(TestSupport.Scenarios.ExtensionFullCooperation, Version), null);
+ TestSupport.Scenarios.ExtensionFullCooperation, Version, null);
Assert.IsNull(response);
}
@@ -21,7 +21,7 @@ namespace DotNetOpenId.Test.Extensions { request.FullName = DemandLevel.Request;
request.Email = DemandLevel.Require;
var response = ParameterizedTest<ClaimsResponse>(
- TestSupport.GetIdentityUrl(TestSupport.Scenarios.ExtensionFullCooperation, Version), request);
+ TestSupport.Scenarios.ExtensionFullCooperation, Version, request);
Assert.AreEqual("Andrew Arnott", response.FullName);
Assert.AreEqual("andrewarnott@gmail.com", response.Email);
}
@@ -31,7 +31,7 @@ namespace DotNetOpenId.Test.Extensions { request.FullName = DemandLevel.Request;
request.Email = DemandLevel.Require;
var response = ParameterizedTest<ClaimsResponse>(
- TestSupport.GetIdentityUrl(TestSupport.Scenarios.ExtensionPartialCooperation, Version), request);
+ TestSupport.Scenarios.ExtensionPartialCooperation, Version, request);
Assert.IsNull(response.FullName);
Assert.AreEqual("andrewarnott@gmail.com", response.Email);
}
diff --git a/src/DotNetOpenId.Test/Hosting/AspNetHost.cs b/src/DotNetOpenId.Test/Hosting/AspNetHost.cs index f171f5a..47480f2 100644 --- a/src/DotNetOpenId.Test/Hosting/AspNetHost.cs +++ b/src/DotNetOpenId.Test/Hosting/AspNetHost.cs @@ -21,13 +21,9 @@ namespace DotNetOpenId.Test.Hosting { httpHost = HttpHost.CreateHost(this);
if (!UntrustedWebRequest.WhitelistHosts.Contains("localhost"))
UntrustedWebRequest.WhitelistHosts.Add("localhost");
- DotNetOpenId.Provider.SigningMessageEncoder.Signing += (s, e) => {
- if (MessageInterceptor != null) MessageInterceptor.OnSigningMessage(e.Message);
- };
}
public Uri BaseUri { get { return httpHost.BaseUri; } }
- public EncodingInterceptor MessageInterceptor { get; set; }
public static AspNetHost CreateHost(string webDirectory) {
AspNetHost host = (AspNetHost)ApplicationHost.
@@ -53,7 +49,7 @@ namespace DotNetOpenId.Test.Hosting { HttpRuntime.ProcessRequest(new TestingWorkerRequest(context, tw));
}
} catch (Exception ex) {
- Console.WriteLine(ex);
+ TestSupport.Logger.Error("Exception in AspNetHost", ex);
throw;
}
}
diff --git a/src/DotNetOpenId.Test/Hosting/EncodingInterceptor.cs b/src/DotNetOpenId.Test/Hosting/EncodingInterceptor.cs deleted file mode 100644 index 0759ba2..0000000 --- a/src/DotNetOpenId.Test/Hosting/EncodingInterceptor.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using DotNetOpenId.Provider;
-using System.Diagnostics;
-
-namespace DotNetOpenId.Test.Hosting {
- /// <remarks>
- /// This should be instantiated in the test app domain,
- /// and passed to the ASP.NET host app domain.
- /// </remarks>
- public class EncodingInterceptor : MarshalByRefObject {
- /// <summary>
- /// Forwards a call from the ASP.NET host on to any interested test.
- /// </summary>
- /// <param name="message"></param>
- internal void OnSigningMessage(IEncodable message) {
- if (SigningMessage != null) {
- try {
- SigningMessage(message);
- } catch (Exception e) {
- Trace.TraceWarning("Unhandled exception in cross app-domain event handler: {0}", e);
- }
- }
- }
- internal delegate void InterceptorHandler(IEncodable message);
- internal InterceptorHandler SigningMessage;
- }
-}
diff --git a/src/DotNetOpenId.Test/Hosting/HttpHost.cs b/src/DotNetOpenId.Test/Hosting/HttpHost.cs index d6e73ab..dc69848 100644 --- a/src/DotNetOpenId.Test/Hosting/HttpHost.cs +++ b/src/DotNetOpenId.Test/Hosting/HttpHost.cs @@ -73,10 +73,10 @@ namespace DotNetOpenId.Test.Hosting { return sr.ReadToEnd();
}
} catch (WebException ex) {
- Console.Error.WriteLine(ex);
+ TestSupport.Logger.Error("Exception in HttpHost", ex);
using (StreamReader sr = new StreamReader(ex.Response.GetResponseStream())) {
string streamContent = sr.ReadToEnd();
- Console.Error.WriteLine(streamContent);
+ TestSupport.Logger.ErrorFormat("Error content stream follows: {0}", streamContent);
}
throw;
}
diff --git a/src/DotNetOpenId.Test/Logging.config b/src/DotNetOpenId.Test/Logging.config new file mode 100644 index 0000000..184568b --- /dev/null +++ b/src/DotNetOpenId.Test/Logging.config @@ -0,0 +1,33 @@ +<?xml version="1.0" encoding="utf-8" ?>
+<log4net>
+ <appender name="RollingFileAppender" type="log4net.Appender.RollingFileAppender">
+ <file value="Testing.log" />
+ <appendToFile value="true" />
+ <rollingStyle value="Size" />
+ <maxSizeRollBackups value="10" />
+ <maximumFileSize value="1024KB" />
+ <staticLogFileName value="true" />
+ <layout type="log4net.Layout.PatternLayout">
+ <conversionPattern value="%date [%thread] %-5level %logger - %message%newline" />
+ </layout>
+ </appender>
+ <appender name="TraceAppender" type="log4net.Appender.TraceAppender">
+ <immediateFlush value="true" />
+ <layout type="log4net.Layout.PatternLayout">
+ <conversionPattern value="%-5level - %message%newline" />
+ </layout>
+ </appender>
+ <!-- Setup the root category, add the appenders and set the default level -->
+ <root>
+ <level value="Warn" />
+ <!--<appender-ref ref="RollingFileAppender" />-->
+ <appender-ref ref="TraceAppender" />
+ </root>
+ <!-- Specify the level for some specific categories -->
+ <logger name="DotNetOpenId">
+ <level value="Off" />
+ </logger>
+ <logger name="DotNetOpenId.Test">
+ <level value="Info" />
+ </logger>
+</log4net>
diff --git a/src/DotNetOpenId.Test/Mocks/DirectMessageSniffWrapper.cs b/src/DotNetOpenId.Test/Mocks/DirectMessageSniffWrapper.cs new file mode 100644 index 0000000..1bcbfb9 --- /dev/null +++ b/src/DotNetOpenId.Test/Mocks/DirectMessageSniffWrapper.cs @@ -0,0 +1,41 @@ +using System;
+using System.Collections.Generic;
+using DotNetOpenId.RelyingParty;
+
+namespace DotNetOpenId.Test.Mocks {
+ class DirectMessageSniffWrapper : IDirectMessageChannel {
+ IDirectMessageChannel channel;
+
+ internal DirectMessageSniffWrapper(IDirectMessageChannel channel) {
+ this.channel = channel;
+ }
+
+ internal event Action<ServiceEndpoint, IDictionary<string, string>> Sending;
+ internal event Action<ServiceEndpoint, IDictionary<string, string>> Receiving;
+
+ protected virtual void OnSending(ServiceEndpoint provider, IDictionary<string, string> fields) {
+ var sending = Sending;
+ if (sending != null) {
+ sending(provider, fields);
+ }
+ }
+
+ protected virtual void OnReceiving(ServiceEndpoint provider, IDictionary<string, string> fields) {
+ var receiving = Receiving;
+ if (receiving != null) {
+ receiving(provider, fields);
+ }
+ }
+
+ #region IDirectMessageChannel Members
+
+ public IDictionary<string, string> SendDirectMessageAndGetResponse(ServiceEndpoint provider, IDictionary<string, string> fields) {
+ OnSending(provider, fields);
+ var results = channel.SendDirectMessageAndGetResponse(provider, fields);
+ OnReceiving(provider, results);
+ return results;
+ }
+
+ #endregion
+ }
+}
diff --git a/src/DotNetOpenId.Test/Mocks/DirectMessageTestRedirector.cs b/src/DotNetOpenId.Test/Mocks/DirectMessageTestRedirector.cs new file mode 100644 index 0000000..c63e2de --- /dev/null +++ b/src/DotNetOpenId.Test/Mocks/DirectMessageTestRedirector.cs @@ -0,0 +1,31 @@ +using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using DotNetOpenId.Provider;
+using DotNetOpenId.RelyingParty;
+using IProviderAssociationStore = DotNetOpenId.IAssociationStore<DotNetOpenId.AssociationRelyingPartyType>;
+
+namespace DotNetOpenId.Test.Mocks {
+ class DirectMessageTestRedirector : IDirectMessageChannel {
+
+ IProviderAssociationStore providerStore;
+
+ public DirectMessageTestRedirector(IProviderAssociationStore providerStore) {
+ if (providerStore == null) throw new ArgumentNullException("providerStore");
+ this.providerStore = providerStore;
+ }
+
+ #region IDirectMessageChannel Members
+
+ public IDictionary<string, string> SendDirectMessageAndGetResponse(ServiceEndpoint providerEndpoint, IDictionary<string, string> fields) {
+ OpenIdProvider provider = new OpenIdProvider(providerStore, providerEndpoint.ProviderEndpoint,
+ providerEndpoint.ProviderEndpoint, fields.ToNameValueCollection());
+ Debug.Assert(provider.Request.IsResponseReady, "Direct messages should always have an immediately available response.");
+ Response webResponse = (Response)provider.Request.Response;
+ EncodableResponse opAuthResponse = (EncodableResponse)webResponse.EncodableMessage;
+ return opAuthResponse.EncodedFields;
+ }
+
+ #endregion
+ }
+}
diff --git a/src/DotNetOpenId.Test/Mocks/MockHttpRequest.cs b/src/DotNetOpenId.Test/Mocks/MockHttpRequest.cs new file mode 100644 index 0000000..1a0547c --- /dev/null +++ b/src/DotNetOpenId.Test/Mocks/MockHttpRequest.cs @@ -0,0 +1,175 @@ +using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.IO;
+using System.Net;
+using DotNetOpenId.RelyingParty;
+using DotNetOpenId.Yadis;
+using NUnit.Framework;
+using System.Diagnostics;
+using System.Web;
+using System.Text;
+
+namespace DotNetOpenId.Test.Mocks {
+ class MockHttpRequest {
+ static Dictionary<Uri, UntrustedWebResponse> registeredMockResponses = new Dictionary<Uri, UntrustedWebResponse>();
+
+ static UntrustedWebResponse MockRequestResponse(Uri uri, byte[] body, string[] acceptTypes) {
+ UntrustedWebResponse response;
+ if (registeredMockResponses.TryGetValue(uri, out response)) {
+ // reset response stream position so this response can be reused on a subsequent request.
+ response.ResponseStream.Seek(0, SeekOrigin.Begin);
+ return response;
+ } else {
+ //Assert.Fail("Unexpected HTTP request: {0}", uri);
+ return new UntrustedWebResponse(uri, uri, new WebHeaderCollection(), HttpStatusCode.NotFound,
+ "text/html", null, new MemoryStream());
+ }
+ }
+
+ /// <summary>
+ /// Clears all all mock HTTP responses and deactivates HTTP mocking.
+ /// </summary>
+ internal static void Reset() {
+ UntrustedWebRequest.MockRequests = null;
+ registeredMockResponses.Clear();
+ }
+
+ internal static void RegisterMockResponse(UntrustedWebResponse response) {
+ if (response == null) throw new ArgumentNullException("response");
+ UntrustedWebRequest.MockRequests = MockRequestResponse;
+ if (registeredMockResponses.ContainsKey(response.RequestUri)) {
+ TestSupport.Logger.WarnFormat("Mock HTTP response already registered for {0}.", response.RequestUri);
+ } else {
+ registeredMockResponses.Add(response.RequestUri, response);
+ }
+ }
+
+ internal static void RegisterMockResponse(Uri requestUri, string contentType, string responseBody) {
+ RegisterMockResponse(requestUri, requestUri, contentType, responseBody);
+ }
+
+ internal static void RegisterMockResponse(Uri requestUri, Uri responseUri, string contentType, string responseBody) {
+ RegisterMockResponse(requestUri, responseUri, contentType, new WebHeaderCollection(), responseBody);
+ }
+
+ internal static void RegisterMockResponse(Uri requestUri, Uri responseUri, string contentType, WebHeaderCollection headers, string responseBody) {
+ if (requestUri == null) throw new ArgumentNullException("requestUri");
+ if (responseUri == null) throw new ArgumentNullException("responseUri");
+ if (String.IsNullOrEmpty(contentType)) throw new ArgumentNullException("contentType");
+
+ // Set up the redirect if appropriate
+ if (requestUri != responseUri) {
+ RegisterMockRedirect(requestUri, responseUri);
+ }
+
+ string contentEncoding = null;
+ MemoryStream stream = new MemoryStream();
+ StreamWriter sw = new StreamWriter(stream);
+ sw.Write(responseBody);
+ sw.Flush();
+ stream.Seek(0, SeekOrigin.Begin);
+ RegisterMockResponse(new UntrustedWebResponse(responseUri, responseUri, headers ?? new WebHeaderCollection(),
+ HttpStatusCode.OK, contentType, contentEncoding, stream));
+ }
+
+ internal static void RegisterMockXrdsResponses(IDictionary<string, string> requestUriAndResponseBody) {
+ foreach (var pair in requestUriAndResponseBody) {
+ RegisterMockResponse(new Uri(pair.Key), "text/xml; saml=false; https=false; charset=UTF-8", pair.Value);
+ }
+ }
+
+ internal static void RegisterMockXrdsResponse(ServiceEndpoint endpoint) {
+ if (endpoint == null) throw new ArgumentNullException("endpoint");
+
+ string identityUri;
+ if (endpoint.ClaimedIdentifier == endpoint.Protocol.ClaimedIdentifierForOPIdentifier) {
+ identityUri = endpoint.UserSuppliedIdentifier;
+ } else {
+ identityUri = endpoint.UserSuppliedIdentifier ?? endpoint.ClaimedIdentifier;
+ }
+ RegisterMockXrdsResponse(new Uri(identityUri), new ServiceEndpoint[] { endpoint });
+ }
+
+ internal static void RegisterMockXrdsResponse(Uri respondingUri, IEnumerable<ServiceEndpoint> endpoints) {
+ if (endpoints == null) throw new ArgumentNullException("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)'>
+ <XRD>");
+ foreach (var endpoint in endpoints) {
+ string template = @"
+ <Service priority='10'>
+ <Type>{0}</Type>
+ <URI>{1}</URI>
+ <LocalID>{2}</LocalID>
+ <openid:Delegate xmlns:openid='http://openid.net/xmlns/1.0'>{2}</openid:Delegate>
+ </Service>";
+ string serviceTypeUri;
+ if (endpoint.ClaimedIdentifier == endpoint.Protocol.ClaimedIdentifierForOPIdentifier) {
+ serviceTypeUri = endpoint.Protocol.OPIdentifierServiceTypeURI;
+ } else {
+ serviceTypeUri = endpoint.Protocol.ClaimedIdentifierServiceTypeURI;
+ }
+ string xrd = string.Format(CultureInfo.InvariantCulture, template,
+ HttpUtility.HtmlEncode(serviceTypeUri),
+ HttpUtility.HtmlEncode(endpoint.ProviderEndpoint.AbsoluteUri),
+ HttpUtility.HtmlEncode(endpoint.ProviderLocalIdentifier)
+ );
+ xrds.Append(xrd);
+ }
+ xrds.Append(@"
+ </XRD>
+</xrds:XRDS>");
+
+ RegisterMockResponse(respondingUri, ContentTypes.Xrds, xrds.ToString());
+ }
+ internal static void RegisterMockXrdsResponse(UriIdentifier directedIdentityAssignedIdentifier, ServiceEndpoint providerEndpoint) {
+ ServiceEndpoint identityEndpoint = ServiceEndpoint.CreateForClaimedIdentifier(
+ directedIdentityAssignedIdentifier,
+ directedIdentityAssignedIdentifier,
+ providerEndpoint.ProviderEndpoint,
+ new string[] { providerEndpoint.Protocol.ClaimedIdentifierServiceTypeURI },
+ 10,
+ 10
+ );
+ RegisterMockXrdsResponse(identityEndpoint);
+ }
+ internal static Identifier RegisterMockXrdsResponse(string embeddedResourcePath) {
+ UriIdentifier id = TestSupport.GetFullUrl(embeddedResourcePath);
+ RegisterMockResponse(id, "application/xrds+xml", TestSupport.LoadEmbeddedFile(embeddedResourcePath));
+ return id;
+ }
+ internal static void RegisterMockRPDiscovery() {
+ Uri rpRealmUri = TestSupport.Realm.UriWithWildcardChangedToWww;
+
+ string template = @"<xrds:XRDS xmlns:xrds='xri://$xrds' xmlns:openid='http://openid.net/xmlns/1.0' xmlns='xri://$xrd*($v*2.0)'>
+ <XRD>
+ <Service priority='10'>
+ <Type>{0}</Type>
+ <URI>{1}</URI>
+ </Service>
+ </XRD>
+</xrds:XRDS>";
+ string xrds = string.Format(CultureInfo.InvariantCulture, template,
+ HttpUtility.HtmlEncode(Protocol.v20.RPReturnToTypeURI),
+ HttpUtility.HtmlEncode(rpRealmUri.AbsoluteUri)
+ );
+
+ RegisterMockResponse(rpRealmUri, ContentTypes.Xrds, xrds);
+ }
+
+ internal static void DeleteResponse(Uri requestUri) {
+ registeredMockResponses.Remove(requestUri);
+ }
+
+ internal static void RegisterMockRedirect(Uri origin, Uri redirectLocation) {
+ var redirectionHeaders = new WebHeaderCollection {
+ { HttpResponseHeader.Location, redirectLocation.AbsoluteUri },
+ };
+ UntrustedWebResponse response = new UntrustedWebResponse(origin, origin,
+ redirectionHeaders, HttpStatusCode.Redirect, null, null, new MemoryStream());
+ RegisterMockResponse(response);
+ }
+ }
+}
diff --git a/src/DotNetOpenId.Test/Mocks/MockIdentifier.cs b/src/DotNetOpenId.Test/Mocks/MockIdentifier.cs new file mode 100644 index 0000000..7ab2d74 --- /dev/null +++ b/src/DotNetOpenId.Test/Mocks/MockIdentifier.cs @@ -0,0 +1,58 @@ +using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using DotNetOpenId.RelyingParty;
+
+namespace DotNetOpenId.Test.Mocks {
+ /// <summary>
+ /// Performs similar to an ordinary <see cref="Identifier"/>, but when called upon
+ /// to perform discovery, it returns a preset list of sevice endpoints to avoid
+ /// having a dependency on a hosted web site to actually perform discovery on.
+ /// </summary>
+ class MockIdentifier : Identifier {
+ IEnumerable<ServiceEndpoint> endpoints;
+ Identifier wrappedIdentifier;
+
+ public MockIdentifier(Identifier wrappedIdentifier, IEnumerable<ServiceEndpoint> endpoints)
+ : base(false) {
+ if (wrappedIdentifier == null) throw new ArgumentNullException("wrappedIdentifier");
+ if (endpoints == null) throw new ArgumentNullException("endpoints");
+ this.wrappedIdentifier = wrappedIdentifier;
+ this.endpoints = endpoints;
+
+ // Register a mock HTTP response to enable discovery of this identifier within the RP
+ // without having to host an ASP.NET site within the test.
+ MockHttpRequest.RegisterMockXrdsResponse(new Uri(wrappedIdentifier.ToString()), endpoints);
+ }
+
+ internal override IEnumerable<ServiceEndpoint> Discover() {
+ return endpoints;
+ }
+
+ internal override Identifier TrimFragment() {
+ return this;
+ }
+
+ internal override bool TryRequireSsl(out Identifier secureIdentifier) {
+ // We take special care to make our wrapped identifier secure, but still
+ // return a mocked (secure) identifier.
+ Identifier secureWrappedIdentifier;
+ bool result = wrappedIdentifier.TryRequireSsl(out secureWrappedIdentifier);
+ secureIdentifier = new MockIdentifier(secureWrappedIdentifier, endpoints);
+ return result;
+ }
+
+ public override string ToString() {
+ return wrappedIdentifier.ToString();
+ }
+
+ public override bool Equals(object obj) {
+ return wrappedIdentifier.Equals(obj);
+ }
+
+ public override int GetHashCode() {
+ return wrappedIdentifier.GetHashCode();
+ }
+ }
+}
diff --git a/src/DotNetOpenId.Test/Provider/IAuthenticationRequestTest.cs b/src/DotNetOpenId.Test/Provider/IAuthenticationRequestTest.cs index 3ea7d9c..8b23229 100644 --- a/src/DotNetOpenId.Test/Provider/IAuthenticationRequestTest.cs +++ b/src/DotNetOpenId.Test/Provider/IAuthenticationRequestTest.cs @@ -15,22 +15,25 @@ namespace DotNetOpenId.Test.Provider { UntrustedWebRequest.WhitelistHosts.Add("localhost");
}
- [Test, ExpectedException(typeof(WebException), UserMessage = "OP should throw WebException when return URL is unverifiable.")]
- public void UnverifiableReturnUrl() {
- Uri returnTo;
- Realm realm;
- getUnverifiableRP(out returnTo, out realm);
- var consumer = new OpenIdRelyingParty(new ApplicationMemoryStore(), null, null);
- var request = consumer.CreateRequest(TestSupport.GetIdentityUrl(TestSupport.Scenarios.AutoApproval, ProtocolVersion.V20), realm, returnTo);
- WebRequest.Create(request.RedirectingResponse.ExtractUrl()).GetResponse(); // the OP should return 500, causing exception here.
+ [TearDown]
+ public void TearDown() {
+ Mocks.MockHttpRequest.Reset();
}
- static void getUnverifiableRP(out Uri returnTo, out Realm realm) {
- var disableDiscovery = new Dictionary<string, string> {
- {"AllowRPDiscovery", "false"},
- };
- returnTo = TestSupport.GetFullUrl(TestSupport.ConsumerPage, disableDiscovery);
- realm = new Realm(returnTo);
+ [Test]
+ public void UnverifiableReturnUrl() {
+ var request = TestSupport.CreateRelyingPartyRequest(true, TestSupport.Scenarios.AutoApproval, ProtocolVersion.V20);
+
+ // Clear out the RP discovery information registered by TestSupport
+ Mocks.MockHttpRequest.DeleteResponse(TestSupport.Realm.UriWithWildcardChangedToWww);
+
+ bool reachedOP = false;
+ var response = TestSupport.CreateRelyingPartyResponseThroughProvider(request, req => {
+ Assert.IsFalse(req.IsReturnUrlDiscoverable);
+ reachedOP = true;
+ req.IsAuthenticated = false;
+ });
+ Assert.IsTrue(reachedOP);
}
}
}
diff --git a/src/DotNetOpenId.Test/Provider/OpenIdProviderTest.cs b/src/DotNetOpenId.Test/Provider/OpenIdProviderTest.cs index 9f5c2e9..d0dd4ee 100644 --- a/src/DotNetOpenId.Test/Provider/OpenIdProviderTest.cs +++ b/src/DotNetOpenId.Test/Provider/OpenIdProviderTest.cs @@ -1,13 +1,9 @@ using System;
-using System.Collections.Generic;
-using System.Text;
-using NUnit.Framework;
-using System.IO;
-using System.Diagnostics;
-using DotNetOpenId.Test.Hosting;
-using System.Text.RegularExpressions;
-using DotNetOpenId.Provider;
using System.Collections.Specialized;
+using DotNetOpenId.Provider;
+using DotNetOpenId.RelyingParty;
+using DotNetOpenId.Test.Mocks;
+using NUnit.Framework;
using ProviderMemoryStore = DotNetOpenId.AssociationMemoryStore<DotNetOpenId.AssociationRelyingPartyType>;
namespace DotNetOpenId.Test.Provider {
@@ -16,6 +12,17 @@ namespace DotNetOpenId.Test.Provider { readonly Uri providerEndpoint = new Uri("http://someendpoint");
readonly Uri emptyRequestUrl = new Uri("http://someendpoint/request");
+ [SetUp]
+ public void Setup() {
+ if (!UntrustedWebRequest.WhitelistHosts.Contains("localhost"))
+ UntrustedWebRequest.WhitelistHosts.Add("localhost");
+ }
+
+ [TearDown]
+ public void TearDown() {
+ MockHttpRequest.Reset();
+ }
+
/// <summary>
/// Verifies that without an ASP.NET context, the default constructor fails.
/// </summary>
@@ -26,31 +33,31 @@ namespace DotNetOpenId.Test.Provider { [Test]
public void CtorNonDefault() {
- OpenIdProvider op = new OpenIdProvider(new ProviderMemoryStore(),
+ OpenIdProvider op = new OpenIdProvider(new ProviderMemoryStore(),
providerEndpoint, emptyRequestUrl, new NameValueCollection());
}
[Test, ExpectedException(typeof(ArgumentNullException))]
public void CtorNullStore() {
- OpenIdProvider op = new OpenIdProvider(null, providerEndpoint,
+ OpenIdProvider op = new OpenIdProvider(null, providerEndpoint,
emptyRequestUrl, new NameValueCollection());
}
[Test, ExpectedException(typeof(ArgumentNullException))]
public void CtorNullEndpoint() {
- OpenIdProvider op = new OpenIdProvider(new ProviderMemoryStore(),
+ OpenIdProvider op = new OpenIdProvider(new ProviderMemoryStore(),
null, emptyRequestUrl, new NameValueCollection());
}
[Test, ExpectedException(typeof(ArgumentNullException))]
public void CtorNullRequestUrl() {
- OpenIdProvider op = new OpenIdProvider(new ProviderMemoryStore(),
+ OpenIdProvider op = new OpenIdProvider(new ProviderMemoryStore(),
providerEndpoint, null, new NameValueCollection());
}
[Test, ExpectedException(typeof(ArgumentNullException))]
public void CtorNullQuery() {
- OpenIdProvider op = new OpenIdProvider(new ProviderMemoryStore(),
+ OpenIdProvider op = new OpenIdProvider(new ProviderMemoryStore(),
providerEndpoint, emptyRequestUrl, null);
}
@@ -60,5 +67,60 @@ namespace DotNetOpenId.Test.Provider { providerEndpoint, emptyRequestUrl, new NameValueCollection());
Assert.IsNull(op.Request);
}
+
+ [Test]
+ public void BasicUnsolicitedAssertion() {
+ Mocks.MockHttpRequest.RegisterMockRPDiscovery();
+ TestSupport.Scenarios scenario = TestSupport.Scenarios.AutoApproval;
+ Identifier claimedId = TestSupport.GetMockIdentifier(scenario, ProtocolVersion.V20);
+ Identifier localId = TestSupport.GetDelegateUrl(scenario);
+
+ OpenIdProvider op = TestSupport.CreateProvider(null);
+ IResponse assertion = op.PrepareUnsolicitedAssertion(TestSupport.Realm, claimedId, localId);
+ var rpResponse = TestSupport.CreateRelyingPartyResponse(TestSupport.RelyingPartyStore, assertion);
+ Assert.AreEqual(AuthenticationStatus.Authenticated, rpResponse.Status);
+ Assert.AreEqual(claimedId, rpResponse.ClaimedIdentifier);
+ }
+
+ [Test]
+ public void UnsolicitedAssertionWithBadCapitalization() {
+ Mocks.MockHttpRequest.RegisterMockRPDiscovery();
+ TestSupport.Scenarios scenario = TestSupport.Scenarios.AutoApproval;
+ Identifier claimedId = TestSupport.GetMockIdentifier(scenario, ProtocolVersion.V20);
+ claimedId = claimedId.ToString().ToUpper(); // make all caps, which is not right
+ Identifier localId = TestSupport.GetDelegateUrl(scenario);
+
+ OpenIdProvider op = TestSupport.CreateProvider(null);
+ IResponse assertion = op.PrepareUnsolicitedAssertion(TestSupport.Realm, claimedId, localId);
+ var rpResponse = TestSupport.CreateRelyingPartyResponse(TestSupport.RelyingPartyStore, assertion);
+ Assert.AreEqual(AuthenticationStatus.Failed, rpResponse.Status);
+ }
+
+ /// <summary>
+ /// Verifies that OP will properly report RP versions in requests.
+ /// </summary>
+ [Test]
+ public void RelyingPartyVersion() {
+ Protocol simulatedVersion = Protocol.v11;
+ UriIdentifier id = TestSupport.GetIdentityUrl(TestSupport.Scenarios.AutoApproval, simulatedVersion.ProtocolVersion);
+
+ // make up some OpenID 1.x looking message...
+ NameValueCollection rp10Request = new NameValueCollection();
+ rp10Request[simulatedVersion.openid.mode] = simulatedVersion.Args.Mode.checkid_immediate;
+ rp10Request[simulatedVersion.openid.identity] = id;
+ rp10Request[simulatedVersion.openid.return_to] = TestSupport.ReturnTo.AbsoluteUri;
+ rp10Request[simulatedVersion.openid.Realm] = TestSupport.Realm;
+
+ OpenIdProvider op = TestSupport.CreateProvider(rp10Request);
+ Assert.AreEqual(simulatedVersion.ProtocolVersion,
+ ((DotNetOpenId.Provider.IAuthenticationRequest)op.Request).RelyingPartyVersion);
+
+ // Verify V2.0 reporting.
+ var rp20Request = TestSupport.CreateRelyingPartyRequest(true, TestSupport.Scenarios.AutoApproval, ProtocolVersion.V20);
+ TestSupport.CreateRelyingPartyResponseThroughProvider(rp20Request, opReq => {
+ Assert.AreEqual(ProtocolVersion.V20, opReq.RelyingPartyVersion);
+ opReq.IsAuthenticated = true;
+ });
+ }
}
}
diff --git a/src/DotNetOpenId.Test/RelyingParty/AuthenticationRequestTests.cs b/src/DotNetOpenId.Test/RelyingParty/AuthenticationRequestTests.cs index 399e4e7..9041d26 100644 --- a/src/DotNetOpenId.Test/RelyingParty/AuthenticationRequestTests.cs +++ b/src/DotNetOpenId.Test/RelyingParty/AuthenticationRequestTests.cs @@ -1,30 +1,29 @@ using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using NUnit.Framework;
using DotNetOpenId.RelyingParty;
-using DotNetOpenId.Extensions.SimpleRegistration;
-using DotNetOpenId.Extensions.AttributeExchange;
+using DotNetOpenId.Test.Mocks;
+using NUnit.Framework;
namespace DotNetOpenId.Test.RelyingParty {
[TestFixture]
public class AuthenticationRequestTests {
- IRelyingPartyApplicationStore store;
Realm realm = new Realm(TestSupport.GetFullUrl(TestSupport.ConsumerPage).AbsoluteUri);
Uri returnTo = TestSupport.GetFullUrl(TestSupport.ConsumerPage);
[SetUp]
public void SetUp() {
- store = new ApplicationMemoryStore();
if (!UntrustedWebRequest.WhitelistHosts.Contains("localhost"))
UntrustedWebRequest.WhitelistHosts.Add("localhost");
}
+ [TearDown]
+ public void TearDown() {
+ MockHttpRequest.Reset();
+ }
+
[Test]
public void Provider() {
- OpenIdRelyingParty rp = new OpenIdRelyingParty(store, null, null);
- Identifier id = TestSupport.GetFullUrl("xrdsdiscovery/xrds20.aspx");
+ OpenIdRelyingParty rp = new OpenIdRelyingParty(null, null, null);
+ Identifier id = TestSupport.GetMockIdentifier(TestSupport.Scenarios.AutoApproval, ProtocolVersion.V20);
IAuthenticationRequest request = rp.CreateRequest(id, realm, returnTo);
Assert.IsNotNull(request.Provider);
}
diff --git a/src/DotNetOpenId.Test/RelyingParty/AuthenticationResponseTests.cs b/src/DotNetOpenId.Test/RelyingParty/AuthenticationResponseTests.cs index 7dcb792..0c775e5 100644 --- a/src/DotNetOpenId.Test/RelyingParty/AuthenticationResponseTests.cs +++ b/src/DotNetOpenId.Test/RelyingParty/AuthenticationResponseTests.cs @@ -1,14 +1,10 @@ using System;
using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using NUnit.Framework;
-using DotNetOpenId.RelyingParty;
-using System.Net;
-using System.Diagnostics;
-using System.IO;
-using System.Web;
using System.Collections.Specialized;
+using System.Web;
+using DotNetOpenId.RelyingParty;
+using DotNetOpenId.Test.Mocks;
+using NUnit.Framework;
namespace DotNetOpenId.Test.RelyingParty {
[TestFixture]
@@ -16,7 +12,6 @@ namespace DotNetOpenId.Test.RelyingParty { Realm realm = new Realm(TestSupport.GetFullUrl(TestSupport.ConsumerPage).AbsoluteUri);
Uri returnTo;
const string returnToRemovableParameter = "a";
- ApplicationMemoryStore store;
public AuthenticationResponseTests() {
UriBuilder builder = new UriBuilder(TestSupport.GetFullUrl(TestSupport.ConsumerPage));
@@ -28,38 +23,23 @@ namespace DotNetOpenId.Test.RelyingParty { [SetUp]
public void SetUp() {
- store = new ApplicationMemoryStore();
if (!UntrustedWebRequest.WhitelistHosts.Contains("localhost"))
UntrustedWebRequest.WhitelistHosts.Add("localhost");
}
+ [TearDown]
+ public void TearDown() {
+ MockHttpRequest.Reset();
+ }
+
Uri getPositiveAssertion(ProtocolVersion version) {
- try {
- OpenIdRelyingParty rp = new OpenIdRelyingParty(store, null, null);
- Identifier id = TestSupport.GetIdentityUrl(TestSupport.Scenarios.AutoApproval, version);
- var request = rp.CreateRequest(id, realm, returnTo);
- HttpWebRequest providerRequest = (HttpWebRequest)WebRequest.Create(request.RedirectingResponse.ExtractUrl());
- providerRequest.AllowAutoRedirect = false;
- Uri redirectUrl;
- try {
- using (HttpWebResponse providerResponse = (HttpWebResponse)providerRequest.GetResponse()) {
- Assert.AreEqual(HttpStatusCode.Redirect, providerResponse.StatusCode);
- redirectUrl = new Uri(providerResponse.Headers[HttpResponseHeader.Location]);
- }
- } catch (WebException ex) {
- Trace.WriteLine(ex);
- if (ex.Response != null) {
- using (StreamReader sr = new StreamReader(ex.Response.GetResponseStream())) {
- Trace.WriteLine(sr.ReadToEnd());
- }
- }
- throw;
- }
- return redirectUrl;
- } catch (OpenIdException ex) {
- Assert.Ignore("Test failed to verify good or bad behavior on account of failing to set itself up: {0}", ex);
- return null; // Assert.Ignore will throw an exception anyway
- }
+ OpenIdRelyingParty rp = TestSupport.CreateRelyingParty(null);
+ Identifier id = TestSupport.GetMockIdentifier(TestSupport.Scenarios.AutoApproval, version);
+ var request = rp.CreateRequest(id, realm, returnTo);
+ var provider = TestSupport.CreateProviderForRequest(request);
+ var opRequest = provider.Request as DotNetOpenId.Provider.IAuthenticationRequest;
+ opRequest.IsAuthenticated = true;
+ return opRequest.Response.ExtractUrl();
}
void removeQueryParameter(ref Uri uri, string parameterToRemove) {
UriBuilder builder = new UriBuilder(uri);
@@ -92,7 +72,7 @@ namespace DotNetOpenId.Test.RelyingParty { void resign(ref Uri uri) {
UriBuilder builder = new UriBuilder(uri);
NameValueCollection nvc = HttpUtility.ParseQueryString(builder.Query);
- TestSupport.Resign(nvc, store);
+ TestSupport.Resign(nvc, TestSupport.RelyingPartyStore);
builder.Query = UriUtil.CreateQueryString(nvc);
uri = builder.Uri;
}
@@ -106,7 +86,7 @@ namespace DotNetOpenId.Test.RelyingParty { // which should cause a failure because the return_to argument
// says that parameter is supposed to be there.
removeQueryParameter(ref assertion, returnToRemovableParameter);
- var response = new OpenIdRelyingParty(store, assertion, HttpUtility.ParseQueryString(assertion.Query)).Response;
+ var response = TestSupport.CreateRelyingParty(TestSupport.RelyingPartyStore, assertion, HttpUtility.ParseQueryString(assertion.Query)).Response;
Assert.AreEqual(AuthenticationStatus.Failed, response.Status);
Assert.IsNotNull(response.Exception);
}
@@ -140,10 +120,27 @@ namespace DotNetOpenId.Test.RelyingParty { resign(ref assertion); // resign changed URL to simulate a contrived OP for breaking into RPs.
// (triggers exception) "... you're in trouble up to your ears."
- var response = new OpenIdRelyingParty(store, assertion, HttpUtility.ParseQueryString(assertion.Query)).Response;
+ var response = TestSupport.CreateRelyingParty(TestSupport.RelyingPartyStore, assertion, HttpUtility.ParseQueryString(assertion.Query)).Response;
Assert.AreEqual(AuthenticationStatus.Failed, response.Status);
Assert.IsNotNull(response.Exception);
}
+ [Test]
+ public void ClaimedIdentifierChangesAtProviderUnexpectedly() {
+ OpenIdRelyingParty rp = TestSupport.CreateRelyingParty(null);
+ Identifier id = TestSupport.GetMockIdentifier(TestSupport.Scenarios.ApproveOnSetup, ProtocolVersion.V20);
+ Identifier newClaimedId = TestSupport.GetMockIdentifier(TestSupport.Scenarios.AutoApproval, ProtocolVersion.V20);
+ Identifier newLocalId = TestSupport.GetDelegateUrl(TestSupport.Scenarios.AutoApproval);
+ MockHttpRequest.RegisterMockXrdsResponse(new Uri(newClaimedId), newClaimedId.Discover());
+ var request = rp.CreateRequest(id, realm, returnTo);
+ var provider = TestSupport.CreateProviderForRequest(request);
+ var opRequest = provider.Request as DotNetOpenId.Provider.IAuthenticationRequest;
+ opRequest.IsAuthenticated = true;
+ opRequest.ClaimedIdentifier = newClaimedId;
+ opRequest.LocalIdentifier = newLocalId;
+ var assertion = opRequest.Response.ExtractUrl();
+ var response = TestSupport.CreateRelyingParty(TestSupport.RelyingPartyStore, assertion, HttpUtility.ParseQueryString(assertion.Query)).Response;
+ Assert.AreEqual(AuthenticationStatus.Authenticated, response.Status);
+ }
}
}
diff --git a/src/DotNetOpenId.Test/RelyingParty/IProviderEndpointTests.cs b/src/DotNetOpenId.Test/RelyingParty/IProviderEndpointTests.cs index 04f5873..8830691 100644 --- a/src/DotNetOpenId.Test/RelyingParty/IProviderEndpointTests.cs +++ b/src/DotNetOpenId.Test/RelyingParty/IProviderEndpointTests.cs @@ -1,11 +1,9 @@ using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using NUnit.Framework;
-using DotNetOpenId.RelyingParty;
-using DotNetOpenId.Extensions.SimpleRegistration;
using DotNetOpenId.Extensions.AttributeExchange;
+using DotNetOpenId.Extensions.SimpleRegistration;
+using DotNetOpenId.RelyingParty;
+using NUnit.Framework;
+using DotNetOpenId.Test.Mocks;
namespace DotNetOpenId.Test.RelyingParty {
[TestFixture]
@@ -21,11 +19,16 @@ namespace DotNetOpenId.Test.RelyingParty { UntrustedWebRequest.WhitelistHosts.Add("localhost");
}
+ [TearDown]
+ public void TearDown() {
+ Mocks.MockHttpRequest.Reset();
+ }
+
[Test]
public void IsExtensionSupportedTest() {
- OpenIdRelyingParty rp = new OpenIdRelyingParty(store, null, null);
- Identifier id = TestSupport.GetFullUrl("xrdsdiscovery/xrds20.aspx");
- IAuthenticationRequest request = rp.CreateRequest(id, realm, returnTo);
+ OpenIdRelyingParty rp = TestSupport.CreateRelyingParty(null);
+ Identifier id = MockHttpRequest.RegisterMockXrdsResponse("/Discovery/xrdsdiscovery/xrds20.xml");
+ IAuthenticationRequest request = rp.CreateRequest(id, TestSupport.Realm, TestSupport.ReturnTo);
IProviderEndpoint provider = request.Provider;
Assert.IsTrue(provider.IsExtensionSupported<ClaimsRequest>());
Assert.IsTrue(provider.IsExtensionSupported(typeof(ClaimsRequest)));
@@ -34,7 +37,7 @@ namespace DotNetOpenId.Test.RelyingParty { // Test the AdditionalTypeUris list by pulling from an XRDS page with one of the
// TypeURIs that only shows up in that list.
- id = TestSupport.GetFullUrl("xrdsdiscovery/xrds10.aspx");
+ id = MockHttpRequest.RegisterMockXrdsResponse("/Discovery/xrdsdiscovery/xrds10.xml");
request = rp.CreateRequest(id, realm, returnTo);
Assert.IsTrue(provider.IsExtensionSupported<ClaimsRequest>());
Assert.IsTrue(provider.IsExtensionSupported(typeof(ClaimsRequest)));
@@ -42,9 +45,9 @@ namespace DotNetOpenId.Test.RelyingParty { [Test]
public void UriTest() {
- OpenIdRelyingParty rp = new OpenIdRelyingParty(store, null, null);
- Identifier id = TestSupport.GetFullUrl("xrdsdiscovery/xrds20.aspx");
- IAuthenticationRequest request = rp.CreateRequest(id, realm, returnTo);
+ OpenIdRelyingParty rp = TestSupport.CreateRelyingParty(null);
+ Identifier id = MockHttpRequest.RegisterMockXrdsResponse("/Discovery/xrdsdiscovery/xrds20.xml");
+ IAuthenticationRequest request = rp.CreateRequest(id, TestSupport.Realm, TestSupport.ReturnTo);
IProviderEndpoint provider = request.Provider;
Assert.AreEqual(new Uri("http://a/b"), provider.Uri);
}
diff --git a/src/DotNetOpenId.Test/RelyingParty/OpenIdRelyingPartyTest.cs b/src/DotNetOpenId.Test/RelyingParty/OpenIdRelyingPartyTest.cs index 2503207..fb36e71 100644 --- a/src/DotNetOpenId.Test/RelyingParty/OpenIdRelyingPartyTest.cs +++ b/src/DotNetOpenId.Test/RelyingParty/OpenIdRelyingPartyTest.cs @@ -3,13 +3,13 @@ using System.Collections.Generic; using System.Collections.Specialized;
using System.Web;
using DotNetOpenId.RelyingParty;
+using DotNetOpenId.Test.Mocks;
using NUnit.Framework;
-using ProviderMemoryStore = DotNetOpenId.AssociationMemoryStore<DotNetOpenId.AssociationRelyingPartyType>;
+using OpenIdProvider = DotNetOpenId.Provider.OpenIdProvider;
namespace DotNetOpenId.Test.RelyingParty {
[TestFixture]
public class OpenIdRelyingPartyTest {
- IRelyingPartyApplicationStore store;
UriIdentifier simpleOpenId = new UriIdentifier("http://nonexistant.openid.com");
readonly Realm realm = new Realm(TestSupport.GetFullUrl(TestSupport.ConsumerPage).AbsoluteUri);
readonly Uri returnTo = TestSupport.GetFullUrl(TestSupport.ConsumerPage);
@@ -17,14 +17,13 @@ namespace DotNetOpenId.Test.RelyingParty { [SetUp]
public void Setup() {
- store = new ApplicationMemoryStore();
if (!UntrustedWebRequest.WhitelistHosts.Contains("localhost"))
UntrustedWebRequest.WhitelistHosts.Add("localhost");
}
[TearDown]
public void TearDown() {
- UntrustedWebRequest.MockRequests = null;
+ MockHttpRequest.Reset();
}
[Test]
@@ -35,7 +34,7 @@ namespace DotNetOpenId.Test.RelyingParty { [Test]
public void CtorWithNullRequestUri() {
- new OpenIdRelyingParty(store, null, null);
+ new OpenIdRelyingParty(new ApplicationMemoryStore(), null, null);
}
[Test]
@@ -46,59 +45,64 @@ namespace DotNetOpenId.Test.RelyingParty { [Test]
[ExpectedException(typeof(InvalidOperationException))]
public void CreateRequestWithoutContext1() {
- var consumer = new OpenIdRelyingParty(store, simpleNonOpenIdRequest, new NameValueCollection());
+ var consumer = new OpenIdRelyingParty(new ApplicationMemoryStore(), simpleNonOpenIdRequest, new NameValueCollection());
consumer.CreateRequest(simpleOpenId);
}
[Test]
[ExpectedException(typeof(InvalidOperationException))]
public void CreateRequestWithoutContext2() {
- var consumer = new OpenIdRelyingParty(store, simpleNonOpenIdRequest, new NameValueCollection());
+ var consumer = new OpenIdRelyingParty(new ApplicationMemoryStore(), simpleNonOpenIdRequest, new NameValueCollection());
consumer.CreateRequest(simpleOpenId, realm);
}
[Test]
public void CreateRequestStripsFragment() {
- var consumer = new OpenIdRelyingParty(store, simpleNonOpenIdRequest, new NameValueCollection());
+ var consumer = TestSupport.CreateRelyingParty(null);
UriBuilder userSuppliedIdentifier = new UriBuilder((Uri)TestSupport.GetIdentityUrl(TestSupport.Scenarios.AutoApproval, ProtocolVersion.V20));
userSuppliedIdentifier.Fragment = "c";
- IAuthenticationRequest request = consumer.CreateRequest(userSuppliedIdentifier.Uri, realm, returnTo);
+ Identifier mockIdentifer = new MockIdentifier(userSuppliedIdentifier.Uri,
+ TestSupport.GetMockIdentifier(TestSupport.Scenarios.AutoApproval, ProtocolVersion.V20).Discover());
+ Assert.IsTrue(mockIdentifer.ToString().EndsWith("#c"), "Test broken");
+ IAuthenticationRequest request = consumer.CreateRequest(mockIdentifer, TestSupport.Realm, TestSupport.ReturnTo);
Assert.AreEqual(0, new Uri(request.ClaimedIdentifier).Fragment.Length);
}
[Test]
public void AssociationCreationWithStore() {
- var providerStore = new ProviderMemoryStore();
+ TestSupport.ResetStores(); // get rid of existing associations so a new one is created
- OpenIdRelyingParty rp = new OpenIdRelyingParty(new ApplicationMemoryStore(), null, null);
- var idUrl = TestSupport.GetIdentityUrl(TestSupport.Scenarios.AutoApproval, ProtocolVersion.V20);
+ OpenIdRelyingParty rp = TestSupport.CreateRelyingParty(null);
+ var directMessageSniffer = new DirectMessageSniffWrapper(rp.DirectMessageChannel);
+ rp.DirectMessageChannel = directMessageSniffer;
+ var idUrl = TestSupport.GetMockIdentifier(TestSupport.Scenarios.AutoApproval, ProtocolVersion.V20);
DotNetOpenId.RelyingParty.IAuthenticationRequest req;
bool associationMade = false;
- TestSupport.Interceptor.SigningMessage = m => {
- if (m.EncodedFields.ContainsKey("assoc_handle") && m.EncodedFields.ContainsKey("session_type"))
+ directMessageSniffer.Receiving += (provider, fields) => {
+ if (fields.ContainsKey("assoc_handle") && fields.ContainsKey("session_type"))
associationMade = true;
};
req = rp.CreateRequest(idUrl, realm, returnTo);
- TestSupport.Interceptor.SigningMessage = null;
Assert.IsTrue(associationMade);
}
[Test]
public void NoAssociationRequestWithoutStore() {
- var providerStore = new ProviderMemoryStore();
+ TestSupport.ResetStores(); // get rid of existing associations so a new one is created
- OpenIdRelyingParty rp = new OpenIdRelyingParty(null, null, null);
- var idUrl = TestSupport.GetIdentityUrl(TestSupport.Scenarios.AutoApproval, ProtocolVersion.V20);
+ OpenIdRelyingParty rp = TestSupport.CreateRelyingParty(null, null);
+ var directMessageSniffer = new DirectMessageSniffWrapper(rp.DirectMessageChannel);
+ rp.DirectMessageChannel = directMessageSniffer;
+ var idUrl = TestSupport.GetMockIdentifier(TestSupport.Scenarios.AutoApproval, ProtocolVersion.V20);
DotNetOpenId.RelyingParty.IAuthenticationRequest req;
bool associationMade = false;
- TestSupport.Interceptor.SigningMessage = m => {
- if (m.EncodedFields.ContainsKey("assoc_handle") && m.EncodedFields.ContainsKey("session_type"))
+ directMessageSniffer.Receiving += (provider, fields) => {
+ if (fields.ContainsKey("assoc_handle") && fields.ContainsKey("session_type"))
associationMade = true;
};
req = rp.CreateRequest(idUrl, realm, returnTo);
- TestSupport.Interceptor.SigningMessage = null;
Assert.IsFalse(associationMade);
}
@@ -128,9 +132,7 @@ namespace DotNetOpenId.Test.RelyingParty { }
private static void testExplicitPortOnRealmAndReturnTo(Uri returnTo, Realm realm) {
- var identityUrl = TestSupport.GetIdentityUrl(TestSupport.Scenarios.AutoApproval, ProtocolVersion.V20);
- var consumer = new OpenIdRelyingParty(null, null, null);
- var request = consumer.CreateRequest(identityUrl, realm, returnTo);
+ var request = TestSupport.CreateRelyingPartyRequest(true, TestSupport.Scenarios.AutoApproval, ProtocolVersion.V20);
Protocol protocol = Protocol.Lookup(request.Provider.Version);
var nvc = HttpUtility.ParseQueryString(request.RedirectingResponse.ExtractUrl().Query);
string realmString = nvc[protocol.openid.Realm];
@@ -147,10 +149,7 @@ namespace DotNetOpenId.Test.RelyingParty { [Test]
public void ReturnToUrlEncodingTest() {
- Uri origin = TestSupport.GetFullUrl(TestSupport.ConsumerPage);
- var identityUrl = TestSupport.GetIdentityUrl(TestSupport.Scenarios.AutoApproval, ProtocolVersion.V20);
- var consumer = new OpenIdRelyingParty(null, null, null);
- var request = consumer.CreateRequest(identityUrl, origin, origin);
+ var request = TestSupport.CreateRelyingPartyRequest(true, TestSupport.Scenarios.AutoApproval, ProtocolVersion.V20);
Protocol protocol = Protocol.Lookup(request.Provider.Version);
request.AddCallbackArguments("a+b", "c+d");
var requestArgs = HttpUtility.ParseQueryString(request.RedirectingResponse.ExtractUrl().Query);
@@ -205,6 +204,7 @@ namespace DotNetOpenId.Test.RelyingParty { string xrds = @"<?xml version='1.0' encoding='UTF-8'?>
<XRD xmlns='xri://$xrd*($v*2.0)'>
<Query>=MultipleEndpoint</Query>
+ <Status cid='verified' code='100' />
<ProviderID>=!91F2.8153.F600.AE24</ProviderID>
<CanonicalID>=!91F2.8153.F600.AE24</CanonicalID>
<Service>
@@ -236,9 +236,8 @@ namespace DotNetOpenId.Test.RelyingParty { </Service>
<ServedBy>OpenXRI</ServedBy>
</XRD>";
- UntrustedWebRequest.MockRequests = TestSupport.GenerateMockXrdsResponses(new Dictionary<string, string> {
+ MockHttpRequest.RegisterMockXrdsResponses(new Dictionary<string, string> {
{"https://xri.net/=MultipleEndpoint?_xrd_r=application/xrd%2Bxml;sep=false", xrds},
- {"https://xri.net/=!91F2.8153.F600.AE24?_xrd_r=application/xrd%2Bxml;sep=false", xrds},
});
OpenIdRelyingParty rp = new OpenIdRelyingParty(null, null, null);
Realm realm = new Realm("http://somerealm");
@@ -248,12 +247,181 @@ namespace DotNetOpenId.Test.RelyingParty { rp.EndpointOrder = (se1, se2) => -se1.ServicePriority.Value.CompareTo(se2.ServicePriority.Value);
request = rp.CreateRequest("=MultipleEndpoint", realm, return_to);
Assert.AreEqual("https://authn.freexri.com/auth10/", request.Provider.Uri.AbsoluteUri);
-
+
// Now test the filter. Auth20 would come out on top, if we didn't select it out with the filter.
rp.EndpointOrder = OpenIdRelyingParty.DefaultEndpointOrder;
rp.EndpointFilter = (se) => se.Uri.AbsoluteUri == "https://authn.freexri.com/auth10/";
request = rp.CreateRequest("=MultipleEndpoint", realm, return_to);
Assert.AreEqual("https://authn.freexri.com/auth10/", request.Provider.Uri.AbsoluteUri);
}
+
+ private string stripScheme(string identifier) {
+ return identifier.Substring(identifier.IndexOf("://") + 3);
+ }
+
+ [Test]
+ public void RequireSslPrependsHttpsScheme() {
+ MockHttpRequest.Reset();
+ OpenIdRelyingParty rp = TestSupport.CreateRelyingParty(null);
+ rp.Settings.RequireSsl = true;
+ Identifier mockId = TestSupport.GetMockIdentifier(TestSupport.Scenarios.AutoApproval, ProtocolVersion.V20, true);
+ string noSchemeId = stripScheme(mockId);
+ var request = rp.CreateRequest(noSchemeId, TestSupport.Realm, TestSupport.ReturnTo);
+ Assert.IsTrue(request.ClaimedIdentifier.ToString().StartsWith("https://", StringComparison.OrdinalIgnoreCase));
+ }
+
+ [Test]
+ public void DirectedIdentityWithRequireSslSucceeds() {
+ Uri claimedId = TestSupport.GetFullUrl("/secureClaimedId", null, true);
+ Identifier opIdentifier = TestSupport.GetMockOPIdentifier(TestSupport.Scenarios.AutoApproval, claimedId, true, true);
+ var rp = TestSupport.CreateRelyingParty(null);
+ rp.Settings.RequireSsl = true;
+ var rpRequest = rp.CreateRequest(opIdentifier, TestSupport.Realm, TestSupport.ReturnTo);
+ var rpResponse = TestSupport.CreateRelyingPartyResponseThroughProvider(rpRequest, opRequest => {
+ opRequest.IsAuthenticated = true;
+ opRequest.ClaimedIdentifier = claimedId;
+ });
+ Assert.AreEqual(AuthenticationStatus.Authenticated, rpResponse.Status);
+ }
+
+ [Test]
+ public void DirectedIdentityWithRequireSslFailsWithoutSecureIdentity() {
+ Uri claimedId = TestSupport.GetFullUrl("/insecureClaimedId", null, false);
+ Identifier opIdentifier = TestSupport.GetMockOPIdentifier(TestSupport.Scenarios.AutoApproval, claimedId, true, true);
+ var rp = TestSupport.CreateRelyingParty(null);
+ rp.Settings.RequireSsl = true;
+ var rpRequest = rp.CreateRequest(opIdentifier, TestSupport.Realm, TestSupport.ReturnTo);
+ var rpResponse = TestSupport.CreateRelyingPartyResponseThroughProvider(rpRequest, opRequest => {
+ opRequest.IsAuthenticated = true;
+ opRequest.ClaimedIdentifier = claimedId;
+ });
+ Assert.AreEqual(AuthenticationStatus.Failed, rpResponse.Status);
+ }
+
+ [Test]
+ public void DirectedIdentityWithRequireSslFailsWithoutSecureProviderEndpoint() {
+ Uri claimedId = TestSupport.GetFullUrl("/secureClaimedId", null, true);
+ // We want to generate an OP Identifier that itself is secure, but whose
+ // XRDS doc describes an insecure provider endpoint.
+ Identifier opIdentifier = TestSupport.GetMockOPIdentifier(TestSupport.Scenarios.AutoApproval, claimedId, true, false);
+ var rp = TestSupport.CreateRelyingParty(null);
+ rp.Settings.RequireSsl = true;
+ var rpRequest = rp.CreateRequest(opIdentifier, TestSupport.Realm, TestSupport.ReturnTo);
+ var rpResponse = TestSupport.CreateRelyingPartyResponseThroughProvider(rpRequest, opRequest => {
+ opRequest.IsAuthenticated = true;
+ opRequest.ClaimedIdentifier = claimedId;
+ });
+ Assert.AreEqual(AuthenticationStatus.Failed, rpResponse.Status);
+ }
+
+ [Test]
+ public void UnsolicitedAssertionWithRequireSsl() {
+ MockHttpRequest.Reset();
+ Mocks.MockHttpRequest.RegisterMockRPDiscovery();
+ TestSupport.Scenarios scenario = TestSupport.Scenarios.AutoApproval;
+ Identifier claimedId = TestSupport.GetMockIdentifier(scenario, ProtocolVersion.V20, true);
+ Identifier localId = TestSupport.GetDelegateUrl(scenario, true);
+
+ OpenIdProvider op = TestSupport.CreateProvider(null, true);
+ IResponse assertion = op.PrepareUnsolicitedAssertion(TestSupport.Realm, claimedId, localId);
+
+ var opAuthWebResponse = (Response)assertion;
+ var opAuthResponse = (DotNetOpenId.Provider.EncodableResponse)opAuthWebResponse.EncodableMessage;
+ var rp = TestSupport.CreateRelyingParty(TestSupport.RelyingPartyStore, opAuthResponse.RedirectUrl,
+ opAuthResponse.EncodedFields.ToNameValueCollection());
+ rp.Settings.RequireSsl = true;
+
+ Assert.AreEqual(AuthenticationStatus.Authenticated, rp.Response.Status);
+ Assert.AreEqual(claimedId, rp.Response.ClaimedIdentifier);
+ }
+
+ [Test]
+ public void UnsolicitedAssertionWithRequireSslWithoutSecureIdentityUrl() {
+ MockHttpRequest.Reset();
+ Mocks.MockHttpRequest.RegisterMockRPDiscovery();
+ TestSupport.Scenarios scenario = TestSupport.Scenarios.AutoApproval;
+ Identifier claimedId = TestSupport.GetMockIdentifier(scenario, ProtocolVersion.V20);
+ Identifier localId = TestSupport.GetDelegateUrl(scenario);
+
+ OpenIdProvider op = TestSupport.CreateProvider(null);
+ IResponse assertion = op.PrepareUnsolicitedAssertion(TestSupport.Realm, claimedId, localId);
+
+ var opAuthWebResponse = (Response)assertion;
+ var opAuthResponse = (DotNetOpenId.Provider.EncodableResponse)opAuthWebResponse.EncodableMessage;
+ var rp = TestSupport.CreateRelyingParty(TestSupport.RelyingPartyStore, opAuthResponse.RedirectUrl,
+ opAuthResponse.EncodedFields.ToNameValueCollection());
+ rp.Settings.RequireSsl = true;
+
+ Assert.AreEqual(AuthenticationStatus.Failed, rp.Response.Status);
+ Assert.IsNull(rp.Response.ClaimedIdentifier);
+ }
+
+ [Test]
+ public void UnsolicitedAssertionWithRequireSslWithSecureIdentityButInsecureProviderEndpoint() {
+ MockHttpRequest.Reset();
+ Mocks.MockHttpRequest.RegisterMockRPDiscovery();
+ TestSupport.Scenarios scenario = TestSupport.Scenarios.AutoApproval;
+ ProtocolVersion version = ProtocolVersion.V20;
+ ServiceEndpoint providerEndpoint = TestSupport.GetServiceEndpoint(scenario, version, 10, false);
+ Identifier claimedId = new MockIdentifier(TestSupport.GetIdentityUrl(scenario, version, true),
+ new ServiceEndpoint[] { providerEndpoint });
+ Identifier localId = TestSupport.GetDelegateUrl(scenario, true);
+
+ OpenIdProvider op = TestSupport.CreateProvider(null, false);
+ IResponse assertion = op.PrepareUnsolicitedAssertion(TestSupport.Realm, claimedId, localId);
+
+ var opAuthWebResponse = (Response)assertion;
+ var opAuthResponse = (DotNetOpenId.Provider.EncodableResponse)opAuthWebResponse.EncodableMessage;
+ var rp = TestSupport.CreateRelyingParty(TestSupport.RelyingPartyStore, opAuthResponse.RedirectUrl,
+ opAuthResponse.EncodedFields.ToNameValueCollection());
+ rp.Settings.RequireSsl = true;
+
+ Assert.AreEqual(AuthenticationStatus.Failed, rp.Response.Status);
+ Assert.IsNull(rp.Response.ClaimedIdentifier);
+ }
+
+ /// <summary>
+ /// Verifies that an RP will not "discover" endpoints below OpenID 2.0 when appropriate.
+ /// </summary>
+ [Test, ExpectedException(typeof(OpenIdException))]
+ public void MinimumOPVersion20() {
+ MockIdentifier id = TestSupport.GetMockIdentifier(TestSupport.Scenarios.AutoApproval, ProtocolVersion.V11);
+
+ var rp = TestSupport.CreateRelyingParty(null);
+ rp.Settings.MinimumRequiredOpenIdVersion = ProtocolVersion.V20;
+ rp.CreateRequest(id, TestSupport.Realm, TestSupport.ReturnTo);
+ }
+
+ /// <summary>
+ /// Verifies that an RP configured to require 2.0 OPs will fail on communicating with 1.x OPs
+ /// that merely advertise 2.0 support but don't really have it.
+ /// </summary>
+ [Test]
+ public void MinimumOPVersion20WithDeceptiveEndpointRealizedAtAuthentication() {
+ // Create an identifier that claims to have a 2.0 OP endpoint.
+ MockIdentifier id = TestSupport.GetMockIdentifier(TestSupport.Scenarios.AutoApproval, ProtocolVersion.V20);
+
+ var rp = TestSupport.CreateRelyingParty(null, null);
+
+ IAuthenticationRequest req = rp.CreateRequest(id, TestSupport.Realm, TestSupport.ReturnTo);
+ IResponse providerResponse = TestSupport.CreateProviderResponseToRequest(req, opReq => {
+ opReq.IsAuthenticated = true;
+ });
+
+ var opAuthWebResponse = (Response)providerResponse;
+ var opAuthResponse = (DotNetOpenId.Provider.EncodableResponse)opAuthWebResponse.EncodableMessage;
+ var rp2 =TestSupport. CreateRelyingParty(null, opAuthResponse.RedirectUrl,
+ opAuthResponse.EncodedFields.ToNameValueCollection());
+ rp2.Settings.MinimumRequiredOpenIdVersion = ProtocolVersion.V20;
+ // Rig an intercept between the provider and RP to make our own Provider LOOK like a 1.x provider.
+ var sniffer = new DirectMessageSniffWrapper(rp2.DirectMessageChannel);
+ rp2.DirectMessageChannel = sniffer;
+ sniffer.Receiving += (endpoint, fields) => {
+ fields.Remove(Protocol.v20.openidnp.ns);
+ };
+ var resp = rp2.Response;
+
+ Assert.AreEqual(AuthenticationStatus.Failed, resp.Status, "Authentication should have failed since OP is really a 1.x OP masquerading as a 2.0 OP.");
+ }
}
}
diff --git a/src/DotNetOpenId.Test/RelyingParty/ServiceEndpointTests.cs b/src/DotNetOpenId.Test/RelyingParty/ServiceEndpointTests.cs index cb2a61b..d2677b8 100644 --- a/src/DotNetOpenId.Test/RelyingParty/ServiceEndpointTests.cs +++ b/src/DotNetOpenId.Test/RelyingParty/ServiceEndpointTests.cs @@ -116,7 +116,9 @@ namespace DotNetOpenId.Test.RelyingParty { public void FriendlyIdentifierForDisplay() {
Uri providerEndpoint= new Uri("http://someprovider");
Identifier localId = "someuser";
- string[] serviceTypeUris = new string[0];
+ string[] serviceTypeUris = new string[] {
+ Protocol.v20.ClaimedIdentifierServiceTypeURI,
+ };
ServiceEndpoint se;
// strip of protocol and fragment
@@ -132,7 +134,7 @@ namespace DotNetOpenId.Test.RelyingParty { // restore user supplied identifier to XRIs
se = ServiceEndpoint.CreateForClaimedIdentifier(new XriIdentifier("=!9B72.7DD1.50A9.5CCD"),
new XriIdentifier("=Arnott崎村"), localId, providerEndpoint, serviceTypeUris, null, null);
- Assert.AreEqual("=!9B72.7DD1.50A9.5CCD (=Arnott崎村)", se.FriendlyIdentifierForDisplay);
+ Assert.AreEqual("=Arnott崎村", se.FriendlyIdentifierForDisplay);
// If UserSuppliedIdentifier is the same as the ClaimedIdentifier, don't display it twice...
se = ServiceEndpoint.CreateForClaimedIdentifier(
diff --git a/src/DotNetOpenId.Test/TestSupport.cs b/src/DotNetOpenId.Test/TestSupport.cs index 7022941..0f90483 100644 --- a/src/DotNetOpenId.Test/TestSupport.cs +++ b/src/DotNetOpenId.Test/TestSupport.cs @@ -1,15 +1,19 @@ using System;
using System.Collections.Generic;
-using System.Text;
-using NUnit.Framework;
+using System.Collections.Specialized;
+using System.Diagnostics;
using System.IO;
-using System.Globalization;
-using DotNetOpenId.Test.Hosting;
+using System.Reflection;
+using System.Web;
using DotNetOpenId;
-using System.Net;
-using System.Collections.Specialized;
+using DotNetOpenId.Provider;
using DotNetOpenId.RelyingParty;
-using System.Diagnostics;
+using DotNetOpenId.Test.Mocks;
+using DotNetOpenId.Test.UI;
+using log4net;
+using NUnit.Framework;
+using IProviderAssociationStore = DotNetOpenId.IAssociationStore<DotNetOpenId.AssociationRelyingPartyType>;
+using ProviderMemoryStore = DotNetOpenId.AssociationMemoryStore<DotNetOpenId.AssociationRelyingPartyType>;
[SetUpFixture]
public class TestSupport {
@@ -18,8 +22,14 @@ public class TestSupport { const string identityPage = "IdentityEndpoint.aspx";
const string directedIdentityPage = "DirectedIdentityEndpoint.aspx";
public const string ProviderPage = "ProviderEndpoint.aspx";
+ public const string DirectedProviderEndpoint = "DirectedProviderEndpoint.aspx";
public const string MobileConsumerPage = "RelyingPartyMobile.aspx";
public const string ConsumerPage = "RelyingParty.aspx";
+ public const string OPDefaultPage = "OPDefault.aspx";
+ public static Uri ReturnTo { get { return TestSupport.GetFullUrl(TestSupport.ConsumerPage); } }
+ public static Realm Realm { get { return new Realm(TestSupport.GetFullUrl(TestSupport.ConsumerPage).AbsoluteUri); } }
+ public readonly static ILog Logger = LogManager.GetLogger("DotNetOpenId.Test");
+
public enum Scenarios {
// Authentication test scenarios
AutoApproval,
@@ -37,70 +47,278 @@ public class TestSupport { /// </summary>
ExtensionPartialCooperation,
}
- internal static UriIdentifier GetOPIdentityUrl(Scenarios scenario) {
- UriBuilder builder = new UriBuilder(Host.BaseUri);
- builder.Path = "/opdefault.aspx";
- builder.Query = "user=" + scenario;
- return new UriIdentifier(builder.Uri);
+ internal static UriIdentifier GetOPIdentityUrl(Scenarios scenario, bool useSsl) {
+ var args = new Dictionary<string, string> {
+ { "user", scenario.ToString() },
+ };
+ return new UriIdentifier(GetFullUrl("/" + OPDefaultPage, args, useSsl));
}
internal static UriIdentifier GetIdentityUrl(Scenarios scenario, ProtocolVersion providerVersion) {
- UriBuilder builder = new UriBuilder(Host.BaseUri);
- builder.Path = "/" + identityPage;
- builder.Query = "user=" + scenario + "&version=" + providerVersion;
- return new UriIdentifier(builder.Uri);
+ return GetIdentityUrl(scenario, providerVersion, false);
+ }
+ internal static UriIdentifier GetIdentityUrl(Scenarios scenario, ProtocolVersion providerVersion, bool useSsl) {
+ return new UriIdentifier(GetFullUrl("/" + identityPage, new Dictionary<string, string> {
+ { "user", scenario.ToString() },
+ { "version", providerVersion.ToString() },
+ }, useSsl));
}
internal static UriIdentifier GetDirectedIdentityUrl(Scenarios scenario, ProtocolVersion providerVersion) {
- UriBuilder builder = new UriBuilder(Host.BaseUri);
- builder.Path = "/" + directedIdentityPage;
- builder.Query = "user=" + scenario + "&version=" + providerVersion;
- return new UriIdentifier(builder.Uri);
+ return GetDirectedIdentityUrl(scenario, providerVersion, false);
+ }
+ internal static UriIdentifier GetDirectedIdentityUrl(Scenarios scenario, ProtocolVersion providerVersion, bool useSsl) {
+ return new UriIdentifier(GetFullUrl("/" + directedIdentityPage, new Dictionary<string, string> {
+ { "user", scenario.ToString() },
+ { "version", providerVersion.ToString() },
+ }, useSsl));
}
public static Identifier GetDelegateUrl(Scenarios scenario) {
- return new UriIdentifier(new Uri(Host.BaseUri, "/" + scenario));
+ return GetDelegateUrl(scenario, false);
+ }
+ public static Identifier GetDelegateUrl(Scenarios scenario, bool useSsl) {
+ return new UriIdentifier(GetFullUrl("/" + scenario, null, useSsl));
+ }
+ internal static MockIdentifier GetMockIdentifier(Scenarios scenario, ProtocolVersion providerVersion) {
+ return GetMockIdentifier(scenario, providerVersion, false);
+ }
+ internal static MockIdentifier GetMockIdentifier(Scenarios scenario, ProtocolVersion providerVersion, bool useSsl) {
+ ServiceEndpoint se = GetServiceEndpoint(scenario, providerVersion, 10, useSsl);
+ return new MockIdentifier(GetIdentityUrl(scenario, providerVersion, useSsl), new ServiceEndpoint[] { se });
+ }
+ internal static ServiceEndpoint GetServiceEndpoint(Scenarios scenario, ProtocolVersion providerVersion, int servicePriority, bool useSsl) {
+ return ServiceEndpoint.CreateForClaimedIdentifier(
+ GetIdentityUrl(scenario, providerVersion, useSsl),
+ GetDelegateUrl(scenario, useSsl),
+ GetFullUrl("/" + ProviderPage, null, useSsl),
+ new string[] { Protocol.Lookup(providerVersion).ClaimedIdentifierServiceTypeURI },
+ servicePriority,
+ 10
+ );
+ }
+ internal static MockIdentifier GetMockOPIdentifier(Scenarios scenario, UriIdentifier expectedClaimedId) {
+ return GetMockOPIdentifier(scenario, expectedClaimedId, false, false);
+ }
+ internal static MockIdentifier GetMockOPIdentifier(Scenarios scenario, UriIdentifier expectedClaimedId, bool useSslOpIdentifier, bool useSslProviderEndpoint) {
+ var fields = new Dictionary<string, string> {
+ { "user", scenario.ToString() },
+ };
+ Uri opEndpoint = GetFullUrl(DirectedProviderEndpoint, fields, useSslProviderEndpoint);
+ Uri opIdentifier = GetOPIdentityUrl(scenario, useSslOpIdentifier);
+ ServiceEndpoint se = ServiceEndpoint.CreateForProviderIdentifier(
+ opIdentifier,
+ opEndpoint,
+ new string[] { Protocol.v20.OPIdentifierServiceTypeURI },
+ 10,
+ 10
+ );
+
+ // Register the Claimed Identifier that directed identity will choose so that RP
+ // discovery on that identifier can be mocked up.
+ MockHttpRequest.RegisterMockXrdsResponse(expectedClaimedId, se);
+
+ return new MockIdentifier(opIdentifier, new ServiceEndpoint[] { se });
}
public static Uri GetFullUrl(string url) {
- return GetFullUrl(url, null);
+ return GetFullUrl(url, null, false);
+ }
+ public static Uri GetFullUrl(string url, string key, object value) {
+ return GetFullUrl(url, new Dictionary<string, string> {
+ { key, value.ToString() },
+ }, false);
}
- public static Uri GetFullUrl(string url, IDictionary<string, string> args) {
- UriBuilder builder = new UriBuilder(new Uri(Host.BaseUri, url));
+ public static Uri GetFullUrl(string url, IDictionary<string, string> args, bool useSsl) {
+ Uri defaultUriBase = new Uri(useSsl ? "https://localhost/" : "http://localhost/");
+ Uri baseUri = UITestSupport.Host != null ? UITestSupport.Host.BaseUri : defaultUriBase;
+ UriBuilder builder = new UriBuilder(new Uri(baseUri, url));
UriUtil.AppendQueryArgs(builder, args);
return builder.Uri;
}
- internal static AspNetHost Host { get; private set; }
- internal static EncodingInterceptor Interceptor { get; private set; }
-
- internal static UntrustedWebRequest.MockRequestResponse GenerateMockXrdsResponses(IDictionary<string, string> requestUriAndResponseBody) {
- return (uri, body, acceptTypes) => {
- string contentType = "text/xml; saml=false; https=false; charset=UTF-8";
- string contentEncoding = null;
- MemoryStream stream = new MemoryStream();
- StreamWriter sw = new StreamWriter(stream);
- Assert.IsNull(body);
- string responseBody;
- if (!requestUriAndResponseBody.TryGetValue(uri.AbsoluteUri, out responseBody)) {
- Assert.Fail("Unexpected HTTP request: {0}", uri);
- }
- sw.Write(responseBody);
- sw.Flush();
- stream.Seek(0, SeekOrigin.Begin);
- return new UntrustedWebResponse(uri, uri, new WebHeaderCollection(),
- HttpStatusCode.OK, contentType, contentEncoding, stream);
- };
+ /// <summary>
+ /// Returns the content of a given embedded resource.
+ /// </summary>
+ /// <param name="path">The path of the file as it appears within the project,
+ /// where the leading / marks the root directory of the project.</param>
+ internal static string LoadEmbeddedFile(string path) {
+ if (!path.StartsWith("/")) path = "/" + path;
+ path = "DotNetOpenId.Test" + path.Replace('/', '.');
+ Stream resource = Assembly.GetExecutingAssembly().GetManifestResourceStream(path);
+ if (resource == null) throw new ArgumentException();
+ using (StreamReader sr = new StreamReader(resource)) {
+ return sr.ReadToEnd();
+ }
+ }
+
+ internal static IRelyingPartyApplicationStore RelyingPartyStore;
+ internal static IProviderAssociationStore ProviderStore;
+ /// <summary>
+ /// Generates a new, stateful <see cref="OpenIdRelyingParty"/> whose direct messages
+ /// will be automatically handled by an internal <see cref="OpenIdProvider"/>
+ /// that uses the shared <see cref="ProviderStore"/>.
+ /// </summary>
+ internal static OpenIdRelyingParty CreateRelyingParty(NameValueCollection fields) {
+ return CreateRelyingParty(RelyingPartyStore, null, fields);
+ }
+ internal static OpenIdRelyingParty CreateRelyingParty(IRelyingPartyApplicationStore store, NameValueCollection fields) {
+ return CreateRelyingParty(store, null, fields);
+ }
+ /// <summary>
+ /// Generates a new <see cref="OpenIdRelyingParty"/> whose direct messages
+ /// will be automatically handled by an internal <see cref="OpenIdProvider"/>
+ /// that uses the shared <see cref="ProviderStore"/>.
+ /// </summary>
+ internal static OpenIdRelyingParty CreateRelyingParty(IRelyingPartyApplicationStore store, Uri requestUrl, NameValueCollection fields) {
+ var rp = new OpenIdRelyingParty(store, requestUrl ?? GetFullUrl(ConsumerPage), fields ?? new NameValueCollection());
+ if (fields == null || fields.Count == 0) {
+ Assert.IsNull(rp.Response);
+ }
+ rp.DirectMessageChannel = new DirectMessageTestRedirector(ProviderStore);
+ return rp;
+ }
+ internal static DotNetOpenId.RelyingParty.IAuthenticationRequest CreateRelyingPartyRequest(bool stateless, Scenarios scenario, ProtocolVersion version) {
+ // Publish RP discovery information
+ MockHttpRequest.RegisterMockRPDiscovery();
+
+ var rp = TestSupport.CreateRelyingParty(stateless ? null : RelyingPartyStore, null);
+ var rpReq = rp.CreateRequest(TestSupport.GetMockIdentifier(scenario, version), Realm, ReturnTo);
+
+ {
+ // Sidetrack: verify URLs and other default properties
+ Assert.AreEqual(AuthenticationRequestMode.Setup, rpReq.Mode);
+ Assert.AreEqual(Realm, rpReq.Realm);
+ Assert.AreEqual(ReturnTo, rpReq.ReturnToUrl);
+ }
+
+ return rpReq;
+ }
+ /// <summary>
+ /// Generates a new <see cref="OpenIdRelyingParty"/> ready to process a
+ /// response from an <see cref="OpenIdProvider"/>.
+ /// </summary>
+ internal static IAuthenticationResponse CreateRelyingPartyResponse(IRelyingPartyApplicationStore store, IResponse providerResponse) {
+ return CreateRelyingPartyResponse(store, providerResponse, false);
+ }
+ internal static IAuthenticationResponse CreateRelyingPartyResponse(IRelyingPartyApplicationStore store, IResponse providerResponse, bool requireSsl) {
+ if (providerResponse == null) throw new ArgumentNullException("providerResponse");
+
+ var opAuthWebResponse = (Response)providerResponse;
+ var opAuthResponse = (EncodableResponse)opAuthWebResponse.EncodableMessage;
+ var rp = CreateRelyingParty(store, opAuthResponse.RedirectUrl,
+ opAuthResponse.EncodedFields.ToNameValueCollection());
+ rp.Settings.RequireSsl = requireSsl;
+ // Get the response now, before trying the replay attack. The Response
+ // property is lazily-evaluated, so the replay attack can be evaluated first
+ // and pass, while this one that SUPPOSED to pass fails, if we don't force it now.
+ var response = rp.Response;
+
+ // Side-track to test for replay attack while we're at it.
+ // This simulates a network sniffing user who caught the
+ // authenticating query en route to either the user agent or
+ // the consumer, and tries the same query to the consumer in an
+ // attempt to spoof the identity of the authenticating user.
+ try {
+ Logger.Info("Attempting replay attack...");
+ var replayRP = CreateRelyingParty(store, opAuthResponse.RedirectUrl,
+ opAuthResponse.EncodedFields.ToNameValueCollection());
+ replayRP.Settings.RequireSsl = requireSsl;
+ Assert.AreNotEqual(AuthenticationStatus.Authenticated, replayRP.Response.Status, "Replay attack succeeded!");
+ } catch (OpenIdException) { // nonce already used
+ // another way to pass
+ }
+
+ // Return the result of the initial response (not the replay attack one).
+ return response;
+ }
+ /// <summary>
+ /// Generates a new <see cref="OpenIdProvider"/> that uses the shared
+ /// store in <see cref="ProviderStore"/>.
+ /// </summary>
+ internal static OpenIdProvider CreateProvider(NameValueCollection fields) {
+ return CreateProvider(fields, false);
+ }
+ internal static OpenIdProvider CreateProvider(NameValueCollection fields, bool useSsl) {
+ Protocol protocol = fields != null ? Protocol.Detect(fields.ToDictionary()) : Protocol.v20;
+ Uri opEndpoint = GetFullUrl(ProviderPage, null, useSsl);
+ var provider = new OpenIdProvider(ProviderStore, opEndpoint, opEndpoint, fields ?? new NameValueCollection());
+ return provider;
+ }
+ internal static OpenIdProvider CreateProviderForRequest(DotNetOpenId.RelyingParty.IAuthenticationRequest request) {
+ IResponse relyingPartyAuthenticationRequest = request.RedirectingResponse;
+ var rpWebMessageToOP = (Response)relyingPartyAuthenticationRequest;
+ var rpMessageToOP = (IndirectMessageRequest)rpWebMessageToOP.EncodableMessage;
+ var opEndpoint = (ServiceEndpoint)request.Provider;
+ var provider = new OpenIdProvider(ProviderStore, opEndpoint.ProviderEndpoint,
+ opEndpoint.ProviderEndpoint, rpMessageToOP.EncodedFields.ToNameValueCollection());
+ return provider;
+ }
+ internal static IResponse CreateProviderResponseToRequest(
+ DotNetOpenId.RelyingParty.IAuthenticationRequest request,
+ Action<DotNetOpenId.Provider.IAuthenticationRequest> prepareProviderResponse) {
+
+ {
+ // Sidetrack: Verify the return_to and realm URLs
+ var consumerToProviderQuery = HttpUtility.ParseQueryString(request.RedirectingResponse.ExtractUrl().Query);
+ Protocol protocol = Protocol.Detect(consumerToProviderQuery.ToDictionary());
+ Assert.IsTrue(consumerToProviderQuery[protocol.openid.return_to].StartsWith(request.ReturnToUrl.AbsoluteUri, StringComparison.Ordinal));
+ Assert.AreEqual(request.Realm.ToString(), consumerToProviderQuery[protocol.openid.Realm]);
+ }
+
+ var op = TestSupport.CreateProviderForRequest(request);
+ var opReq = (DotNetOpenId.Provider.IAuthenticationRequest)op.Request;
+ prepareProviderResponse(opReq);
+ Assert.IsTrue(opReq.IsResponseReady);
+ return opReq.Response;
+ }
+ internal static IAuthenticationResponse CreateRelyingPartyResponseThroughProvider(
+ DotNetOpenId.RelyingParty.IAuthenticationRequest request,
+ Action<DotNetOpenId.Provider.IAuthenticationRequest> providerAction) {
+
+ var rpReq = (AuthenticationRequest)request;
+ var opResponse = CreateProviderResponseToRequest(rpReq, providerAction);
+ // Be careful to use whatever store the original RP was using.
+ var rp = CreateRelyingPartyResponse(rpReq.RelyingParty.Store, opResponse,
+ ((AuthenticationRequest)request).RelyingParty.Settings.RequireSsl);
+ Assert.IsNotNull(rp);
+ return rp;
}
[SetUp]
public void SetUp() {
- Host = AspNetHost.CreateHost(TestSupport.TestWebDirectory);
- Host.MessageInterceptor = Interceptor = new EncodingInterceptor();
+ log4net.Config.XmlConfigurator.Configure(Assembly.GetExecutingAssembly().GetManifestResourceStream("DotNetOpenId.Test.Logging.config"));
+
+ ResetStores();
}
[TearDown]
public void TearDown() {
- Host.MessageInterceptor = null;
- if (Host != null) {
- Host.CloseHttp();
- Host = null;
+ log4net.LogManager.Shutdown();
+ }
+
+ internal static void ResetStores() {
+ RelyingPartyStore = new ApplicationMemoryStore();
+ ProviderStore = new ProviderMemoryStore();
+ }
+
+ internal static void SetAuthenticationFromScenario(Scenarios scenario, DotNetOpenId.Provider.IAuthenticationRequest request) {
+ Assert.IsTrue(request.IsReturnUrlDiscoverable);
+ switch (scenario) {
+ case TestSupport.Scenarios.ExtensionFullCooperation:
+ case TestSupport.Scenarios.ExtensionPartialCooperation:
+ case TestSupport.Scenarios.AutoApproval:
+ // immediately approve
+ request.IsAuthenticated = true;
+ break;
+ case TestSupport.Scenarios.AutoApprovalAddFragment:
+ request.SetClaimedIdentifierFragment("frag");
+ request.IsAuthenticated = true;
+ break;
+ case TestSupport.Scenarios.ApproveOnSetup:
+ request.IsAuthenticated = !request.Immediate;
+ break;
+ case TestSupport.Scenarios.AlwaysDeny:
+ request.IsAuthenticated = false;
+ break;
+ default:
+ throw new InvalidOperationException("Unrecognized scenario");
}
}
@@ -109,7 +327,7 @@ public class TestSupport { /// to simulate a Provider that deliberately sent a bad message in an attempt
/// to thwart RP security.
/// </summary>
- internal static void Resign(NameValueCollection nvc, ApplicationMemoryStore store) {
+ internal static void Resign(NameValueCollection nvc, IRelyingPartyApplicationStore store) {
Debug.Assert(nvc != null);
Debug.Assert(store != null);
var dict = Util.NameValueCollectionToDictionary(nvc);
@@ -117,6 +335,7 @@ public class TestSupport { Uri providerEndpoint = new Uri(nvc[protocol.openid.op_endpoint]);
string assoc_handle = nvc[protocol.openid.assoc_handle];
Association assoc = store.GetAssociation(providerEndpoint, assoc_handle);
+ Debug.Assert(assoc != null, "Association not found in RP's store. Maybe you're communicating with a hosted OP instead of the TestSupport one?");
IList<string> signed = nvc[protocol.openid.signed].Split(',');
var subsetDictionary = new Dictionary<string, string>();
foreach (string signedKey in signed) {
@@ -144,4 +363,20 @@ static class TestExtensions { UriUtil.AppendQueryArgs(builder, encodable.EncodedFields);
return builder.Uri;
}
+
+ public static NameValueCollection ToNameValueCollection(this IDictionary<string, string> dictionary) {
+ NameValueCollection nvc = new NameValueCollection(dictionary.Count);
+ foreach (var pair in dictionary) {
+ nvc.Add(pair.Key, pair.Value);
+ }
+ return nvc;
+ }
+ public static IDictionary<string, string> ToDictionary(this NameValueCollection nvc) {
+ if (nvc == null) return null;
+ Dictionary<string, string> dict = new Dictionary<string, string>(nvc.Count);
+ foreach (string key in nvc) {
+ dict[key] = nvc[key];
+ }
+ return dict;
+ }
}
diff --git a/src/DotNetOpenId.Test/Provider/IdentityEndpointTest.cs b/src/DotNetOpenId.Test/UI/IdentityEndpointTest.cs index 3fef4ec..89ec02a 100644 --- a/src/DotNetOpenId.Test/Provider/IdentityEndpointTest.cs +++ b/src/DotNetOpenId.Test/UI/IdentityEndpointTest.cs @@ -9,15 +9,15 @@ using System.Text.RegularExpressions; using System.Net;
using System.Globalization;
-namespace DotNetOpenId.Test.Provider {
+namespace DotNetOpenId.Test.UI {
[TestFixture]
public class IdentityEndpointTest {
void parameterizedIdentityEndpointPage(ProtocolVersion version) {
Protocol protocol = Protocol.Lookup(version);
TestSupport.Scenarios scenario = TestSupport.Scenarios.AutoApproval;
- UriIdentifier identityUrl = TestSupport.GetIdentityUrl(scenario, version);
- string html = TestSupport.Host.ProcessRequest(identityUrl.Uri.AbsoluteUri);
- Trace.TraceInformation("{0} response:{1}{2}", identityUrl, Environment.NewLine, html);
+ Identifier identityUrl = TestSupport.GetIdentityUrl(scenario, version);
+ string html = UITestSupport.Host.ProcessRequest(identityUrl);
+ TestSupport.Logger.InfoFormat("{0} response:{1}{2}", identityUrl, Environment.NewLine, html);
Assert.IsTrue(Regex.IsMatch(html, string.Format(CultureInfo.InvariantCulture,
@"\<link rel=""{1}"" href=""http://[^/]+/{0}""\>\</link\>",
Regex.Escape(TestSupport.ProviderPage),
diff --git a/src/DotNetOpenId.Test/RelyingParty/OpenIdMobileTextBoxTest.cs b/src/DotNetOpenId.Test/UI/OpenIdMobileTextBoxTest.cs index 0feea56..7ab78c6 100644 --- a/src/DotNetOpenId.Test/RelyingParty/OpenIdMobileTextBoxTest.cs +++ b/src/DotNetOpenId.Test/UI/OpenIdMobileTextBoxTest.cs @@ -4,12 +4,12 @@ using System.Linq; using System.Text;
using NUnit.Framework;
-namespace DotNetOpenId.Test.RelyingParty {
+namespace DotNetOpenId.Test.UI {
[TestFixture]
public class OpenIdMobileTextBoxTest {
[Test]
public void TextBoxAppears() {
- string html = TestSupport.Host.ProcessRequest(TestSupport.MobileConsumerPage);
+ string html = UITestSupport.Host.ProcessRequest(TestSupport.MobileConsumerPage);
Assert.IsTrue(html.Contains("<input "));
}
}
diff --git a/src/DotNetOpenId.Test/RelyingParty/OpenIdTextBoxTest.cs b/src/DotNetOpenId.Test/UI/OpenIdTextBoxTest.cs index 9176096..4012a05 100644 --- a/src/DotNetOpenId.Test/RelyingParty/OpenIdTextBoxTest.cs +++ b/src/DotNetOpenId.Test/UI/OpenIdTextBoxTest.cs @@ -5,12 +5,12 @@ using NUnit.Framework; using DotNetOpenId.Test.Hosting;
using System.Net;
-namespace DotNetOpenId.Test.RelyingParty {
+namespace DotNetOpenId.Test.UI {
[TestFixture]
public class OpenIdTextBoxTest {
[Test]
public void TextBoxAppears() {
- string html = TestSupport.Host.ProcessRequest(TestSupport.ConsumerPage);
+ string html = UITestSupport.Host.ProcessRequest(TestSupport.ConsumerPage);
Assert.IsTrue(html.Contains("<input "));
}
}
diff --git a/src/DotNetOpenId.Test/Provider/ProviderEndpointTest.cs b/src/DotNetOpenId.Test/UI/ProviderEndpointTest.cs index 7148f5b..6da933a 100644 --- a/src/DotNetOpenId.Test/Provider/ProviderEndpointTest.cs +++ b/src/DotNetOpenId.Test/UI/ProviderEndpointTest.cs @@ -6,7 +6,7 @@ using DotNetOpenId.Test.Hosting; using DotNetOpenId.Provider;
using System.Net;
-namespace DotNetOpenId.Test.Provider {
+namespace DotNetOpenId.Test.UI {
[TestFixture]
public class ProviderEndpointTest {
[Test]
diff --git a/src/DotNetOpenId.Test/TestSupportSanityTest.cs b/src/DotNetOpenId.Test/UI/TestSupportSanityTest.cs index df78dd5..e84625b 100644 --- a/src/DotNetOpenId.Test/TestSupportSanityTest.cs +++ b/src/DotNetOpenId.Test/UI/TestSupportSanityTest.cs @@ -8,14 +8,14 @@ using System.IO; using System.Diagnostics;
using System.Text.RegularExpressions;
-namespace DotNetOpenId.Test {
+namespace DotNetOpenId.Test.UI {
[TestFixture]
public class TestSupportSanityTest {
[Test]
public void TestHost() {
string query = "a=b&c=d";
string body = "aa=bb&cc=dd";
- string resultHtml = TestSupport.Host.ProcessRequest(TestSupport.HostTestPage + "?" + query, body);
+ string resultHtml = UITestSupport.Host.ProcessRequest(TestSupport.HostTestPage + "?" + query, body);
Assert.IsFalse(string.IsNullOrEmpty(resultHtml));
Debug.WriteLine(resultHtml);
@@ -25,7 +25,7 @@ namespace DotNetOpenId.Test { [Test]
public void TestProviderPage() {
- string html = TestSupport.Host.ProcessRequest(TestSupport.ProviderPage);
+ string html = UITestSupport.Host.ProcessRequest(TestSupport.ProviderPage);
Assert.IsFalse(string.IsNullOrEmpty(html));
}
}
diff --git a/src/DotNetOpenId.Test/UI/UITestSupport.cs b/src/DotNetOpenId.Test/UI/UITestSupport.cs new file mode 100644 index 0000000..40d169a --- /dev/null +++ b/src/DotNetOpenId.Test/UI/UITestSupport.cs @@ -0,0 +1,26 @@ +using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using NUnit.Framework;
+using DotNetOpenId.Test.Hosting;
+
+namespace DotNetOpenId.Test.UI {
+ [SetUpFixture]
+ public class UITestSupport {
+ internal static AspNetHost Host { get; private set; }
+
+ [SetUp]
+ public void SetUp() {
+ Host = AspNetHost.CreateHost(TestSupport.TestWebDirectory);
+ }
+
+ [TearDown]
+ public void TearDown() {
+ if (Host != null) {
+ Host.CloseHttp();
+ Host = null;
+ }
+ }
+ }
+}
diff --git a/src/DotNetOpenId.Test/UI/WebControlTesting.cs b/src/DotNetOpenId.Test/UI/WebControlTesting.cs new file mode 100644 index 0000000..2ec010f --- /dev/null +++ b/src/DotNetOpenId.Test/UI/WebControlTesting.cs @@ -0,0 +1,103 @@ +using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using NUnit.Framework;
+using DotNetOpenId.RelyingParty;
+using System.Net;
+using System.Collections.Specialized;
+using System.IO;
+using System.Text.RegularExpressions;
+using System.Diagnostics;
+
+namespace DotNetOpenId.Test.UI {
+ [TestFixture]
+ public class WebControlTesting {
+ void parameterizedWebClientTest(Identifier identityUrl,
+ AuthenticationRequestMode requestMode, AuthenticationStatus expectedResult) {
+
+ Uri redirectToProviderUrl;
+ HttpWebRequest rpRequest = (HttpWebRequest)WebRequest.Create(TestSupport.GetFullUrl(TestSupport.ConsumerPage));
+ NameValueCollection query = new NameValueCollection();
+ using (HttpWebResponse response = (HttpWebResponse)rpRequest.GetResponse()) {
+ using (StreamReader sr = new StreamReader(response.GetResponseStream())) {
+ Regex regex = new Regex(@"\<input\b.*\bname=""(\w+)"".*\bvalue=""([^""]+)""", RegexOptions.IgnoreCase);
+ while (!sr.EndOfStream) {
+ string line = sr.ReadLine();
+ Match m = regex.Match(line);
+ if (m.Success) {
+ query[m.Groups[1].Value] = m.Groups[2].Value;
+ }
+ }
+ }
+ }
+ query["OpenIdTextBox1$wrappedTextBox"] = identityUrl;
+ rpRequest = (HttpWebRequest)WebRequest.Create(TestSupport.GetFullUrl(TestSupport.ConsumerPage));
+ rpRequest.Method = "POST";
+ rpRequest.AllowAutoRedirect = false;
+ string queryString = UriUtil.CreateQueryString(query);
+ rpRequest.ContentLength = queryString.Length;
+ rpRequest.ContentType = "application/x-www-form-urlencoded";
+ using (StreamWriter sw = new StreamWriter(rpRequest.GetRequestStream())) {
+ sw.Write(queryString);
+ }
+ using (HttpWebResponse response = (HttpWebResponse)rpRequest.GetResponse()) {
+ using (StreamReader sr = new StreamReader(response.GetResponseStream())) {
+ string doc = sr.ReadToEnd();
+ Debug.WriteLine(doc);
+ }
+ redirectToProviderUrl = new Uri(response.Headers[HttpResponseHeader.Location]);
+ }
+
+ HttpWebRequest providerRequest = (HttpWebRequest)WebRequest.Create(redirectToProviderUrl);
+ providerRequest.AllowAutoRedirect = false;
+ Uri redirectUrl;
+ try {
+ using (HttpWebResponse providerResponse = (HttpWebResponse)providerRequest.GetResponse()) {
+ Assert.AreEqual(HttpStatusCode.Redirect, providerResponse.StatusCode);
+ redirectUrl = new Uri(providerResponse.Headers[HttpResponseHeader.Location]);
+ }
+ } catch (WebException ex) {
+ TestSupport.Logger.Error("WebException", ex);
+ if (ex.Response != null) {
+ using (StreamReader sr = new StreamReader(ex.Response.GetResponseStream())) {
+ TestSupport.Logger.ErrorFormat("Response stream follows: {0}", sr.ReadToEnd());
+ }
+ }
+ throw;
+ }
+ rpRequest = (HttpWebRequest)WebRequest.Create(redirectUrl);
+ rpRequest.AllowAutoRedirect = false;
+ using (HttpWebResponse response = (HttpWebResponse)rpRequest.GetResponse()) {
+ Assert.AreEqual(HttpStatusCode.Redirect, response.StatusCode); // redirect on login
+ }
+
+ // Try replay attack
+ if (expectedResult == AuthenticationStatus.Authenticated) {
+ // This simulates a network sniffing user who caught the
+ // authenticating query en route to either the user agent or
+ // the consumer, and tries the same query to the consumer in an
+ // attempt to spoof the identity of the authenticating user.
+ rpRequest = (HttpWebRequest)WebRequest.Create(redirectUrl);
+ rpRequest.AllowAutoRedirect = false;
+ using (HttpWebResponse response = (HttpWebResponse)rpRequest.GetResponse()) {
+ Assert.AreEqual(HttpStatusCode.OK, response.StatusCode); // error message
+ }
+ }
+ }
+
+ [Test]
+ public void Pass_Setup_AutoApproval_20() {
+ Identifier userSuppliedIdentifier = TestSupport.GetMockIdentifier(TestSupport.Scenarios.AutoApproval, ProtocolVersion.V20);
+ Identifier claimedId = userSuppliedIdentifier;
+ parameterizedWebClientTest(userSuppliedIdentifier, AuthenticationRequestMode.Setup, AuthenticationStatus.Authenticated);
+ }
+
+ [Test]
+ public void Fail_Immediate_ApproveOnSetup_20() {
+ Identifier userSuppliedIdentifier = TestSupport.GetMockIdentifier(TestSupport.Scenarios.ApproveOnSetup, ProtocolVersion.V20);
+ Identifier claimedId = userSuppliedIdentifier;
+ parameterizedWebClientTest(userSuppliedIdentifier, AuthenticationRequestMode.Immediate, AuthenticationStatus.Authenticated);
+ }
+ }
+}
diff --git a/src/DotNetOpenId.Test/UntrustedWebRequestTests.cs b/src/DotNetOpenId.Test/UntrustedWebRequestTests.cs index c44354c..7bba993 100644 --- a/src/DotNetOpenId.Test/UntrustedWebRequestTests.cs +++ b/src/DotNetOpenId.Test/UntrustedWebRequestTests.cs @@ -1,10 +1,9 @@ using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using NUnit.Framework;
-using System.Text.RegularExpressions;
+using System.IO;
using System.Net;
+using System.Text.RegularExpressions;
+using DotNetOpenId.Test.Mocks;
+using NUnit.Framework;
namespace DotNetOpenId.Test {
[TestFixture]
@@ -25,7 +24,7 @@ namespace DotNetOpenId.Test { [Test]
public void DisallowUnsafeHosts() {
- string[] unsafeHosts = new [] {
+ string[] unsafeHosts = new[] {
// IPv4 loopback representations
"http://127.0.0.1",
"http://127.100.0.1",
@@ -89,5 +88,38 @@ namespace DotNetOpenId.Test { UntrustedWebRequest.BlacklistHostsRegex.Add(new Regex(@"\Wmicrosoft.com$"));
UntrustedWebRequest.Request(new Uri("http://WWW.MICROSOFT.COM"));
}
+
+ /// <summary>
+ /// Tests an implicit redirect where the HTTP server changes the responding URI without even
+ /// redirecting the client.
+ /// </summary>
+ [Test]
+ public void Redirects() {
+ UntrustedWebRequest.WhitelistHosts.Add("localhost");
+ UntrustedWebResponse resp = new UntrustedWebResponse(
+ new Uri("http://localhost/req"), new Uri("http://localhost/resp"),
+ new WebHeaderCollection(), HttpStatusCode.OK, "text/html", null, new MemoryStream());
+ MockHttpRequest.RegisterMockResponse(resp);
+ Assert.AreSame(resp, UntrustedWebRequest.Request(new Uri("http://localhost/req")));
+ }
+
+ /// <summary>
+ /// Tests that HTTP Location headers that only use a relative path get interpreted correctly.
+ /// </summary>
+ [Test]
+ public void RelativeRedirect() {
+ UntrustedWebRequest.WhitelistHosts.Add("localhost");
+ UntrustedWebResponse resp1 = new UntrustedWebResponse(
+ new Uri("http://localhost/dir/file1"), new Uri("http://localhost/dir/file1"),
+ new WebHeaderCollection {
+ { HttpResponseHeader.Location, "file2" },
+ }, HttpStatusCode.Redirect, "text/html", null, new MemoryStream());
+ MockHttpRequest.RegisterMockResponse(resp1);
+ UntrustedWebResponse resp2 = new UntrustedWebResponse(
+ new Uri("http://localhost/dir/file2"), new Uri("http://localhost/dir/file2"),
+ new WebHeaderCollection(), HttpStatusCode.OK, "text/html", null, new MemoryStream());
+ MockHttpRequest.RegisterMockResponse(resp2);
+ Assert.AreSame(resp2, UntrustedWebRequest.Request(new Uri("http://localhost/dir/file1")));
+ }
}
}
diff --git a/src/DotNetOpenId.Test/UriIdentifierTests.cs b/src/DotNetOpenId.Test/UriIdentifierTests.cs index 16d8b51..d24a695 100644 --- a/src/DotNetOpenId.Test/UriIdentifierTests.cs +++ b/src/DotNetOpenId.Test/UriIdentifierTests.cs @@ -1,8 +1,10 @@ using System;
using System.Linq;
using System.Net;
+using System.Web;
using DotNetOpenId.Extensions.SimpleRegistration;
using DotNetOpenId.RelyingParty;
+using DotNetOpenId.Test.Mocks;
using NUnit.Framework;
namespace DotNetOpenId.Test {
@@ -18,6 +20,11 @@ namespace DotNetOpenId.Test { UntrustedWebRequest.WhitelistHosts.Add("localhost");
}
+ [TearDown]
+ public void TearDown() {
+ Mocks.MockHttpRequest.Reset();
+ }
+
[Test, ExpectedException(typeof(ArgumentNullException))]
public void CtorNullUri() {
new UriIdentifier((Uri)null);
@@ -42,6 +49,39 @@ namespace DotNetOpenId.Test { public void CtorGoodUri() {
var uri = new UriIdentifier(goodUri);
Assert.AreEqual(new Uri(goodUri), uri.Uri);
+ Assert.IsFalse(uri.SchemeImplicitlyPrepended);
+ Assert.IsFalse(uri.IsDiscoverySecureEndToEnd);
+ }
+
+ [Test]
+ public void CtorStringNoSchemeSecure() {
+ var uri = new UriIdentifier("host/path", true);
+ Assert.AreEqual("https://host/path", uri.Uri.AbsoluteUri);
+ Assert.IsTrue(uri.IsDiscoverySecureEndToEnd);
+ }
+
+ [Test]
+ public void CtorStringHttpsSchemeSecure() {
+ var uri = new UriIdentifier("https://host/path", true);
+ Assert.AreEqual("https://host/path", uri.Uri.AbsoluteUri);
+ Assert.IsTrue(uri.IsDiscoverySecureEndToEnd);
+ }
+
+ [Test, ExpectedException(typeof(ArgumentException))]
+ public void CtorStringHttpSchemeSecure() {
+ new UriIdentifier("http://host/path", true);
+ }
+
+ [Test]
+ public void CtorUriHttpsSchemeSecure() {
+ var uri = new UriIdentifier(new Uri("https://host/path"), true);
+ Assert.AreEqual("https://host/path", uri.Uri.AbsoluteUri);
+ Assert.IsTrue(uri.IsDiscoverySecureEndToEnd);
+ }
+
+ [Test, ExpectedException(typeof(ArgumentException))]
+ public void CtorUriHttpSchemeSecure() {
+ new UriIdentifier(new Uri("http://host/path"), true);
}
/// <summary>
@@ -100,45 +140,65 @@ namespace DotNetOpenId.Test { }
void discover(string url, ProtocolVersion version, Identifier expectedLocalId, bool expectSreg, bool useRedirect) {
+ discover(url, version, expectedLocalId, expectSreg, useRedirect, null);
+ }
+ void discover(string url, ProtocolVersion version, Identifier expectedLocalId, bool expectSreg, bool useRedirect, WebHeaderCollection headers) {
Protocol protocol = Protocol.Lookup(version);
UriIdentifier claimedId = TestSupport.GetFullUrl(url);
UriIdentifier userSuppliedIdentifier = TestSupport.GetFullUrl(
- "htmldiscovery/redirect.aspx?target=" + url);
+ "Discovery/htmldiscovery/redirect.aspx?target=" + url);
if (expectedLocalId == null) expectedLocalId = claimedId;
Identifier idToDiscover = useRedirect ? userSuppliedIdentifier : claimedId;
- // confirm the page exists (validates the test)
- WebRequest.Create(idToDiscover).GetResponse().Close();
+
+ string contentType;
+ if (url.EndsWith("html")) {
+ contentType = "text/html";
+ } else if (url.EndsWith("xml")) {
+ contentType = "application/xrds+xml";
+ } else {
+ throw new InvalidOperationException();
+ }
+ MockHttpRequest.RegisterMockResponse(new Uri(idToDiscover), claimedId, contentType,
+ headers ?? new WebHeaderCollection(), TestSupport.LoadEmbeddedFile(url));
+
ServiceEndpoint se = idToDiscover.Discover().FirstOrDefault();
Assert.IsNotNull(se, url + " failed to be discovered.");
Assert.AreSame(protocol, se.Protocol);
Assert.AreEqual(claimedId, se.ClaimedIdentifier);
Assert.AreEqual(expectedLocalId, se.ProviderLocalIdentifier);
Assert.AreEqual(expectSreg ? 2 : 1, se.ProviderSupportedServiceTypeUris.Length);
- Assert.IsTrue(Array.IndexOf(se.ProviderSupportedServiceTypeUris, protocol.ClaimedIdentifierServiceTypeURI)>=0);
+ Assert.IsTrue(Array.IndexOf(se.ProviderSupportedServiceTypeUris, protocol.ClaimedIdentifierServiceTypeURI) >= 0);
Assert.AreEqual(expectSreg, se.IsExtensionSupported(new ClaimsRequest()));
}
void discoverXrds(string page, ProtocolVersion version, Identifier expectedLocalId) {
- discover("/xrdsdiscovery/" + page + ".aspx", version, expectedLocalId, true, false);
- discover("/xrdsdiscovery/" + page + ".aspx", version, expectedLocalId, true, true);
+ discoverXrds(page, version, expectedLocalId, null);
+ }
+ void discoverXrds(string page, ProtocolVersion version, Identifier expectedLocalId, WebHeaderCollection headers) {
+ if (!page.Contains(".")) page += ".xml";
+ discover("/Discovery/xrdsdiscovery/" + page, version, expectedLocalId, true, false, headers);
+ discover("/Discovery/xrdsdiscovery/" + page, version, expectedLocalId, true, true, headers);
}
void discoverHtml(string page, ProtocolVersion version, Identifier expectedLocalId, bool useRedirect) {
- discover("/htmldiscovery/" + page, version, expectedLocalId, false, useRedirect);
+ discover("/Discovery/htmldiscovery/" + page, version, expectedLocalId, false, useRedirect);
}
void discoverHtml(string scenario, ProtocolVersion version, Identifier expectedLocalId) {
- string page = scenario + ".aspx";
+ string page = scenario + ".html";
discoverHtml(page, version, expectedLocalId, false);
discoverHtml(page, version, expectedLocalId, true);
}
void failDiscover(string url) {
UriIdentifier userSuppliedId = TestSupport.GetFullUrl(url);
- WebRequest.Create((Uri)userSuppliedId).GetResponse().Close(); // confirm the page exists ...
+
+ Mocks.MockHttpRequest.RegisterMockResponse(new Uri(userSuppliedId), userSuppliedId, "text/html",
+ TestSupport.LoadEmbeddedFile(url));
+
Assert.AreEqual(0, userSuppliedId.Discover().Count()); // ... but that no endpoint info is discoverable
}
void failDiscoverHtml(string scenario) {
- failDiscover("htmldiscovery/" + scenario + ".aspx");
+ failDiscover("/Discovery/htmldiscovery/" + scenario + ".html");
}
void failDiscoverXrds(string scenario) {
- failDiscover("xrdsdiscovery/" + scenario + ".aspx");
+ failDiscover("/Discovery/xrdsdiscovery/" + scenario + ".xml");
}
[Test]
public void HtmlDiscover_11() {
@@ -160,11 +220,17 @@ namespace DotNetOpenId.Test { }
[Test]
public void XrdsDiscoveryFromHead() {
- discoverXrds("XrdsReferencedInHead", ProtocolVersion.V10, null);
+ Mocks.MockHttpRequest.RegisterMockResponse(new Uri("http://localhost/xrds1020.xml"),
+ "application/xrds+xml", TestSupport.LoadEmbeddedFile("/Discovery/xrdsdiscovery/xrds1020.xml"));
+ discoverXrds("XrdsReferencedInHead.html", ProtocolVersion.V10, null);
}
[Test]
public void XrdsDiscoveryFromHttpHeader() {
- discoverXrds("XrdsReferencedInHttpHeader", ProtocolVersion.V10, null);
+ WebHeaderCollection headers = new WebHeaderCollection();
+ headers.Add("X-XRDS-Location", TestSupport.GetFullUrl("http://localhost/xrds1020.xml").AbsoluteUri);
+ Mocks.MockHttpRequest.RegisterMockResponse(new Uri("http://localhost/xrds1020.xml"),
+ "application/xrds+xml", TestSupport.LoadEmbeddedFile("/Discovery/xrdsdiscovery/xrds1020.xml"));
+ discoverXrds("XrdsReferencedInHttpHeader.html", ProtocolVersion.V10, null, headers);
}
[Test]
public void XrdsDirectDiscovery_10() {
@@ -179,5 +245,167 @@ namespace DotNetOpenId.Test { discoverXrds("xrds2010a", ProtocolVersion.V20, null);
discoverXrds("xrds2010b", ProtocolVersion.V20, null);
}
+
+ [Test]
+ public void NormalizeCase() {
+ // only the host name can be normalized in casing safely.
+ Identifier id = "http://HOST:80/PaTH?KeY=VaLUE#fRag";
+ Assert.AreEqual("http://host/PaTH?KeY=VaLUE#fRag", id.ToString());
+ // make sure https is preserved, along with port 80, which is NON-default for https
+ id = "https://HOST:80/PaTH?KeY=VaLUE#fRag";
+ Assert.AreEqual("https://host:80/PaTH?KeY=VaLUE#fRag", id.ToString());
+ }
+
+ [Test]
+ public void HttpSchemePrepended() {
+ UriIdentifier id = new UriIdentifier("www.yahoo.com");
+ Assert.AreEqual("http://www.yahoo.com/", id.ToString());
+ Assert.IsTrue(id.SchemeImplicitlyPrepended);
+ }
+
+ //[Test, Ignore("The spec says http:// must be prepended in this case, but that just creates an invalid URI. Our UntrustedWebRequest will stop disallowed schemes.")]
+ public void CtorDisallowedScheme() {
+ UriIdentifier id = new UriIdentifier(new Uri("ftp://host/path"));
+ Assert.AreEqual("http://ftp://host/path", id.ToString());
+ Assert.IsTrue(id.SchemeImplicitlyPrepended);
+ }
+
+ [Test]
+ public void DiscoveryWithRedirects() {
+ MockHttpRequest.Reset();
+ Identifier claimedId = TestSupport.GetMockIdentifier(TestSupport.Scenarios.AutoApproval, ProtocolVersion.V20);
+
+ // Add a couple of chained redirect pages that lead to the claimedId.
+ Uri userSuppliedUri = TestSupport.GetFullUrl("/someSecurePage", null, true);
+ Uri insecureMidpointUri = TestSupport.GetFullUrl("/insecureStop");
+ MockHttpRequest.RegisterMockRedirect(userSuppliedUri, insecureMidpointUri);
+ MockHttpRequest.RegisterMockRedirect(insecureMidpointUri, new Uri(claimedId.ToString()));
+
+ // don't require secure SSL discovery for this test.
+ Identifier userSuppliedIdentifier = new UriIdentifier(userSuppliedUri, false);
+ Assert.AreEqual(1, userSuppliedIdentifier.Discover().Count());
+ }
+
+ [Test]
+ public void TryRequireSslAdjustsIdentifier() {
+ Identifier secureId;
+ // Try Parse and ctor without explicit scheme
+ var id = Identifier.Parse("www.yahoo.com");
+ Assert.AreEqual("http://www.yahoo.com/", id.ToString());
+ Assert.IsTrue(id.TryRequireSsl(out secureId));
+ Assert.IsTrue(secureId.IsDiscoverySecureEndToEnd);
+ Assert.AreEqual("https://www.yahoo.com/", secureId.ToString());
+
+ id = new UriIdentifier("www.yahoo.com");
+ Assert.AreEqual("http://www.yahoo.com/", id.ToString());
+ Assert.IsTrue(id.TryRequireSsl(out secureId));
+ Assert.IsTrue(secureId.IsDiscoverySecureEndToEnd);
+ Assert.AreEqual("https://www.yahoo.com/", secureId.ToString());
+
+ // Try Parse and ctor with explicit http:// scheme
+ id = Identifier.Parse("http://www.yahoo.com");
+ Assert.IsFalse(id.TryRequireSsl(out secureId));
+ Assert.IsFalse(secureId.IsDiscoverySecureEndToEnd);
+ Assert.AreEqual("http://www.yahoo.com/", secureId.ToString());
+ Assert.AreEqual(0, secureId.Discover().Count());
+
+ id = new UriIdentifier("http://www.yahoo.com");
+ Assert.IsFalse(id.TryRequireSsl(out secureId));
+ Assert.IsFalse(secureId.IsDiscoverySecureEndToEnd);
+ Assert.AreEqual("http://www.yahoo.com/", secureId.ToString());
+ Assert.AreEqual(0, secureId.Discover().Count());
+ }
+
+ [Test]
+ public void DiscoverRequireSslWithSecureRedirects() {
+ MockHttpRequest.Reset();
+ Identifier claimedId = TestSupport.GetMockIdentifier(TestSupport.Scenarios.AutoApproval, ProtocolVersion.V20, true);
+
+ // Add a couple of chained redirect pages that lead to the claimedId.
+ // All redirects should be secure.
+ Uri userSuppliedUri = TestSupport.GetFullUrl("/someSecurePage", null, true);
+ Uri secureMidpointUri = TestSupport.GetFullUrl("/secureStop", null, true);
+ MockHttpRequest.RegisterMockRedirect(userSuppliedUri, secureMidpointUri);
+ MockHttpRequest.RegisterMockRedirect(secureMidpointUri, new Uri(claimedId.ToString()));
+
+ Identifier userSuppliedIdentifier = new UriIdentifier(userSuppliedUri, true);
+ Assert.AreEqual(1, userSuppliedIdentifier.Discover().Count());
+ }
+
+ [Test, ExpectedException(typeof(OpenIdException))]
+ public void DiscoverRequireSslWithInsecureRedirect() {
+ MockHttpRequest.Reset();
+ Identifier claimedId = TestSupport.GetMockIdentifier(TestSupport.Scenarios.AutoApproval, ProtocolVersion.V20, true);
+
+ // Add a couple of chained redirect pages that lead to the claimedId.
+ // Include an insecure HTTP jump in those redirects to verify that
+ // the ultimate endpoint is never found as a result of high security profile.
+ Uri userSuppliedUri = TestSupport.GetFullUrl("/someSecurePage", null, true);
+ Uri insecureMidpointUri = TestSupport.GetFullUrl("/insecureStop");
+ MockHttpRequest.RegisterMockRedirect(userSuppliedUri, insecureMidpointUri);
+ MockHttpRequest.RegisterMockRedirect(insecureMidpointUri, new Uri(claimedId.ToString()));
+
+ Identifier userSuppliedIdentifier = new UriIdentifier(userSuppliedUri, true);
+ userSuppliedIdentifier.Discover();
+ }
+
+ [Test]
+ public void DiscoveryRequireSslWithInsecureXrdsInSecureHtmlHead() {
+ var insecureXrdsSource = TestSupport.GetMockIdentifier(TestSupport.Scenarios.AutoApproval, ProtocolVersion.V20, false);
+ Uri secureClaimedUri = TestSupport.GetFullUrl("/secureId", null, true);
+
+ string html = string.Format("<html><head><meta http-equiv='X-XRDS-Location' content='{0}'/></head><body></body></html>",
+ insecureXrdsSource);
+ MockHttpRequest.RegisterMockResponse(secureClaimedUri, "text/html", html);
+
+ Identifier userSuppliedIdentifier = new UriIdentifier(secureClaimedUri, true);
+ Assert.AreEqual(0, userSuppliedIdentifier.Discover().Count());
+ }
+
+ [Test]
+ public void DiscoveryRequireSslWithInsecureXrdsInSecureHttpHeader() {
+ var insecureXrdsSource = TestSupport.GetMockIdentifier(TestSupport.Scenarios.AutoApproval, ProtocolVersion.V20, false);
+ Uri secureClaimedUri = TestSupport.GetFullUrl("/secureId", null, true);
+
+ string html = "<html><head></head><body></body></html>";
+ WebHeaderCollection headers = new WebHeaderCollection {
+ { "X-XRDS-Location", insecureXrdsSource }
+ };
+ MockHttpRequest.RegisterMockResponse(secureClaimedUri, secureClaimedUri, "text/html", headers, html);
+
+ Identifier userSuppliedIdentifier = new UriIdentifier(secureClaimedUri, true);
+ Assert.AreEqual(0, userSuppliedIdentifier.Discover().Count());
+ }
+
+ [Test]
+ public void DiscoveryRequireSslWithInsecureXrdsButSecureLinkTags() {
+ var insecureXrdsSource = TestSupport.GetMockIdentifier(TestSupport.Scenarios.AutoApproval, ProtocolVersion.V20, false);
+ Uri secureClaimedUri = TestSupport.GetFullUrl("/secureId", null, true);
+
+ Identifier localIdForLinkTag = TestSupport.GetDelegateUrl(TestSupport.Scenarios.AlwaysDeny, true);
+ string html = string.Format(@"
+ <html><head>
+ <meta http-equiv='X-XRDS-Location' content='{0}'/> <!-- this one will be insecure and ignored -->
+ <link rel='openid2.provider' href='{1}' />
+ <link rel='openid2.local_id' href='{2}' />
+ </head><body></body></html>",
+ HttpUtility.HtmlEncode(insecureXrdsSource),
+ HttpUtility.HtmlEncode(TestSupport.GetFullUrl("/" + TestSupport.ProviderPage, null, true).AbsoluteUri),
+ HttpUtility.HtmlEncode(localIdForLinkTag.ToString())
+ );
+ MockHttpRequest.RegisterMockResponse(secureClaimedUri, "text/html", html);
+
+ Identifier userSuppliedIdentifier = new UriIdentifier(secureClaimedUri, true);
+ Assert.AreEqual(localIdForLinkTag, userSuppliedIdentifier.Discover().Single().ProviderLocalIdentifier);
+ }
+
+ [Test]
+ public void DiscoveryRequiresSslIgnoresInsecureEndpointsInXrds() {
+ var insecureEndpoint = TestSupport.GetServiceEndpoint(TestSupport.Scenarios.AutoApproval, ProtocolVersion.V20, 10, false);
+ var secureEndpoint = TestSupport.GetServiceEndpoint(TestSupport.Scenarios.ApproveOnSetup, ProtocolVersion.V20, 20, true);
+ UriIdentifier secureClaimedId = new UriIdentifier(TestSupport.GetFullUrl("/claimedId", null, true), true);
+ MockHttpRequest.RegisterMockXrdsResponse(secureClaimedId, new ServiceEndpoint[] { insecureEndpoint, secureEndpoint });
+ Assert.AreEqual(secureEndpoint.ProviderLocalIdentifier, secureClaimedId.Discover().Single().ProviderLocalIdentifier);
+ }
}
}
diff --git a/src/DotNetOpenId.Test/XriIdentifierTests.cs b/src/DotNetOpenId.Test/XriIdentifierTests.cs index 8ce2726..3255776 100644 --- a/src/DotNetOpenId.Test/XriIdentifierTests.cs +++ b/src/DotNetOpenId.Test/XriIdentifierTests.cs @@ -1,9 +1,8 @@ using System;
using System.Collections.Generic;
-using System.IO;
using System.Linq;
-using System.Net;
using DotNetOpenId.RelyingParty;
+using DotNetOpenId.Test.Mocks;
using NUnit.Framework;
namespace DotNetOpenId.Test {
@@ -14,7 +13,7 @@ namespace DotNetOpenId.Test { [TearDown]
public void TearDown() {
- UntrustedWebRequest.MockRequests = null;
+ MockHttpRequest.Reset();
}
[Test, ExpectedException(typeof(ArgumentNullException))]
@@ -37,6 +36,15 @@ namespace DotNetOpenId.Test { var xri = new XriIdentifier(goodXri);
Assert.AreEqual(goodXri, xri.OriginalXri);
Assert.AreEqual(goodXri, xri.CanonicalXri); // assumes 'goodXri' is canonical already
+ Assert.IsFalse(xri.IsDiscoverySecureEndToEnd);
+ }
+
+ [Test]
+ public void CtorGoodXriSecure() {
+ var xri = new XriIdentifier(goodXri, true);
+ Assert.AreEqual(goodXri, xri.OriginalXri);
+ Assert.AreEqual(goodXri, xri.CanonicalXri); // assumes 'goodXri' is canonical already
+ Assert.IsTrue(xri.IsDiscoverySecureEndToEnd);
}
[Test]
@@ -123,14 +131,14 @@ namespace DotNetOpenId.Test { {"https://xri.net/=Arnott?_xrd_r=application/xrd%2Bxml;sep=false", xrds},
{"https://xri.net/=!9B72.7DD1.50A9.5CCD?_xrd_r=application/xrd%2Bxml;sep=false", xrds},
};
- UntrustedWebRequest.MockRequests = TestSupport.GenerateMockXrdsResponses(mocks);
+ MockHttpRequest.RegisterMockXrdsResponses(mocks);
string expectedCanonicalId = "=!9B72.7DD1.50A9.5CCD";
ServiceEndpoint se = verifyCanonicalId("=Arnott", expectedCanonicalId);
Assert.AreEqual(Protocol.v10, se.Protocol);
Assert.AreEqual("http://1id.com/sso", se.ProviderEndpoint.ToString());
Assert.AreEqual(se.ClaimedIdentifier, se.ProviderLocalIdentifier);
- Assert.AreEqual("=!9B72.7DD1.50A9.5CCD (=Arnott)", se.FriendlyIdentifierForDisplay);
+ Assert.AreEqual("=Arnott", se.FriendlyIdentifierForDisplay);
}
[Test]
@@ -349,21 +357,12 @@ uEyb50RJ7DWmXctSC0b3eymZ2lSXxAWNOsNy </X509Data>
</KeyInfo>
</XRD>";
- UntrustedWebRequest.MockRequests = TestSupport.GenerateMockXrdsResponses(new Dictionary<string, string> {
+ MockHttpRequest.RegisterMockXrdsResponses(new Dictionary<string, string> {
{ "https://xri.net/@llli?_xrd_r=application/xrd%2Bxml;sep=false", llliResponse},
- { "https://xri.net/@!72CD.A072.157E.A9C6?_xrd_r=application/xrd%2Bxml;sep=false", llliResponse},
-
{ "https://xri.net/@llli*area?_xrd_r=application/xrd%2Bxml;sep=false", llliAreaResponse},
- { "https://xri.net/@!72CD.A072.157E.A9C6!0000.0000.3B9A.CA0C?_xrd_r=application/xrd%2Bxml;sep=false", llliAreaResponse},
-
{ "https://xri.net/@llli*area*canada.unattached?_xrd_r=application/xrd%2Bxml;sep=false", llliAreaCanadaUnattachedResponse},
- { "https://xri.net/@!72CD.A072.157E.A9C6!0000.0000.3B9A.CA0C!0000.0000.3B9A.CA41?_xrd_r=application/xrd%2Bxml;sep=false", llliAreaCanadaUnattachedResponse},
-
{ "https://xri.net/@llli*area*canada.unattached*ada?_xrd_r=application/xrd%2Bxml;sep=false", llliAreaCanadaUnattachedAdaResponse},
- { "https://xri.net/@!72CD.A072.157E.A9C6!0000.0000.3B9A.CA0C!0000.0000.3B9A.CA41!0000.0000.3B9A.CA01?_xrd_r=application/xrd%2Bxml;sep=false", llliAreaCanadaUnattachedAdaResponse},
-
{ "https://xri.net/=Web?_xrd_r=application/xrd%2Bxml;sep=false", webResponse},
- { "https://xri.net/=!91F2.8153.F600.AE24?_xrd_r=application/xrd%2Bxml;sep=false", webResponse},
});
verifyCanonicalId("@llli", "@!72CD.A072.157E.A9C6");
verifyCanonicalId("@llli*area", "@!72CD.A072.157E.A9C6!0000.0000.3B9A.CA0C");
@@ -374,7 +373,7 @@ uEyb50RJ7DWmXctSC0b3eymZ2lSXxAWNOsNy [Test]
public void DiscoveryCommunityInameDelegateWithoutCanonicalID() {
- UntrustedWebRequest.MockRequests = TestSupport.GenerateMockXrdsResponses(new Dictionary<string, string> {
+ MockHttpRequest.RegisterMockXrdsResponses(new Dictionary<string, string> {
{ "https://xri.net/=Web*andrew.arnott?_xrd_r=application/xrd%2Bxml;sep=false", @"<?xml version='1.0' encoding='UTF-8'?>
<XRD xmlns='xri://$xrd*($v*2.0)'>
<Query>*andrew.arnott</Query>
@@ -458,5 +457,11 @@ uEyb50RJ7DWmXctSC0b3eymZ2lSXxAWNOsNy verifyCanonicalId("=Web*andrew.arnott", null);
verifyCanonicalId("@id*andrewarnott", null);
}
+
+ //[Test, Ignore("XRI parsing and normalization is not implemented (yet).")]
+ public void NormalizeCase() {
+ Identifier id = "=!9B72.7dd1.50a9.5ccd";
+ Assert.AreEqual("=!9B72.7DD1.50A9.5CCD", id.ToString());
+ }
}
}
diff --git a/src/DotNetOpenId.TestWeb/Default.aspx b/src/DotNetOpenId.TestWeb/Default.aspx index e8f6a1a..706baff 100644 --- a/src/DotNetOpenId.TestWeb/Default.aspx +++ b/src/DotNetOpenId.TestWeb/Default.aspx @@ -10,7 +10,7 @@ <html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title>Untitled Page</title>
- <openid:XrdsPublisher runat="server" ID="xrdsPublisher" XrdsUrl="~/rp_xrds.aspx" />
+ <openid:XrdsPublisher runat="server" ID="xrdsPublisher" XrdsUrl="~/rp_xrds.aspx" XrdsAdvertisement=Both />
</head>
<body>
<form id="form1" runat="server">
diff --git a/src/DotNetOpenId.TestWeb/ProviderEndpoint.aspx.cs b/src/DotNetOpenId.TestWeb/ProviderEndpoint.aspx.cs index 8eab9c7..362d4a8 100644 --- a/src/DotNetOpenId.TestWeb/ProviderEndpoint.aspx.cs +++ b/src/DotNetOpenId.TestWeb/ProviderEndpoint.aspx.cs @@ -8,99 +8,6 @@ using System.Globalization; using DotNetOpenId;
public partial class ProviderEndpoint : System.Web.UI.Page {
- const string nicknameTypeUri = WellKnownAttributes.Name.Alias;
- const string emailTypeUri = WellKnownAttributes.Contact.Email;
-
- IDictionary<string, AttributeValues> storedAttributes {
- get {
- var atts = (Dictionary<string, AttributeValues>)Application["storedAttributes"];
- if (atts == null) {
- atts = new Dictionary<string, AttributeValues>();
- Application["storedAttributes"] = atts;
- }
- return atts;
- }
- }
-
- void respondToExtensions(DotNetOpenId.Provider.IRequest request, TestSupport.Scenarios scenario) {
- var sregRequest = request.GetExtension<ClaimsRequest>();
- var sregResponse = sregRequest != null ? sregRequest.CreateResponse() : null;
- var aeFetchRequest = request.GetExtension<FetchRequest>();
- var aeFetchResponse = new FetchResponse();
- var aeStoreRequest = request.GetExtension<StoreRequest>();
- var aeStoreResponse = new StoreResponse();
- var papeRequest = request.GetExtension<PolicyRequest>();
- var papeResponse = new PolicyResponse();
- switch (scenario) {
- case TestSupport.Scenarios.ExtensionFullCooperation:
- if (sregRequest != null) {
- if (sregRequest.FullName != SregDemandLevel.NoRequest)
- sregResponse.FullName = "Andrew Arnott";
- if (sregRequest.Email != SregDemandLevel.NoRequest)
- sregResponse.Email = "andrewarnott@gmail.com";
- }
- if (aeFetchRequest != null) {
- var att = aeFetchRequest.GetAttribute(nicknameTypeUri);
- if (att != null)
- aeFetchResponse.AddAttribute(att.Respond("Andrew"));
- att = aeFetchRequest.GetAttribute(emailTypeUri);
- if (att != null) {
- string[] emails = new[] { "a@a.com", "b@b.com" };
- string[] subset = new string[Math.Min(emails.Length, att.Count)];
- Array.Copy(emails, subset, subset.Length);
- aeFetchResponse.AddAttribute(att.Respond(subset));
- }
- foreach (var att2 in aeFetchRequest.Attributes) {
- if (storedAttributes.ContainsKey(att2.TypeUri))
- aeFetchResponse.AddAttribute(storedAttributes[att2.TypeUri]);
- }
- }
- if (papeRequest != null) {
- if (papeRequest.MaximumAuthenticationAge.HasValue) {
- papeResponse.AuthenticationTimeUtc = DateTime.UtcNow - (papeRequest.MaximumAuthenticationAge.Value - TimeSpan.FromSeconds(30));
- }
- if (papeRequest.PreferredAuthLevelTypes.Contains("http://csrc.nist.gov/publications/nistpubs/800-63/SP800-63V1_0_2.pdf")) {
- papeResponse.NistAssuranceLevel = NistAssuranceLevel.Level1;
- }
- }
- break;
- case TestSupport.Scenarios.ExtensionPartialCooperation:
- if (sregRequest != null) {
- if (sregRequest.FullName == SregDemandLevel.Require)
- sregResponse.FullName = "Andrew Arnott";
- if (sregRequest.Email == SregDemandLevel.Require)
- sregResponse.Email = "andrewarnott@gmail.com";
- }
- if (aeFetchRequest != null) {
- var att = aeFetchRequest.GetAttribute(nicknameTypeUri);
- if (att != null && att.IsRequired)
- aeFetchResponse.AddAttribute(att.Respond("Andrew"));
- att = aeFetchRequest.GetAttribute(emailTypeUri);
- if (att != null && att.IsRequired) {
- string[] emails = new[] { "a@a.com", "b@b.com" };
- string[] subset = new string[Math.Min(emails.Length, att.Count)];
- Array.Copy(emails, subset, subset.Length);
- aeFetchResponse.AddAttribute(att.Respond(subset));
- }
- foreach (var att2 in aeFetchRequest.Attributes) {
- if (att2.IsRequired && storedAttributes.ContainsKey(att2.TypeUri))
- aeFetchResponse.AddAttribute(storedAttributes[att2.TypeUri]);
- }
- }
- break;
- }
- if (aeStoreRequest != null) {
- foreach (var att in aeStoreRequest.Attributes) {
- storedAttributes[att.TypeUri] = att;
- }
- aeStoreResponse.Succeeded = true;
- }
- if (sregRequest != null) request.AddResponseExtension(sregResponse);
- if (aeFetchRequest != null) request.AddResponseExtension(aeFetchResponse);
- if (aeStoreRequest != null) request.AddResponseExtension(aeStoreResponse);
- if (papeRequest != null) request.AddResponseExtension(papeResponse);
- }
-
protected void ProviderEndpoint1_AuthenticationChallenge(object sender, DotNetOpenId.Provider.AuthenticationChallengeEventArgs e) {
if (!e.Request.IsReturnUrlDiscoverable) {
throw new ArgumentException(string.Format(CultureInfo.CurrentCulture,
@@ -110,25 +17,13 @@ public partial class ProviderEndpoint : System.Web.UI.Page { new Uri(e.Request.LocalIdentifier).AbsolutePath.TrimStart('/'));
switch (scenario) {
case TestSupport.Scenarios.AutoApproval:
- // immediately approve
- e.Request.IsAuthenticated = true;
- break;
- case TestSupport.Scenarios.AutoApprovalAddFragment:
- e.Request.SetClaimedIdentifierFragment("frag");
e.Request.IsAuthenticated = true;
break;
case TestSupport.Scenarios.ApproveOnSetup:
e.Request.IsAuthenticated = !e.Request.Immediate;
break;
- case TestSupport.Scenarios.AlwaysDeny:
- e.Request.IsAuthenticated = false;
- break;
- case TestSupport.Scenarios.ExtensionFullCooperation:
- case TestSupport.Scenarios.ExtensionPartialCooperation:
- respondToExtensions(e.Request, scenario);
- e.Request.IsAuthenticated = true;
- break;
default:
+ // All other scenarios are done programmatically only.
throw new InvalidOperationException("Unrecognized scenario");
}
e.Request.Response.Send();
diff --git a/src/DotNetOpenId.TestWeb/htmldiscovery/redirect.aspx b/src/DotNetOpenId.TestWeb/htmldiscovery/redirect.aspx deleted file mode 100644 index 8b0373d..0000000 --- a/src/DotNetOpenId.TestWeb/htmldiscovery/redirect.aspx +++ /dev/null @@ -1,9 +0,0 @@ -<%@ Page Language="C#" %>
-
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
-
-<script runat="server">
- protected override void OnLoad(EventArgs e) {
- Response.Redirect(Request.QueryString["target"]);
- }
-</script>
diff --git a/src/DotNetOpenId.TestWeb/xrdsdiscovery/XrdsReferencedInHead.aspx b/src/DotNetOpenId.TestWeb/xrdsdiscovery/XrdsReferencedInHead.aspx deleted file mode 100644 index fabb2aa..0000000 --- a/src/DotNetOpenId.TestWeb/xrdsdiscovery/XrdsReferencedInHead.aspx +++ /dev/null @@ -1,17 +0,0 @@ -<%@ Page Language="C#" %>
-
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
-
-<script runat="server">
-</script>
-
-<html xmlns="http://www.w3.org/1999/xhtml">
-<head runat="server">
- <title>Untitled Page</title>
- <meta http-equiv="X-XRDS-Location" content="<%=Util.GetFullUrl("xrds1020.aspx")%>"/>
-</head>
-<body>
- <form id="form1" runat="server">
- </form>
-</body>
-</html>
diff --git a/src/DotNetOpenId.TestWeb/xrdsdiscovery/XrdsReferencedInHttpHeader.aspx b/src/DotNetOpenId.TestWeb/xrdsdiscovery/XrdsReferencedInHttpHeader.aspx deleted file mode 100644 index b31c707..0000000 --- a/src/DotNetOpenId.TestWeb/xrdsdiscovery/XrdsReferencedInHttpHeader.aspx +++ /dev/null @@ -1,20 +0,0 @@ -<%@ Page Language="C#" %>
-
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
-
-<script runat="server">
- protected override void OnLoad(EventArgs e) {
- base.OnLoad(e);
- Response.AddHeader("X-XRDS-Location", Util.GetFullUrl("xrds1020.aspx").AbsoluteUri);
- }
-</script>
-
-<html xmlns="http://www.w3.org/1999/xhtml">
-<head runat="server">
- <title>Untitled Page</title>
-</head>
-<body>
- <form id="form1" runat="server">
- </form>
-</body>
-</html>
diff --git a/src/DotNetOpenId.sln b/src/DotNetOpenId.sln index e3371ce..bfd2752 100644 --- a/src/DotNetOpenId.sln +++ b/src/DotNetOpenId.sln @@ -3,6 +3,7 @@ Microsoft Visual Studio Solution File, Format Version 10.00 # Visual Studio 2008
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Docs", "Docs", "{14D00F8C-C7CB-49B3-A668-7D0C5A4A6D9F}"
ProjectSection(SolutionItems) = preProject
+ ..\doc\Configuration.htm = ..\doc\Configuration.htm
..\doc\README.html = ..\doc\README.html
..\doc\WebFarms.htm = ..\doc\WebFarms.htm
EndProjectSection
@@ -40,10 +41,6 @@ Project("{E24C65DC-7377-472B-9ABA-BC803B73C61A}") = "DotNetOpenId.TestWeb", "Dot StartServerOnDebug = "false"
EndProjectSection
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RelyingPartyCustomStore", "..\samples\RelyingPartyCustomStore\RelyingPartyCustomStore.csproj", "{DB54DC19-BA56-4C22-A8A0-C49289EA4F53}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ProviderCustomStore", "..\samples\ProviderCustomStore\ProviderCustomStore.csproj", "{2D0B2C39-3F90-484E-848B-F3EF956835C3}"
-EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RelyingPartyMvc", "..\samples\RelyingPartyMvc\RelyingPartyMvc.csproj", "{07B193F1-68AD-4E9C-98AF-BEFB5E9403CB}"
EndProject
Global
@@ -106,26 +103,6 @@ Global {C733BE07-37B5-4328-8C1A-81F129670E6E}.Release|Any CPU.ActiveCfg = Debug|.NET
{C733BE07-37B5-4328-8C1A-81F129670E6E}.Release|Mixed Platforms.ActiveCfg = Debug|.NET
{C733BE07-37B5-4328-8C1A-81F129670E6E}.Release|Mixed Platforms.Build.0 = Debug|.NET
- {DB54DC19-BA56-4C22-A8A0-C49289EA4F53}.Debug|.NET.ActiveCfg = Debug|Any CPU
- {DB54DC19-BA56-4C22-A8A0-C49289EA4F53}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {DB54DC19-BA56-4C22-A8A0-C49289EA4F53}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {DB54DC19-BA56-4C22-A8A0-C49289EA4F53}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
- {DB54DC19-BA56-4C22-A8A0-C49289EA4F53}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
- {DB54DC19-BA56-4C22-A8A0-C49289EA4F53}.Release|.NET.ActiveCfg = Release|Any CPU
- {DB54DC19-BA56-4C22-A8A0-C49289EA4F53}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {DB54DC19-BA56-4C22-A8A0-C49289EA4F53}.Release|Any CPU.Build.0 = Release|Any CPU
- {DB54DC19-BA56-4C22-A8A0-C49289EA4F53}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
- {DB54DC19-BA56-4C22-A8A0-C49289EA4F53}.Release|Mixed Platforms.Build.0 = Release|Any CPU
- {2D0B2C39-3F90-484E-848B-F3EF956835C3}.Debug|.NET.ActiveCfg = Debug|Any CPU
- {2D0B2C39-3F90-484E-848B-F3EF956835C3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {2D0B2C39-3F90-484E-848B-F3EF956835C3}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {2D0B2C39-3F90-484E-848B-F3EF956835C3}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
- {2D0B2C39-3F90-484E-848B-F3EF956835C3}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
- {2D0B2C39-3F90-484E-848B-F3EF956835C3}.Release|.NET.ActiveCfg = Release|Any CPU
- {2D0B2C39-3F90-484E-848B-F3EF956835C3}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {2D0B2C39-3F90-484E-848B-F3EF956835C3}.Release|Any CPU.Build.0 = Release|Any CPU
- {2D0B2C39-3F90-484E-848B-F3EF956835C3}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
- {2D0B2C39-3F90-484E-848B-F3EF956835C3}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{07B193F1-68AD-4E9C-98AF-BEFB5E9403CB}.Debug|.NET.ActiveCfg = Debug|Any CPU
{07B193F1-68AD-4E9C-98AF-BEFB5E9403CB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{07B193F1-68AD-4E9C-98AF-BEFB5E9403CB}.Debug|Any CPU.Build.0 = Debug|Any CPU
@@ -143,8 +120,6 @@ Global GlobalSection(NestedProjects) = preSolution
{2A59DE0A-B76A-4B42-9A33-04D34548353D} = {48A90678-A754-4E6E-98E2-7C519607C85F}
{51BCD5E9-E17A-4FB2-BAC8-C156DD7A1CA4} = {48A90678-A754-4E6E-98E2-7C519607C85F}
- {DB54DC19-BA56-4C22-A8A0-C49289EA4F53} = {48A90678-A754-4E6E-98E2-7C519607C85F}
- {2D0B2C39-3F90-484E-848B-F3EF956835C3} = {48A90678-A754-4E6E-98E2-7C519607C85F}
{07B193F1-68AD-4E9C-98AF-BEFB5E9403CB} = {48A90678-A754-4E6E-98E2-7C519607C85F}
EndGlobalSection
EndGlobal
diff --git a/src/DotNetOpenId/Association.cs b/src/DotNetOpenId/Association.cs index 5c9c302..ee3fd70 100644 --- a/src/DotNetOpenId/Association.cs +++ b/src/DotNetOpenId/Association.cs @@ -56,11 +56,11 @@ namespace DotNetOpenId { TimeSpan remainingLifeLength = expires - 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.
- if(secret.Length == CryptUtil.Sha1.HashSize / 8)
- return new HmacSha1Association(handle, secret, remainingLifeLength);
- if (secret.Length == CryptUtil.Sha256.HashSize / 8)
- return new HmacSha256Association(handle, secret, remainingLifeLength);
- throw new ArgumentException(Strings.BadAssociationPrivateData, "privateData");
+ try {
+ return HmacShaAssociation.Create(secret.Length, handle, secret, remainingLifeLength);
+ } catch (ArgumentException ex) {
+ throw new ArgumentException(Strings.BadAssociationPrivateData, "privateData", ex);
+ }
}
static TimeSpan minimumUsefulAssociationLifetime {
diff --git a/src/DotNetOpenId/Configuration/ProviderSection.cs b/src/DotNetOpenId/Configuration/ProviderSection.cs new file mode 100644 index 0000000..cfd6052 --- /dev/null +++ b/src/DotNetOpenId/Configuration/ProviderSection.cs @@ -0,0 +1,27 @@ +using System.Configuration;
+using DotNetOpenId.Provider;
+using IProviderAssociationStore = DotNetOpenId.IAssociationStore<DotNetOpenId.AssociationRelyingPartyType>;
+
+namespace DotNetOpenId.Configuration {
+ internal class ProviderSection : ConfigurationSection {
+ internal static ProviderSection Configuration {
+ get { return (ProviderSection)ConfigurationManager.GetSection("dotNetOpenId/provider") ?? new ProviderSection(); }
+ }
+
+ public ProviderSection() { }
+
+ const string securitySettingsConfigName = "security";
+ [ConfigurationProperty(securitySettingsConfigName)]
+ public ProviderSecuritySettingsElement SecuritySettings {
+ get { return (ProviderSecuritySettingsElement)this[securitySettingsConfigName] ?? new ProviderSecuritySettingsElement(); }
+ set { this[securitySettingsConfigName] = value; }
+ }
+
+ const string storeConfigName = "store";
+ [ConfigurationProperty(storeConfigName)]
+ public StoreConfigurationElement<IProviderAssociationStore> Store {
+ get { return (StoreConfigurationElement<IProviderAssociationStore>)this[storeConfigName] ?? new StoreConfigurationElement<IProviderAssociationStore>(); }
+ set { this[storeConfigName] = value; }
+ }
+ }
+}
diff --git a/src/DotNetOpenId/Configuration/ProviderSecuritySettingsElement.cs b/src/DotNetOpenId/Configuration/ProviderSecuritySettingsElement.cs new file mode 100644 index 0000000..8f3ffb8 --- /dev/null +++ b/src/DotNetOpenId/Configuration/ProviderSecuritySettingsElement.cs @@ -0,0 +1,38 @@ +using System.Configuration;
+using DotNetOpenId.Provider;
+
+namespace DotNetOpenId.Configuration {
+ internal class ProviderSecuritySettingsElement : ConfigurationElement {
+ public ProviderSecuritySettingsElement() {
+ }
+
+ public ProviderSecuritySettings CreateSecuritySettings() {
+ ProviderSecuritySettings settings = new ProviderSecuritySettings();
+ settings.MinimumHashBitLength = MinimumHashBitLength;
+ settings.MaximumHashBitLength = MaximumHashBitLength;
+ settings.ProtectDownlevelReplayAttacks = ProtectDownlevelReplayAttacks;
+ return settings;
+ }
+
+ const string minimumHashBitLengthConfigName = "minimumHashBitLength";
+ [ConfigurationProperty(minimumHashBitLengthConfigName, DefaultValue = DotNetOpenId.SecuritySettings.minimumHashBitLengthDefault)]
+ public int MinimumHashBitLength {
+ get { return (int)this[minimumHashBitLengthConfigName]; }
+ set { this[minimumHashBitLengthConfigName] = value; }
+ }
+
+ const string maximumHashBitLengthConfigName = "maximumHashBitLength";
+ [ConfigurationProperty(maximumHashBitLengthConfigName, DefaultValue = DotNetOpenId.SecuritySettings.maximumHashBitLengthRPDefault)]
+ public int MaximumHashBitLength {
+ get { return (int)this[maximumHashBitLengthConfigName]; }
+ set { this[maximumHashBitLengthConfigName] = value; }
+ }
+
+ const string protectDownlevelReplayAttacksConfigName = "protectDownlevelReplayAttacks";
+ [ConfigurationProperty(protectDownlevelReplayAttacksConfigName, DefaultValue = false)]
+ public bool ProtectDownlevelReplayAttacks {
+ get { return (bool)this[protectDownlevelReplayAttacksConfigName]; }
+ set { this[protectDownlevelReplayAttacksConfigName] = value; }
+ }
+ }
+}
diff --git a/src/DotNetOpenId/Configuration/RelyingPartySection.cs b/src/DotNetOpenId/Configuration/RelyingPartySection.cs new file mode 100644 index 0000000..100641c --- /dev/null +++ b/src/DotNetOpenId/Configuration/RelyingPartySection.cs @@ -0,0 +1,27 @@ +using System.Configuration;
+using DotNetOpenId.RelyingParty;
+
+namespace DotNetOpenId.Configuration {
+ internal class RelyingPartySection : ConfigurationSection {
+ internal static RelyingPartySection Configuration {
+ get { return (RelyingPartySection)ConfigurationManager.GetSection("dotNetOpenId/relyingParty") ?? new RelyingPartySection(); }
+ }
+
+ public RelyingPartySection() {
+ }
+
+ const string securitySettingsConfigName = "security";
+ [ConfigurationProperty(securitySettingsConfigName)]
+ public RelyingPartySecuritySettingsElement SecuritySettings {
+ get { return (RelyingPartySecuritySettingsElement)this[securitySettingsConfigName] ?? new RelyingPartySecuritySettingsElement(); }
+ set { this[securitySettingsConfigName] = value; }
+ }
+
+ const string storeConfigName = "store";
+ [ConfigurationProperty(storeConfigName)]
+ public StoreConfigurationElement<IRelyingPartyApplicationStore> Store {
+ get { return (StoreConfigurationElement<IRelyingPartyApplicationStore>)this[storeConfigName] ?? new StoreConfigurationElement<IRelyingPartyApplicationStore>(); }
+ set { this[storeConfigName] = value; }
+ }
+ }
+}
diff --git a/src/DotNetOpenId/Configuration/RelyingPartySecuritySettingsElement.cs b/src/DotNetOpenId/Configuration/RelyingPartySecuritySettingsElement.cs new file mode 100644 index 0000000..a76d993 --- /dev/null +++ b/src/DotNetOpenId/Configuration/RelyingPartySecuritySettingsElement.cs @@ -0,0 +1,45 @@ +using System.Configuration;
+using DotNetOpenId.RelyingParty;
+
+namespace DotNetOpenId.Configuration {
+ internal class RelyingPartySecuritySettingsElement : ConfigurationElement {
+ public RelyingPartySecuritySettingsElement() { }
+
+ public RelyingPartySecuritySettings CreateSecuritySettings() {
+ RelyingPartySecuritySettings settings = new RelyingPartySecuritySettings();
+ settings.RequireSsl = RequireSsl;
+ settings.MinimumRequiredOpenIdVersion = MinimumRequiredOpenIdVersion;
+ settings.MinimumHashBitLength = MinimumHashBitLength;
+ settings.MaximumHashBitLength = MaximumHashBitLength;
+ return settings;
+ }
+
+ const string requireSslConfigName = "requireSsl";
+ [ConfigurationProperty(requireSslConfigName, DefaultValue = false)]
+ public bool RequireSsl {
+ get { return (bool)this[requireSslConfigName]; }
+ set { this[requireSslConfigName] = value; }
+ }
+
+ const string minimumRequiredOpenIdVersionConfigName = "minimumRequiredOpenIdVersion";
+ [ConfigurationProperty(minimumRequiredOpenIdVersionConfigName, DefaultValue = "V10")]
+ public ProtocolVersion MinimumRequiredOpenIdVersion {
+ get { return (ProtocolVersion)this[minimumRequiredOpenIdVersionConfigName]; }
+ set { this[minimumRequiredOpenIdVersionConfigName] = value; }
+ }
+
+ const string minimumHashBitLengthConfigName = "minimumHashBitLength";
+ [ConfigurationProperty(minimumHashBitLengthConfigName, DefaultValue = DotNetOpenId.SecuritySettings.minimumHashBitLengthDefault)]
+ public int MinimumHashBitLength {
+ get { return (int)this[minimumHashBitLengthConfigName]; }
+ set { this[minimumHashBitLengthConfigName] = value; }
+ }
+
+ const string maximumHashBitLengthConfigName = "maximumHashBitLength";
+ [ConfigurationProperty(maximumHashBitLengthConfigName, DefaultValue = DotNetOpenId.SecuritySettings.maximumHashBitLengthRPDefault)]
+ public int MaximumHashBitLength {
+ get { return (int)this[maximumHashBitLengthConfigName]; }
+ set { this[maximumHashBitLengthConfigName] = value; }
+ }
+ }
+}
diff --git a/src/DotNetOpenId/Configuration/StoreElement.cs b/src/DotNetOpenId/Configuration/StoreElement.cs new file mode 100644 index 0000000..d2c94cb --- /dev/null +++ b/src/DotNetOpenId/Configuration/StoreElement.cs @@ -0,0 +1,24 @@ +using System;
+using System.Configuration;
+
+namespace DotNetOpenId.Configuration {
+ internal class StoreConfigurationElement<T> : ConfigurationElement {
+ public StoreConfigurationElement() { }
+
+ const string customStoreTypeConfigName = "type";
+ [ConfigurationProperty(customStoreTypeConfigName)]
+ //[SubclassTypeValidator(typeof(T))]
+ public string TypeName {
+ get { return (string)this[customStoreTypeConfigName]; }
+ set { this[customStoreTypeConfigName] = value; }
+ }
+
+ public Type CustomStoreType {
+ get { return string.IsNullOrEmpty(TypeName) ? null : Type.GetType(TypeName); }
+ }
+
+ public T CreateInstanceOfStore(T defaultValue) {
+ return CustomStoreType != null ? (T)Activator.CreateInstance(CustomStoreType) : defaultValue;
+ }
+ }
+}
diff --git a/src/DotNetOpenId/Configuration/UntrustedWebRequestSection.cs b/src/DotNetOpenId/Configuration/UntrustedWebRequestSection.cs new file mode 100644 index 0000000..392acf8 --- /dev/null +++ b/src/DotNetOpenId/Configuration/UntrustedWebRequestSection.cs @@ -0,0 +1,78 @@ +using System;
+using System.Configuration;
+
+namespace DotNetOpenId.Configuration {
+ internal class UntrustedWebRequestSection : ConfigurationSection {
+ internal static UntrustedWebRequestSection Configuration {
+ get { return (UntrustedWebRequestSection)ConfigurationManager.GetSection("dotNetOpenId/untrustedWebRequest") ?? new UntrustedWebRequestSection(); }
+ }
+
+ public UntrustedWebRequestSection() {
+ SectionInformation.AllowLocation = false;
+ }
+
+ const string readWriteTimeoutConfigName = "readWriteTimeout";
+ [ConfigurationProperty(readWriteTimeoutConfigName, DefaultValue = "00:00:00.800")]
+ [PositiveTimeSpanValidator]
+ public TimeSpan ReadWriteTimeout {
+ get { return (TimeSpan)this[readWriteTimeoutConfigName]; }
+ set { this[readWriteTimeoutConfigName] = value; }
+ }
+
+ const string timeoutConfigName = "timeout";
+ [ConfigurationProperty(timeoutConfigName, DefaultValue = "00:00:10")]
+ [PositiveTimeSpanValidator]
+ public TimeSpan Timeout {
+ get { return (TimeSpan)this[timeoutConfigName]; }
+ set { this[timeoutConfigName] = value; }
+ }
+
+ const string maximumBytesToReadConfigName = "maximumBytesToRead";
+ [ConfigurationProperty(maximumBytesToReadConfigName, DefaultValue = 1024 * 1024)]
+ [IntegerValidator(MinValue = 2048)]
+ public int MaximumBytesToRead {
+ get { return (int)this[maximumBytesToReadConfigName]; }
+ set { this[maximumBytesToReadConfigName] = value; }
+ }
+
+ const string maximumRedirectionsConfigName = "maximumRedirections";
+ [ConfigurationProperty(maximumRedirectionsConfigName, DefaultValue = 10)]
+ [IntegerValidator(MinValue = 0)]
+ public int MaximumRedirections {
+ get { return (int)this[maximumRedirectionsConfigName]; }
+ set { this[maximumRedirectionsConfigName] = value; }
+ }
+
+ const string whitelistHostsConfigName = "whitelistHosts";
+ [ConfigurationProperty(whitelistHostsConfigName, IsDefaultCollection = false)]
+ [ConfigurationCollection(typeof(WhiteBlackListCollection))]
+ public WhiteBlackListCollection WhitelistHosts {
+ get { return (WhiteBlackListCollection)this[whitelistHostsConfigName] ?? new WhiteBlackListCollection(); }
+ set { this[whitelistHostsConfigName] = value; }
+ }
+
+ const string blacklistHostsConfigName = "blacklistHosts";
+ [ConfigurationProperty(blacklistHostsConfigName, IsDefaultCollection = false)]
+ [ConfigurationCollection(typeof(WhiteBlackListCollection))]
+ public WhiteBlackListCollection BlacklistHosts {
+ get { return (WhiteBlackListCollection)this[blacklistHostsConfigName] ?? new WhiteBlackListCollection(); }
+ set { this[blacklistHostsConfigName] = value; }
+ }
+
+ const string whitelistHostsRegexConfigName = "whitelistHostsRegex";
+ [ConfigurationProperty(whitelistHostsRegexConfigName, IsDefaultCollection = false)]
+ [ConfigurationCollection(typeof(WhiteBlackListCollection))]
+ public WhiteBlackListCollection WhitelistHostsRegex {
+ get { return (WhiteBlackListCollection)this[whitelistHostsRegexConfigName] ?? new WhiteBlackListCollection(); }
+ set { this[whitelistHostsRegexConfigName] = value; }
+ }
+
+ const string blacklistHostsRegexConfigName = "blacklistHostsRegex";
+ [ConfigurationProperty(blacklistHostsRegexConfigName, IsDefaultCollection = false)]
+ [ConfigurationCollection(typeof(WhiteBlackListCollection))]
+ public WhiteBlackListCollection BlacklistHostsRegex {
+ get { return (WhiteBlackListCollection)this[blacklistHostsRegexConfigName] ?? new WhiteBlackListCollection(); }
+ set { this[blacklistHostsRegexConfigName] = value; }
+ }
+ }
+}
diff --git a/src/DotNetOpenId/Configuration/WhiteBlackListCollection.cs b/src/DotNetOpenId/Configuration/WhiteBlackListCollection.cs new file mode 100644 index 0000000..29485d1 --- /dev/null +++ b/src/DotNetOpenId/Configuration/WhiteBlackListCollection.cs @@ -0,0 +1,33 @@ +using System.Collections.Generic;
+using System.Configuration;
+using System.Text.RegularExpressions;
+
+namespace DotNetOpenId.Configuration {
+ internal class WhiteBlackListCollection : ConfigurationElementCollection {
+ public WhiteBlackListCollection() { }
+
+ protected override ConfigurationElement CreateNewElement() {
+ return new WhiteBlackListElement();
+ }
+
+ protected override object GetElementKey(ConfigurationElement element) {
+ return ((WhiteBlackListElement)element).Name;
+ }
+
+ internal IEnumerable<string> KeysAsStrings {
+ get {
+ foreach (WhiteBlackListElement element in this) {
+ yield return element.Name;
+ }
+ }
+ }
+
+ internal IEnumerable<Regex> KeysAsRegexs {
+ get {
+ foreach (WhiteBlackListElement element in this) {
+ yield return new Regex(element.Name);
+ }
+ }
+ }
+ }
+}
diff --git a/src/DotNetOpenId/Configuration/WhiteBlackListElement.cs b/src/DotNetOpenId/Configuration/WhiteBlackListElement.cs new file mode 100644 index 0000000..02a909d --- /dev/null +++ b/src/DotNetOpenId/Configuration/WhiteBlackListElement.cs @@ -0,0 +1,13 @@ +using System.Configuration;
+
+namespace DotNetOpenId.Configuration {
+ internal class WhiteBlackListElement : ConfigurationElement {
+ const string nameConfigName = "name";
+ [ConfigurationProperty(nameConfigName, IsRequired = true)]
+ //[StringValidator(MinLength = 1)]
+ public string Name {
+ get { return (string)this[nameConfigName]; }
+ set { this[nameConfigName] = value; }
+ }
+ }
+}
diff --git a/src/DotNetOpenId/CryptUtil.cs b/src/DotNetOpenId/DiffieHellmanUtil.cs index 254b4cb..492f757 100644 --- a/src/DotNetOpenId/CryptUtil.cs +++ b/src/DotNetOpenId/DiffieHellmanUtil.cs @@ -1,13 +1,48 @@ -using System;
+using System;
+using System.Globalization;
using System.Collections.Generic;
-using System.Text;
using System.Security.Cryptography;
using Org.Mentalis.Security.Cryptography;
-using System.Globalization;
-
namespace DotNetOpenId {
- internal static class CryptUtil {
+ class DiffieHellmanUtil {
+ class DHSha {
+ public DHSha(HashAlgorithm algorithm, Util.Func<Protocol, string> getName) {
+ if (algorithm == null) throw new ArgumentNullException("algorithm");
+ if (getName == null) throw new ArgumentNullException("getName");
+
+ GetName = getName;
+ Algorithm = algorithm;
+ }
+ internal Util.Func<Protocol, string> GetName;
+ internal readonly HashAlgorithm Algorithm;
+ }
+
+ 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();
+
+ public static HashAlgorithm Lookup(Protocol protocol, string name) {
+ foreach (DHSha dhsha in DiffieHellmanSessionTypes) {
+ if (String.Equals(dhsha.GetName(protocol), name, StringComparison.Ordinal)) {
+ return dhsha.Algorithm;
+ }
+ }
+ throw new ArgumentOutOfRangeException("name");
+ }
+
+ public static string GetNameForSize(Protocol protocol, int hashSizeInBits) {
+ foreach (DHSha dhsha in DiffieHellmanSessionTypes) {
+ if (dhsha.Algorithm.HashSize == hashSizeInBits) {
+ return dhsha.GetName(protocol);
+ }
+ }
+ return null;
+ }
+
public static byte[] DEFAULT_GEN = { 2 };
public static byte[] DEFAULT_MOD = {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,
@@ -19,13 +54,6 @@ namespace DotNetOpenId { 154, 72, 59, 138, 118, 34, 62, 93, 73, 10, 37, 127, 5, 189, 255, 22,
242, 251, 34, 197, 131, 171};
- internal static SHA1CryptoServiceProvider Sha1 = new SHA1CryptoServiceProvider();
- internal static SHA256Managed Sha256 = new SHA256Managed();
-
- public static string UnsignedToBase64(byte[] inputBytes) {
- return Convert.ToBase64String(ensurePositive(inputBytes));
- }
-
public static DiffieHellman CreateDiffieHellman() {
return new DiffieHellmanManaged(DEFAULT_MOD, DEFAULT_GEN, 1024);
}
@@ -46,6 +74,10 @@ namespace DotNetOpenId { return secret;
}
+ public static string UnsignedToBase64(byte[] inputBytes) {
+ return Convert.ToBase64String(ensurePositive(inputBytes));
+ }
+
/// <summary>
/// Ensures that the big integer represented by a given series of bytes
/// is a positive integer.
diff --git a/src/DotNetOpenId/DotNetOpenId.csproj b/src/DotNetOpenId/DotNetOpenId.csproj index 6916d24..9360b99 100644 --- a/src/DotNetOpenId/DotNetOpenId.csproj +++ b/src/DotNetOpenId/DotNetOpenId.csproj @@ -2,7 +2,7 @@ <PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
- <ProductVersion>9.0.21022</ProductVersion>
+ <ProductVersion>9.0.30729</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>{5D6EDC86-F5B2-4786-8376-4E7C24C63D39}</ProjectGuid>
<OutputType>Library</OutputType>
@@ -21,7 +21,7 @@ <WarningLevel>4</WarningLevel>
<AllowUnsafeBlocks>false</AllowUnsafeBlocks>
<DocumentationFile>..\..\bin\debug\DotNetOpenId.xml</DocumentationFile>
- <RunCodeAnalysis>true</RunCodeAnalysis>
+ <RunCodeAnalysis>false</RunCodeAnalysis>
<CodeAnalysisRules>-Microsoft.Design#CA1054;-Microsoft.Design#CA1056;-Microsoft.Design#CA1055</CodeAnalysisRules>
<NoWarn>
</NoWarn>
@@ -49,6 +49,7 @@ <HintPath>..\..\lib\log4net.dll</HintPath>
</Reference>
<Reference Include="System" />
+ <Reference Include="System.configuration" />
<Reference Include="System.Data" />
<Reference Include="System.Drawing" />
<Reference Include="System.Web" />
@@ -59,7 +60,23 @@ <Compile Include="Association.cs" />
<Compile Include="AssociationMemoryStore.cs" />
<Compile Include="Associations.cs" />
+ <Compile Include="Configuration\UntrustedWebRequestSection.cs" />
+ <Compile Include="Configuration\WhiteBlackListCollection.cs" />
+ <Compile Include="Configuration\WhiteBlackListElement.cs" />
+ <Compile Include="DiffieHellmanUtil.cs" />
+ <Compile Include="Extensions\ExtensionManager.cs" />
+ <Compile Include="Provider\ProviderSecuritySettings.cs" />
+ <Compile Include="Configuration\ProviderSecuritySettingsElement.cs" />
+ <Compile Include="Configuration\ProviderSection.cs" />
+ <Compile Include="Configuration\RelyingPartySection.cs" />
+ <Compile Include="RelyingParty\RelyingPartySecuritySettings.cs" />
+ <Compile Include="Configuration\RelyingPartySecuritySettingsElement.cs" />
+ <Compile Include="Configuration\StoreElement.cs" />
+ <Compile Include="SecuritySettings.cs" />
+ <Compile Include="NoDiscoveryIdentifier.cs" />
<Compile Include="Provider\SigningMessageEncoder.cs" />
+ <Compile Include="RelyingParty\DirectMessageHttpChannel.cs" />
+ <Compile Include="RelyingParty\IDirectMessageChannel.cs" />
<Compile Include="RelyingParty\IndirectMessageRequest.cs" />
<Compile Include="ExtensionArgumentsManager.cs" />
<Compile Include="Extensions\AliasManager.cs" />
@@ -78,7 +95,6 @@ <Compile Include="Extensions\AttributeExchange\StoreRequest.cs" />
<Compile Include="Extensions\AttributeExchange\StoreResponse.cs" />
<Compile Include="Extensions\IExtension.cs" />
- <Compile Include="HmacSha256Association.cs" />
<Compile Include="Identifier.cs" />
<Compile Include="IResponse.cs" />
<Compile Include="Loggers\TraceLogger.cs" />
@@ -96,6 +112,7 @@ <Compile Include="RelyingParty\CheckAuthResponse.cs" />
<Compile Include="RelyingParty\ApplicationMemoryStore.cs" />
<Compile Include="RelyingParty\IProviderEndpoint.cs" />
+ <Compile Include="RelyingParty\ISetupRequiredAuthenticationResponse.cs" />
<Compile Include="RelyingParty\IXrdsProviderEndpoint.cs" />
<Compile Include="RelyingParty\OpenIdMobileTextBox.cs" />
<Compile Include="RelyingParty\DirectRequest.cs" />
@@ -111,7 +128,7 @@ <Compile Include="RelyingParty\OpenIdRelyingParty.cs" />
<Compile Include="RelyingParty\Token.cs" />
<Compile Include="GlobalSuppressions.cs" />
- <Compile Include="HmacSha1Association.cs" />
+ <Compile Include="HmacShaAssociation.cs" />
<Compile Include="HttpEncoding.cs" />
<Compile Include="IAssociationStore.cs" />
<Compile Include="KeyValueFormEncoding.cs" />
@@ -136,7 +153,6 @@ <Compile Include="RelyingParty\OpenIdTextBox.cs" />
<Compile Include="Extensions\SimpleRegistration\DemandLevel.cs" />
<Compile Include="RelyingParty\ServiceEndpoint.cs" />
- <Compile Include="CryptUtil.cs" />
<Compile Include="DiffieHellman\DHKeyGeneration.cs" />
<Compile Include="DiffieHellman\DHParameters.cs" />
<Compile Include="DiffieHellman\DiffieHellman.cs" />
@@ -193,4 +209,4 @@ </ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<Import Project="..\..\tools\DotNetOpenId.Versioning.targets" />
-</Project>
+</Project> diff --git a/src/DotNetOpenId/Extensions/ExtensionManager.cs b/src/DotNetOpenId/Extensions/ExtensionManager.cs new file mode 100644 index 0000000..9b197c9 --- /dev/null +++ b/src/DotNetOpenId/Extensions/ExtensionManager.cs @@ -0,0 +1,23 @@ +using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace DotNetOpenId.Extensions {
+ internal class ExtensionManager {
+ /// <summary>
+ /// A list of request extensions that may be enumerated over for logging purposes.
+ /// </summary>
+ internal static Dictionary<IExtensionRequest, string> RequestExtensions = new Dictionary<IExtensionRequest, string> {
+ {new AttributeExchange.FetchRequest(), "AX fetch"},
+ {new AttributeExchange.StoreRequest(), "AX store"},
+ {new ProviderAuthenticationPolicy.PolicyRequest(), "PAPE"},
+ {new SimpleRegistration.ClaimsRequest(), "sreg"},
+ };
+ //internal static List<IExtensionResponse> ResponseExtensions = new List<IExtensionResponse> {
+ // new AttributeExchange.FetchResponse(),
+ // new AttributeExchange.StoreResponse(),
+ // new ProviderAuthenticationPolicy.PolicyResponse(),
+ // new SimpleRegistration.ClaimsResponse(),
+ //};
+ }
+}
diff --git a/src/DotNetOpenId/HmacSha1Association.cs b/src/DotNetOpenId/HmacSha1Association.cs deleted file mode 100644 index da344f1..0000000 --- a/src/DotNetOpenId/HmacSha1Association.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System;
-using System.Security.Cryptography;
-using System.Collections.Generic;
-using System.Diagnostics;
-
-namespace DotNetOpenId {
- internal class HmacSha1Association : Association {
-
- public HmacSha1Association(string handle, byte[] secret, TimeSpan totalLifeLength)
- : base(handle, secret, totalLifeLength, DateTime.UtcNow) {
- Debug.Assert(secret.Length == CryptUtil.Sha1.HashSize / 8);
- }
-
- internal override string GetAssociationType(Protocol protocol) {
- return protocol.Args.SignatureAlgorithm.HMAC_SHA1;
- }
-
- protected override HashAlgorithm CreateHasher() {
- return new HMACSHA1(SecretKey);
- }
- }
-}
\ No newline at end of file diff --git a/src/DotNetOpenId/HmacSha256Association.cs b/src/DotNetOpenId/HmacSha256Association.cs deleted file mode 100644 index fddc811..0000000 --- a/src/DotNetOpenId/HmacSha256Association.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System;
-using System.Collections.Generic;
-using System.Text;
-using System.Security.Cryptography;
-using System.Diagnostics;
-
-namespace DotNetOpenId {
- class HmacSha256Association : Association {
- public HmacSha256Association(string handle, byte[] secret, TimeSpan totalLifeLength)
- : base(handle, secret, totalLifeLength, DateTime.UtcNow) {
- Debug.Assert(secret.Length == CryptUtil.Sha256.HashSize / 8);
- }
-
- protected override HashAlgorithm CreateHasher() {
- return new HMACSHA256(SecretKey);
- }
-
- internal override string GetAssociationType(Protocol protocol) {
- return protocol.Args.SignatureAlgorithm.HMAC_SHA256;
- }
- }
-}
diff --git a/src/DotNetOpenId/HmacShaAssociation.cs b/src/DotNetOpenId/HmacShaAssociation.cs new file mode 100644 index 0000000..9822bf1 --- /dev/null +++ b/src/DotNetOpenId/HmacShaAssociation.cs @@ -0,0 +1,142 @@ +using System;
+using System.Diagnostics;
+using System.Collections.Generic;
+using System.Security.Cryptography;
+
+namespace DotNetOpenId {
+ internal class HmacShaAssociation : Association {
+
+ class HmacSha {
+ internal Util.Func<Protocol, string> GetAssociationType;
+ internal Util.Func<byte[], HashAlgorithm> CreateHasher;
+ internal HashAlgorithm BaseHashAlgorithm;
+ /// <summary>
+ /// The size of the hash (in bytes).
+ /// </summary>
+ internal int SecretLength { get { return BaseHashAlgorithm.HashSize / 8; } }
+ }
+ 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();
+
+ public static HmacShaAssociation Create(Protocol protocol, string associationType,
+ string handle, byte[] secret, TimeSpan totalLifeLength) {
+ foreach (HmacSha shaType in HmacShaAssociationTypes) {
+ if (String.Equals(shaType.GetAssociationType(protocol), associationType, StringComparison.Ordinal)) {
+ return new HmacShaAssociation(shaType, handle, secret, totalLifeLength);
+ }
+ }
+ throw new ArgumentOutOfRangeException("associationType");
+ }
+
+ public static HmacShaAssociation Create(int secretLength,
+ string handle, byte[] secret, TimeSpan totalLifeLength) {
+ foreach (HmacSha shaType in HmacShaAssociationTypes) {
+ if (shaType.SecretLength == secretLength) {
+ return new HmacShaAssociation(shaType, handle, secret, totalLifeLength);
+ }
+ }
+ throw new ArgumentOutOfRangeException("secretLength");
+ }
+
+ /// <summary>
+ /// Returns the length of the shared secret (in bytes).
+ /// </summary>
+ public static int GetSecretLength(Protocol protocol, string associationType) {
+ foreach (HmacSha shaType in HmacShaAssociationTypes) {
+ if (String.Equals(shaType.GetAssociationType(protocol), associationType, StringComparison.Ordinal)) {
+ return shaType.SecretLength;
+ }
+ }
+ throw new ArgumentOutOfRangeException("associationType");
+ }
+
+ /// <summary>
+ /// Looks for the longest hash length for a given protocol for which we have an association,
+ /// and perhaps a matching Diffie-Hellman session type.
+ /// </summary>
+ /// <param name="protocol">The OpenID version that dictates which associations are available.</param>
+ /// <param name="minimumHashSizeInBits">The minimum required hash length given security settings.</param>
+ /// <param name="maximumHashSizeInBits">The maximum hash length to even attempt. Useful for the RP side where we support SHA512 but most OPs do not -- why waste time trying?</param>
+ /// <param name="requireMatchingDHSessionType">True for HTTP associations, False 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>
+ internal static bool TryFindBestAssociation(Protocol protocol,
+ int? minimumHashSizeInBits, int? maximumHashSizeInBits, bool requireMatchingDHSessionType,
+ out string associationType, out string sessionType) {
+ if (protocol == null) throw new ArgumentNullException("protocol");
+ associationType = null;
+ sessionType = null;
+
+ // We assume this enumeration is in decreasing bit length order.
+ foreach (HmacSha sha in HmacShaAssociationTypes) {
+ int hashSizeInBits = sha.SecretLength * 8;
+ if (maximumHashSizeInBits.HasValue && hashSizeInBits > maximumHashSizeInBits.Value)
+ continue;
+ if (minimumHashSizeInBits.HasValue && hashSizeInBits < minimumHashSizeInBits.Value)
+ break;
+ sessionType = DiffieHellmanUtil.GetNameForSize(protocol, hashSizeInBits);
+ if (requireMatchingDHSessionType && sessionType == null)
+ continue;
+ associationType = sha.GetAssociationType(protocol);
+ return true;
+ }
+ return false;
+ }
+
+ internal static bool IsDHSessionCompatible(Protocol protocol, string associationType, string sessionType) {
+ // Under HTTPS, no DH encryption is required regardless of association type.
+ 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.
+ foreach (HmacSha sha in HmacShaAssociationTypes) {
+ if (string.Equals(associationType, sha.GetAssociationType(protocol), StringComparison.Ordinal)) {
+ int hashSizeInBits = sha.SecretLength * 8;
+ string matchingSessionName = DiffieHellmanUtil.GetNameForSize(protocol, hashSizeInBits);
+ if (string.Equals(sessionType, matchingSessionName, StringComparison.Ordinal)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ HmacShaAssociation(HmacSha typeIdentity, string handle, byte[] secret, TimeSpan totalLifeLength)
+ : base(handle, secret, totalLifeLength, DateTime.UtcNow) {
+ if (typeIdentity == null) throw new ArgumentNullException("typeIdentity");
+
+ Debug.Assert(secret.Length == typeIdentity.SecretLength);
+ this.typeIdentity = typeIdentity;
+ }
+
+ HmacSha typeIdentity;
+
+ internal override string GetAssociationType(Protocol protocol) {
+ return typeIdentity.GetAssociationType(protocol);
+ }
+
+ protected override HashAlgorithm CreateHasher() {
+ return typeIdentity.CreateHasher(SecretKey);
+ }
+ }
+}
diff --git a/src/DotNetOpenId/IEncodable.cs b/src/DotNetOpenId/IEncodable.cs index 4dd830b..18a0ba2 100644 --- a/src/DotNetOpenId/IEncodable.cs +++ b/src/DotNetOpenId/IEncodable.cs @@ -33,6 +33,5 @@ namespace DotNetOpenId { /// Does not apply to <see cref="DotNetOpenId.EncodingType.DirectResponse"/>.
/// </summary>
Uri RedirectUrl { get; }
- Protocol Protocol { get; }
}
}
diff --git a/src/DotNetOpenId/Identifier.cs b/src/DotNetOpenId/Identifier.cs index 54e9c36..aa733a3 100644 --- a/src/DotNetOpenId/Identifier.cs +++ b/src/DotNetOpenId/Identifier.cs @@ -11,6 +11,26 @@ namespace DotNetOpenId { /// </summary>
public abstract class Identifier {
/// <summary>
+ /// Constructs an <see cref="Identifier"/>.
+ /// </summary>
+ /// <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>
+ protected Identifier(bool isDiscoverySecureEndToEnd) {
+ IsDiscoverySecureEndToEnd = isDiscoverySecureEndToEnd;
+ }
+
+ /// <summary>
+ /// Whether this Identifier will ensure SSL is used throughout the discovery phase
+ /// and initial redirect of authentication.
+ /// </summary>
+ /// <remarks>
+ /// If this is False, a value of True may be obtained by calling <see cref="TryRequireSsl"/>.
+ /// </remarks>
+ protected internal bool IsDiscoverySecureEndToEnd { get; private set; }
+
+ /// <summary>
/// Converts the string representation of an Identifier to its strong type.
/// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2225:OperatorOverloadsHaveNamedAlternates"), SuppressMessage("Microsoft.Design", "CA1057:StringUriOverloadsCallSystemUriOverloads")]
@@ -115,5 +135,21 @@ namespace DotNetOpenId { /// a <see cref="UriIdentifier"/> or no fragment exists.
/// </summary>
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/DotNetOpenId/NoDiscoveryIdentifier.cs b/src/DotNetOpenId/NoDiscoveryIdentifier.cs new file mode 100644 index 0000000..8f772c4 --- /dev/null +++ b/src/DotNetOpenId/NoDiscoveryIdentifier.cs @@ -0,0 +1,43 @@ +using System;
+using System.Collections.Generic;
+using System.Text;
+using DotNetOpenId.RelyingParty;
+
+namespace DotNetOpenId {
+ /// <summary>
+ /// Wraps an existing Identifier and prevents it from performing discovery.
+ /// </summary>
+ class NoDiscoveryIdentifier : Identifier {
+ Identifier wrappedIdentifier ;
+ internal NoDiscoveryIdentifier(Identifier wrappedIdentifier)
+ : base(false) {
+ if (wrappedIdentifier == null) throw new ArgumentNullException("wrappedIdentifier");
+
+ this.wrappedIdentifier = wrappedIdentifier;
+ }
+
+ internal override IEnumerable<ServiceEndpoint> Discover() {
+ return new ServiceEndpoint[0];
+ }
+
+ internal override Identifier TrimFragment() {
+ return new NoDiscoveryIdentifier(wrappedIdentifier.TrimFragment());
+ }
+
+ internal override bool TryRequireSsl(out Identifier secureIdentifier) {
+ return wrappedIdentifier.TryRequireSsl(out secureIdentifier);
+ }
+
+ public override string ToString() {
+ return wrappedIdentifier.ToString();
+ }
+
+ public override bool Equals(object obj) {
+ return wrappedIdentifier.Equals(obj);
+ }
+
+ public override int GetHashCode() {
+ return wrappedIdentifier.GetHashCode();
+ }
+ }
+}
diff --git a/src/DotNetOpenId/OpenIdException.cs b/src/DotNetOpenId/OpenIdException.cs index 3285494..f526d66 100644 --- a/src/DotNetOpenId/OpenIdException.cs +++ b/src/DotNetOpenId/OpenIdException.cs @@ -20,7 +20,6 @@ namespace DotNetOpenId { /// </summary>
public Identifier Identifier { get; private set; }
internal Protocol Protocol = Protocol.Default;
- Protocol IEncodable.Protocol { get { return this.Protocol; } }
internal IDictionary<string, string> ExtraArgsToReturn;
internal OpenIdException(string message, Identifier identifier, IDictionary<string, string> query, Exception innerException)
diff --git a/src/DotNetOpenId/Protocol.cs b/src/DotNetOpenId/Protocol.cs index a9b7cf2..aacec53 100644 --- a/src/DotNetOpenId/Protocol.cs +++ b/src/DotNetOpenId/Protocol.cs @@ -75,9 +75,13 @@ namespace DotNetOpenId { 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",
@@ -108,20 +112,29 @@ namespace DotNetOpenId { }
/// <summary>
/// Attempts to detect the right OpenID protocol version based on the contents
- /// of an incoming query string.
+ /// of an incoming OpenID <i>indirect</i> message or <i>direct request</i>.
/// </summary>
internal static Protocol Detect(IDictionary<string, string> Query) {
if (Query == null) throw new ArgumentNullException("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) {
+ if (Query == null) throw new ArgumentNullException("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(string[] serviceTypeURIs) {
if (serviceTypeURIs == null) throw new ArgumentNullException("serviceTypeURIs");
return Util.FindBestVersion(p => p.OPIdentifierServiceTypeURI, serviceTypeURIs) ??
- Util.FindBestVersion(p => p.ClaimedIdentifierServiceTypeURI, serviceTypeURIs);
+ Util.FindBestVersion(p => p.ClaimedIdentifierServiceTypeURI, serviceTypeURIs) ??
+ Util.FindBestVersion(p => p.RPReturnToTypeURI, serviceTypeURIs);
}
/// <summary>
@@ -129,6 +142,18 @@ namespace DotNetOpenId { /// </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;
@@ -265,19 +290,43 @@ namespace DotNetOpenId { /// <summary>
/// A preference order list of all supported session types.
/// </summary>
- public string[] All { get { return new[] { DH_SHA256, DH_SHA1, NoEncryption }; } }
- public string[] AllDiffieHellman { get { return new[] { DH_SHA256, DH_SHA1 }; } }
+ 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 = null;
+ public string DH_SHA384 = null;
+ public string DH_SHA512 = null;
public string NoEncryption = "";
+ public string Best {
+ get {
+ foreach (string algorithmName in All) {
+ if (algorithmName != null) {
+ return algorithmName;
+ }
+ }
+ throw new OpenIdException(); // really bad... we have no signing algorithms at all
+ }
+ }
}
internal class SignatureAlgorithms {
/// <summary>
/// A preference order list of signature algorithms we support.
/// </summary>
- public string[] All { get { return new[] { HMAC_SHA256, HMAC_SHA1 }; } }
+ public string[] All { get { return new[] { HMAC_SHA512, HMAC_SHA384, HMAC_SHA256, HMAC_SHA1 }; } }
public string HMAC_SHA1 = "HMAC-SHA1";
public string HMAC_SHA256 = null;
+ public string HMAC_SHA384 = null;
+ public string HMAC_SHA512 = null;
+ public string Best {
+ get {
+ foreach (string algorithmName in All) {
+ if (algorithmName != null) {
+ return algorithmName;
+ }
+ }
+ throw new OpenIdException(); // really bad... we have no signing algorithms at all
+ }
+ }
}
internal class Modes {
public string cancel = "cancel";
diff --git a/src/DotNetOpenId/Provider/AssociateRequest.cs b/src/DotNetOpenId/Provider/AssociateRequest.cs index 8e5f178..a2f6f9a 100644 --- a/src/DotNetOpenId/Provider/AssociateRequest.cs +++ b/src/DotNetOpenId/Provider/AssociateRequest.cs @@ -28,7 +28,7 @@ namespace DotNetOpenId.Provider { }
/// <summary>
- /// Used to throw a carefully crafted exception that will end up getting
+ /// This method is used to throw a carefully crafted exception that will end up getting
/// encoded as a response to the RP, given hints as to what
/// assoc_type and session_type args we support.
/// </summary>
@@ -79,7 +79,7 @@ namespace DotNetOpenId.Provider { return response;
}
- internal override IEncodable CreateResponse() {
+ protected override IEncodable CreateResponse() {
return Answer();
}
diff --git a/src/DotNetOpenId/Provider/CheckAuthRequest.cs b/src/DotNetOpenId/Provider/CheckAuthRequest.cs index 82f979f..bda4918 100644 --- a/src/DotNetOpenId/Provider/CheckAuthRequest.cs +++ b/src/DotNetOpenId/Provider/CheckAuthRequest.cs @@ -70,7 +70,7 @@ namespace DotNetOpenId.Provider { return response;
}
- internal override IEncodable CreateResponse() {
+ protected override IEncodable CreateResponse() {
return Answer();
}
diff --git a/src/DotNetOpenId/Provider/CheckIdRequest.cs b/src/DotNetOpenId/Provider/CheckIdRequest.cs index 314e813..ec6087b 100644 --- a/src/DotNetOpenId/Provider/CheckIdRequest.cs +++ b/src/DotNetOpenId/Provider/CheckIdRequest.cs @@ -98,17 +98,16 @@ namespace DotNetOpenId.Provider { public Identifier LocalIdentifier {
get { return localIdentifier; }
set {
+ // Keep LocalIdentifier and ClaimedIdentifier in sync for directed identity.
if (IsDirectedIdentity) {
- // Keep LocalIdentifier and ClaimedIdentifier in sync
if (ClaimedIdentifier != null && ClaimedIdentifier != value) {
throw new InvalidOperationException(Strings.IdentifierSelectRequiresMatchingIdentifiers);
- } else {
- localIdentifier = value;
- claimedIdentifier = value;
}
- } else {
- throw new InvalidOperationException(Strings.IdentifierSelectModeOnly);
+
+ claimedIdentifier = value;
}
+
+ localIdentifier = value;
}
}
Identifier claimedIdentifier;
@@ -118,17 +117,16 @@ namespace DotNetOpenId.Provider { public Identifier ClaimedIdentifier {
get { return claimedIdentifier; }
set {
+ // Keep LocalIdentifier and ClaimedIdentifier in sync for directed identity.
if (IsDirectedIdentity) {
- // Keep LocalIdentifier and ClaimedIdentifier in sync
if (LocalIdentifier != null && LocalIdentifier != value) {
throw new InvalidOperationException(Strings.IdentifierSelectRequiresMatchingIdentifiers);
- } else {
- claimedIdentifier = value;
- localIdentifier = value;
}
- } else {
- throw new InvalidOperationException(Strings.IdentifierSelectModeOnly);
+
+ localIdentifier = value;
}
+
+ claimedIdentifier = value;
}
}
@@ -248,7 +246,7 @@ namespace DotNetOpenId.Provider { }
}
- internal override IEncodable CreateResponse() {
+ protected override IEncodable CreateResponse() {
Debug.Assert(IsAuthenticated.HasValue, "This should be checked internally before CreateResponse is called.");
return AssertionMessage.CreateAssertion(this);
}
diff --git a/src/DotNetOpenId/Provider/FaultyRequest.cs b/src/DotNetOpenId/Provider/FaultyRequest.cs index 2d38a50..c2e3b3a 100644 --- a/src/DotNetOpenId/Provider/FaultyRequest.cs +++ b/src/DotNetOpenId/Provider/FaultyRequest.cs @@ -18,7 +18,7 @@ namespace DotNetOpenId.Provider { get { return true; }
}
- internal override IEncodable CreateResponse() {
+ protected override IEncodable CreateResponse() {
return Response;
}
}
diff --git a/src/DotNetOpenId/Provider/IAuthenticationRequest.cs b/src/DotNetOpenId/Provider/IAuthenticationRequest.cs index 38f4be0..4716642 100644 --- a/src/DotNetOpenId/Provider/IAuthenticationRequest.cs +++ b/src/DotNetOpenId/Provider/IAuthenticationRequest.cs @@ -10,6 +10,10 @@ namespace DotNetOpenId.Provider { /// </summary>
public interface IAuthenticationRequest : IRequest {
/// <summary>
+ /// Gets the version of OpenID being used by the relying party that sent the request.
+ /// </summary>
+ ProtocolVersion RelyingPartyVersion { get; }
+ /// <summary>
/// Whether the consumer demands an immediate response.
/// If false, the consumer is willing to wait for the identity provider
/// to authenticate the user.
diff --git a/src/DotNetOpenId/Provider/IdentityEndpoint.cs b/src/DotNetOpenId/Provider/IdentityEndpoint.cs index 604619b..dd7e30d 100644 --- a/src/DotNetOpenId/Provider/IdentityEndpoint.cs +++ b/src/DotNetOpenId/Provider/IdentityEndpoint.cs @@ -1,13 +1,60 @@ using System;
-using System.Collections.Generic;
using System.ComponentModel;
-using System.Text;
-using System.Web;
using System.Web.UI;
-using System.Web.UI.WebControls;
namespace DotNetOpenId.Provider {
/// <summary>
+ /// The event arguments passed to the <see cref="IdentityEndpoint.NormalizeUri"/> event handler.
+ /// </summary>
+ public class IdentityEndpointNormalizationEventArgs : EventArgs {
+ internal IdentityEndpointNormalizationEventArgs(UriIdentifier userSuppliedIdentifier) {
+ UserSuppliedIdentifier = userSuppliedIdentifier;
+ }
+
+ /// <summary>
+ /// Gets or sets the portion of the incoming page request URI that is relevant to normalization.
+ /// </summary>
+ /// <remarks>
+ /// This identifier should be used to look up the user whose identity page is being queried.
+ /// It MAY be set in case some clever web server URL rewriting is taking place that ASP.NET
+ /// does not know about but your site does. If this is the case this property should be set
+ /// to whatever the original request URL was.
+ /// </remarks>
+ public Uri UserSuppliedIdentifier { get; set; }
+
+ /// <summary>
+ /// Gets/sets the normalized form of the user's identifier, according to the host site's policy.
+ /// </summary>
+ /// <remarks>
+ /// <para>This should be set to some constant value for an individual user.
+ /// For example, if <see cref="UserSuppliedIdentifier"/> indicates that identity page
+ /// for "BOB" is being called up, then the following things should be considered:</para>
+ /// <list>
+ /// <item>Normalize the capitalization of the URL: for example, change http://provider/BOB to
+ /// http://provider/bob.</item>
+ /// <item>Switch to HTTPS is it is offered: change http://provider/bob to https://provider/bob.</item>
+ /// <item>Strip off the query string if it is not part of the canonical identity:
+ /// https://provider/bob?timeofday=now becomes https://provider/bob</item>
+ /// <item>Ensure that any trailing slash is either present or absent consistently. For example,
+ /// change https://provider/bob/ to https://provider/bob.</item>
+ /// </list>
+ /// <para>When this property is set, the <see cref="IdentityEndpoint"/> control compares it to
+ /// the request that actually came in, and redirects the browser to use the normalized identifier
+ /// if necessary.</para>
+ /// <para>Using the normalized identifier in the request is <i>very</i> important as it
+ /// helps the user maintain a consistent identity across sites and across site visits to an individual site.
+ /// For example, without normalizing the URL, Bob might sign into a relying party site as
+ /// http://provider/bob one day and https://provider/bob the next day, and the relying party
+ /// site <i>should</i> interpret Bob as two different people because the URLs are different.
+ /// By normalizing the URL at the Provider's identity page for Bob, whichever URL Bob types in
+ /// from day-to-day gets redirected to a normalized form, so Bob is seen as the same person
+ /// all the time, which is of course what Bob wants.
+ /// </para>
+ /// </remarks>
+ public Uri NormalizedIdentifier { get; set; }
+ }
+
+ /// <summary>
/// An ASP.NET control that manages the OpenID identity advertising tags
/// of a user's Identity Page that allow a relying party web site to discover
/// how to authenticate a user.
@@ -26,6 +73,7 @@ namespace DotNetOpenId.Provider { /// </summary>
[Category("Behavior")]
[DefaultValue(providerVersionDefault)]
+ [Description("The OpenID version supported by the provider.")]
public ProtocolVersion ProviderVersion {
get {
return ViewState[providerVersionViewStateKey] == null ?
@@ -40,6 +88,7 @@ namespace DotNetOpenId.Provider { /// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1056:UriPropertiesShouldNotBeStrings"), Bindable(true)]
[Category("Behavior")]
+ [Description("The Provider URL that processes OpenID requests.")]
public string ProviderEndpointUrl {
get { return (string)ViewState[providerEndpointUrlViewStateKey]; }
set {
@@ -54,6 +103,7 @@ namespace DotNetOpenId.Provider { /// </summary>
[Bindable(true)]
[Category("Behavior")]
+ [Description("The user Identifier that is controlled by the Provider.")]
public string ProviderLocalIdentifier {
get { return (string)ViewState[providerLocalIdentifierViewStateKey]; }
set {
@@ -61,6 +111,25 @@ namespace DotNetOpenId.Provider { ViewState[providerLocalIdentifierViewStateKey] = value;
}
}
+
+ const string autoNormalizeRequestViewStateKey = "AutoNormalizeRequest";
+ /// <summary>
+ /// Whether every incoming request will be checked for normalized form and redirected if it is not.
+ /// </summary>
+ /// <remarks>
+ /// <para>If set to true (and it should be), you should also handle the <see cref="NormalizeUri"/>
+ /// event and apply your own policy for normalizing the URI.</para>
+ /// If multiple <see cref="IdentityEndpoint"/> controls are on a single page (to support
+ /// multiple versions of OpenID for example) then only one of them should have this
+ /// property set to true.
+ /// </remarks>
+ [Bindable(true)]
+ [Category("Behavior")]
+ [Description("Whether every incoming request will be checked for normalized form and redirected if it is not. If set to true, consider handling the NormalizeUri event.")]
+ public bool AutoNormalizeRequest {
+ get { return (bool)(ViewState[autoNormalizeRequestViewStateKey] ?? false); }
+ set { ViewState[autoNormalizeRequestViewStateKey] = value; }
+ }
#endregion
internal Protocol Protocol {
@@ -68,6 +137,69 @@ namespace DotNetOpenId.Provider { }
/// <summary>
+ /// Fired at each page request so the host web site can return the normalized
+ /// version of the request URI.
+ /// </summary>
+ public event EventHandler<IdentityEndpointNormalizationEventArgs> NormalizeUri;
+
+ /// <summary>
+ /// Checks the incoming request and invokes a browser redirect if the URL has not been normalized.
+ /// </summary>
+ /// <seealso cref="IdentityEndpointNormalizationEventArgs.NormalizedIdentifier"/>
+ protected virtual void OnNormalize() {
+ UriIdentifier userSuppliedIdentifier = Util.GetRequestUrlFromContext();
+ var normalizationArgs = new IdentityEndpointNormalizationEventArgs(userSuppliedIdentifier);
+
+ var normalizeUri = NormalizeUri;
+ if (normalizeUri != null) {
+ normalizeUri(this, normalizationArgs);
+ } else {
+ // Do some best-guess normalization.
+ normalizationArgs.NormalizedIdentifier = bestGuessNormalization(normalizationArgs.UserSuppliedIdentifier);
+ }
+ // If we have a normalized form, we should use it.
+ // We compare path and query with case sensitivity and host name without case sensitivity deliberately,
+ // and the fragment will be asserted or cleared by the OP during authentication.
+ if (normalizationArgs.NormalizedIdentifier != null &&
+ (!String.Equals(normalizationArgs.NormalizedIdentifier.Host, normalizationArgs.UserSuppliedIdentifier.Host, StringComparison.OrdinalIgnoreCase) ||
+ !String.Equals(normalizationArgs.NormalizedIdentifier.PathAndQuery, normalizationArgs.UserSuppliedIdentifier.PathAndQuery, StringComparison.Ordinal))) {
+ Page.Response.Redirect(normalizationArgs.NormalizedIdentifier.AbsoluteUri);
+ }
+ }
+
+ /// <summary>
+ /// Normalizes the URL by making the path and query lowercase, and trimming trailing slashes.
+ /// </summary>
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase", Justification="FxCop is probably right, but we've been lowercasing host names for normalization elsewhere in the project for a long time now.")]
+ private static Uri bestGuessNormalization(Uri uri) {
+ UriBuilder uriBuilder = new UriBuilder(uri);
+ uriBuilder.Path = uriBuilder.Path.ToLowerInvariant();
+ // Ensure no trailing slash unless it is the only element of the path.
+ if (uriBuilder.Path != "/") {
+ uriBuilder.Path = uriBuilder.Path.TrimEnd('/');
+ }
+ // We trim the ? from the start of the query when we reset it because
+ // the UriBuilder.Query setter automatically prepends one, and we don't
+ // want to double them up.
+ uriBuilder.Query = uriBuilder.Query.TrimStart('?').ToLowerInvariant();
+ return uriBuilder.Uri;
+ }
+
+ /// <summary>
+ /// Checks the incoming request and invokes a browser redirect if the URL has not been normalized.
+ /// </summary>
+ protected override void OnLoad(EventArgs e) {
+ // Perform URL normalization BEFORE calling base.OnLoad, to keep
+ // our base XrdsPublisher from over-eagerly responding with an XRDS
+ // document before we've redirected.
+ if (AutoNormalizeRequest && !Page.IsPostBack) {
+ OnNormalize();
+ }
+
+ base.OnLoad(e);
+ }
+
+ /// <summary>
/// Renders OpenID identity tags.
/// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2234:PassSystemUriObjectsInsteadOfStrings")]
diff --git a/src/DotNetOpenId/Provider/OpenIdProvider.cs b/src/DotNetOpenId/Provider/OpenIdProvider.cs index b493fda..0fdb4b4 100644 --- a/src/DotNetOpenId/Provider/OpenIdProvider.cs +++ b/src/DotNetOpenId/Provider/OpenIdProvider.cs @@ -1,12 +1,12 @@ using System;
+using System.Collections.Generic;
using System.Collections.Specialized;
-using System.Text;
+using System.Configuration;
+using System.Diagnostics;
using System.Web;
using IProviderAssociationStore = DotNetOpenId.IAssociationStore<DotNetOpenId.AssociationRelyingPartyType>;
using ProviderMemoryStore = DotNetOpenId.AssociationMemoryStore<DotNetOpenId.AssociationRelyingPartyType>;
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.Net;
+using DotNetOpenId.Configuration;
namespace DotNetOpenId.Provider {
/// <summary>
@@ -35,6 +35,10 @@ namespace DotNetOpenId.Provider { /// </summary>
internal Protocol Protocol { get; private set; }
+ internal static Uri DefaultProviderEndpoint { get { return getProviderEndpointFromContext(); } }
+ internal static Uri DefaultRequestUrl { get { return Util.GetRequestUrlFromContext(); } }
+ internal static NameValueCollection DefaultQuery { get { return Util.GetQueryFromContextNVC(); } }
+
/// <summary>
/// Constructs an OpenId server that uses the HttpApplication dictionary as
/// its association store and detects common settings.
@@ -43,7 +47,7 @@ namespace DotNetOpenId.Provider { /// This method requires a current ASP.NET HttpContext.
/// </remarks>
public OpenIdProvider()
- : this(HttpApplicationStore,
+ : this(Configuration.Store.CreateInstanceOfStore(HttpApplicationStore),
getProviderEndpointFromContext(), Util.GetRequestUrlFromContext(), Util.GetQueryFromContext()) { }
/// <summary>
/// Constructs an OpenId server that uses a given query and IAssociationStore.
@@ -67,6 +71,7 @@ namespace DotNetOpenId.Provider { if (providerEndpoint == null) throw new ArgumentNullException("providerEndpoint");
if (requestUrl == null) throw new ArgumentNullException("requestUrl");
if (query == null) throw new ArgumentNullException("query");
+ Settings = new ProviderSecuritySettings();
Endpoint = providerEndpoint;
RequestUrl = requestUrl;
Query = query;
@@ -83,6 +88,13 @@ namespace DotNetOpenId.Provider { /// </remarks>
internal Uri Endpoint { get; private set; }
+ // TODO: make this property public WHEN its security settings are actually supported.
+ /// <summary>
+ /// Provides access to the adjustable security settings of this instance
+ /// of <see cref="OpenIdProvider"/>.
+ /// </summary>
+ internal ProviderSecuritySettings Settings { get; private set; }
+
bool requestProcessed;
Request request;
/// <summary>
@@ -189,5 +201,16 @@ namespace DotNetOpenId.Provider { builder.Fragment = null;
return builder.Uri;
}
+
+ /// <summary>
+ /// Gets the relevant Configuration section for this OpenIdRelyingParty.
+ /// </summary>
+ /// <remarks>
+ /// This is not a static member because depending on the context within which we are
+ /// invoked, the configuration section might be different. (location tag, for example).
+ /// </remarks>
+ internal static ProviderSection Configuration {
+ get { return ProviderSection.Configuration; }
+ }
}
}
diff --git a/src/DotNetOpenId/Provider/ProviderEndpoint.cs b/src/DotNetOpenId/Provider/ProviderEndpoint.cs index d46d248..708da31 100644 --- a/src/DotNetOpenId/Provider/ProviderEndpoint.cs +++ b/src/DotNetOpenId/Provider/ProviderEndpoint.cs @@ -5,6 +5,7 @@ using System.Text; using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
+using IProviderAssociationStore = DotNetOpenId.IAssociationStore<DotNetOpenId.AssociationRelyingPartyType>;
namespace DotNetOpenId.Provider {
/// <summary>
@@ -48,6 +49,15 @@ namespace DotNetOpenId.Provider { }
/// <summary>
+ /// A custom application store to use. Null 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 IProviderAssociationStore CustomApplicationStore { get; set; }
+
+ /// <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.
@@ -56,7 +66,14 @@ namespace DotNetOpenId.Provider { base.OnLoad(e);
if (Enabled) {
- OpenIdProvider provider = new OpenIdProvider();
+ // 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.
+ OpenIdProvider provider = new OpenIdProvider(
+ CustomApplicationStore ?? OpenIdProvider.Configuration.Store.CreateInstanceOfStore(OpenIdProvider.HttpApplicationStore),
+ OpenIdProvider.DefaultProviderEndpoint,
+ OpenIdProvider.DefaultRequestUrl,
+ OpenIdProvider.DefaultQuery);
// determine what incoming message was received
if (provider.Request != null) {
diff --git a/src/DotNetOpenId/Provider/ProviderSecuritySettings.cs b/src/DotNetOpenId/Provider/ProviderSecuritySettings.cs new file mode 100644 index 0000000..88c1e07 --- /dev/null +++ b/src/DotNetOpenId/Provider/ProviderSecuritySettings.cs @@ -0,0 +1,26 @@ +using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace DotNetOpenId.Provider {
+ sealed class ProviderSecuritySettings : SecuritySettings {
+ internal ProviderSecuritySettings() : base(true) { }
+
+ // This property is a placeholder for a feature that has not been written yet.
+ /// <summary>
+ /// Gets/sets whether OpenID 1.x relying parties that may not be
+ /// protecting their users from replay attacks are protected from
+ /// replay attacks by this provider.
+ /// </summary>
+ /// <remarks>
+ /// <para>Nonces for protection against replay attacks were not mandated
+ /// by OpenID 1.x, which leaves users open to replay attacks.</para>
+ /// <para>This feature works by preventing associations from being formed
+ /// with OpenID 1.x relying parties, thereby forcing them into
+ /// "dumb" mode and verifying every claim with this provider.
+ /// This gives the provider an opportunity to verify its own nonce
+ /// to protect against replay attacks.</para>
+ /// </remarks>
+ internal bool ProtectDownlevelReplayAttacks { get; set; }
+ }
+}
diff --git a/src/DotNetOpenId/Provider/ProviderSession.cs b/src/DotNetOpenId/Provider/ProviderSession.cs index b524399..bbfd278 100644 --- a/src/DotNetOpenId/Provider/ProviderSession.cs +++ b/src/DotNetOpenId/Provider/ProviderSession.cs @@ -77,8 +77,8 @@ namespace DotNetOpenId.Provider { sessionType = Util.GetRequiredArg(provider.Query, Protocol.openid.session_type);
Debug.Assert(Array.IndexOf(Protocol.Args.SessionType.AllDiffieHellman, sessionType) >= 0, "We should not have been invoked if this wasn't a recognized DH session request.");
- byte[] dh_modulus = Util.GetOptionalBase64Arg(Provider.Query, Protocol.openid.dh_modulus) ?? CryptUtil.DEFAULT_MOD;
- byte[] dh_gen = Util.GetOptionalBase64Arg(Provider.Query, Protocol.openid.dh_gen) ?? CryptUtil.DEFAULT_GEN;
+ byte[] dh_modulus = Util.GetOptionalBase64Arg(Provider.Query, Protocol.openid.dh_modulus) ?? DiffieHellmanUtil.DEFAULT_MOD;
+ byte[] dh_gen = Util.GetOptionalBase64Arg(Provider.Query, Protocol.openid.dh_gen) ?? DiffieHellmanUtil.DEFAULT_GEN;
dh = new DiffieHellmanManaged(dh_modulus, dh_gen, 1024);
consumerPublicKey = Util.GetRequiredBase64Arg(Provider.Query, Protocol.openid.dh_consumer_public);
@@ -89,13 +89,11 @@ namespace DotNetOpenId.Provider { }
public override Dictionary<string, string> Answer(byte[] secret) {
- bool useSha256 = SessionType.Equals(Protocol.Args.SessionType.DH_SHA256, StringComparison.Ordinal);
- byte[] mac_key = CryptUtil.SHAHashXorSecret(
- useSha256 ? (HashAlgorithm) CryptUtil.Sha256 : CryptUtil.Sha1,
+ byte[] mac_key = DiffieHellmanUtil.SHAHashXorSecret(DiffieHellmanUtil.Lookup(Protocol, SessionType),
dh, consumerPublicKey, secret);
var nvc = new Dictionary<string, string>();
- nvc.Add(Protocol.openidnp.dh_server_public, CryptUtil.UnsignedToBase64(dh.CreateKeyExchange()));
+ nvc.Add(Protocol.openidnp.dh_server_public, DiffieHellmanUtil.UnsignedToBase64(dh.CreateKeyExchange()));
nvc.Add(Protocol.openidnp.enc_mac_key, Convert.ToBase64String(mac_key));
return nvc;
diff --git a/src/DotNetOpenId/Provider/Request.cs b/src/DotNetOpenId/Provider/Request.cs index 7813a2b..eb3edaf 100644 --- a/src/DotNetOpenId/Provider/Request.cs +++ b/src/DotNetOpenId/Provider/Request.cs @@ -84,10 +84,19 @@ namespace DotNetOpenId.Provider }
/// <summary>
+ /// Gets the version of OpenID being used by the relying party that sent the request.
+ /// </summary>
+ public ProtocolVersion RelyingPartyVersion {
+ get {
+ return Protocol.Lookup(Protocol.Version).ProtocolVersion;
+ }
+ }
+
+ /// <summary>
/// Indicates whether this request has all the information necessary to formulate a response.
/// </summary>
public abstract bool IsResponseReady { get; }
- internal abstract IEncodable CreateResponse();
+ protected abstract IEncodable CreateResponse();
/// <summary>
/// Called whenever a property changes that would cause the response to need to be
/// regenerated if it had already been generated.
diff --git a/src/DotNetOpenId/Provider/Signatory.cs b/src/DotNetOpenId/Provider/Signatory.cs index 53c6139..eaf3d21 100644 --- a/src/DotNetOpenId/Provider/Signatory.cs +++ b/src/DotNetOpenId/Provider/Signatory.cs @@ -79,24 +79,25 @@ namespace DotNetOpenId.Provider { if (provider == null && associationType == AssociationRelyingPartyType.Smart)
throw new ArgumentNullException("provider", "For Smart associations, the provider must be given.");
- bool useSha256;
string assoc_type;
+ Protocol associationProtocol;
if (associationType == AssociationRelyingPartyType.Dumb) {
- useSha256 = true;
- assoc_type = Protocol.v20.Args.SignatureAlgorithm.HMAC_SHA256;
+ // We'll just use the best association available.
+ associationProtocol = Protocol.Default;
+ assoc_type = associationProtocol.Args.SignatureAlgorithm.Best;
} else {
+ associationProtocol = provider.Protocol;
assoc_type = Util.GetRequiredArg(provider.Query, provider.Protocol.openid.assoc_type);
Debug.Assert(Array.IndexOf(provider.Protocol.Args.SignatureAlgorithm.All, assoc_type) >= 0, "This should have been checked by our caller.");
- useSha256 = assoc_type.Equals(provider.Protocol.Args.SignatureAlgorithm.HMAC_SHA256, StringComparison.Ordinal);
}
- int hashSize = useSha256 ? CryptUtil.Sha256.HashSize : CryptUtil.Sha1.HashSize;
+ int secretLength = HmacShaAssociation.GetSecretLength(associationProtocol, assoc_type);
RNGCryptoServiceProvider generator = new RNGCryptoServiceProvider();
- byte[] secret = new byte[hashSize / 8];
+ byte[] secret = new byte[secretLength];
byte[] uniq_bytes = new byte[4];
string uniq;
string handle;
- Association assoc;
+ HmacShaAssociation assoc;
generator.GetBytes(secret);
generator.GetBytes(uniq_bytes);
@@ -108,9 +109,7 @@ namespace DotNetOpenId.Provider { handle = "{{" + assoc_type + "}{" + seconds + "}{" + uniq + "}";
TimeSpan lifeSpan = associationType == AssociationRelyingPartyType.Dumb ? dumbSecretLifetime : smartAssociationLifetime;
- assoc = useSha256 ? (Association)
- new HmacSha256Association(handle, secret, lifeSpan) :
- new HmacSha1Association(handle, secret, lifeSpan);
+ assoc = HmacShaAssociation.Create(secretLength, handle, secret, lifeSpan);
store.StoreAssociation(associationType, assoc);
diff --git a/src/DotNetOpenId/Provider/SigningMessageEncoder.cs b/src/DotNetOpenId/Provider/SigningMessageEncoder.cs index ea5a522..f05f731 100644 --- a/src/DotNetOpenId/Provider/SigningMessageEncoder.cs +++ b/src/DotNetOpenId/Provider/SigningMessageEncoder.cs @@ -16,26 +16,14 @@ namespace DotNetOpenId.Provider { }
public override Response Encode(IEncodable encodable) {
- OnSigning(encodable);
var response = encodable as EncodableResponse;
if (response != null) {
if (response.NeedsSigning) {
- Debug.Assert(!response.Fields.ContainsKey(encodable.Protocol.openidnp.sig));
signatory.Sign(response);
}
}
return base.Encode(encodable);
}
-
- /// <summary>
- /// Used for testing. Allows interception and modification of messages
- /// that are about to be returned to the RP.
- /// </summary>
- public static event EventHandler<EncodeEventArgs> Signing;
- protected virtual void OnSigning(IEncodable encodable) {
- if (Signing != null)
- Signing(this, new EncodeEventArgs(encodable));
- }
}
}
diff --git a/src/DotNetOpenId/Realm.cs b/src/DotNetOpenId/Realm.cs index c48265e..f7bb361 100644 --- a/src/DotNetOpenId/Realm.cs +++ b/src/DotNetOpenId/Realm.cs @@ -268,7 +268,7 @@ namespace DotNetOpenId { /// <returns>The details of the endpoints if found, otherwise null.</returns>
internal IEnumerable<DotNetOpenId.Provider.RelyingPartyReceivingEndpoint> Discover(bool allowRedirects) {
// Attempt YADIS discovery
- DiscoveryResult yadisResult = Yadis.Yadis.Discover(UriWithWildcardChangedToWww);
+ DiscoveryResult yadisResult = Yadis.Yadis.Discover(UriWithWildcardChangedToWww, false);
if (yadisResult != null) {
if (!allowRedirects && yadisResult.NormalizedUri != yadisResult.RequestUri) {
// Redirect occurred when it was not allowed.
diff --git a/src/DotNetOpenId/RelyingParty/AssociateRequest.cs b/src/DotNetOpenId/RelyingParty/AssociateRequest.cs index 89b8182..b2d4751 100644 --- a/src/DotNetOpenId/RelyingParty/AssociateRequest.cs +++ b/src/DotNetOpenId/RelyingParty/AssociateRequest.cs @@ -1,8 +1,8 @@ using System;
using System.Collections.Generic;
-using System.Text;
-using Org.Mentalis.Security.Cryptography;
using System.Diagnostics;
+using System.Globalization;
+using Org.Mentalis.Security.Cryptography;
namespace DotNetOpenId.RelyingParty {
[DebuggerDisplay("Mode: {Args[\"openid.mode\"]}, {Args[\"openid.assoc_type\"]}, OpenId: {Protocol.Version}")]
@@ -10,35 +10,44 @@ namespace DotNetOpenId.RelyingParty { /// <summary>
/// Instantiates an <see cref="AssociateRequest"/> object.
/// </summary>
+ /// <param name="relyingParty">The RP instance that is creating this request.</param>
/// <param name="provider">The discovered OpenID Provider endpoint information.</param>
/// <param name="args">The arguments assembled for sending to the Provider.</param>
/// <param name="dh">Optional. Supplied only if Diffie-Hellman is used for encrypting the association secret key.</param>
- AssociateRequest(ServiceEndpoint provider, IDictionary<string, string> args, DiffieHellman dh)
- : base(provider, args) {
+ AssociateRequest(OpenIdRelyingParty relyingParty, ServiceEndpoint provider, IDictionary<string, string> args, DiffieHellman dh)
+ : base(relyingParty, provider, args) {
DH = dh;
}
public DiffieHellman DH { get; private set; }
- public static AssociateRequest Create(ServiceEndpoint provider) {
- bool useSha256 = provider.Protocol.Version.Major >= 2;
- string assoc_type = useSha256 ?
- provider.Protocol.Args.SignatureAlgorithm.HMAC_SHA256 :
- provider.Protocol.Args.SignatureAlgorithm.HMAC_SHA1;
- string session_type = useSha256 ?
- provider.Protocol.Args.SessionType.DH_SHA256 :
- provider.Protocol.Args.SessionType.DH_SHA1;
- return Create(provider, assoc_type, session_type);
+ public static AssociateRequest Create(OpenIdRelyingParty relyingParty, ServiceEndpoint provider) {
+ if (relyingParty == null) throw new ArgumentNullException("relyingParty");
+ if (provider == null) throw new ArgumentNullException("provider");
+
+ string assoc_type, session_type;
+ if (HmacShaAssociation.TryFindBestAssociation(provider.Protocol,
+ relyingParty.Settings.MinimumHashBitLength, relyingParty.Settings.MaximumHashBitLength,
+ true, out assoc_type, out session_type)) {
+ return Create(relyingParty, provider, assoc_type, session_type, true);
+ } else {
+ // There are no associations that meet all requirements.
+ Logger.Warn("Security requirements and protocol combination knock out all possible association types. Dumb mode forced.");
+ return null;
+ }
}
- public static AssociateRequest Create(ServiceEndpoint provider, string assoc_type, string session_type) {
+ public static AssociateRequest Create(OpenIdRelyingParty relyingParty, ServiceEndpoint provider, string assoc_type, string session_type, bool allowNoSession) {
+ if (relyingParty == null) throw new ArgumentNullException("relyingParty");
if (provider == null) throw new ArgumentNullException("provider");
if (assoc_type == null) throw new ArgumentNullException("assoc_type");
if (session_type == null) throw new ArgumentNullException("session_type");
Debug.Assert(Array.IndexOf(provider.Protocol.Args.SignatureAlgorithm.All, assoc_type) >= 0);
Debug.Assert(Array.IndexOf(provider.Protocol.Args.SessionType.All, session_type) >= 0);
- Logger.InfoFormat("Requesting association with {0} (assoc_type = '{1}', session_type = '{2}').",
- provider.ProviderEndpoint, assoc_type, session_type);
+ if (!HmacShaAssociation.IsDHSessionCompatible(provider.Protocol, assoc_type, session_type)) {
+ throw new OpenIdException(string.Format(CultureInfo.CurrentCulture,
+ Strings.IncompatibleAssociationAndSessionTypes, assoc_type, session_type));
+ }
var args = new Dictionary<string, string>();
Protocol protocol = provider.Protocol;
@@ -48,27 +57,32 @@ namespace DotNetOpenId.RelyingParty { DiffieHellman dh = null;
- if (provider.ProviderEndpoint.Scheme == Uri.UriSchemeHttps) {
+ if (provider.ProviderEndpoint.Scheme == Uri.UriSchemeHttps && allowNoSession) {
+ Logger.InfoFormat("Requesting association with {0} (assoc_type = '{1}', session_type = '{2}').",
+ provider.ProviderEndpoint, assoc_type, protocol.Args.SessionType.NoEncryption);
args.Add(protocol.openid.session_type, protocol.Args.SessionType.NoEncryption);
} else {
+ Logger.InfoFormat("Requesting association with {0} (assoc_type = '{1}', session_type = '{2}').",
+ provider.ProviderEndpoint, assoc_type, session_type);
+
// Initiate Diffie-Hellman Exchange
- dh = CryptUtil.CreateDiffieHellman();
+ dh = DiffieHellmanUtil.CreateDiffieHellman();
byte[] dhPublic = dh.CreateKeyExchange();
- string cpub = CryptUtil.UnsignedToBase64(dhPublic);
+ string cpub = DiffieHellmanUtil.UnsignedToBase64(dhPublic);
args.Add(protocol.openid.session_type, session_type);
args.Add(protocol.openid.dh_consumer_public, cpub);
DHParameters dhps = dh.ExportParameters(true);
- if (dhps.P != CryptUtil.DEFAULT_MOD || dhps.G != CryptUtil.DEFAULT_GEN) {
- args.Add(protocol.openid.dh_modulus, CryptUtil.UnsignedToBase64(dhps.P));
- args.Add(protocol.openid.dh_gen, CryptUtil.UnsignedToBase64(dhps.G));
+ if (dhps.P != DiffieHellmanUtil.DEFAULT_MOD || dhps.G != DiffieHellmanUtil.DEFAULT_GEN) {
+ args.Add(protocol.openid.dh_modulus, DiffieHellmanUtil.UnsignedToBase64(dhps.P));
+ args.Add(protocol.openid.dh_gen, DiffieHellmanUtil.UnsignedToBase64(dhps.G));
}
}
- return new AssociateRequest(provider, args, dh);
+ return new AssociateRequest(relyingParty, provider, args, dh);
}
AssociateResponse response;
[DebuggerBrowsable(DebuggerBrowsableState.Never)] // code execution in getter
@@ -76,10 +90,10 @@ namespace DotNetOpenId.RelyingParty { get {
if (response == null) {
try {
- response = new AssociateResponse(Provider, GetResponse(), DH);
+ response = new AssociateResponse(RelyingParty, Provider, GetResponse(), DH);
} catch (OpenIdException ex) {
if (ex.Query != null) {
- response = new AssociateResponse(Provider, ex.Query, DH);
+ response = new AssociateResponse(RelyingParty, Provider, ex.Query, DH);
}
// Silently fail at associate attempt, since we can recover
// using dumb mode.
diff --git a/src/DotNetOpenId/RelyingParty/AssociateResponse.cs b/src/DotNetOpenId/RelyingParty/AssociateResponse.cs index 8543d24..c555f8c 100644 --- a/src/DotNetOpenId/RelyingParty/AssociateResponse.cs +++ b/src/DotNetOpenId/RelyingParty/AssociateResponse.cs @@ -7,8 +7,8 @@ using System.Diagnostics; namespace DotNetOpenId.RelyingParty {
class AssociateResponse : DirectResponse {
- public AssociateResponse(ServiceEndpoint provider, IDictionary<string, string> args, DiffieHellman dh)
- : base(provider, args) {
+ public AssociateResponse(OpenIdRelyingParty relyingParty, ServiceEndpoint provider, IDictionary<string, string> args, DiffieHellman dh)
+ : base(relyingParty, provider, args) {
DH = dh;
if (Args.ContainsKey(Protocol.openidnp.assoc_handle)) {
@@ -21,8 +21,9 @@ namespace DotNetOpenId.RelyingParty { string session_type = Util.GetRequiredArg(Args, Protocol.openidnp.session_type);
// If the suggested options are among those we support...
if (Array.IndexOf(Protocol.Args.SignatureAlgorithm.All, assoc_type) >= 0 &&
- Array.IndexOf(Protocol.Args.SessionType.All, session_type) >= 0) {
- SecondAttempt = AssociateRequest.Create(Provider, assoc_type, session_type);
+ Array.IndexOf(Protocol.Args.SessionType.All, session_type) >= 0 &&
+ RelyingParty.Settings.IsAssociationInPermittedRange(Protocol, assoc_type)) {
+ SecondAttempt = AssociateRequest.Create(RelyingParty, Provider, assoc_type, session_type, false);
}
}
}
@@ -31,39 +32,35 @@ namespace DotNetOpenId.RelyingParty { void initializeAssociation() {
string assoc_type = Util.GetRequiredArg(Args, Protocol.openidnp.assoc_type);
- if (Protocol.Args.SignatureAlgorithm.HMAC_SHA1.Equals(assoc_type, StringComparison.Ordinal) ||
- Protocol.Args.SignatureAlgorithm.HMAC_SHA256.Equals(assoc_type, StringComparison.Ordinal)) {
+ if (Array.IndexOf(Protocol.Args.SignatureAlgorithm.All, assoc_type) >= 0) {
byte[] secret;
string session_type;
if (!Args.TryGetValue(Protocol.openidnp.session_type, out session_type) ||
Protocol.Args.SessionType.NoEncryption.Equals(session_type, StringComparison.Ordinal)) {
secret = getDecoded(Protocol.openidnp.mac_key);
- } else if (Protocol.Args.SessionType.DH_SHA1.Equals(session_type, StringComparison.Ordinal)) {
- byte[] dh_server_public = getDecoded(Protocol.openidnp.dh_server_public);
- byte[] enc_mac_key = getDecoded(Protocol.openidnp.enc_mac_key);
- secret = CryptUtil.SHAHashXorSecret(CryptUtil.Sha1, DH, dh_server_public, enc_mac_key);
- } else if (Protocol.Args.SessionType.DH_SHA256.Equals(session_type, StringComparison.Ordinal)) {
- byte[] dh_server_public = getDecoded(Protocol.openidnp.dh_server_public);
- byte[] enc_mac_key = getDecoded(Protocol.openidnp.enc_mac_key);
- secret = CryptUtil.SHAHashXorSecret(CryptUtil.Sha256, DH, dh_server_public, enc_mac_key);
} else {
- throw new OpenIdException(string.Format(CultureInfo.CurrentCulture,
- Strings.InvalidOpenIdQueryParameterValue,
- Protocol.openid.session_type, session_type));
+ try {
+ byte[] dh_server_public = getDecoded(Protocol.openidnp.dh_server_public);
+ byte[] enc_mac_key = getDecoded(Protocol.openidnp.enc_mac_key);
+ secret = DiffieHellmanUtil.SHAHashXorSecret(DiffieHellmanUtil.Lookup(Protocol, session_type), DH, dh_server_public, enc_mac_key);
+ } catch (ArgumentException ex) {
+ throw new OpenIdException(string.Format(CultureInfo.CurrentCulture,
+ Strings.InvalidOpenIdQueryParameterValue,
+ Protocol.openid.session_type, session_type), ex);
+ }
}
string assocHandle = Util.GetRequiredArg(Args, Protocol.openidnp.assoc_handle);
TimeSpan expiresIn = new TimeSpan(0, 0, Convert.ToInt32(Util.GetRequiredArg(Args, Protocol.openidnp.expires_in), CultureInfo.InvariantCulture));
- if (assoc_type == Protocol.Args.SignatureAlgorithm.HMAC_SHA1) {
- Association = new HmacSha1Association(assocHandle, secret, expiresIn);
- } else if (assoc_type == Protocol.Args.SignatureAlgorithm.HMAC_SHA256) {
- Association = new HmacSha256Association(assocHandle, secret, expiresIn);
- } else {
+ try {
+ Association = HmacShaAssociation.Create(Protocol, assoc_type,
+ assocHandle, secret, expiresIn);
+ } catch (ArgumentException ex) {
throw new OpenIdException(string.Format(CultureInfo.CurrentCulture,
Strings.InvalidOpenIdQueryParameterValue,
- Protocol.openid.assoc_type, assoc_type));
+ Protocol.openid.assoc_type, assoc_type), ex);
}
} else {
throw new OpenIdException(string.Format(CultureInfo.CurrentCulture,
diff --git a/src/DotNetOpenId/RelyingParty/AuthenticationRequest.cs b/src/DotNetOpenId/RelyingParty/AuthenticationRequest.cs index fddd397..bccdca5 100644 --- a/src/DotNetOpenId/RelyingParty/AuthenticationRequest.cs +++ b/src/DotNetOpenId/RelyingParty/AuthenticationRequest.cs @@ -29,18 +29,19 @@ namespace DotNetOpenId.RelyingParty { class AuthenticationRequest : IAuthenticationRequest {
Association assoc;
ServiceEndpoint endpoint;
- MessageEncoder encoder;
Protocol protocol { get { return endpoint.Protocol; } }
+ internal OpenIdRelyingParty RelyingParty;
AuthenticationRequest(string token, Association assoc, ServiceEndpoint endpoint,
- Realm realm, Uri returnToUrl, MessageEncoder encoder) {
+ Realm realm, Uri returnToUrl, OpenIdRelyingParty relyingParty) {
if (endpoint == null) throw new ArgumentNullException("endpoint");
if (realm == null) throw new ArgumentNullException("realm");
if (returnToUrl == null) throw new ArgumentNullException("returnToUrl");
- if (encoder == null) throw new ArgumentNullException("encoder");
+ if (relyingParty == null) throw new ArgumentNullException("relyingParty");
+
this.assoc = assoc;
this.endpoint = endpoint;
- this.encoder = encoder;
+ RelyingParty = relyingParty;
Realm = realm;
ReturnToUrl = returnToUrl;
@@ -51,12 +52,17 @@ namespace DotNetOpenId.RelyingParty { AddCallbackArguments(DotNetOpenId.RelyingParty.Token.TokenKey, token);
}
internal static AuthenticationRequest Create(Identifier userSuppliedIdentifier,
- OpenIdRelyingParty relyingParty, Realm realm, Uri returnToUrl, IRelyingPartyApplicationStore store) {
+ OpenIdRelyingParty relyingParty, Realm realm, Uri returnToUrl) {
if (userSuppliedIdentifier == null) throw new ArgumentNullException("userSuppliedIdentifier");
if (relyingParty == null) throw new ArgumentNullException("relyingParty");
if (realm == null) throw new ArgumentNullException("realm");
userSuppliedIdentifier = userSuppliedIdentifier.TrimFragment();
+ if (relyingParty.Settings.RequireSsl) {
+ // Rather than check for successful SSL conversion at this stage,
+ // We'll wait for secure discovery to fail on the new identifier.
+ userSuppliedIdentifier.TryRequireSsl(out userSuppliedIdentifier);
+ }
Logger.InfoFormat("Creating authentication request for user supplied Identifier: {0}",
userSuppliedIdentifier);
Logger.DebugFormat("Realm: {0}", realm);
@@ -73,10 +79,9 @@ namespace DotNetOpenId.RelyingParty { }
var endpoints = new List<ServiceEndpoint>(userSuppliedIdentifier.Discover());
- ServiceEndpoint endpoint = selectEndpoint(endpoints.AsReadOnly(), relyingParty, store);
+ ServiceEndpoint endpoint = selectEndpoint(endpoints.AsReadOnly(), relyingParty);
if (endpoint == null)
throw new OpenIdException(Strings.OpenIdEndpointNotFound);
- Logger.DebugFormat("Discovered provider endpoint: {0}", endpoint);
// 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
@@ -85,13 +90,13 @@ namespace DotNetOpenId.RelyingParty { throw new OpenIdException(string.Format(CultureInfo.CurrentCulture,
Strings.ReturnToNotUnderRealm, returnToUrl, realm));
- string token = new Token(endpoint).Serialize(store);
+ string token = new Token(endpoint).Serialize(relyingParty.Store);
// Retrieve the association, but don't create one, as a creation was already
// attempted by the selectEndpoint method.
- Association association = store != null ? getAssociation(endpoint, store, false) : null;
+ Association association = relyingParty.Store != null ? getAssociation(relyingParty, endpoint, false) : null;
return new AuthenticationRequest(
- token, association, endpoint, realm, returnToUrl, relyingParty.Encoder);
+ token, association, endpoint, realm, returnToUrl, relyingParty);
}
/// <summary>
@@ -102,11 +107,13 @@ namespace DotNetOpenId.RelyingParty { if (endpoints == null) throw new ArgumentNullException("endpoints");
if (relyingParty == null) throw new ArgumentNullException("relyingParty");
- // Filter the endpoints based on criteria given by the host web site.
- List<IXrdsProviderEndpoint> filteredEndpoints = new List<IXrdsProviderEndpoint>(endpoints.Count);
- var filter = relyingParty.EndpointFilter;
+ // Construct the endpoints filters based on criteria given by the host web site.
+ EndpointSelector versionFilter = ep => ((ServiceEndpoint)ep).Protocol.Version >= Protocol.Lookup(relyingParty.Settings.MinimumRequiredOpenIdVersion).Version;
+ EndpointSelector hostingSiteFilter = relyingParty.EndpointFilter ?? (ep => true);
+
+ var filteredEndpoints = new List<IXrdsProviderEndpoint>(endpoints.Count);
foreach (ServiceEndpoint endpoint in endpoints) {
- if (filter == null || filter(endpoint)) {
+ if (versionFilter(endpoint) && hostingSiteFilter(endpoint)) {
filteredEndpoints.Add(endpoint);
}
}
@@ -125,10 +132,21 @@ namespace DotNetOpenId.RelyingParty { /// Chooses which provider endpoint is the best one to use.
/// </summary>
/// <returns>The best endpoint, or null if no acceptable endpoints were found.</returns>
- private static ServiceEndpoint selectEndpoint(ReadOnlyCollection<ServiceEndpoint> endpoints,
- OpenIdRelyingParty relyingParty, IRelyingPartyApplicationStore store) {
+ private static ServiceEndpoint selectEndpoint(ReadOnlyCollection<ServiceEndpoint> endpoints,
+ OpenIdRelyingParty relyingParty) {
List<ServiceEndpoint> filteredEndpoints = filterAndSortEndpoints(endpoints, relyingParty);
+ if (filteredEndpoints.Count != endpoints.Count) {
+ Logger.DebugFormat("Some endpoints were filtered out. Total endpoints remaining: {0}", filteredEndpoints.Count);
+ }
+ if (Logger.IsDebugEnabled) {
+ if (Util.AreSequencesEquivalent(endpoints, filteredEndpoints)) {
+ Logger.Debug("Filtering and sorting of endpoints did not affect the list.");
+ } else {
+ Logger.Debug("After filtering and sorting service endpoints, this is the new prioritized list:");
+ Logger.Debug(Util.ToString(filteredEndpoints, true));
+ }
+ }
// If there are no endpoint candidates...
if (filteredEndpoints.Count == 0) {
@@ -137,7 +155,8 @@ namespace DotNetOpenId.RelyingParty { // If we don't have an application store, we have no place to record an association to
// and therefore can only take our best shot at one of the endpoints.
- if (store == null) {
+ if (relyingParty.Store == null) {
+ Logger.Debug("No state store, so the first endpoint available is selected.");
return filteredEndpoints[0];
}
@@ -146,11 +165,14 @@ namespace DotNetOpenId.RelyingParty { // The idea here is that we don't want to redirect the user to a dead OP for authentication.
// If the user has multiple OPs listed in his/her XRDS document, then we'll go down the list
// and try each one until we find one that's good.
+ int winningEndpointIndex = 0;
foreach (ServiceEndpoint endpointCandidate in filteredEndpoints) {
+ winningEndpointIndex++;
// One weakness of this method is that an OP that's down, but with whom we already
// created an association in the past will still pass this "are you alive?" test.
- Association association = getAssociation(endpointCandidate, store, true);
+ Association association = getAssociation(relyingParty, endpointCandidate, true);
if (association != null) {
+ Logger.DebugFormat("Endpoint #{0} (1-based index) responded to an association request. Selecting that endpoint.", winningEndpointIndex);
// We have a winner!
return endpointCandidate;
}
@@ -158,15 +180,29 @@ namespace DotNetOpenId.RelyingParty { // Since all OPs failed to form an association with us, just return the first endpoint
// and hope for the best.
+ Logger.Debug("All endpoints failed to respond to an association request. Selecting first endpoint to try to authenticate to.");
return endpoints[0];
}
- static Association getAssociation(ServiceEndpoint provider, IRelyingPartyApplicationStore store, bool createNewAssociationIfNeeded) {
+ static Association getAssociation(OpenIdRelyingParty relyingParty, ServiceEndpoint provider, bool createNewAssociationIfNeeded) {
+ if (relyingParty == null) throw new ArgumentNullException("relyingParty");
if (provider == null) throw new ArgumentNullException("provider");
- if (store == null) throw new ArgumentNullException("store");
- Association assoc = store.GetAssociation(provider.ProviderEndpoint);
+ // TODO: we need a way to lookup an association that fulfills a given set of security
+ // requirements. We may have a SHA-1 association and a SHA-256 association that need
+ // to be called for specifically. (a bizzare scenario, admittedly, making this low priority).
+ Association assoc = relyingParty.Store.GetAssociation(provider.ProviderEndpoint);
+
+ // If the returned association does not fulfill security requirements, ignore it.
+ if (assoc != null && !relyingParty.Settings.IsAssociationInPermittedRange(provider.Protocol, assoc.GetAssociationType(provider.Protocol))) {
+ assoc = null;
+ }
if ((assoc == null || !assoc.HasUsefulLifeRemaining) && createNewAssociationIfNeeded) {
- var req = AssociateRequest.Create(provider);
+ var req = AssociateRequest.Create(relyingParty, provider);
+ if (req == null) {
+ // this can happen if security requirements and protocol conflict
+ // to where there are no association types to choose from.
+ return null;
+ }
if (req.Response != null) {
// try again if we failed the first time and have a worthy second-try.
if (req.Response.Association == null && req.Response.SecondAttempt != null) {
@@ -174,9 +210,29 @@ namespace DotNetOpenId.RelyingParty { req = req.Response.SecondAttempt;
}
assoc = req.Response.Association;
+ // Confirm that the association matches the type we requested (section 8.2.1)
+ // if this is a 2.0 OP (1.x OPs had freedom to differ from the requested type).
+ if (assoc != null && provider.Protocol.Version.Major >= 2) {
+ if (!string.Equals(
+ req.Args[provider.Protocol.openid.assoc_type],
+ Util.GetRequiredArg(req.Response.Args, provider.Protocol.openidnp.assoc_type),
+ StringComparison.Ordinal) ||
+ !string.Equals(
+ req.Args[provider.Protocol.openid.session_type],
+ Util.GetRequiredArg(req.Response.Args, provider.Protocol.openidnp.session_type),
+ StringComparison.Ordinal)) {
+ Logger.ErrorFormat("Provider responded with contradicting association parameters. Requested [{0}, {1}] but got [{2}, {3}] back.",
+ req.Args[provider.Protocol.openid.assoc_type],
+ req.Args[provider.Protocol.openid.session_type],
+ Util.GetRequiredArg(req.Response.Args, provider.Protocol.openidnp.assoc_type),
+ Util.GetRequiredArg(req.Response.Args, provider.Protocol.openidnp.session_type));
+
+ assoc = null;
+ }
+ }
if (assoc != null) {
Logger.InfoFormat("Association with {0} established.", provider.ProviderEndpoint);
- store.StoreAssociation(provider.ProviderEndpoint, assoc);
+ relyingParty.Store.StoreAssociation(provider.ProviderEndpoint, assoc);
} else {
Logger.ErrorFormat("Association attempt with {0} provider failed.", provider.ProviderEndpoint);
}
@@ -215,6 +271,7 @@ namespace DotNetOpenId.RelyingParty { /// location.
/// </summary>
IProviderEndpoint IAuthenticationRequest.Provider { get { return endpoint; } }
+
/// <summary>
/// Gets the response to send to the user agent to begin the
/// OpenID authentication process.
@@ -245,7 +302,7 @@ namespace DotNetOpenId.RelyingParty { qsArgs.Add(pair.Key, pair.Value);
var request = new IndirectMessageRequest(this.endpoint.ProviderEndpoint, qsArgs);
- return this.encoder.Encode(request);
+ return RelyingParty.Encoder.Encode(request);
}
}
diff --git a/src/DotNetOpenId/RelyingParty/AuthenticationResponse.cs b/src/DotNetOpenId/RelyingParty/AuthenticationResponse.cs index 87424fa..903366b 100644 --- a/src/DotNetOpenId/RelyingParty/AuthenticationResponse.cs +++ b/src/DotNetOpenId/RelyingParty/AuthenticationResponse.cs @@ -21,9 +21,12 @@ namespace DotNetOpenId.RelyingParty { /// </summary>
Failed,
/// <summary>
- /// The Provider responded to a request for immediate authentication approval
+ /// <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.
+ /// 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>
@@ -33,7 +36,7 @@ namespace DotNetOpenId.RelyingParty { }
[DebuggerDisplay("Status: {Status}, ClaimedIdentifier: {ClaimedIdentifier}")]
- class AuthenticationResponse : IAuthenticationResponse {
+ class AuthenticationResponse : IAuthenticationResponse, ISetupRequiredAuthenticationResponse {
internal AuthenticationResponse(AuthenticationStatus status, ServiceEndpoint provider, IDictionary<string, string> query) {
if (provider == null) throw new ArgumentNullException("provider");
if (query == null) throw new ArgumentNullException("query");
@@ -74,8 +77,12 @@ namespace DotNetOpenId.RelyingParty { /// An Identifier that the end user claims to own.
/// </summary>
public Identifier ClaimedIdentifier {
- [DebuggerStepThrough]
- get { return Provider.ClaimedIdentifier; }
+ get {
+ if (Provider.ClaimedIdentifier == Provider.Protocol.ClaimedIdentifierForOPIdentifier) {
+ return null; // no claimed identifier -- failed directed identity authentication
+ }
+ return Provider.ClaimedIdentifier;
+ }
}
/// <summary>
/// Gets a user-friendly OpenID Identifier for display purposes ONLY.
@@ -146,7 +153,7 @@ namespace DotNetOpenId.RelyingParty { }
internal static AuthenticationResponse Parse(IDictionary<string, string> query,
- IRelyingPartyApplicationStore store, Uri requestUrl) {
+ OpenIdRelyingParty relyingParty, Uri requestUrl) {
if (query == null) throw new ArgumentNullException("query");
if (requestUrl == null) throw new ArgumentNullException("requestUrl");
@@ -160,7 +167,7 @@ namespace DotNetOpenId.RelyingParty { HttpUtility.ParseQueryString(requestUrl.Query));
string token = Util.GetOptionalArg(requestUrlQuery, Token.TokenKey);
if (token != null) {
- tokenEndpoint = Token.Deserialize(token, store).Endpoint;
+ tokenEndpoint = Token.Deserialize(token, relyingParty.Store).Endpoint;
}
Protocol protocol = Protocol.Detect(query);
@@ -196,7 +203,7 @@ namespace DotNetOpenId.RelyingParty { // verified.
// For the error-handling and cancellation cases, the info does not have to
// be verified, so we'll use whichever one is available.
- return parseIdResResponse(query, tokenEndpoint, responseEndpoint, store, requestUrl);
+ return parseIdResResponse(query, tokenEndpoint, responseEndpoint, relyingParty, requestUrl);
} else {
throw new OpenIdException(string.Format(CultureInfo.CurrentCulture,
Strings.InvalidOpenIdQueryParameterValue,
@@ -206,7 +213,7 @@ namespace DotNetOpenId.RelyingParty { static AuthenticationResponse parseIdResResponse(IDictionary<string, string> query,
ServiceEndpoint tokenEndpoint, ServiceEndpoint responseEndpoint,
- IRelyingPartyApplicationStore store, Uri requestUrl) {
+ OpenIdRelyingParty relyingParty, Uri requestUrl) {
// Use responseEndpoint if it is available so we get the
// Claimed Identifer correct in the AuthenticationResponse.
ServiceEndpoint unverifiedEndpoint = responseEndpoint ?? tokenEndpoint;
@@ -218,9 +225,9 @@ namespace DotNetOpenId.RelyingParty { }
verifyReturnTo(query, unverifiedEndpoint, requestUrl);
- verifyDiscoveredInfoMatchesAssertedInfo(query, tokenEndpoint, responseEndpoint);
- verifyNonceUnused(query, unverifiedEndpoint, store);
- verifySignature(query, unverifiedEndpoint, store);
+ verifyDiscoveredInfoMatchesAssertedInfo(relyingParty, query, tokenEndpoint, responseEndpoint);
+ verifyNonceUnused(query, unverifiedEndpoint, relyingParty.Store);
+ verifySignature(relyingParty, query, unverifiedEndpoint);
return new AuthenticationResponse(AuthenticationStatus.Authenticated, unverifiedEndpoint, query);
}
@@ -260,7 +267,8 @@ namespace DotNetOpenId.RelyingParty { /// <remarks>
/// This is documented in OpenId Authentication 2.0 section 11.2.
/// </remarks>
- static void verifyDiscoveredInfoMatchesAssertedInfo(IDictionary<string, string> query,
+ static void verifyDiscoveredInfoMatchesAssertedInfo(OpenIdRelyingParty relyingParty,
+ IDictionary<string, string> query,
ServiceEndpoint tokenEndpoint, ServiceEndpoint responseEndpoint) {
Logger.Debug("Verifying assertion matches identifier discovery results...");
@@ -277,22 +285,28 @@ namespace DotNetOpenId.RelyingParty { }
} else {
// In 2.0, we definitely have a responseEndpoint, but may not have a
- // tokenEndpoint. If we don't have a tokenEndpoint or if the user
- // gave us an OP Identifier originally, we need to perform discovery on
+ // tokenEndpoint. If we don't have a tokenEndpoint, or it doesn't match the assertion,
+ // or if the user gave us an OP Identifier originally, then we need to perform discovery on
// the responseEndpoint.ClaimedIdentifier to verify the OP has authority
// to speak for it.
- if (tokenEndpoint == null ||
- tokenEndpoint.ClaimedIdentifier == tokenEndpoint.Protocol.ClaimedIdentifierForOPIdentifier) {
+ if (tokenEndpoint == null || // no token included (unsolicited assertion)
+ tokenEndpoint != responseEndpoint || // the OP is asserting something different than we asked for
+ tokenEndpoint.ClaimedIdentifier == tokenEndpoint.Protocol.ClaimedIdentifierForOPIdentifier) { // or directed identity is in effect
Identifier claimedIdentifier = Util.GetRequiredArg(query, responseEndpoint.Protocol.openid.claimed_id);
+ // Require SSL where appropriate. This will filter out insecure identifiers,
+ // redirects and provider endpoints automatically. If we find a match after all that
+ // filtering with the responseEndpoint, then the unsolicited assertion is secure.
+ if (relyingParty.Settings.RequireSsl && !claimedIdentifier.TryRequireSsl(out claimedIdentifier)) {
+ throw new OpenIdException(Strings.InsecureWebRequestWithSslRequired, query);
+ }
+ Logger.InfoFormat("Provider asserted an identifier that requires (re)discovery to confirm.");
List<ServiceEndpoint> discoveredEndpoints = new List<ServiceEndpoint>(claimedIdentifier.Discover());
// Make sure the response endpoint matches one of the discovered endpoints.
if (!discoveredEndpoints.Contains(responseEndpoint)) {
- throw new OpenIdException(Strings.IssuedAssertionFailsIdentifierDiscovery);
+ throw new OpenIdException(string.Format(CultureInfo.CurrentCulture,
+ Strings.IssuedAssertionFailsIdentifierDiscovery,
+ responseEndpoint, Util.ToString(discoveredEndpoints)));
}
- } else {
- // Check that the assertion matches the service endpoint we know about.
- if (responseEndpoint != tokenEndpoint)
- throw new OpenIdException(Strings.IssuedAssertionFailsIdentifierDiscovery);
}
}
}
@@ -306,7 +320,7 @@ namespace DotNetOpenId.RelyingParty { nonce.Consume(store);
}
- static void verifySignature(IDictionary<string, string> query, ServiceEndpoint endpoint, IRelyingPartyApplicationStore store) {
+ static void verifySignature(OpenIdRelyingParty relyingParty, IDictionary<string, string> query, ServiceEndpoint endpoint) {
string signed = Util.GetRequiredArg(query, endpoint.Protocol.openid.signed);
string[] signedFields = signed.Split(',');
@@ -329,13 +343,13 @@ namespace DotNetOpenId.RelyingParty { // Now actually validate the signature itself.
string assoc_handle = Util.GetRequiredArg(query, endpoint.Protocol.openid.assoc_handle);
- Association assoc = store != null ? store.GetAssociation(endpoint.ProviderEndpoint, assoc_handle) : null;
+ Association assoc = relyingParty.Store != null ? relyingParty.Store.GetAssociation(endpoint.ProviderEndpoint, assoc_handle) : null;
if (assoc == null) {
// It's not an association we know about. Dumb mode is our
// only possible path for recovery.
Logger.Debug("Passing signature back to Provider for verification (no association available)...");
- verifySignatureByProvider(query, endpoint, store);
+ verifySignatureByProvider(relyingParty, query, endpoint);
} else {
if (assoc.IsExpired)
throw new OpenIdException(string.Format(CultureInfo.CurrentCulture,
@@ -378,12 +392,33 @@ namespace DotNetOpenId.RelyingParty { /// to the consumer site with an authenticated status.
/// </summary>
/// <returns>Whether the authentication is valid.</returns>
- static void verifySignatureByProvider(IDictionary<string, string> query, ServiceEndpoint provider, IRelyingPartyApplicationStore store) {
- var request = CheckAuthRequest.Create(provider, query);
- if (request.Response.InvalidatedAssociationHandle != null && store != null)
- store.RemoveAssociation(provider.ProviderEndpoint, request.Response.InvalidatedAssociationHandle);
+ static void verifySignatureByProvider(OpenIdRelyingParty relyingParty, IDictionary<string, string> query, ServiceEndpoint provider) {
+ var request = CheckAuthRequest.Create(relyingParty, provider, query);
+ if (request.Response.InvalidatedAssociationHandle != null && relyingParty.Store != null)
+ relyingParty.Store.RemoveAssociation(provider.ProviderEndpoint, request.Response.InvalidatedAssociationHandle);
if (!request.Response.IsAuthenticationValid)
throw new OpenIdException(Strings.InvalidSignature);
}
+
+ #region ISetupRequiredAuthenticationResponse Members
+
+ /// <summary>
+ /// The <see cref="Identifier"/> to pass to <see cref="OpenIdRelyingParty.CreateRequest(Identifier)"/>
+ /// in a subsequent authentication attempt.
+ /// </summary>
+ /// <remarks>
+ /// When directed identity is used, this will be the Provider Identifier given by the user.
+ /// Otherwise it will be the Claimed Identifier derived from the user-supplied identifier.
+ /// </remarks>
+ public Identifier ClaimedOrProviderIdentifier {
+ get {
+ if (Status != AuthenticationStatus.SetupRequired) {
+ throw new InvalidOperationException(Strings.OperationOnlyValidForSetupRequiredState);
+ }
+ return ClaimedIdentifier ?? Provider.UserSuppliedIdentifier;
+ }
+ }
+
+ #endregion
}
}
diff --git a/src/DotNetOpenId/RelyingParty/CheckAuthRequest.cs b/src/DotNetOpenId/RelyingParty/CheckAuthRequest.cs index 4fbf380..f46232a 100644 --- a/src/DotNetOpenId/RelyingParty/CheckAuthRequest.cs +++ b/src/DotNetOpenId/RelyingParty/CheckAuthRequest.cs @@ -5,11 +5,12 @@ using System.Diagnostics; namespace DotNetOpenId.RelyingParty {
class CheckAuthRequest : DirectRequest {
- CheckAuthRequest(ServiceEndpoint provider, IDictionary<string, string> args) :
- base(provider, args) {
+ CheckAuthRequest(OpenIdRelyingParty relyingParty, ServiceEndpoint provider, IDictionary<string, string> args) :
+ base(relyingParty, provider, args) {
}
- public static CheckAuthRequest Create(ServiceEndpoint provider, IDictionary<string, string> query) {
+ public static CheckAuthRequest Create(OpenIdRelyingParty relyingParty, ServiceEndpoint provider, IDictionary<string, string> query) {
+ if (relyingParty == null) throw new ArgumentNullException("relyingParty");
Protocol protocol = provider.Protocol;
string signed = query[protocol.openid.signed];
@@ -36,7 +37,7 @@ namespace DotNetOpenId.RelyingParty { }
check_args[protocol.openid.mode] = protocol.Args.Mode.check_authentication;
- return new CheckAuthRequest(provider, check_args);
+ return new CheckAuthRequest(relyingParty, provider, check_args);
}
CheckAuthResponse response;
@@ -44,7 +45,7 @@ namespace DotNetOpenId.RelyingParty { public CheckAuthResponse Response {
get {
if (response == null) {
- response = new CheckAuthResponse(Provider, GetResponse());
+ response = new CheckAuthResponse(RelyingParty, Provider, GetResponse());
}
return response;
}
diff --git a/src/DotNetOpenId/RelyingParty/CheckAuthResponse.cs b/src/DotNetOpenId/RelyingParty/CheckAuthResponse.cs index 277a226..297fb82 100644 --- a/src/DotNetOpenId/RelyingParty/CheckAuthResponse.cs +++ b/src/DotNetOpenId/RelyingParty/CheckAuthResponse.cs @@ -6,8 +6,8 @@ using System.Diagnostics; namespace DotNetOpenId.RelyingParty {
[DebuggerDisplay("IsAuthenticationValid: {IsAuthenticationValid}, OpenId: {Protocol.Version}")]
class CheckAuthResponse : DirectResponse {
- public CheckAuthResponse(ServiceEndpoint provider, IDictionary<string, string> args)
- : base(provider, args) {
+ public CheckAuthResponse(OpenIdRelyingParty relyingParty, ServiceEndpoint provider, IDictionary<string, string> args)
+ : base(relyingParty, provider, args) {
}
public string InvalidatedAssociationHandle {
diff --git a/src/DotNetOpenId/RelyingParty/DirectMessageHttpChannel.cs b/src/DotNetOpenId/RelyingParty/DirectMessageHttpChannel.cs new file mode 100644 index 0000000..781228d --- /dev/null +++ b/src/DotNetOpenId/RelyingParty/DirectMessageHttpChannel.cs @@ -0,0 +1,59 @@ +using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.IO;
+using System.Net;
+
+namespace DotNetOpenId.RelyingParty {
+ internal class DirectMessageHttpChannel : IDirectMessageChannel {
+ #region IDirectMessageChannel Members
+
+ public IDictionary<string, string> SendDirectMessageAndGetResponse(ServiceEndpoint provider, IDictionary<string, string> fields) {
+ if (provider == null) throw new ArgumentNullException("provider");
+ if (fields == null) throw new ArgumentNullException("fields");
+
+ byte[] body = ProtocolMessages.Http.GetBytes(fields);
+ IDictionary<string, string> args;
+ UntrustedWebResponse resp = null;
+ string fullResponseText = null;
+ try {
+ resp = UntrustedWebRequest.Request(provider.ProviderEndpoint, body);
+ // If an internal server error occurred, there won't be any KV-form stream
+ // to read in. So instead, preserve whatever error the server did send back
+ // and throw it in the exception.
+ if (resp.StatusCode == HttpStatusCode.InternalServerError) {
+ string errorStream = new StreamReader(resp.ResponseStream).ReadToEnd();
+ throw new OpenIdException(string.Format(CultureInfo.CurrentCulture,
+ Strings.ProviderRespondedWithError, errorStream));
+ }
+ if (Logger.IsDebugEnabled) {
+ fullResponseText = resp.ReadResponseString();
+ }
+ args = ProtocolMessages.KeyValueForm.GetDictionary(resp.ResponseStream);
+ Logger.DebugFormat("Received direct response from {0}: {1}{2}", provider.ProviderEndpoint,
+ Environment.NewLine, Util.ToString(args));
+ } catch (ArgumentException e) {
+ Logger.DebugFormat("Full response from provider (where KVF was expected):{0}{1}",
+ Environment.NewLine, fullResponseText);
+ throw new OpenIdException("Failure decoding Key-Value Form response from provider.", e);
+ } catch (WebException e) {
+ throw new OpenIdException("Failure while connecting to provider.", e);
+ }
+ // All error codes are supposed to be returned with 400, but
+ // some (like myopenid.com) sometimes send errors as 200's.
+ if (resp.StatusCode == HttpStatusCode.BadRequest ||
+ Util.GetOptionalArg(args, provider.Protocol.openidnp.mode) == provider.Protocol.Args.Mode.error) {
+ throw new OpenIdException(string.Format(CultureInfo.CurrentCulture,
+ Strings.ProviderRespondedWithError,
+ Util.GetOptionalArg(args, provider.Protocol.openidnp.error)), args);
+ } else if (resp.StatusCode == HttpStatusCode.OK) {
+ return args;
+ } else {
+ throw new OpenIdException(string.Format(CultureInfo.CurrentCulture,
+ Strings.ProviderRespondedWithUnrecognizedHTTPStatusCode, resp.StatusCode));
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/src/DotNetOpenId/RelyingParty/DirectRequest.cs b/src/DotNetOpenId/RelyingParty/DirectRequest.cs index dbc91f8..2d0cc05 100644 --- a/src/DotNetOpenId/RelyingParty/DirectRequest.cs +++ b/src/DotNetOpenId/RelyingParty/DirectRequest.cs @@ -9,9 +9,11 @@ using System.IO; namespace DotNetOpenId.RelyingParty {
[DebuggerDisplay("OpenId: {Protocol.Version}")]
abstract class DirectRequest {
- protected DirectRequest(ServiceEndpoint provider, IDictionary<string, string> args) {
+ protected DirectRequest(OpenIdRelyingParty relyingParty, ServiceEndpoint provider, IDictionary<string, string> args) {
+ if (relyingParty == null) throw new ArgumentNullException("relyingParty");
if (provider == null) throw new ArgumentNullException("provider");
if (args == null) throw new ArgumentNullException("args");
+ RelyingParty = relyingParty;
Provider = provider;
Args = args;
if (Protocol.QueryDeclaredNamespaceVersion != null &&
@@ -20,45 +22,13 @@ namespace DotNetOpenId.RelyingParty { }
protected ServiceEndpoint Provider { get; private set; }
protected Protocol Protocol { get { return Provider.Protocol; } }
- protected IDictionary<string, string> Args { get; private set; }
+ protected internal IDictionary<string, string> Args { get; private set; }
+ protected OpenIdRelyingParty RelyingParty { get; private set; }
protected IDictionary<string, string> GetResponse() {
Logger.DebugFormat("Sending direct message to {0}: {1}{2}", Provider.ProviderEndpoint,
Environment.NewLine, Util.ToString(Args));
- byte[] body = ProtocolMessages.Http.GetBytes(Args);
- UntrustedWebResponse resp = null;
- IDictionary<string, string> args = null;
- try {
- resp = UntrustedWebRequest.Request(Provider.ProviderEndpoint, body);
- // If an internal server error occurred, there won't be any KV-form stream
- // to read in. So instead, preserve whatever error the server did send back
- // and throw it in the exception.
- if (resp.StatusCode == HttpStatusCode.InternalServerError) {
- string errorStream = new StreamReader(resp.ResponseStream).ReadToEnd();
- throw new OpenIdException(string.Format(CultureInfo.CurrentCulture,
- Strings.ProviderRespondedWithError, errorStream));
- }
- args = ProtocolMessages.KeyValueForm.GetDictionary(resp.ResponseStream);
- Logger.DebugFormat("Received direct response from {0}: {1}{2}", Provider.ProviderEndpoint,
- Environment.NewLine, Util.ToString(args));
- } catch (ArgumentException e) {
- throw new OpenIdException("Failure decoding Key-Value Form response from provider.", e);
- } catch (WebException e) {
- throw new OpenIdException("Failure while connecting to provider.", e);
- }
- // All error codes are supposed to be returned with 400, but
- // some (like myopenid.com) sometimes send errors as 200's.
- if (resp.StatusCode == HttpStatusCode.BadRequest ||
- Util.GetOptionalArg(args, Protocol.openidnp.mode) == Protocol.Args.Mode.error) {
- throw new OpenIdException(string.Format(CultureInfo.CurrentCulture,
- Strings.ProviderRespondedWithError,
- Util.GetOptionalArg(args, Protocol.openidnp.error)), args);
- } else if (resp.StatusCode == HttpStatusCode.OK) {
- return args;
- } else {
- throw new OpenIdException(string.Format(CultureInfo.CurrentCulture,
- Strings.ProviderRespondedWithUnrecognizedHTTPStatusCode, resp.StatusCode));
- }
+ return RelyingParty.DirectMessageChannel.SendDirectMessageAndGetResponse(Provider, Args);
}
}
}
diff --git a/src/DotNetOpenId/RelyingParty/DirectResponse.cs b/src/DotNetOpenId/RelyingParty/DirectResponse.cs index 8c3cec1..6a88a3a 100644 --- a/src/DotNetOpenId/RelyingParty/DirectResponse.cs +++ b/src/DotNetOpenId/RelyingParty/DirectResponse.cs @@ -2,16 +2,30 @@ using System.Collections.Generic;
using System.Text;
using System.Diagnostics;
+using System.Globalization;
namespace DotNetOpenId.RelyingParty {
[DebuggerDisplay("OpenId: {Protocol.Version}")]
class DirectResponse {
- protected DirectResponse(ServiceEndpoint provider, IDictionary<string, string> args) {
+ protected DirectResponse(OpenIdRelyingParty relyingParty, ServiceEndpoint provider, IDictionary<string, string> args) {
+ if (relyingParty == null) throw new ArgumentNullException("relyingParty");
if (provider == null) throw new ArgumentNullException("provider");
if (args == null) throw new ArgumentNullException("args");
+ RelyingParty = relyingParty;
Provider = provider;
Args = args;
+ // Make sure that the OP fulfills the required OpenID version.
+ // We don't use Provider.Protocol here because that's just a cache of
+ // what we _thought_ the OP would support, and our purpose is to double-check this.
+ ProtocolVersion detectedProtocol = Protocol.DetectFromDirectResponse(args).ProtocolVersion;
+ if (detectedProtocol < relyingParty.Settings.MinimumRequiredOpenIdVersion) {
+ throw new OpenIdException(string.Format(CultureInfo.CurrentCulture,
+ Strings.MinimumOPVersionRequirementNotMet,
+ Protocol.Lookup(relyingParty.Settings.MinimumRequiredOpenIdVersion).Version,
+ Protocol.Lookup(detectedProtocol).Version));
+ }
+
if (Logger.IsErrorEnabled) {
if (provider.Protocol.QueryDeclaredNamespaceVersion != null) {
if (!Args.ContainsKey(Protocol.openidnp.ns)) {
@@ -22,10 +36,10 @@ namespace DotNetOpenId.RelyingParty { }
}
}
-
}
+ protected OpenIdRelyingParty RelyingParty { get; private set; }
protected ServiceEndpoint Provider { get; private set; }
- protected IDictionary<string, string> Args { get; private set; }
+ protected internal IDictionary<string, string> Args { get; private set; }
protected Protocol Protocol { get { return Provider.Protocol; } }
}
}
diff --git a/src/DotNetOpenId/RelyingParty/IAuthenticationResponse.cs b/src/DotNetOpenId/RelyingParty/IAuthenticationResponse.cs index 26b6c03..8cf2dbe 100644 --- a/src/DotNetOpenId/RelyingParty/IAuthenticationResponse.cs +++ b/src/DotNetOpenId/RelyingParty/IAuthenticationResponse.cs @@ -30,6 +30,7 @@ namespace DotNetOpenId.RelyingParty { IExtensionResponse GetExtension(Type extensionType);
/// <summary>
/// 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>
diff --git a/src/DotNetOpenId/RelyingParty/IDirectMessageChannel.cs b/src/DotNetOpenId/RelyingParty/IDirectMessageChannel.cs new file mode 100644 index 0000000..122f258 --- /dev/null +++ b/src/DotNetOpenId/RelyingParty/IDirectMessageChannel.cs @@ -0,0 +1,7 @@ +using System.Collections.Generic;
+
+namespace DotNetOpenId.RelyingParty {
+ internal interface IDirectMessageChannel {
+ IDictionary<string, string> SendDirectMessageAndGetResponse(ServiceEndpoint provider, IDictionary<string, string> fields);
+ }
+}
diff --git a/src/DotNetOpenId/RelyingParty/ISetupRequiredAuthenticationResponse.cs b/src/DotNetOpenId/RelyingParty/ISetupRequiredAuthenticationResponse.cs new file mode 100644 index 0000000..5c4f3dc --- /dev/null +++ b/src/DotNetOpenId/RelyingParty/ISetupRequiredAuthenticationResponse.cs @@ -0,0 +1,22 @@ +using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace DotNetOpenId.RelyingParty {
+ /// <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>
+ public interface ISetupRequiredAuthenticationResponse {
+ /// <summary>
+ /// The <see cref="Identifier"/> to pass to <see cref="OpenIdRelyingParty.CreateRequest(Identifier)"/>
+ /// in a subsequent authentication attempt.
+ /// </summary>
+ /// <remarks>
+ /// When directed identity is used, this will be the Provider Identifier given by the user.
+ /// Otherwise it will be the Claimed Identifier derived from the user-supplied identifier.
+ /// </remarks>
+ Identifier ClaimedOrProviderIdentifier { get; }
+ }
+}
diff --git a/src/DotNetOpenId/RelyingParty/IXrdsProviderEndpoint.cs b/src/DotNetOpenId/RelyingParty/IXrdsProviderEndpoint.cs index bc25747..78c873f 100644 --- a/src/DotNetOpenId/RelyingParty/IXrdsProviderEndpoint.cs +++ b/src/DotNetOpenId/RelyingParty/IXrdsProviderEndpoint.cs @@ -8,6 +8,10 @@ namespace DotNetOpenId.RelyingParty { [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Xrds")]
public interface IXrdsProviderEndpoint : IProviderEndpoint {
/// <summary>
+ /// Checks for the presence of a given Type URI in an XRDS service.
+ /// </summary>
+ bool IsTypeUriPresent(string typeUri);
+ /// <summary>
/// Gets the priority associated with this service that may have been given
/// in the XRDS document.
/// </summary>
diff --git a/src/DotNetOpenId/RelyingParty/IndirectMessageRequest.cs b/src/DotNetOpenId/RelyingParty/IndirectMessageRequest.cs index 8348374..c562be1 100644 --- a/src/DotNetOpenId/RelyingParty/IndirectMessageRequest.cs +++ b/src/DotNetOpenId/RelyingParty/IndirectMessageRequest.cs @@ -16,9 +16,6 @@ namespace DotNetOpenId.RelyingParty { public EncodingType EncodingType { get { return EncodingType.IndirectMessage ; } }
public IDictionary<string, string> EncodedFields { get; private set; }
public Uri RedirectUrl { get; private set; }
- public Protocol Protocol {
- get { throw new NotImplementedException(); }
- }
#endregion
}
diff --git a/src/DotNetOpenId/RelyingParty/OpenIdLogin.cs b/src/DotNetOpenId/RelyingParty/OpenIdLogin.cs index 48df69c..564f0e7 100644 --- a/src/DotNetOpenId/RelyingParty/OpenIdLogin.cs +++ b/src/DotNetOpenId/RelyingParty/OpenIdLogin.cs @@ -20,8 +20,8 @@ namespace DotNetOpenId.RelyingParty /// An ASP.NET control providing a complete OpenID login experience.
/// </summary>
[SuppressMessage("Microsoft.Naming", "CA1726:UsePreferredTerms", MessageId = "Login")]
- [DefaultProperty("OpenIdUrl")]
- [ToolboxData("<{0}:OpenIdLogin runat=\"server\"></{0}:OpenIdLogin>")]
+ [DefaultProperty("Text"), ValidationProperty("Text")]
+ [ToolboxData("<{0}:OpenIdLogin runat=\"server\" />")]
public class OpenIdLogin : OpenIdTextBox
{
Panel panel;
diff --git a/src/DotNetOpenId/RelyingParty/OpenIdRelyingParty.cs b/src/DotNetOpenId/RelyingParty/OpenIdRelyingParty.cs index 1e727a8..ef96ffd 100644 --- a/src/DotNetOpenId/RelyingParty/OpenIdRelyingParty.cs +++ b/src/DotNetOpenId/RelyingParty/OpenIdRelyingParty.cs @@ -2,8 +2,10 @@ using System; using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
+using System.Configuration;
using System.Diagnostics;
using System.Web;
+using DotNetOpenId.Configuration;
namespace DotNetOpenId.RelyingParty {
/// <summary>
@@ -82,12 +84,16 @@ namespace DotNetOpenId.RelyingParty { ///}
/// </code>
/// </example>
- [DebuggerDisplay("isAuthenticationResponseReady: {isAuthenticationResponseReady}, stateless: {store == null}")]
+ [DebuggerDisplay("isAuthenticationResponseReady: {isAuthenticationResponseReady}, stateless: {Store == null}")]
public class OpenIdRelyingParty {
- IRelyingPartyApplicationStore store;
+ internal IRelyingPartyApplicationStore Store;
Uri request;
IDictionary<string, string> query;
MessageEncoder encoder = new MessageEncoder();
+ internal IDirectMessageChannel DirectMessageChannel = new DirectMessageHttpChannel();
+
+ internal static Uri DefaultRequestUrl { get { return Util.GetRequestUrlFromContext(); } }
+ internal static NameValueCollection DefaultQuery { get { return Util.GetQueryFromContextNVC(); } }
/// <summary>
/// Constructs an OpenId consumer that uses the current HttpContext request
@@ -97,7 +103,7 @@ namespace DotNetOpenId.RelyingParty { /// This method requires a current ASP.NET HttpContext.
/// </remarks>
public OpenIdRelyingParty()
- : this(HttpApplicationStore,
+ : this(Configuration.Store.CreateInstanceOfStore(HttpApplicationStore),
Util.GetRequestUrlFromContext(), Util.GetQueryFromContext()) { }
/// <summary>
/// Constructs an OpenId consumer that uses a given querystring and IAssociationStore.
@@ -131,7 +137,11 @@ namespace DotNetOpenId.RelyingParty { this(store, requestUrl, Util.NameValueCollectionToDictionary(query)) {
}
OpenIdRelyingParty(IRelyingPartyApplicationStore store, Uri requestUrl, IDictionary<string, string> query) {
- this.store = store;
+ // Initialize settings with defaults and config section
+ Settings = Configuration.SecuritySettings.CreateSecuritySettings();
+ Settings.RequireSslChanged += new EventHandler(Settings_RequireSslChanged);
+
+ this.Store = store;
if (store != null) {
store.ClearExpiredAssociations(); // every so often we should do this.
}
@@ -163,7 +173,7 @@ namespace DotNetOpenId.RelyingParty { /// send to the user agent to initiate the authentication.
/// </returns>
public IAuthenticationRequest CreateRequest(Identifier userSuppliedIdentifier, Realm realm, Uri returnToUrl) {
- return AuthenticationRequest.Create(userSuppliedIdentifier, this, realm, returnToUrl, store);
+ return AuthenticationRequest.Create(userSuppliedIdentifier, this, realm, returnToUrl);
}
/// <summary>
@@ -274,7 +284,7 @@ namespace DotNetOpenId.RelyingParty { get {
if (response == null && isAuthenticationResponseReady) {
try {
- response = AuthenticationResponse.Parse(query, store, request);
+ response = AuthenticationResponse.Parse(query, this, request);
} catch (OpenIdException ex) {
response = new FailedAuthenticationResponse(ex);
}
@@ -315,10 +325,13 @@ namespace DotNetOpenId.RelyingParty { [EditorBrowsable(EditorBrowsableState.Advanced)]
public static Comparison<IXrdsProviderEndpoint> DefaultEndpointOrder {
get {
- // Sort first by Service/@priority, then by Service/Uri/@priority
+ // 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) {
- int result = se1.ServicePriority.Value.CompareTo(se2.ServicePriority.Value);
+ result = se1.ServicePriority.Value.CompareTo(se2.ServicePriority.Value);
if (result != 0) return result;
if (se1.UriPriority.HasValue && se2.UriPriority.HasValue) {
return se1.UriPriority.Value.CompareTo(se2.UriPriority.Value);
@@ -351,6 +364,24 @@ namespace DotNetOpenId.RelyingParty { }
}
+ static double getEndpointPrecedenceOrderByServiceType(IXrdsProviderEndpoint 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.IsTypeUriPresent(Protocol.v20.OPIdentifierServiceTypeURI)) {
+ return 0;
+ }
+ if (endpoint.IsTypeUriPresent(Protocol.v20.ClaimedIdentifierServiceTypeURI)) {
+ return 1;
+ }
+ if (endpoint.IsTypeUriPresent(Protocol.v11.ClaimedIdentifierServiceTypeURI)) {
+ return 2;
+ }
+ if (endpoint.IsTypeUriPresent(Protocol.v10.ClaimedIdentifierServiceTypeURI)) {
+ return 3;
+ }
+ return 10;
+ }
+
/// <summary>
/// Provides a way to optionally filter the providers that may be used in authenticating a user.
/// </summary>
@@ -386,6 +417,25 @@ namespace DotNetOpenId.RelyingParty { return store;
}
}
+
+ /// <summary>
+ /// Provides access to the adjustable security settings of this instance
+ /// of <see cref="OpenIdRelyingParty"/>.
+ /// </summary>
+ public RelyingPartySecuritySettings Settings { get; private set; }
+
+ void Settings_RequireSslChanged(object sender, EventArgs e) {
+ // reset response that may have been calculated to force
+ // reconsideration with new security policy.
+ response = null;
+ }
+
+ /// <summary>
+ /// Gets the relevant Configuration section for this OpenIdRelyingParty.
+ /// </summary>
+ internal static RelyingPartySection Configuration {
+ get { return RelyingPartySection.Configuration; }
+ }
}
/// <summary>
diff --git a/src/DotNetOpenId/RelyingParty/OpenIdTextBox.cs b/src/DotNetOpenId/RelyingParty/OpenIdTextBox.cs index b4b55c8..26b1aa8 100644 --- a/src/DotNetOpenId/RelyingParty/OpenIdTextBox.cs +++ b/src/DotNetOpenId/RelyingParty/OpenIdTextBox.cs @@ -32,9 +32,9 @@ namespace DotNetOpenId.RelyingParty /// control, but requires more work to be done by the hosting web site to
/// assemble a complete login experience.
/// </remarks>
- [DefaultProperty("Text")]
- [ToolboxData("<{0}:OpenIdTextBox runat=\"server\"></{0}:OpenIdTextBox>")]
- public class OpenIdTextBox : CompositeControl
+ [DefaultProperty("Text"), ValidationProperty("Text")]
+ [ToolboxData("<{0}:OpenIdTextBox runat=\"server\" />")]
+ public class OpenIdTextBox : CompositeControl, IEditableTextControl, ITextControl
{
/// <summary>
/// Instantiates an <see cref="OpenIdTextBox"/> instance.
@@ -179,6 +179,20 @@ namespace DotNetOpenId.RelyingParty set { ViewState[immediateModeViewStateKey] = value; }
}
+ const string statelessViewStateKey = "Stateless";
+ const bool statelessDefault = false;
+ /// <summary>
+ /// Controls whether stateless mode is used.
+ /// </summary>
+ [Bindable(true)]
+ [Category(behaviorCategory)]
+ [DefaultValue(statelessDefault)]
+ [Description("Controls whether stateless mode is used.")]
+ public bool Stateless {
+ get { return (bool)(ViewState[statelessViewStateKey] ?? statelessDefault); }
+ set { ViewState[statelessViewStateKey] = value; }
+ }
+
const string cssClassDefault = "openid";
/// <summary>
/// Gets/sets the CSS class assigned to the text box.
@@ -444,6 +458,30 @@ namespace DotNetOpenId.RelyingParty get { return (bool)(ViewState[enableRequestProfileViewStateKey] ?? enableRequestProfileDefault); }
set { ViewState[enableRequestProfileViewStateKey] = value; }
}
+
+ const string requireSslViewStateKey = "RequireSsl";
+ const bool requireSslDefault = false;
+ /// <summary>
+ /// Turns on high security mode, requiring the full authentication pipeline to be protected by SSL.
+ /// </summary>
+ [Bindable(true)]
+ [Category(behaviorCategory)]
+ [DefaultValue(requireSslDefault)]
+ [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>
+ /// A custom application store to use, or null 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 IRelyingPartyApplicationStore CustomApplicationStore { get; set; }
+
#endregion
#region Properties to hide
@@ -554,7 +592,7 @@ namespace DotNetOpenId.RelyingParty base.OnLoad(e);
if (!Enabled || Page.IsPostBack) return;
- var consumer = new OpenIdRelyingParty();
+ var consumer = createRelyingParty();
if (consumer.Response != null) {
string persistentString = Page.Request.QueryString[usePersistentCookieCallbackKey];
bool persistentBool;
@@ -581,6 +619,23 @@ namespace DotNetOpenId.RelyingParty }
}
+ 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.
+ IRelyingPartyApplicationStore store = Stateless ? null :
+ (CustomApplicationStore ?? OpenIdRelyingParty.Configuration.Store.CreateInstanceOfStore(OpenIdRelyingParty.HttpApplicationStore));
+ Uri request = OpenIdRelyingParty.DefaultRequestUrl;
+ NameValueCollection query = OpenIdRelyingParty.DefaultQuery;
+ var rp = new OpenIdRelyingParty(store, request, query);
+ // Only set RequireSsl to true, as we don't want to override
+ // a .config setting of true with false.
+ if (RequireSsl) {
+ rp.Settings.RequireSsl = true;
+ }
+ return rp;
+ }
+
/// <summary>
/// Prepares the text box to be rendered.
/// </summary>
@@ -621,7 +676,7 @@ namespace DotNetOpenId.RelyingParty throw new InvalidOperationException(DotNetOpenId.Strings.OpenIdTextBoxEmpty);
try {
- var consumer = new OpenIdRelyingParty();
+ var consumer = createRelyingParty();
// 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
@@ -791,6 +846,18 @@ namespace DotNetOpenId.RelyingParty }
#endregion
+
+ #region IEditableTextControl Members
+
+ /// <summary>
+ /// Occurs when the content of the text box changes between posts to the server.
+ /// </summary>
+ public event EventHandler TextChanged {
+ add { WrappedTextBox.TextChanged += value; }
+ remove { WrappedTextBox.TextChanged -= value; }
+ }
+
+ #endregion
}
/// <summary>
diff --git a/src/DotNetOpenId/RelyingParty/RelyingPartySecuritySettings.cs b/src/DotNetOpenId/RelyingParty/RelyingPartySecuritySettings.cs new file mode 100644 index 0000000..236d6e2 --- /dev/null +++ b/src/DotNetOpenId/RelyingParty/RelyingPartySecuritySettings.cs @@ -0,0 +1,67 @@ +using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace DotNetOpenId.RelyingParty {
+ /// <summary>
+ /// Security settings that are applicable to relying parties.
+ /// </summary>
+ public sealed class RelyingPartySecuritySettings : SecuritySettings {
+ internal RelyingPartySecuritySettings() : base(false) { }
+
+ private bool requireSsl;
+ /// <summary>
+ /// Gets/sets whether the entire pipeline from Identifier discovery to Provider redirect
+ /// is guaranteed to be encrypted using HTTPS for authentication to succeed.
+ /// </summary>
+ /// <remarks>
+ /// <para>Setting this property to true is appropriate for RPs with highly sensitive
+ /// personal information behind the authentication (money management, health records, etc.)</para>
+ /// <para>When set to true, some behavioral changes and additional restrictions are placed:</para>
+ /// <list>
+ /// <item>User-supplied identifiers lacking a scheme are prepended with
+ /// HTTPS:// rather than the standard HTTP:// automatically.</item>
+ /// <item>User-supplied identifiers are not allowed to use HTTP for the scheme.</item>
+ /// <item>All redirects during discovery on the user-supplied identifier must be HTTPS.</item>
+ /// <item>Any XRDS file found by discovery on the User-supplied identifier must be protected using HTTPS.</item>
+ /// <item>Only Provider endpoints found at HTTPS URLs will be considered.</item>
+ /// <item>If the discovered identifier is an OP Identifier (directed identity), the
+ /// Claimed Identifier eventually asserted by the Provider must be an HTTPS identifier.</item>
+ /// <item>In the case of an unsolicited assertion, the asserted Identifier, discovery on it and
+ /// the asserting provider endpoint must all be secured by HTTPS.</item>
+ /// </list>
+ /// <para>Although the first redirect from this relying party to the Provider is required
+ /// to use HTTPS, any additional redirects within the Provider cannot be protected and MAY
+ /// revert the user's connection to HTTP, based on individual Provider implementation.
+ /// There is nothing that the RP can do to detect or prevent this.</para>
+ /// <para>
+ /// An <see cref="OpenIdException"/> is thrown when a secure pipeline cannot be established.
+ /// </para>
+ /// </remarks>
+ public bool RequireSsl {
+ get { return requireSsl; }
+ set {
+ if (requireSsl == value) return;
+ requireSsl = value;
+ OnRequireSslChanged();
+ }
+ }
+
+ internal event EventHandler RequireSslChanged;
+ /// <summary>
+ /// Fires the <see cref="RequireSslChanged"/> event.
+ /// </summary>
+ void OnRequireSslChanged() {
+ EventHandler requireSslChanged = RequireSslChanged;
+ if (requireSslChanged != null) {
+ requireSslChanged(this, new EventArgs());
+ }
+ }
+
+ /// <summary>
+ /// Gets/sets the oldest version of OpenID the remote party is allowed to implement.
+ /// </summary>
+ /// <value>Defaults to <see cref="ProtocolVersion.V10"/></value>
+ public ProtocolVersion MinimumRequiredOpenIdVersion { get; set; }
+ }
+}
diff --git a/src/DotNetOpenId/RelyingParty/ServiceEndpoint.cs b/src/DotNetOpenId/RelyingParty/ServiceEndpoint.cs index f6946bf..b5ede55 100644 --- a/src/DotNetOpenId/RelyingParty/ServiceEndpoint.cs +++ b/src/DotNetOpenId/RelyingParty/ServiceEndpoint.cs @@ -24,6 +24,12 @@ namespace DotNetOpenId.RelyingParty { /// This value MUST be an absolute HTTP or HTTPS URL.
/// </remarks>
public Uri ProviderEndpoint { get; private set; }
+ /// <summary>
+ /// Returns true if the <see cref="ProviderEndpoint"/> is using an encrypted channel.
+ /// </summary>
+ internal bool IsSecure {
+ get { return string.Equals(ProviderEndpoint.Scheme, Uri.UriSchemeHttps, StringComparison.OrdinalIgnoreCase); }
+ }
Uri IProviderEndpoint.Uri { get { return ProviderEndpoint; } }
/*
/// <summary>
@@ -63,16 +69,17 @@ namespace DotNetOpenId.RelyingParty { if (UserSuppliedIdentifier == null || String.Equals(UserSuppliedIdentifier, ClaimedIdentifier, StringComparison.OrdinalIgnoreCase)) {
friendlyIdentifierForDisplay = ClaimedIdentifier;
} else {
- friendlyIdentifierForDisplay = String.Format(CultureInfo.CurrentCulture, "{0} ({1})",
- ClaimedIdentifier, UserSuppliedIdentifier);
+ friendlyIdentifierForDisplay = UserSuppliedIdentifier;
}
} else if (uri != null) {
- string displayUri = uri.Uri.Authority + uri.Uri.PathAndQuery;
- displayUri = displayUri.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.
- friendlyIdentifierForDisplay = Uri.UnescapeDataString(displayUri);
+ if (uri != Protocol.ClaimedIdentifierForOPIdentifier) {
+ string displayUri = uri.Uri.Authority + uri.Uri.PathAndQuery;
+ displayUri = displayUri.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.
+ friendlyIdentifierForDisplay = Uri.UnescapeDataString(displayUri);
+ }
} else {
Debug.Fail("Doh! We never should have reached here.");
friendlyIdentifierForDisplay = ClaimedIdentifier;
@@ -83,7 +90,7 @@ namespace DotNetOpenId.RelyingParty { }
/// <summary>
/// Gets the list of services available at this OP Endpoint for the
- /// claimed Identifier.
+ /// claimed Identifier. May be null.
/// </summary>
public string[] ProviderSupportedServiceTypeUris { get; private set; }
@@ -156,6 +163,10 @@ namespace DotNetOpenId.RelyingParty { }
}
+ public bool IsTypeUriPresent(string typeUri) {
+ return IsExtensionSupported(typeUri);
+ }
+
public bool IsExtensionSupported(string extensionUri) {
if (ProviderSupportedServiceTypeUris == null)
throw new InvalidOperationException("Cannot lookup extension support on a rehydrated ServiceEndpoint.");
@@ -265,7 +276,27 @@ namespace DotNetOpenId.RelyingParty { return ClaimedIdentifier.GetHashCode();
}
public override string ToString() {
- return ProviderEndpoint.AbsoluteUri;
+ StringBuilder builder = new StringBuilder();
+ builder.AppendLine("ClaimedIdentifier: " + ClaimedIdentifier);
+ builder.AppendLine("ProviderLocalIdentifier: " + ProviderLocalIdentifier);
+ builder.AppendLine("ProviderEndpoint: " + ProviderEndpoint.AbsoluteUri);
+ builder.AppendLine("OpenID version: " + Protocol.Version);
+ builder.AppendLine("Service Type URIs:");
+ if (ProviderSupportedServiceTypeUris != null) {
+ foreach (string serviceTypeUri in ProviderSupportedServiceTypeUris) {
+ builder.Append("\t");
+ var matchingExtension = Util.FirstOrDefault(ExtensionManager.RequestExtensions, ext => ext.Key.TypeUri == serviceTypeUri);
+ if (matchingExtension.Key != null) {
+ builder.AppendLine(string.Format(CultureInfo.CurrentCulture, "{0} ({1})", serviceTypeUri, matchingExtension.Value));
+ } else {
+ builder.AppendLine(serviceTypeUri);
+ }
+ }
+ } else {
+ builder.AppendLine("\t(unavailable)");
+ }
+ builder.Length -= Environment.NewLine.Length; // trim last newline
+ return builder.ToString();
}
#region IXrdsProviderEndpoint Members
diff --git a/src/DotNetOpenId/SecuritySettings.cs b/src/DotNetOpenId/SecuritySettings.cs new file mode 100644 index 0000000..fb9c079 --- /dev/null +++ b/src/DotNetOpenId/SecuritySettings.cs @@ -0,0 +1,58 @@ +using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace DotNetOpenId {
+ /// <summary>
+ /// Security settings that may be applicable to both relying parties and providers.
+ /// </summary>
+ public class SecuritySettings {
+ internal SecuritySettings(bool isProvider) {
+ if (isProvider) {
+ maximumHashBitLength = maximumHashBitLengthOPDefault;
+ }
+ }
+
+ internal const int minimumHashBitLengthDefault = 160;
+ int minimumHashBitLength = minimumHashBitLengthDefault;
+ /// <summary>
+ /// Gets/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 { return minimumHashBitLength; }
+ set { minimumHashBitLength = value; }
+ }
+ internal const int maximumHashBitLengthRPDefault = 256;
+ internal const int maximumHashBitLengthOPDefault = 512;
+ int maximumHashBitLength = maximumHashBitLengthRPDefault;
+ /// <summary>
+ /// Gets/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 { return maximumHashBitLength; }
+ set { maximumHashBitLength = value; }
+ }
+
+ internal bool IsAssociationInPermittedRange(Protocol protocol, string associationType) {
+ int lengthInBits = HmacShaAssociation.GetSecretLength(protocol, associationType) * 8;
+ return lengthInBits >= MinimumHashBitLength && lengthInBits <= MaximumHashBitLength;
+ }
+ }
+}
diff --git a/src/DotNetOpenId/Strings.Designer.cs b/src/DotNetOpenId/Strings.Designer.cs index 8382b9b..4798c7f 100644 --- a/src/DotNetOpenId/Strings.Designer.cs +++ b/src/DotNetOpenId/Strings.Designer.cs @@ -88,6 +88,15 @@ namespace DotNetOpenId { }
/// <summary>
+ /// Looks up a localized string similar to XRI CanonicalID verification failed..
+ /// </summary>
+ internal static string CIDVerificationFailed {
+ get {
+ return ResourceManager.GetString("CIDVerificationFailed", resourceCulture);
+ }
+ }
+
+ /// <summary>
/// Looks up a localized string similar to The ClaimedIdentifier property must be set first..
/// </summary>
internal static string ClaimedIdentifierMustBeSetFirst {
@@ -142,6 +151,15 @@ namespace DotNetOpenId { }
/// <summary>
+ /// Looks up a localized string similar to URI is not SSL yet requireSslDiscovery is set to true..
+ /// </summary>
+ internal static string ExplicitHttpUriSuppliedWithSslRequirement {
+ get {
+ return ResourceManager.GetString("ExplicitHttpUriSuppliedWithSslRequirement", resourceCulture);
+ }
+ }
+
+ /// <summary>
/// Looks up a localized string similar to An extension sharing namespace '{0}' has already been added. Only one extension per namespace is allowed in a given request..
/// </summary>
internal static string ExtensionAlreadyAddedWithSameTypeURI {
@@ -196,20 +214,20 @@ namespace DotNetOpenId { }
/// <summary>
- /// Looks up a localized string similar to This operation is only allowed when IsIdentifierSelect is true..
+ /// Looks up a localized string similar to ClaimedIdentifier and LocalIdentifier must be the same when IsIdentifierSelect is true..
/// </summary>
- internal static string IdentifierSelectModeOnly {
+ internal static string IdentifierSelectRequiresMatchingIdentifiers {
get {
- return ResourceManager.GetString("IdentifierSelectModeOnly", resourceCulture);
+ return ResourceManager.GetString("IdentifierSelectRequiresMatchingIdentifiers", resourceCulture);
}
}
/// <summary>
- /// Looks up a localized string similar to ClaimedIdentifier and LocalIdentifier must be the same when IsIdentifierSelect is true..
+ /// Looks up a localized string similar to The Provider requested association type '{0}' and session type '{1}', which are not compatible with each other..
/// </summary>
- internal static string IdentifierSelectRequiresMatchingIdentifiers {
+ internal static string IncompatibleAssociationAndSessionTypes {
get {
- return ResourceManager.GetString("IdentifierSelectRequiresMatchingIdentifiers", resourceCulture);
+ return ResourceManager.GetString("IncompatibleAssociationAndSessionTypes", resourceCulture);
}
}
@@ -223,6 +241,15 @@ namespace DotNetOpenId { }
/// <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 Cannot encode '{0}' because it contains an illegal character for Key-Value Form encoding. (line {1}: '{2}').
/// </summary>
internal static string InvalidCharacterInKeyValueFormInput {
@@ -322,7 +349,11 @@ namespace DotNetOpenId { }
/// <summary>
- /// Looks up a localized string similar to The OpenId Provider issued an assertion for an Identifier whose discovery information did not match..
+ /// Looks up a localized string similar to The OpenId Provider issued an assertion for an Identifier whose discovery information did not match.
+ ///Assertion endpoint info:
+ ///{0}
+ ///Discovered endpoint info:
+ ///{1}.
/// </summary>
internal static string IssuedAssertionFailsIdentifierDiscovery {
get {
@@ -358,6 +389,15 @@ namespace DotNetOpenId { }
/// <summary>
+ /// Looks up a localized string similar to This Relying Party requires a Provider that supports at least OpenID version {0}, but Provider is detected to only support OpenID version {1}..
+ /// </summary>
+ internal static string MinimumOPVersionRequirementNotMet {
+ get {
+ return ResourceManager.GetString("MinimumOPVersionRequirementNotMet", resourceCulture);
+ }
+ }
+
+ /// <summary>
/// Looks up a localized string similar to The XRDS document for XRI {0} is missing the required CanonicalID element..
/// </summary>
internal static string MissingCanonicalIDElement {
@@ -421,6 +461,15 @@ namespace DotNetOpenId { }
/// <summary>
+ /// Looks up a localized string similar to This operation is only allowed when IAuthenticationResponse.State == AuthenticationStatus.SetupRequired..
+ /// </summary>
+ internal static string OperationOnlyValidForSetupRequiredState {
+ get {
+ return ResourceManager.GetString("OperationOnlyValidForSetupRequiredState", resourceCulture);
+ }
+ }
+
+ /// <summary>
/// Looks up a localized string similar to Prefix should not begin or end with a period..
/// </summary>
internal static string PrefixWithoutPeriodsExpected {
@@ -520,6 +569,15 @@ namespace DotNetOpenId { }
/// <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 type must implement {0}..
/// </summary>
internal static string TypeMustImplementX {
@@ -545,5 +603,23 @@ namespace DotNetOpenId { return ResourceManager.GetString("UnspecifiedDateTimeKindNotAllowed", resourceCulture);
}
}
+
+ /// <summary>
+ /// Looks up a localized string similar to XRI resolution failed..
+ /// </summary>
+ internal static string XriResolutionFailed {
+ get {
+ return ResourceManager.GetString("XriResolutionFailed", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to Could not find XRI resolution Status tag or code attribute was invalid..
+ /// </summary>
+ internal static string XriResolutionStatusMissing {
+ get {
+ return ResourceManager.GetString("XriResolutionStatusMissing", resourceCulture);
+ }
+ }
}
}
diff --git a/src/DotNetOpenId/Strings.resx b/src/DotNetOpenId/Strings.resx index 53b3d4a..ab769a7 100644 --- a/src/DotNetOpenId/Strings.resx +++ b/src/DotNetOpenId/Strings.resx @@ -126,6 +126,9 @@ <data name="BadAssociationPrivateData" xml:space="preserve">
<value>The private data supplied does not meet the requirements of any known Association type. Its length may be too short, or it may have been corrupted.</value>
</data>
+ <data name="CIDVerificationFailed" xml:space="preserve">
+ <value>XRI CanonicalID verification failed.</value>
+ </data>
<data name="ClaimedIdentifierMustBeSetFirst" xml:space="preserve">
<value>The ClaimedIdentifier property must be set first.</value>
</data>
@@ -144,6 +147,9 @@ <data name="ExpiredNonce" xml:space="preserve">
<value>The nonce has expired. It was good until {0} (UTC), and it is now {1} (UTC). If this looks wrong, check the server's clock, time zone and daylight savings settings.</value>
</data>
+ <data name="ExplicitHttpUriSuppliedWithSslRequirement" xml:space="preserve">
+ <value>URI is not SSL yet requireSslDiscovery is set to true.</value>
+ </data>
<data name="ExtensionAlreadyAddedWithSameTypeURI" xml:space="preserve">
<value>An extension sharing namespace '{0}' has already been added. Only one extension per namespace is allowed in a given request.</value>
</data>
@@ -162,15 +168,18 @@ <data name="IAssociationStoreRequiredWhenNoHttpContextAvailable" xml:space="preserve">
<value>No current HttpContext was detected, so an IAssociationStore must be explicitly provided. Call the Server constructor overload that takes an IAssociationStore.</value>
</data>
- <data name="IdentifierSelectModeOnly" xml:space="preserve">
- <value>This operation is only allowed when IsIdentifierSelect is true.</value>
- </data>
<data name="IdentifierSelectRequiresMatchingIdentifiers" xml:space="preserve">
<value>ClaimedIdentifier and LocalIdentifier must be the same when IsIdentifierSelect is true.</value>
</data>
+ <data name="IncompatibleAssociationAndSessionTypes" xml:space="preserve">
+ <value>The Provider requested association type '{0}' and session type '{1}', which are not compatible with each other.</value>
+ </data>
<data name="InconsistentAppState" xml:space="preserve">
<value>Inconsistent setting of application state. Authentication request was sent with application state available, but authentication response was received without it available. This makes it impossible to validate the token's signature and will cause assertion verification failure.</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="InvalidCharacterInKeyValueFormInput" xml:space="preserve">
<value>Cannot encode '{0}' because it contains an illegal character for Key-Value Form encoding. (line {1}: '{2}')</value>
</data>
@@ -205,7 +214,11 @@ <value>Not a recognized XRI format: '{0}'.</value>
</data>
<data name="IssuedAssertionFailsIdentifierDiscovery" xml:space="preserve">
- <value>The OpenId Provider issued an assertion for an Identifier whose discovery information did not match.</value>
+ <value>The OpenId Provider issued an assertion for an Identifier whose discovery information did not match.
+Assertion endpoint info:
+{0}
+Discovered endpoint info:
+{1}</value>
</data>
<data name="KeyAlreadyExists" xml:space="preserve">
<value>The given key '{0}' already exists.</value>
@@ -216,6 +229,9 @@ <data name="MatchingArgumentsExpected" xml:space="preserve">
<value>The '{0}' and '{1}' parameters must both be or not be '{2}'.</value>
</data>
+ <data name="MinimumOPVersionRequirementNotMet" xml:space="preserve">
+ <value>This Relying Party requires a Provider that supports at least OpenID version {0}, but Provider is detected to only support OpenID version {1}.</value>
+ </data>
<data name="MissingCanonicalIDElement" xml:space="preserve">
<value>The XRDS document for XRI {0} is missing the required CanonicalID element.</value>
</data>
@@ -237,6 +253,9 @@ <data name="OpenIdTextBoxEmpty" xml:space="preserve">
<value>No OpenId url is provided.</value>
</data>
+ <data name="OperationOnlyValidForSetupRequiredState" xml:space="preserve">
+ <value>This operation is only allowed when IAuthenticationResponse.State == AuthenticationStatus.SetupRequired.</value>
+ </data>
<data name="PrefixWithoutPeriodsExpected" xml:space="preserve">
<value>Prefix should not begin or end with a period.</value>
</data>
@@ -270,6 +289,9 @@ <data name="TamperingDetected" xml:space="preserve">
<value>The '{0}' parameter was expected to have the value '{1}' but had '{2}' instead.</value>
</data>
+ <data name="TooManyRedirects" xml:space="preserve">
+ <value>The maximum allowable number of redirects were exceeded while requesting '{0}'.</value>
+ </data>
<data name="TypeMustImplementX" xml:space="preserve">
<value>The type must implement {0}.</value>
</data>
@@ -279,4 +301,10 @@ <data name="UnspecifiedDateTimeKindNotAllowed" xml:space="preserve">
<value>Providing a DateTime whose Kind is Unspecified is not allowed.</value>
</data>
+ <data name="XriResolutionFailed" xml:space="preserve">
+ <value>XRI resolution failed.</value>
+ </data>
+ <data name="XriResolutionStatusMissing" xml:space="preserve">
+ <value>Could not find XRI resolution Status tag or code attribute was invalid.</value>
+ </data>
</root>
\ No newline at end of file diff --git a/src/DotNetOpenId/UntrustedWebRequest.cs b/src/DotNetOpenId/UntrustedWebRequest.cs index 852f21f..f10fdd4 100644 --- a/src/DotNetOpenId/UntrustedWebRequest.cs +++ b/src/DotNetOpenId/UntrustedWebRequest.cs @@ -9,6 +9,8 @@ namespace DotNetOpenId { using System.IO;
using System.Net;
using System.Text.RegularExpressions;
+ using System.Configuration;
+ using DotNetOpenId.Configuration;
/// <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
@@ -24,8 +26,12 @@ namespace DotNetOpenId { /// If a particular host would not be permitted but is in the whitelist, it is allowed.
/// </remarks>
public static class UntrustedWebRequest {
+ static Configuration.UntrustedWebRequestSection Configuration {
+ get { return UntrustedWebRequestSection.Configuration; }
+ }
+
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
- static int maximumBytesToRead = 1024 * 1024;
+ static int maximumBytesToRead = Configuration.MaximumBytesToRead;
/// <summary>
/// The default maximum bytes to read in any given HTTP request.
/// Default is 1MB. Cannot be less than 2KB.
@@ -38,7 +44,7 @@ namespace DotNetOpenId { }
}
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
- static int maximumRedirections = 10;
+ static int maximumRedirections = Configuration.MaximumRedirections;
/// <summary>
/// The total number of redirections to allow on any one request.
/// Default is 10.
@@ -73,8 +79,8 @@ namespace DotNetOpenId { [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline")]
static UntrustedWebRequest() {
- ReadWriteTimeout = TimeSpan.FromMilliseconds(800);
- Timeout = TimeSpan.FromSeconds(10);
+ ReadWriteTimeout = Configuration.ReadWriteTimeout;
+ Timeout = Configuration.Timeout;
#if LONGTIMEOUT
ReadWriteTimeout = TimeSpan.FromHours(1);
Timeout = TimeSpan.FromHours(1);
@@ -90,25 +96,25 @@ namespace DotNetOpenId { return true;
}
static ICollection<string> allowableSchemes = new List<string> { "http", "https" };
- static ICollection<string> whitelistHosts = new List<string>();
+ static ICollection<string> whitelistHosts = new List<string>(Configuration.WhitelistHosts.KeysAsStrings);
/// <summary>
/// A collection of host name literals that should be allowed even if they don't
/// pass standard security checks.
/// </summary>
public static ICollection<string> WhitelistHosts { get { return whitelistHosts; } }
- static ICollection<Regex> whitelistHostsRegex = new List<Regex>();
+ static ICollection<Regex> whitelistHostsRegex = new List<Regex>(Configuration.WhitelistHostsRegex.KeysAsRegexs);
/// <summary>
/// A collection of host name regular expressions that indicate hosts that should
/// be allowed even though they don't pass standard security checks.
/// </summary>
public static ICollection<Regex> WhitelistHostsRegex { get { return whitelistHostsRegex; } }
- static ICollection<string> blacklistHosts = new List<string>();
+ static ICollection<string> blacklistHosts = new List<string>(Configuration.BlacklistHosts.KeysAsStrings);
/// <summary>
/// A collection of host name literals that should be rejected even if they
/// pass standard security checks.
/// </summary>
public static ICollection<string> BlacklistHosts { get { return blacklistHosts; } }
- static ICollection<Regex> blacklistHostsRegex = new List<Regex>();
+ static ICollection<Regex> blacklistHostsRegex = new List<Regex>(Configuration.BlacklistHostsRegex.KeysAsRegexs);
/// <summary>
/// A collection of host name regular expressions that indicate hosts that should
/// be rjected even if they pass standard security checks.
@@ -223,11 +229,36 @@ namespace DotNetOpenId { return Request(uri, body, acceptTypes, false);
}
- static UntrustedWebResponse Request(Uri uri, byte[] body, string[] acceptTypes,
- bool avoidSendingExpect100Continue) {
+ internal static UntrustedWebResponse Request(Uri uri, byte[] body, string[] acceptTypes, bool requireSsl) {
+ // 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 = uri;
+ int i;
+ for (i = 0; i < MaximumRedirections; i++) {
+ UntrustedWebResponse response = RequestInternal(uri, body, acceptTypes, requireSsl, false, originalRequestUri);
+ if (response.StatusCode == HttpStatusCode.MovedPermanently ||
+ response.StatusCode == HttpStatusCode.Redirect ||
+ response.StatusCode == HttpStatusCode.RedirectMethod ||
+ response.StatusCode == HttpStatusCode.RedirectKeepVerb) {
+ uri = new Uri(response.FinalUri, response.Headers[HttpResponseHeader.Location]);
+ } else {
+ return response;
+ }
+ }
+ throw new WebException(string.Format(CultureInfo.CurrentCulture, Strings.TooManyRedirects, originalRequestUri));
+ }
+
+ static UntrustedWebResponse RequestInternal(Uri uri, byte[] body, string[] acceptTypes,
+ bool requireSsl, bool avoidSendingExpect100Continue, Uri originalRequestUri) {
if (uri == null) throw new ArgumentNullException("uri");
+ if (originalRequestUri == null) throw new ArgumentNullException("originalRequestUri");
if (!isUriAllowable(uri)) throw new ArgumentException(string.Format(CultureInfo.CurrentCulture,
Strings.UnsafeWebRequestDetected, uri), "uri");
+ if (requireSsl && !String.Equals(uri.Scheme, Uri.UriSchemeHttps, StringComparison.OrdinalIgnoreCase)) {
+ throw new OpenIdException(string.Format(CultureInfo.CurrentCulture, Strings.InsecureWebRequestWithSslRequired, uri));
+ }
// mock the request if a hosting unit test has configured it.
if (MockRequests != null) {
@@ -235,10 +266,13 @@ namespace DotNetOpenId { }
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uri);
+ // 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, and our caller will be responsible for that.
+ request.AllowAutoRedirect = false;
request.ReadWriteTimeout = (int)ReadWriteTimeout.TotalMilliseconds;
request.Timeout = (int)Timeout.TotalMilliseconds;
request.KeepAlive = false;
- request.MaximumAutomaticRedirections = MaximumRedirections;
if (acceptTypes != null)
request.Accept = string.Join(",", acceptTypes);
if (body != null) {
@@ -266,17 +300,17 @@ namespace DotNetOpenId { }
using (HttpWebResponse response = (HttpWebResponse)request.GetResponse()) {
- return getResponse(uri, response);
+ return getResponse(originalRequestUri, response);
}
} catch (WebException e) {
using (HttpWebResponse response = (HttpWebResponse)e.Response) {
if (response != null) {
if (response.StatusCode == HttpStatusCode.ExpectationFailed) {
if (!avoidSendingExpect100Continue) { // must only try this once more
- return Request(uri, body, acceptTypes, true);
+ return RequestInternal(uri, body, acceptTypes, requireSsl, true, originalRequestUri);
}
}
- return getResponse(uri, response);
+ return getResponse(originalRequestUri, response);
} else {
throw;
}
diff --git a/src/DotNetOpenId/UriIdentifier.cs b/src/DotNetOpenId/UriIdentifier.cs index 6371a59..a0a31c4 100644 --- a/src/DotNetOpenId/UriIdentifier.cs +++ b/src/DotNetOpenId/UriIdentifier.cs @@ -18,21 +18,40 @@ namespace DotNetOpenId { return new UriIdentifier(identifier);
}
- public UriIdentifier(string uri) {
+ public UriIdentifier(string uri) : this(uri, false) { }
+ public UriIdentifier(string uri, bool requireSslDiscovery)
+ : base(requireSslDiscovery) {
if (string.IsNullOrEmpty(uri)) throw new ArgumentNullException("uri");
Uri canonicalUri;
- if (!TryCanonicalize(uri, out canonicalUri))
+ bool schemePrepended;
+ if (!TryCanonicalize(uri, out canonicalUri, requireSslDiscovery, out schemePrepended))
throw new UriFormatException();
+ if (requireSslDiscovery && canonicalUri.Scheme != Uri.UriSchemeHttps) {
+ throw new ArgumentException(Strings.ExplicitHttpUriSuppliedWithSslRequirement);
+ }
Uri = canonicalUri;
+ SchemeImplicitlyPrepended = schemePrepended;
}
- public UriIdentifier(Uri uri) {
+ public UriIdentifier(Uri uri) : this(uri, false) { }
+ public UriIdentifier(Uri uri, bool requireSslDiscovery)
+ : base(requireSslDiscovery) {
if (uri == null) throw new ArgumentNullException("uri");
if (!TryCanonicalize(new UriBuilder(uri), out uri))
throw new UriFormatException();
+ if (requireSslDiscovery && uri.Scheme != Uri.UriSchemeHttps) {
+ throw new ArgumentException(Strings.ExplicitHttpUriSuppliedWithSslRequirement);
+ }
Uri = uri;
+ SchemeImplicitlyPrepended = false;
}
public Uri Uri { get; private set; }
+ /// <summary>
+ /// Gets 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; }
static bool isAllowedScheme(string uri) {
if (string.IsNullOrEmpty(uri)) return false;
@@ -41,15 +60,20 @@ namespace DotNetOpenId { }
static bool isAllowedScheme(Uri uri) {
if (uri == null) return false;
- return Array.FindIndex(allowedSchemes, s =>
+ return Array.FindIndex(allowedSchemes, s =>
uri.Scheme.Equals(s, StringComparison.OrdinalIgnoreCase)) >= 0;
}
- static bool TryCanonicalize(string uri, out Uri canonicalUri) {
+ static bool TryCanonicalize(string uri, out Uri canonicalUri, bool forceHttpsDefaultScheme, out bool schemePrepended) {
canonicalUri = null;
+ schemePrepended = false;
try {
// 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 = "http" + Uri.SchemeDelimiter + uri;
+ if (!isAllowedScheme(uri)) {
+ uri = (forceHttpsDefaultScheme ? Uri.UriSchemeHttps : Uri.UriSchemeHttp) +
+ Uri.SchemeDelimiter + uri;
+ schemePrepended = true;
+ }
// Use a UriBuilder because it helps to normalize the URL as well.
return TryCanonicalize(new UriBuilder(uri), out canonicalUri);
} catch (UriFormatException) {
@@ -82,7 +106,8 @@ namespace DotNetOpenId { }
internal static bool IsValidUri(string uri) {
Uri normalized;
- return TryCanonicalize(uri, out normalized);
+ bool schemePrepended;
+ return TryCanonicalize(uri, out normalized, false, out schemePrepended);
}
internal static bool IsValidUri(Uri uri) {
if (uri == null) return false;
@@ -150,18 +175,33 @@ namespace DotNetOpenId { internal override IEnumerable<ServiceEndpoint> Discover() {
List<ServiceEndpoint> endpoints = new List<ServiceEndpoint>();
// Attempt YADIS discovery
- DiscoveryResult yadisResult = Yadis.Yadis.Discover(this);
+ DiscoveryResult yadisResult = Yadis.Yadis.Discover(this, IsDiscoverySecureEndToEnd);
if (yadisResult != null) {
if (yadisResult.IsXrds) {
XrdsDocument xrds = new XrdsDocument(yadisResult.ResponseText);
- endpoints.AddRange(xrds.CreateServiceEndpoints(yadisResult.NormalizedUri));
+ var xrdsEndpoints = xrds.CreateServiceEndpoints(yadisResult.NormalizedUri);
+ // Filter out insecure endpoints if high security is required.
+ if (IsDiscoverySecureEndToEnd) {
+ xrdsEndpoints = Util.Where(xrdsEndpoints, se => se.IsSecure);
+ }
+ endpoints.AddRange(xrdsEndpoints);
}
// Failing YADIS discovery of an XRDS document, we try HTML discovery.
if (endpoints.Count == 0) {
ServiceEndpoint ep = DiscoverFromHtml(yadisResult.NormalizedUri, yadisResult.ResponseText);
if (ep != null) {
- endpoints.Add(ep);
+ Logger.Debug("HTML discovery found a service endpoint.");
+ Logger.Debug(ep);
+ if (!IsDiscoverySecureEndToEnd || ep.IsSecure) {
+ endpoints.Add(ep);
+ } else {
+ Logger.Info("Skipping HTML discovered endpoint because it is not secure.");
+ }
+ } else {
+ Logger.Debug("HTML discovery failed to find any endpoints.");
}
+ } else {
+ Logger.Debug("Skipping HTML discovery because XRDS contained service endpoints.");
}
}
return endpoints;
@@ -178,6 +218,36 @@ namespace DotNetOpenId { return builder.Uri;
}
+ 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 (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);
+ return false;
+ }
+
public override bool Equals(object obj) {
UriIdentifier other = obj as UriIdentifier;
if (other == null) return false;
diff --git a/src/DotNetOpenId/Util.cs b/src/DotNetOpenId/Util.cs index 1eb7c5f..6568282 100644 --- a/src/DotNetOpenId/Util.cs +++ b/src/DotNetOpenId/Util.cs @@ -126,11 +126,14 @@ namespace DotNetOpenId { return nvc;
}
- public static IDictionary<string, string> GetQueryFromContext() {
+ public static NameValueCollection GetQueryFromContextNVC() {
if (HttpContext.Current == null) throw new InvalidOperationException(Strings.CurrentHttpContextRequired);
var query = HttpContext.Current.Request.RequestType == "GET" ?
HttpContext.Current.Request.QueryString : HttpContext.Current.Request.Form;
- return NameValueCollectionToDictionary(query);
+ return query;
+ }
+ public static IDictionary<string, string> GetQueryFromContext() {
+ return NameValueCollectionToDictionary(GetQueryFromContextNVC());
}
/// <summary>
/// Gets the original request URL, as seen from the browser before any URL rewrites on the server if any.
@@ -269,6 +272,42 @@ namespace DotNetOpenId { return null;
}
+ internal static T FirstOrDefault<T>(IEnumerable<T> sequence, Func<T, bool> predicate) {
+ IEnumerator<T> iterator = Where(sequence, predicate).GetEnumerator();
+ return iterator.MoveNext() ? iterator.Current : default(T);
+ }
+ internal static IEnumerable<T> Where<T>(IEnumerable<T> sequence, Func<T, bool> predicate) {
+ foreach (T item in sequence) {
+ if (predicate(item)) {
+ yield return item;
+ }
+ }
+ }
+ /// <summary>
+ /// Tests two sequences for same contents and ordering.
+ /// </summary>
+ internal static bool AreSequencesEquivalent<T>(IEnumerable<T> sequence1, IEnumerable<T> sequence2) {
+ if (sequence1 == null && sequence2 == null) return true;
+ if (sequence1 == null) throw new ArgumentNullException("sequence1");
+ if (sequence2 == null) throw new ArgumentNullException("sequence2");
+
+ IEnumerator<T> iterator1 = sequence1.GetEnumerator();
+ IEnumerator<T> iterator2 = sequence2.GetEnumerator();
+ bool movenext1 , movenext2;
+ while (true) {
+ movenext1 = iterator1.MoveNext();
+ movenext2 = iterator2.MoveNext();
+ if (!movenext1 || !movenext2) break; // if we've reached the end of at least one sequence
+ 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>
/// Prepares a dictionary for printing as a string.
/// </summary>
@@ -287,6 +326,49 @@ namespace DotNetOpenId { return sb.ToString();
});
}
+ internal static object ToString<T>(IEnumerable<T> list) {
+ return ToString<T>(list, false);
+ }
+ internal static object ToString<T>(IEnumerable<T> list, bool multiLineElements) {
+ return new DelayedToString<IEnumerable<T>>(list, l => {
+ 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(Environment.NewLine, Environment.NewLine + "\t");
+ sb.Append("\t");
+ sb.Append(objString);
+
+ if (!objString.EndsWith(Environment.NewLine)) {
+ sb.AppendLine();
+ }
+ sb.AppendLine("}, {");
+ }
+ if (sb.Length > 2) { // if anything was in the enumeration
+ sb.Length -= 2 + Environment.NewLine.Length; // trim off the last ", {\r\n"
+ } else {
+ sb.Length -= 1; // 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();
+ }
+ });
+ }
private class DelayedToString<T> {
public DelayedToString(T obj, Func<T, string> toString) {
diff --git a/src/DotNetOpenId/XriIdentifier.cs b/src/DotNetOpenId/XriIdentifier.cs index b412e79..51072d4 100644 --- a/src/DotNetOpenId/XriIdentifier.cs +++ b/src/DotNetOpenId/XriIdentifier.cs @@ -12,10 +12,18 @@ namespace DotNetOpenId { internal static readonly char[] GlobalContextSymbols = { '=', '@', '+', '$', '!' };
const string xriScheme = "xri://";
- public XriIdentifier(string xri) {
+ public XriIdentifier(string xri) : this(xri, false) { }
+ public XriIdentifier(string xri, bool requireSsl)
+ : base(requireSsl) {
if (!IsValidXri(xri))
throw new FormatException(string.Format(CultureInfo.CurrentCulture,
Strings.InvalidXri, xri));
+ xriResolverProxy = xriResolverProxyTemplate;
+ if (requireSsl) {
+ // Indicate to xri.net that we require SSL to be used for delegated resolution
+ // of community i-names.
+ xriResolverProxy += ";https=true";
+ }
OriginalXri = xri;
CanonicalXri = canonicalizeXri(xri);
}
@@ -57,25 +65,32 @@ namespace DotNetOpenId { /// 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>
- const string xriResolverProxy = "https://xri.net/{0}?_xrd_r=application/xrd%2Bxml;sep=false";
+ const string xriResolverProxyTemplate = "https://xri.net/{0}?_xrd_r=application/xrd%2Bxml;sep=false";
+ readonly string xriResolverProxy;
/// <summary>
/// Resolves the XRI to a URL from which an XRDS document may be downloaded.
/// </summary>
protected virtual Uri XrdsUrl {
get {
- return new Uri(string.Format(CultureInfo.InvariantCulture,
+ return new Uri(string.Format(CultureInfo.InvariantCulture,
xriResolverProxy, this));
}
}
XrdsDocument downloadXrds() {
var xrdsResponse = UntrustedWebRequest.Request(XrdsUrl);
- return new XrdsDocument(XmlReader.Create(xrdsResponse.ResponseStream));
+ XrdsDocument doc = new XrdsDocument(XmlReader.Create(xrdsResponse.ResponseStream));
+ if (!doc.IsXrdResolutionSuccessful) {
+ throw new OpenIdException(Strings.XriResolutionFailed);
+ }
+ return doc;
}
internal override IEnumerable<ServiceEndpoint> Discover() {
- return downloadXrds().CreateServiceEndpoints(this, this);
+ return downloadXrds().CreateServiceEndpoints(this);
}
/// <summary>
@@ -83,13 +98,18 @@ namespace DotNetOpenId { /// instances that treat another given identifier as the user-supplied identifier.
/// </summary>
internal IEnumerable<ServiceEndpoint> Discover(XriIdentifier userSuppliedIdentifier) {
- return downloadXrds().CreateServiceEndpoints(this, userSuppliedIdentifier);
+ return downloadXrds().CreateServiceEndpoints(userSuppliedIdentifier);
}
internal override Identifier TrimFragment() {
return this;
}
+ internal override bool TryRequireSsl(out Identifier secureIdentifier) {
+ secureIdentifier = IsDiscoverySecureEndToEnd ? this : new XriIdentifier(this, true);
+ return true;
+ }
+
public override bool Equals(object obj) {
XriIdentifier other = obj as XriIdentifier;
if (other == null) return false;
diff --git a/src/DotNetOpenId/Yadis/XrdElement.cs b/src/DotNetOpenId/Yadis/XrdElement.cs index d690298..5665f58 100644 --- a/src/DotNetOpenId/Yadis/XrdElement.cs +++ b/src/DotNetOpenId/Yadis/XrdElement.cs @@ -22,6 +22,28 @@ namespace DotNetOpenId.Yadis { }
}
+
+ int XriResolutionStatusCode {
+ get {
+ var n = Node.SelectSingleNode("xrd:Status", XmlNamespaceResolver);
+ string codeString;
+ if (n == null || string.IsNullOrEmpty(codeString = n.GetAttribute("code", ""))) {
+ throw new OpenIdException(Strings.XriResolutionStatusMissing);
+ }
+ int code;
+ if (!int.TryParse(codeString, out code) || code < 100 || code > 399) {
+ throw new OpenIdException(Strings.XriResolutionStatusMissing);
+ }
+ return code;
+ }
+ }
+
+ public bool IsXriResolutionSuccessful {
+ get {
+ return XriResolutionStatusCode == 100;
+ }
+ }
+
public string CanonicalID {
get {
var n = Node.SelectSingleNode("xrd:CanonicalID", XmlNamespaceResolver);
@@ -29,6 +51,13 @@ namespace DotNetOpenId.Yadis { }
}
+ public bool IsCanonicalIdVerified {
+ get {
+ var n = Node.SelectSingleNode("xrd:Status", XmlNamespaceResolver);
+ return n != null && string.Equals(n.GetAttribute("cid", ""), "verified", StringComparison.Ordinal);
+ }
+ }
+
IEnumerable<ServiceElement> searchForServiceTypeUris(Util.Func<Protocol, string> p) {
var xpath = new StringBuilder();
xpath.Append("xrd:Service[");
diff --git a/src/DotNetOpenId/Yadis/XrdsDocument.cs b/src/DotNetOpenId/Yadis/XrdsDocument.cs index d3510e5..622165a 100644 --- a/src/DotNetOpenId/Yadis/XrdsDocument.cs +++ b/src/DotNetOpenId/Yadis/XrdsDocument.cs @@ -45,17 +45,21 @@ namespace DotNetOpenId.Yadis { if (endpoints.Count == 0) {
endpoints.AddRange(generateClaimedIdentifierServiceEndpoints(claimedIdentifier));
}
+ Logger.DebugFormat("Total services discovered in XRDS: {0}", endpoints.Count);
+ Logger.Debug(Util.ToString(endpoints, true));
return endpoints;
}
- internal IEnumerable<ServiceEndpoint> CreateServiceEndpoints(XriIdentifier discoveredIdentifier, XriIdentifier userSuppliedIdentifier) {
+ internal IEnumerable<ServiceEndpoint> CreateServiceEndpoints(XriIdentifier userSuppliedIdentifier) {
List<ServiceEndpoint> endpoints = new List<ServiceEndpoint>();
endpoints.AddRange(generateOPIdentifierServiceEndpoints(userSuppliedIdentifier));
// If any OP Identifier service elements were found, we must not proceed
// to return any Claimed Identifier services.
if (endpoints.Count == 0) {
- endpoints.AddRange(generateClaimedIdentifierServiceEndpoints(discoveredIdentifier, userSuppliedIdentifier));
+ endpoints.AddRange(generateClaimedIdentifierServiceEndpoints(userSuppliedIdentifier));
}
+ Logger.DebugFormat("Total services discovered in XRDS: {0}", endpoints.Count);
+ Logger.Debug(Util.ToString(endpoints, true));
return endpoints;
}
@@ -80,7 +84,7 @@ namespace DotNetOpenId.Yadis { }
}
- IEnumerable<ServiceEndpoint> generateClaimedIdentifierServiceEndpoints(XriIdentifier claimedIdentifier, XriIdentifier userSuppliedIdentifier) {
+ IEnumerable<ServiceEndpoint> generateClaimedIdentifierServiceEndpoints(XriIdentifier userSuppliedIdentifier) {
foreach (var service in findClaimedIdentifierServices()) {
foreach (var uri in service.UriElements) {
// spec section 7.3.2.3 on Claimed Id -> CanonicalID substitution
@@ -88,24 +92,11 @@ namespace DotNetOpenId.Yadis { Logger.WarnFormat(Strings.MissingCanonicalIDElement, userSuppliedIdentifier);
break; // skip on to next service
}
- // In the case of XRI names, the ClaimedId is actually the CanonicalID.
- // Per http://dev.inames.net/wiki/XRI_CanonicalID_Verification as of 6/20/08,
- // we need to perform CanonicalId verification when using xri.net as our proxy resolver
- // to protect ourselves against a security vulnerability.
- // We do this by asking the proxy to resolve again, based on the CanonicalId that we
- // just got from the XRI i-name. We SHOULD get the same document back, but in case
- // of the attack it would be a different document, and the second document would be
- // the reliable one.
- if (performCIDVerification && claimedIdentifier != service.Xrd.CanonicalID) {
- Logger.InfoFormat("Performing XRI CanonicalID verification on user supplied identifier {0}, canonical id {1}.", userSuppliedIdentifier, service.Xrd.CanonicalID);
- XriIdentifier canonicalId = new XriIdentifier(service.Xrd.CanonicalID);
- foreach (var endpoint in canonicalId.Discover(userSuppliedIdentifier)) {
- yield return endpoint;
- }
- yield break;
- } else {
- claimedIdentifier = new XriIdentifier(service.Xrd.CanonicalID);
+ if (!service.Xrd.IsCanonicalIdVerified) {
+ throw new OpenIdException(Strings.CIDVerificationFailed, userSuppliedIdentifier);
}
+ // In the case of XRI names, the ClaimedId is actually the CanonicalID.
+ var claimedIdentifier = new XriIdentifier(service.Xrd.CanonicalID);
yield return ServiceEndpoint.CreateForClaimedIdentifier(
claimedIdentifier, userSuppliedIdentifier, service.ProviderLocalIdentifier,
uri.Uri, service.TypeElementUris, service.Priority, uri.Priority);
@@ -113,8 +104,6 @@ namespace DotNetOpenId.Yadis { }
}
- const bool performCIDVerification = true;
-
internal IEnumerable<RelyingPartyReceivingEndpoint> FindRelyingPartyReceivingEndpoints() {
foreach (var service in findReturnToServices()) {
foreach (var uri in service.UriElements) {
@@ -150,5 +139,16 @@ namespace DotNetOpenId.Yadis { }
}
}
+
+ internal bool IsXrdResolutionSuccessful {
+ get {
+ foreach (var xrd in XrdElements) {
+ if (!xrd.IsXriResolutionSuccessful) {
+ return false;
+ }
+ }
+ return true;
+ }
+ }
}
}
diff --git a/src/DotNetOpenId/Yadis/Yadis.cs b/src/DotNetOpenId/Yadis/Yadis.cs index ac0d3da..ec67f85 100644 --- a/src/DotNetOpenId/Yadis/Yadis.cs +++ b/src/DotNetOpenId/Yadis/Yadis.cs @@ -13,11 +13,26 @@ namespace DotNetOpenId.Yadis { class Yadis {
internal const string HeaderName = "X-XRDS-Location";
- public static DiscoveryResult Discover(UriIdentifier uri) {
+ /// <summary>
+ /// Performs YADIS discovery on some identifier.
+ /// </summary>
+ /// <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(UriIdentifier uri, bool requireSsl) {
UntrustedWebResponse response;
try {
+ if (requireSsl && !string.Equals(uri.Uri.Scheme, Uri.UriSchemeHttps, StringComparison.OrdinalIgnoreCase)) {
+ Logger.WarnFormat("Discovery on insecure identifier '{0}' aborted.", uri);
+ return null;
+ }
response = UntrustedWebRequest.Request(uri, null,
- new[] { ContentTypes.Html, ContentTypes.XHtml, ContentTypes.Xrds });
+ new[] { ContentTypes.Html, ContentTypes.XHtml, ContentTypes.Xrds }, requireSsl);
if (response.StatusCode != System.Net.HttpStatusCode.OK) {
Logger.ErrorFormat("HTTP error {0} {1} while performing discovery on {2}.", (int)response.StatusCode, response.StatusCode, uri);
return null;
@@ -29,18 +44,30 @@ namespace DotNetOpenId.Yadis { }
UntrustedWebResponse response2 = null;
if (isXrdsDocument(response)) {
+ Logger.Debug("An XRDS response was received from GET at user-supplied identifier.");
response2 = response;
} else {
string uriString = response.Headers.Get(HeaderName);
Uri url = null;
- if (uriString != null)
- Uri.TryCreate(uriString, UriKind.Absolute, out url);
- if (url == null && response.ContentType.MediaType == ContentTypes.Html)
+ if (uriString != null) {
+ if (Uri.TryCreate(uriString, UriKind.Absolute, out url)) {
+ Logger.DebugFormat("{0} found in HTTP header. Preparing to pull XRDS from {1}", HeaderName, url);
+ }
+ }
+ if (url == null && response.ContentType.MediaType == ContentTypes.Html) {
url = FindYadisDocumentLocationInHtmlMetaTags(response.ReadResponseString());
+ if (url != null) {
+ Logger.DebugFormat("{0} found in HTML Http-Equiv tag. Preparing to pull XRDS from {1}", HeaderName, url);
+ }
+ }
if (url != null) {
- response2 = UntrustedWebRequest.Request(url);
- if (response2.StatusCode != System.Net.HttpStatusCode.OK) {
- return null;
+ if (!requireSsl || string.Equals(url.Scheme, Uri.UriSchemeHttps, StringComparison.OrdinalIgnoreCase)) {
+ response2 = UntrustedWebRequest.Request(url, null, null, requireSsl);
+ if (response2.StatusCode != System.Net.HttpStatusCode.OK) {
+ return null;
+ }
+ } else {
+ Logger.WarnFormat("XRDS document at insecure location '{0}'. Aborting YADIS discovery.", url);
}
}
}
diff --git a/src/version.txt b/src/version.txt index 0bee604..79a6144 100644 --- a/src/version.txt +++ b/src/version.txt @@ -1 +1 @@ -2.3.3 +2.4.4 |