summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--samples/ProviderCustomStore/Global.asax.cs5
-rw-r--r--samples/ProviderCustomStore/op_xrds.aspx2
-rw-r--r--samples/ProviderCustomStore/user_xrds.aspx4
-rw-r--r--samples/ProviderPortal/Global.asax.cs5
-rw-r--r--samples/ProviderPortal/ProfileFields.ascx.cs51
-rw-r--r--samples/ProviderPortal/decide.aspx.cs10
-rw-r--r--samples/ProviderPortal/op_xrds.aspx2
-rw-r--r--samples/ProviderPortal/user_xrds.aspx4
-rw-r--r--samples/RelyingPartyCustomStore/Global.asax1
-rw-r--r--samples/RelyingPartyCustomStore/Global.asax.cs16
-rw-r--r--samples/RelyingPartyCustomStore/RelyingPartyCustomStore.csproj4
-rw-r--r--samples/RelyingPartyCustomStore/login.aspx.cs6
-rw-r--r--samples/RelyingPartyMvc/Global.asax.cs5
-rw-r--r--samples/RelyingPartyPortal/Code/State.cs34
-rw-r--r--samples/RelyingPartyPortal/Global.asax.cs5
-rw-r--r--samples/RelyingPartyPortal/MembersOnly/Default.aspx4
-rw-r--r--src/DotNetOpenId.Test/DotNetOpenId.Test.csproj9
-rw-r--r--src/DotNetOpenId.Test/EndToEndTesting.cs16
-rw-r--r--src/DotNetOpenId.Test/Extensions/AttributeExchangeTests.cs2
-rw-r--r--src/DotNetOpenId.Test/Extensions/ClaimsResponseTests.cs (renamed from src/DotNetOpenId.Test/ProfileFieldValuesTests.cs)33
-rw-r--r--src/DotNetOpenId.Test/Extensions/ExtensionTestBase.cs7
-rw-r--r--src/DotNetOpenId.Test/Extensions/ExtensionsTestSetup.cs16
-rw-r--r--src/DotNetOpenId.Test/Extensions/PapeTests.cs33
-rw-r--r--src/DotNetOpenId.Test/Extensions/PolicyRequestTests.cs194
-rw-r--r--src/DotNetOpenId.Test/Extensions/PolicyResponseTests.cs258
-rw-r--r--src/DotNetOpenId.Test/Hosting/AspNetHost.cs4
-rw-r--r--src/DotNetOpenId.Test/Provider/IAuthenticationRequestTest.cs10
-rw-r--r--src/DotNetOpenId.Test/RelyingParty/AuthenticationRequestTests.cs32
-rw-r--r--src/DotNetOpenId.Test/RelyingParty/AuthenticationResponseTests.cs10
-rw-r--r--src/DotNetOpenId.Test/RelyingParty/IProviderEndpointTests.cs52
-rw-r--r--src/DotNetOpenId.Test/RelyingParty/OpenIdRelyingPartyTest.cs27
-rw-r--r--src/DotNetOpenId.Test/TestSupport.cs13
-rw-r--r--src/DotNetOpenId.Test/UntrustedWebRequestTests.cs93
-rw-r--r--src/DotNetOpenId.Test/UriIdentifierTests.cs9
-rw-r--r--src/DotNetOpenId.Test/UtilTest.cs10
-rw-r--r--src/DotNetOpenId.TestWeb/ProviderEndpoint.aspx.cs24
-rw-r--r--src/DotNetOpenId.TestWeb/xrdsdiscovery/xrds10.aspx1
-rw-r--r--src/DotNetOpenId.TestWeb/xrdsdiscovery/xrds1020.aspx4
-rw-r--r--src/DotNetOpenId.TestWeb/xrdsdiscovery/xrds11.aspx2
-rw-r--r--src/DotNetOpenId.TestWeb/xrdsdiscovery/xrds20.aspx2
-rw-r--r--src/DotNetOpenId.TestWeb/xrdsdiscovery/xrds2010a.aspx4
-rw-r--r--src/DotNetOpenId.TestWeb/xrdsdiscovery/xrds2010b.aspx4
-rw-r--r--src/DotNetOpenId.sln4
-rw-r--r--src/DotNetOpenId/DotNetOpenId.csproj18
-rw-r--r--src/DotNetOpenId/ExtensionArgumentsManager.cs5
-rw-r--r--src/DotNetOpenId/Extensions/AliasManager.cs60
-rw-r--r--src/DotNetOpenId/Extensions/AttributeExchange/FetchRequest.cs13
-rw-r--r--src/DotNetOpenId/Extensions/AttributeExchange/FetchResponse.cs5
-rw-r--r--src/DotNetOpenId/Extensions/AttributeExchange/StoreRequest.cs5
-rw-r--r--src/DotNetOpenId/Extensions/AttributeExchange/StoreResponse.cs5
-rw-r--r--src/DotNetOpenId/Extensions/IExtension.cs36
-rw-r--r--src/DotNetOpenId/Extensions/ProviderAuthenticationPolicy/AuthenticationPolicies.cs40
-rw-r--r--src/DotNetOpenId/Extensions/ProviderAuthenticationPolicy/Constants.cs90
-rw-r--r--src/DotNetOpenId/Extensions/ProviderAuthenticationPolicy/NistAssuranceLevel.cs48
-rw-r--r--src/DotNetOpenId/Extensions/ProviderAuthenticationPolicy/PolicyRequest.cs195
-rw-r--r--src/DotNetOpenId/Extensions/ProviderAuthenticationPolicy/PolicyResponse.cs194
-rw-r--r--src/DotNetOpenId/Extensions/SimpleRegistration/ClaimsRequest.cs21
-rw-r--r--src/DotNetOpenId/Extensions/SimpleRegistration/ClaimsResponse.cs23
-rw-r--r--src/DotNetOpenId/Extensions/SimpleRegistration/Constants.cs3
-rw-r--r--src/DotNetOpenId/GlobalSuppressions.cs5
-rw-r--r--src/DotNetOpenId/IEncodable.cs38
-rw-r--r--src/DotNetOpenId/IResponse.cs (renamed from src/DotNetOpenId/Provider/IResponse.cs)12
-rw-r--r--src/DotNetOpenId/MessageEncoder.cs111
-rw-r--r--src/DotNetOpenId/OpenIdException.cs4
-rw-r--r--src/DotNetOpenId/Provider/EncodableResponse.cs4
-rw-r--r--src/DotNetOpenId/Provider/Encoder.cs91
-rw-r--r--src/DotNetOpenId/Provider/IEncodable.cs33
-rw-r--r--src/DotNetOpenId/Provider/OpenIdProvider.cs11
-rw-r--r--src/DotNetOpenId/Provider/Request.cs31
-rw-r--r--src/DotNetOpenId/Provider/SigningMessageEncoder.cs41
-rw-r--r--src/DotNetOpenId/RelyingParty/AuthenticationRequest.cs60
-rw-r--r--src/DotNetOpenId/RelyingParty/AuthenticationResponse.cs31
-rw-r--r--src/DotNetOpenId/RelyingParty/DirectRequest.cs9
-rw-r--r--src/DotNetOpenId/RelyingParty/IAuthenticationRequest.cs41
-rw-r--r--src/DotNetOpenId/RelyingParty/IProviderEndpoint.cs51
-rw-r--r--src/DotNetOpenId/RelyingParty/IndirectMessageRequest.cs25
-rw-r--r--src/DotNetOpenId/RelyingParty/OpenIdRelyingParty.cs133
-rw-r--r--src/DotNetOpenId/RelyingParty/OpenIdTextBox.cs11
-rw-r--r--src/DotNetOpenId/RelyingParty/ServiceEndpoint.cs40
-rw-r--r--src/DotNetOpenId/Response.cs (renamed from src/DotNetOpenId/Provider/Response.cs)20
-rw-r--r--src/DotNetOpenId/Strings.Designer.cs27
-rw-r--r--src/DotNetOpenId/Strings.resx9
-rw-r--r--src/DotNetOpenId/UntrustedWebRequest.cs123
-rw-r--r--src/DotNetOpenId/Util.cs3
-rw-r--r--src/DotNetOpenId/Yadis/Yadis.cs12
-rw-r--r--src/version.txt2
-rw-r--r--tools/decode_token.ps120
87 files changed, 2426 insertions, 364 deletions
diff --git a/samples/ProviderCustomStore/Global.asax.cs b/samples/ProviderCustomStore/Global.asax.cs
index 7b52c6a..ad31f4f 100644
--- a/samples/ProviderCustomStore/Global.asax.cs
+++ b/samples/ProviderCustomStore/Global.asax.cs
@@ -3,6 +3,11 @@ 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();
}
diff --git a/samples/ProviderCustomStore/op_xrds.aspx b/samples/ProviderCustomStore/op_xrds.aspx
index 7d0ca2c..b906bfe 100644
--- a/samples/ProviderCustomStore/op_xrds.aspx
+++ b/samples/ProviderCustomStore/op_xrds.aspx
@@ -12,7 +12,7 @@ This XRDS doc is discovered via the user.aspx page.
<XRD>
<Service priority="10">
<Type>http://specs.openid.net/auth/2.0/server</Type>
- <Type>http://openid.net/sreg/1.0</Type>
+ <Type>http://openid.net/extensions/sreg/1.1</Type>
<URI><%=new Uri(Request.Url, Response.ApplyAppPathModifier("~/server.aspx"))%></URI>
</Service>
</XRD>
diff --git a/samples/ProviderCustomStore/user_xrds.aspx b/samples/ProviderCustomStore/user_xrds.aspx
index 4f3e446..56b1244 100644
--- a/samples/ProviderCustomStore/user_xrds.aspx
+++ b/samples/ProviderCustomStore/user_xrds.aspx
@@ -12,12 +12,12 @@ This XRDS doc is discovered via the user.aspx page.
<XRD>
<Service priority="10">
<Type>http://specs.openid.net/auth/2.0/signon</Type>
- <Type>http://openid.net/sreg/1.0</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/sreg/1.0</Type>
+ <Type>http://openid.net/extensions/sreg/1.1</Type>
<URI><%=new Uri(Request.Url, Response.ApplyAppPathModifier("~/server.aspx"))%></URI>
</Service>
</XRD>
diff --git a/samples/ProviderPortal/Global.asax.cs b/samples/ProviderPortal/Global.asax.cs
index 091ea56..599bae2 100644
--- a/samples/ProviderPortal/Global.asax.cs
+++ b/samples/ProviderPortal/Global.asax.cs
@@ -8,6 +8,11 @@ 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/ProfileFields.ascx.cs b/samples/ProviderPortal/ProfileFields.ascx.cs
index f898227..00f1834 100644
--- a/samples/ProviderPortal/ProfileFields.ascx.cs
+++ b/samples/ProviderPortal/ProfileFields.ascx.cs
@@ -97,31 +97,32 @@ public partial class ProfileFields : System.Web.UI.UserControl {
}
}
- public ClaimsResponse OpenIdProfileFields {
- get {
- ClaimsResponse fields = new ClaimsResponse();
- fields.BirthDate = DateOfBirth;
- fields.Country = countryDropdownList.SelectedValue;
- fields.Email = emailTextBox.Text;
- fields.FullName = fullnameTextBox.Text;
- fields.Gender = Gender;
- fields.Language = languageDropdownList.SelectedValue;
- fields.Nickname = nicknameTextBox.Text;
- fields.PostalCode = postcodeTextBox.Text;
- fields.TimeZone = timezoneDropdownList.SelectedValue;
- return fields;
- }
- set {
- DateOfBirth = value.BirthDate;
- countryDropdownList.SelectedValue = value.Country;
- emailTextBox.Text = value.Email;
- fullnameTextBox.Text = value.FullName;
- Gender = value.Gender;
- languageDropdownList.SelectedValue = value.Language;
- nicknameTextBox.Text = value.Nickname;
- postcodeTextBox.Text = value.PostalCode;
- timezoneDropdownList.SelectedValue = value.TimeZone;
- }
+ public ClaimsResponse GetOpenIdProfileFields(ClaimsRequest request) {
+ if (request == null) throw new ArgumentNullException("request");
+ ClaimsResponse fields = request.CreateResponse();
+ fields.BirthDate = DateOfBirth;
+ fields.Country = countryDropdownList.SelectedValue;
+ fields.Email = emailTextBox.Text;
+ fields.FullName = fullnameTextBox.Text;
+ fields.Gender = Gender;
+ fields.Language = languageDropdownList.SelectedValue;
+ fields.Nickname = nicknameTextBox.Text;
+ fields.PostalCode = postcodeTextBox.Text;
+ fields.TimeZone = timezoneDropdownList.SelectedValue;
+ return fields;
+ }
+
+ public void SetOpenIdProfileFields(ClaimsResponse value) {
+ if (value == null) throw new ArgumentNullException("value");
+ DateOfBirth = value.BirthDate;
+ countryDropdownList.SelectedValue = value.Country;
+ emailTextBox.Text = value.Email;
+ fullnameTextBox.Text = value.FullName;
+ Gender = value.Gender;
+ languageDropdownList.SelectedValue = value.Language;
+ nicknameTextBox.Text = value.Nickname;
+ postcodeTextBox.Text = value.PostalCode;
+ timezoneDropdownList.SelectedValue = value.TimeZone;
}
}
diff --git a/samples/ProviderPortal/decide.aspx.cs b/samples/ProviderPortal/decide.aspx.cs
index 882c320..1e1f95d 100644
--- a/samples/ProviderPortal/decide.aspx.cs
+++ b/samples/ProviderPortal/decide.aspx.cs
@@ -30,9 +30,9 @@ public partial class decide : Page {
this.profileFields.Visible = true;
this.profileFields.SetRequiredFieldsFromRequest(requestedFields);
if (!IsPostBack) {
- this.profileFields.OpenIdProfileFields = new ClaimsResponse() {
- Email = Membership.GetUser().Email,
- };
+ var sregResponse = requestedFields.CreateResponse();
+ sregResponse.Email = Membership.GetUser().Email;
+ this.profileFields.SetOpenIdProfileFields(sregResponse);
}
}
} else {
@@ -42,8 +42,10 @@ public partial class decide : Page {
}
protected void Yes_Click(Object sender, EventArgs e) {
+ var sregRequest = ProviderEndpoint.PendingAuthenticationRequest.GetExtension<ClaimsRequest>();
+ var sregResponse = profileFields.GetOpenIdProfileFields(sregRequest);
ProviderEndpoint.PendingAuthenticationRequest.IsAuthenticated = true;
- ProviderEndpoint.PendingAuthenticationRequest.AddResponseExtension(profileFields.OpenIdProfileFields);
+ ProviderEndpoint.PendingAuthenticationRequest.AddResponseExtension(sregResponse);
Debug.Assert(ProviderEndpoint.PendingAuthenticationRequest.IsResponseReady);
ProviderEndpoint.PendingAuthenticationRequest.Response.Send();
ProviderEndpoint.PendingAuthenticationRequest = null;
diff --git a/samples/ProviderPortal/op_xrds.aspx b/samples/ProviderPortal/op_xrds.aspx
index 7d0ca2c..b906bfe 100644
--- a/samples/ProviderPortal/op_xrds.aspx
+++ b/samples/ProviderPortal/op_xrds.aspx
@@ -12,7 +12,7 @@ This XRDS doc is discovered via the user.aspx page.
<XRD>
<Service priority="10">
<Type>http://specs.openid.net/auth/2.0/server</Type>
- <Type>http://openid.net/sreg/1.0</Type>
+ <Type>http://openid.net/extensions/sreg/1.1</Type>
<URI><%=new Uri(Request.Url, Response.ApplyAppPathModifier("~/server.aspx"))%></URI>
</Service>
</XRD>
diff --git a/samples/ProviderPortal/user_xrds.aspx b/samples/ProviderPortal/user_xrds.aspx
index 4f3e446..56b1244 100644
--- a/samples/ProviderPortal/user_xrds.aspx
+++ b/samples/ProviderPortal/user_xrds.aspx
@@ -12,12 +12,12 @@ This XRDS doc is discovered via the user.aspx page.
<XRD>
<Service priority="10">
<Type>http://specs.openid.net/auth/2.0/signon</Type>
- <Type>http://openid.net/sreg/1.0</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/sreg/1.0</Type>
+ <Type>http://openid.net/extensions/sreg/1.1</Type>
<URI><%=new Uri(Request.Url, Response.ApplyAppPathModifier("~/server.aspx"))%></URI>
</Service>
</XRD>
diff --git a/samples/RelyingPartyCustomStore/Global.asax b/samples/RelyingPartyCustomStore/Global.asax
new file mode 100644
index 0000000..a00a077
--- /dev/null
+++ b/samples/RelyingPartyCustomStore/Global.asax
@@ -0,0 +1 @@
+<%@ Application Codebehind="Global.asax.cs" Inherits="RelyingPartyCustomStore.Global" Language="C#" %>
diff --git a/samples/RelyingPartyCustomStore/Global.asax.cs b/samples/RelyingPartyCustomStore/Global.asax.cs
new file mode 100644
index 0000000..b8b7593
--- /dev/null
+++ b/samples/RelyingPartyCustomStore/Global.asax.cs
@@ -0,0 +1,16 @@
+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/RelyingPartyCustomStore.csproj b/samples/RelyingPartyCustomStore/RelyingPartyCustomStore.csproj
index 3445f04..2519ec1 100644
--- a/samples/RelyingPartyCustomStore/RelyingPartyCustomStore.csproj
+++ b/samples/RelyingPartyCustomStore/RelyingPartyCustomStore.csproj
@@ -53,6 +53,9 @@
<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>
@@ -69,6 +72,7 @@
</ProjectReference>
</ItemGroup>
<ItemGroup>
+ <Content Include="Global.asax" />
<Content Include="MembersOnly\Web.config" />
</ItemGroup>
<ItemGroup>
diff --git a/samples/RelyingPartyCustomStore/login.aspx.cs b/samples/RelyingPartyCustomStore/login.aspx.cs
index 23cb7f7..f7b1088 100644
--- a/samples/RelyingPartyCustomStore/login.aspx.cs
+++ b/samples/RelyingPartyCustomStore/login.aspx.cs
@@ -8,7 +8,8 @@ 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);
+ 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:
@@ -25,7 +26,8 @@ public partial class login : System.Web.UI.Page {
}
protected void loginButton_Click(object sender, EventArgs e) {
- OpenIdRelyingParty rp = new OpenIdRelyingParty(CustomStore.Instance, Request.Url);
+ OpenIdRelyingParty rp = new OpenIdRelyingParty(CustomStore.Instance, Request.Url,
+ Request.HttpMethod == "GET" ? Request.QueryString : Request.Form);
rp.CreateRequest(openIdBox.Text).RedirectToProvider();
}
}
diff --git a/samples/RelyingPartyMvc/Global.asax.cs b/samples/RelyingPartyMvc/Global.asax.cs
index 0659169..0f0b4ef 100644
--- a/samples/RelyingPartyMvc/Global.asax.cs
+++ b/samples/RelyingPartyMvc/Global.asax.cs
@@ -7,6 +7,11 @@ 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/RelyingPartyPortal/Code/State.cs b/samples/RelyingPartyPortal/Code/State.cs
index 65e676c..642780c 100644
--- a/samples/RelyingPartyPortal/Code/State.cs
+++ b/samples/RelyingPartyPortal/Code/State.cs
@@ -1,34 +1,12 @@
-using System;
-using System.Data;
-using System.Configuration;
using System.Web;
-using System.Web.Security;
-using System.Web.UI;
-using System.Web.UI.WebControls;
-using System.Web.UI.WebControls.WebParts;
-using System.Web.UI.HtmlControls;
using DotNetOpenId.Extensions.SimpleRegistration;
/// <summary>
-/// Summary description for State
+/// Strong-typed bag of session state.
/// </summary>
-public class State
-{
- public State()
- {
- }
-
- public static ClaimsResponse ProfileFields
- {
- get
- {
- if (HttpContext.Current .Session["ProfileFields"] == null)
- {
- HttpContext.Current .Session["ProfileFields"] = new ClaimsResponse();
- }
- return (ClaimsResponse)HttpContext.Current .Session["ProfileFields"];
- }
- set { HttpContext.Current .Session["ProfileFields"] = value; }
- }
-
+public class State {
+ public static ClaimsResponse ProfileFields {
+ get { return HttpContext.Current.Session["ProfileFields"] as ClaimsResponse; }
+ set { HttpContext.Current.Session["ProfileFields"] = value; }
+ }
}
diff --git a/samples/RelyingPartyPortal/Global.asax.cs b/samples/RelyingPartyPortal/Global.asax.cs
index eb61da9..259512f 100644
--- a/samples/RelyingPartyPortal/Global.asax.cs
+++ b/samples/RelyingPartyPortal/Global.asax.cs
@@ -8,6 +8,11 @@ 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/MembersOnly/Default.aspx b/samples/RelyingPartyPortal/MembersOnly/Default.aspx
index 494ba71..c9b43e8 100644
--- a/samples/RelyingPartyPortal/MembersOnly/Default.aspx
+++ b/samples/RelyingPartyPortal/MembersOnly/Default.aspx
@@ -10,6 +10,7 @@
</p>
<asp:LoginStatus ID="LoginStatus1" runat="server" />
+<% if (State.ProfileFields != null) { %>
<p>
In addition to authenticating you, your OpenID Provider may
have told us something about you using the
@@ -89,4 +90,5 @@
</td>
</tr>
</table>
-</asp:Content> \ No newline at end of file
+<% } %>
+</asp:Content>
diff --git a/src/DotNetOpenId.Test/DotNetOpenId.Test.csproj b/src/DotNetOpenId.Test/DotNetOpenId.Test.csproj
index 0a20915..451f497 100644
--- a/src/DotNetOpenId.Test/DotNetOpenId.Test.csproj
+++ b/src/DotNetOpenId.Test/DotNetOpenId.Test.csproj
@@ -53,14 +53,20 @@
<Compile Include="Extensions\AttributeExchangeFetchResponseTests.cs" />
<Compile Include="Extensions\AttributeExchangeTests.cs" />
<Compile Include="Extensions\AttributeRequestTests.cs" />
+ <Compile Include="Extensions\ExtensionsTestSetup.cs" />
<Compile Include="Extensions\ExtensionTestBase.cs" />
+ <Compile Include="Extensions\PapeTests.cs" />
+ <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="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="RelyingParty\OpenIdRelyingPartyTest.cs" />
<Compile Include="RelyingParty\OpenIdTextBoxTest.cs" />
@@ -73,13 +79,14 @@
<Compile Include="Hosting\HttpHost.cs" />
<Compile Include="TestSupport.cs" />
<Compile Include="Hosting\TestingWorkerRequest.cs" />
- <Compile Include="ProfileFieldValuesTests.cs" />
+ <Compile Include="Extensions\ClaimsResponseTests.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Provider\IdentityEndpointTest.cs" />
<Compile Include="Provider\OpenIdProviderTest.cs" />
<Compile Include="Provider\ProviderEndpointTest.cs" />
<Compile Include="RelyingParty\TokenTest.cs" />
<Compile Include="RealmTestSuite.cs" />
+ <Compile Include="UntrustedWebRequestTests.cs" />
<Compile Include="UriIdentifierTests.cs" />
<Compile Include="UriUtilTest.cs" />
<Compile Include="UtilTest.cs" />
diff --git a/src/DotNetOpenId.Test/EndToEndTesting.cs b/src/DotNetOpenId.Test/EndToEndTesting.cs
index 4879c47..8f94744 100644
--- a/src/DotNetOpenId.Test/EndToEndTesting.cs
+++ b/src/DotNetOpenId.Test/EndToEndTesting.cs
@@ -18,6 +18,8 @@ namespace DotNetOpenId.Test {
[SetUp]
public void Setup() {
appStore = new ApplicationMemoryStore();
+ if (!UntrustedWebRequest.WhitelistHosts.Contains("localhost"))
+ UntrustedWebRequest.WhitelistHosts.Add("localhost");
}
void parameterizedTest(UriIdentifier identityUrl,
@@ -34,10 +36,10 @@ namespace DotNetOpenId.Test {
Uri redirectToProviderUrl;
var returnTo = TestSupport.GetFullUrl(TestSupport.ConsumerPage);
var realm = new Realm(TestSupport.GetFullUrl(TestSupport.ConsumerPage).AbsoluteUri);
- var consumer = new OpenIdRelyingParty(store, null);
+ var consumer = new OpenIdRelyingParty(store, null, null);
Assert.IsNull(consumer.Response);
var request = consumer.CreateRequest(identityUrl, realm, returnTo);
- Protocol protocol = Protocol.Lookup(request.ProviderVersion);
+ Protocol protocol = Protocol.Lookup(request.Provider.Version);
// Test properties and defaults
Assert.AreEqual(AuthenticationRequestMode.Setup, request.Mode);
@@ -47,11 +49,11 @@ namespace DotNetOpenId.Test {
request.Mode = requestMode;
// Verify the redirect URL
- Assert.IsNotNull(request.RedirectToProviderUrl);
- var consumerToProviderQuery = HttpUtility.ParseQueryString(request.RedirectToProviderUrl.Query);
+ 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.RedirectToProviderUrl;
+ redirectToProviderUrl = request.RedirectingResponse.ExtractUrl();
HttpWebRequest providerRequest = (HttpWebRequest)WebRequest.Create(redirectToProviderUrl);
providerRequest.AllowAutoRedirect = false;
@@ -70,7 +72,7 @@ namespace DotNetOpenId.Test {
}
throw;
}
- consumer = new OpenIdRelyingParty(store, redirectUrl);
+ consumer = new OpenIdRelyingParty(store, redirectUrl, HttpUtility.ParseQueryString(redirectUrl.Query));
Assert.AreEqual(expectedResult, consumer.Response.Status);
Assert.AreEqual(identityUrl, consumer.Response.ClaimedIdentifier);
@@ -81,7 +83,7 @@ namespace DotNetOpenId.Test {
// 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);
+ 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
diff --git a/src/DotNetOpenId.Test/Extensions/AttributeExchangeTests.cs b/src/DotNetOpenId.Test/Extensions/AttributeExchangeTests.cs
index 5f4fd26..62efbc2 100644
--- a/src/DotNetOpenId.Test/Extensions/AttributeExchangeTests.cs
+++ b/src/DotNetOpenId.Test/Extensions/AttributeExchangeTests.cs
@@ -95,7 +95,7 @@ namespace DotNetOpenId.Test.Extensions {
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);
+ var consumer = new OpenIdRelyingParty(AppStore, null, null);
var request = consumer.CreateRequest(identityUrl, realm, returnTo);
request.AddExtension(new FetchRequest());
request.AddExtension(new StoreRequest());
diff --git a/src/DotNetOpenId.Test/ProfileFieldValuesTests.cs b/src/DotNetOpenId.Test/Extensions/ClaimsResponseTests.cs
index 51f6eca..afc2e03 100644
--- a/src/DotNetOpenId.Test/ProfileFieldValuesTests.cs
+++ b/src/DotNetOpenId.Test/Extensions/ClaimsResponseTests.cs
@@ -16,11 +16,11 @@ using NUnit.Framework;
using DotNetOpenId.Extensions.SimpleRegistration;
using DotNetOpenId.Extensions;
-namespace DotNetOpenId.Test {
+namespace DotNetOpenId.Test.Extensions {
[TestFixture]
- public class ProfileFieldValuesTests {
+ public class ClaimsResponseTests {
ClaimsResponse getFilledData() {
- return new ClaimsResponse() {
+ return new ClaimsResponse(Constants.sreg_ns) {
BirthDate = new DateTime(2005, 2, 3),
Culture = new System.Globalization.CultureInfo("en-US"),
Email = "a@b.com",
@@ -33,6 +33,13 @@ namespace DotNetOpenId.Test {
}
[Test]
+ public void EmptyMailAddress() {
+ ClaimsResponse response = new ClaimsResponse(Constants.sreg_ns);
+ response.Email = "";
+ Assert.IsNull(response.MailAddress);
+ }
+
+ [Test]
public void BinarySerialization() {
ClaimsResponse fields = getFilledData();
MemoryStream ms = new MemoryStream();
@@ -104,6 +111,26 @@ namespace DotNetOpenId.Test {
Assert.AreNotEqual(fields1, fields2);
}
+ void parameterizedPreserveVersionFromRequest(string versionTypeUri) {
+ Dictionary<string, string> fields = new Dictionary<string, string>{
+ {"optional", "nickname"},
+ };
+ var req = new ClaimsRequest();
+ Assert.IsTrue(((IExtensionRequest)req).Deserialize(fields, null, versionTypeUri));
+ Assert.AreEqual(DemandLevel.Request, req.Nickname);
+ ClaimsResponse resp = req.CreateResponse();
+ Assert.AreEqual(versionTypeUri, ((IExtensionResponse)resp).TypeUri);
+ }
+
+ [Test]
+ public void PreserveVersionFromRequest() {
+ // some unofficial type URIs...
+ parameterizedPreserveVersionFromRequest("http://openid.net/sreg/1.0");
+ parameterizedPreserveVersionFromRequest("http://openid.net/sreg/1.1");
+ // and the official one.
+ parameterizedPreserveVersionFromRequest("http://openid.net/extensions/sreg/1.1");
+ }
+
//[Test]
public void AddToResponse() {
// TODO
diff --git a/src/DotNetOpenId.Test/Extensions/ExtensionTestBase.cs b/src/DotNetOpenId.Test/Extensions/ExtensionTestBase.cs
index ea3379c..825ef58 100644
--- a/src/DotNetOpenId.Test/Extensions/ExtensionTestBase.cs
+++ b/src/DotNetOpenId.Test/Extensions/ExtensionTestBase.cs
@@ -8,6 +8,7 @@ using System.Net;
using DotNetOpenId.Extensions;
using System.IO;
using System.Diagnostics;
+using System.Web;
namespace DotNetOpenId.Test.Extensions {
public class ExtensionTestBase {
@@ -24,12 +25,12 @@ namespace DotNetOpenId.Test.Extensions {
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);
+ var consumer = new OpenIdRelyingParty(AppStore, null, null);
var request = consumer.CreateRequest(identityUrl, realm, returnTo);
if (extension != null)
request.AddExtension(extension);
- HttpWebRequest providerRequest = (HttpWebRequest)WebRequest.Create(request.RedirectToProviderUrl);
+ HttpWebRequest providerRequest = (HttpWebRequest)WebRequest.Create(request.RedirectingResponse.ExtractUrl());
providerRequest.AllowAutoRedirect = false;
Uri redirectUrl;
try {
@@ -46,7 +47,7 @@ namespace DotNetOpenId.Test.Extensions {
}
throw;
}
- consumer = new OpenIdRelyingParty(AppStore, redirectUrl);
+ 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>();
diff --git a/src/DotNetOpenId.Test/Extensions/ExtensionsTestSetup.cs b/src/DotNetOpenId.Test/Extensions/ExtensionsTestSetup.cs
new file mode 100644
index 0000000..1bde812
--- /dev/null
+++ b/src/DotNetOpenId.Test/Extensions/ExtensionsTestSetup.cs
@@ -0,0 +1,16 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using NUnit.Framework;
+
+namespace DotNetOpenId.Test.Extensions {
+ [SetUpFixture]
+ public class ExtensionsTestSetup {
+ [SetUp]
+ public void SetUp() {
+ if (!UntrustedWebRequest.WhitelistHosts.Contains("localhost"))
+ UntrustedWebRequest.WhitelistHosts.Add("localhost");
+ }
+ }
+}
diff --git a/src/DotNetOpenId.Test/Extensions/PapeTests.cs b/src/DotNetOpenId.Test/Extensions/PapeTests.cs
new file mode 100644
index 0000000..914089e
--- /dev/null
+++ b/src/DotNetOpenId.Test/Extensions/PapeTests.cs
@@ -0,0 +1,33 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using NUnit.Framework;
+using DotNetOpenId.Extensions.ProviderAuthenticationPolicy;
+
+namespace DotNetOpenId.Test.Extensions {
+ [TestFixture]
+ public class PapeTests : ExtensionTestBase {
+ [Test]
+ public void None() {
+ var response = ParameterizedTest<PolicyResponse>(
+ TestSupport.GetIdentityUrl(TestSupport.Scenarios.ExtensionFullCooperation, Version), null);
+ Assert.IsNull(response);
+ }
+
+ [Test]
+ public void Full() {
+ var request = new PolicyRequest();
+ request.MaximumAuthenticationAge = TimeSpan.FromMinutes(10);
+ request.PreferredAuthLevelTypes.Add(Constants.AuthenticationLevels.NistTypeUri);
+ var response = ParameterizedTest<PolicyResponse>(
+ TestSupport.GetIdentityUrl(TestSupport.Scenarios.ExtensionFullCooperation, Version), request);
+ Assert.IsNotNull(response);
+ Assert.IsNotNull(response.AuthenticationTimeUtc);
+ Assert.IsTrue(response.AuthenticationTimeUtc.Value > DateTime.UtcNow - request.MaximumAuthenticationAge);
+ Assert.IsTrue(response.AssuranceLevels.ContainsKey(Constants.AuthenticationLevels.NistTypeUri));
+ Assert.AreEqual("1", response.AssuranceLevels[Constants.AuthenticationLevels.NistTypeUri]);
+ Assert.AreEqual(NistAssuranceLevel.Level1, response.NistAssuranceLevel);
+ }
+ }
+}
diff --git a/src/DotNetOpenId.Test/Extensions/PolicyRequestTests.cs b/src/DotNetOpenId.Test/Extensions/PolicyRequestTests.cs
new file mode 100644
index 0000000..40155db
--- /dev/null
+++ b/src/DotNetOpenId.Test/Extensions/PolicyRequestTests.cs
@@ -0,0 +1,194 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using NUnit.Framework;
+using DotNetOpenId.Extensions.ProviderAuthenticationPolicy;
+using DotNetOpenId.Extensions;
+using System.Globalization;
+
+namespace DotNetOpenId.Test.Extensions {
+ [TestFixture]
+ public class PolicyRequestTests {
+ [Test]
+ public void Ctor() {
+ PolicyRequest req = new PolicyRequest();
+ Assert.IsNull(req.MaximumAuthenticationAge);
+ Assert.IsNotNull(req.PreferredPolicies);
+ Assert.AreEqual(0, req.PreferredPolicies.Count);
+ }
+
+ [Test]
+ public void MaximumAuthenticationAgeTest() {
+ PolicyRequest req = new PolicyRequest();
+ req.MaximumAuthenticationAge = TimeSpan.FromHours(1);
+ Assert.IsNotNull(req.MaximumAuthenticationAge);
+ Assert.AreEqual(TimeSpan.FromHours(1), req.MaximumAuthenticationAge);
+ req.MaximumAuthenticationAge = null;
+ Assert.IsNull(req.MaximumAuthenticationAge);
+ }
+
+ [Test]
+ public void AddPolicies() {
+ PolicyRequest resp = new PolicyRequest();
+ resp.PreferredPolicies.Add(AuthenticationPolicies.MultiFactor);
+ resp.PreferredPolicies.Add(AuthenticationPolicies.PhishingResistant);
+ Assert.AreEqual(2, resp.PreferredPolicies.Count);
+ Assert.AreEqual(AuthenticationPolicies.MultiFactor, resp.PreferredPolicies[0]);
+ Assert.AreEqual(AuthenticationPolicies.PhishingResistant, resp.PreferredPolicies[1]);
+ }
+
+ [Test]
+ public void AddPolicyMultipleTimes() {
+ // Although this isn't really the desired behavior (we'd prefer to see an
+ // exception thrown), since we're using a List<string> internally we can't
+ // expect anything better (for now). But if this is ever fixed, by all means
+ // change this test to expect an exception or something else.
+ PolicyRequest resp = new PolicyRequest();
+ resp.PreferredPolicies.Add(AuthenticationPolicies.MultiFactor);
+ resp.PreferredPolicies.Add(AuthenticationPolicies.MultiFactor);
+ Assert.AreEqual(2, resp.PreferredPolicies.Count);
+ }
+
+ [Test]
+ public void AddAuthLevelTypes() {
+ PolicyRequest req = new PolicyRequest();
+ req.PreferredAuthLevelTypes.Add(Constants.AuthenticationLevels.NistTypeUri);
+ Assert.AreEqual(1, req.PreferredAuthLevelTypes.Count);
+ Assert.IsTrue(req.PreferredAuthLevelTypes.Contains(Constants.AuthenticationLevels.NistTypeUri));
+ }
+
+ [Test]
+ public void EqualsTest() {
+ PolicyRequest req = new PolicyRequest();
+ PolicyRequest req2 = new PolicyRequest();
+ Assert.AreEqual(req, req2);
+ Assert.AreNotEqual(req, null);
+ Assert.AreNotEqual(null, req);
+
+ // Test PreferredPolicies list comparison
+ req.PreferredPolicies.Add(AuthenticationPolicies.PhishingResistant);
+ Assert.AreNotEqual(req, req2);
+ req2.PreferredPolicies.Add(AuthenticationPolicies.MultiFactor);
+ Assert.AreNotEqual(req, req2);
+ req2.PreferredPolicies.Clear();
+ req2.PreferredPolicies.Add(AuthenticationPolicies.PhishingResistant);
+ Assert.AreEqual(req, req2);
+
+ // Test PreferredPolicies list comparison when that list is not in the same order.
+ req.PreferredPolicies.Add(AuthenticationPolicies.MultiFactor);
+ Assert.AreNotEqual(req, req2);
+ req2.PreferredPolicies.Insert(0, AuthenticationPolicies.MultiFactor);
+ Assert.AreEqual(req, req2);
+
+ // Test MaximumAuthenticationAge comparison.
+ req.MaximumAuthenticationAge = TimeSpan.FromHours(1);
+ Assert.AreNotEqual(req, req2);
+ req2.MaximumAuthenticationAge = req.MaximumAuthenticationAge;
+ Assert.AreEqual(req, req2);
+
+ // Test PreferredAuthLevelTypes comparison.
+ req.PreferredAuthLevelTypes.Add("authlevel1");
+ Assert.AreNotEqual(req, req2);
+ req2.PreferredAuthLevelTypes.Add("authlevel2");
+ Assert.AreNotEqual(req, req2);
+ req.PreferredAuthLevelTypes.Add("authlevel2");
+ req2.PreferredAuthLevelTypes.Add("authlevel1");
+ Assert.AreEqual(req, req2);
+ }
+
+ [Test]
+ public void DeserializeNull() {
+ PolicyRequest req = new PolicyRequest();
+ Assert.IsFalse(((IExtensionRequest)req).Deserialize(null, null, Constants.TypeUri));
+ }
+
+ [Test]
+ public void DeserializeEmpty() {
+ PolicyRequest req = new PolicyRequest();
+ Assert.IsFalse(((IExtensionRequest)req).Deserialize(new Dictionary<string, string>(), null, Constants.TypeUri));
+ }
+
+ [Test]
+ public void SerializeRoundTrip() {
+ // This test relies on the PolicyRequest.Equals method. If this and that test
+ // are failing, work on EqualsTest first.
+
+ // Most basic test
+ PolicyRequest req = new PolicyRequest(), req2 = new PolicyRequest();
+ var fields = ((IExtensionRequest)req).Serialize(null);
+ Assert.IsTrue(((IExtensionRequest)req2).Deserialize(fields, null, Constants.TypeUri));
+ Assert.AreEqual(req, req2);
+
+ // Test with all fields set
+ req2 = new PolicyRequest();
+ req.PreferredPolicies.Add(AuthenticationPolicies.MultiFactor);
+ req.PreferredAuthLevelTypes.Add(Constants.AuthenticationLevels.NistTypeUri);
+ req.MaximumAuthenticationAge = TimeSpan.FromHours(1);
+ fields = ((IExtensionRequest)req).Serialize(null);
+ Assert.IsTrue(((IExtensionRequest)req2).Deserialize(fields, null, Constants.TypeUri));
+ Assert.AreEqual(req, req2);
+
+ // Test with an extra policy and auth level
+ req2 = new PolicyRequest();
+ req.PreferredPolicies.Add(AuthenticationPolicies.PhishingResistant);
+ req.PreferredAuthLevelTypes.Add("customAuthLevel");
+ fields = ((IExtensionRequest)req).Serialize(null);
+ Assert.IsTrue(((IExtensionRequest)req2).Deserialize(fields, null, Constants.TypeUri));
+ Assert.AreEqual(req, req2);
+
+ // Test with a policy added twice. We should see it intelligently leave one of
+ // the doubled policies out.
+ req2 = new PolicyRequest();
+ req.PreferredPolicies.Add(AuthenticationPolicies.PhishingResistant);
+ req.PreferredAuthLevelTypes.Add(Constants.AuthenticationLevels.NistTypeUri);
+ fields = ((IExtensionRequest)req).Serialize(null);
+ Assert.IsTrue(((IExtensionRequest)req2).Deserialize(fields, null, Constants.TypeUri));
+ Assert.AreNotEqual(req, req2);
+ // Now go ahead and add the doubled one so we can do our equality test.
+ req2.PreferredPolicies.Add(AuthenticationPolicies.PhishingResistant);
+ req2.PreferredAuthLevelTypes.Add(Constants.AuthenticationLevels.NistTypeUri);
+ Assert.AreEqual(req, req2);
+ }
+
+ [Test]
+ public void Serialize() {
+ PolicyRequest req = new PolicyRequest();
+ var fields = ((IExtensionRequest)req).Serialize(null);
+ Assert.AreEqual(1, fields.Count);
+ Assert.IsTrue(fields.ContainsKey("preferred_auth_policies"));
+ Assert.IsEmpty(fields["preferred_auth_policies"]);
+
+ req.MaximumAuthenticationAge = TimeSpan.FromHours(1);
+ fields = ((IExtensionRequest)req).Serialize(null);
+ Assert.AreEqual(2, fields.Count);
+ Assert.IsTrue(fields.ContainsKey("max_auth_age"));
+ Assert.AreEqual(TimeSpan.FromHours(1).TotalSeconds.ToString(CultureInfo.InvariantCulture), fields["max_auth_age"]);
+
+ req.PreferredPolicies.Add("http://pol1/");
+ fields = ((IExtensionRequest)req).Serialize(null);
+ Assert.AreEqual("http://pol1/", fields["preferred_auth_policies"]);
+
+ req.PreferredPolicies.Add("http://pol2/");
+ fields = ((IExtensionRequest)req).Serialize(null);
+ Assert.AreEqual("http://pol1/ http://pol2/", fields["preferred_auth_policies"]);
+
+ req.PreferredAuthLevelTypes.Add("http://authtype1/");
+ fields = ((IExtensionRequest)req).Serialize(null);
+ Assert.AreEqual(4, fields.Count);
+ Assert.IsTrue(fields.ContainsKey("auth_level.ns.alias1"));
+ Assert.AreEqual("http://authtype1/", fields["auth_level.ns.alias1"]);
+ Assert.IsTrue(fields.ContainsKey("preferred_auth_level_types"));
+ Assert.AreEqual("alias1", fields["preferred_auth_level_types"]);
+
+ req.PreferredAuthLevelTypes.Add(Constants.AuthenticationLevels.NistTypeUri);
+ fields = ((IExtensionRequest)req).Serialize(null);
+ Assert.AreEqual(5, fields.Count);
+ Assert.IsTrue(fields.ContainsKey("auth_level.ns.alias2"));
+ Assert.AreEqual("http://authtype1/", fields["auth_level.ns.alias2"]);
+ Assert.IsTrue(fields.ContainsKey("auth_level.ns.nist"));
+ Assert.AreEqual(Constants.AuthenticationLevels.NistTypeUri, fields["auth_level.ns.nist"]);
+ Assert.AreEqual("alias2 nist", fields["preferred_auth_level_types"]);
+ }
+ }
+}
diff --git a/src/DotNetOpenId.Test/Extensions/PolicyResponseTests.cs b/src/DotNetOpenId.Test/Extensions/PolicyResponseTests.cs
new file mode 100644
index 0000000..7fe240b
--- /dev/null
+++ b/src/DotNetOpenId.Test/Extensions/PolicyResponseTests.cs
@@ -0,0 +1,258 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using NUnit.Framework;
+using DotNetOpenId.Extensions.ProviderAuthenticationPolicy;
+using DotNetOpenId.Extensions;
+
+namespace DotNetOpenId.Test.Extensions {
+ [TestFixture]
+ public class PolicyResponseTests {
+ DateTime someLocalTime = new DateTime(2008, 1, 1, 1, 1, 1, 0, DateTimeKind.Local);
+ DateTime someUtcTime = new DateTime(2008, 1, 1, 1, 1, 1, 0, DateTimeKind.Utc);
+ DateTime someUnspecifiedTime = new DateTime(2008, 1, 1, 1, 1, 1, 0, DateTimeKind.Unspecified);
+ [Test]
+ public void Ctor() {
+ PolicyResponse resp = new PolicyResponse();
+ Assert.IsNotNull(resp.ActualPolicies);
+ Assert.AreEqual(0, resp.ActualPolicies.Count);
+ Assert.IsNull(resp.AuthenticationTimeUtc);
+ Assert.IsNull(resp.NistAssuranceLevel);
+ }
+
+ [Test]
+ public void AddPolicies() {
+ PolicyResponse resp = new PolicyResponse();
+ resp.ActualPolicies.Add(AuthenticationPolicies.MultiFactor);
+ resp.ActualPolicies.Add(AuthenticationPolicies.PhishingResistant);
+ Assert.AreEqual(2, resp.ActualPolicies.Count);
+ Assert.AreEqual(AuthenticationPolicies.MultiFactor, resp.ActualPolicies[0]);
+ Assert.AreEqual(AuthenticationPolicies.PhishingResistant, resp.ActualPolicies[1]);
+ }
+
+ [Test]
+ public void AddPolicyMultipleTimes() {
+ // Although this isn't really the desired behavior (we'd prefer to see an
+ // exception thrown), since we're using a List<string> internally we can't
+ // expect anything better (for now). But if this is ever fixed, by all means
+ // change this test to expect an exception or something else.
+ PolicyResponse resp = new PolicyResponse();
+ resp.ActualPolicies.Add(AuthenticationPolicies.MultiFactor);
+ resp.ActualPolicies.Add(AuthenticationPolicies.MultiFactor);
+ Assert.AreEqual(2, resp.ActualPolicies.Count);
+ }
+
+ [Test]
+ public void AuthenticationTimeUtcConvertsToUtc() {
+ PolicyResponse resp = new PolicyResponse();
+ resp.AuthenticationTimeUtc = someLocalTime;
+ Assert.IsNotNull(resp.AuthenticationTimeUtc);
+ Assert.AreEqual(DateTimeKind.Utc, resp.AuthenticationTimeUtc.Value.Kind);
+ Assert.AreEqual(someLocalTime.ToUniversalTime(), resp.AuthenticationTimeUtc.Value);
+ }
+
+ [Test]
+ public void AuthenticationTimeUtcSetUtc() {
+ PolicyResponse resp = new PolicyResponse();
+ resp.AuthenticationTimeUtc = someUtcTime;
+ Assert.AreEqual(someUtcTime, resp.AuthenticationTimeUtc);
+ }
+
+ [Test, ExpectedException(typeof(ArgumentException))]
+ public void AuthenticationTimeUtcSetUnspecified() {
+ PolicyResponse resp = new PolicyResponse();
+ resp.AuthenticationTimeUtc = someUnspecifiedTime;
+ }
+
+ [Test]
+ public void AuthenticationTimeUtcSetNull() {
+ PolicyResponse resp = new PolicyResponse();
+ resp.AuthenticationTimeUtc = null;
+ Assert.IsNull(resp.AuthenticationTimeUtc);
+ resp.AuthenticationTimeUtc = someUtcTime;
+ Assert.IsNotNull(resp.AuthenticationTimeUtc);
+ resp.AuthenticationTimeUtc = null;
+ Assert.IsNull(resp.AuthenticationTimeUtc);
+ }
+
+ [Test]
+ public void NistAssuranceLevelSetVarious() {
+ PolicyResponse resp = new PolicyResponse();
+ resp.NistAssuranceLevel = NistAssuranceLevel.Level1;
+ Assert.AreEqual(NistAssuranceLevel.Level1, resp.NistAssuranceLevel);
+ resp.NistAssuranceLevel = null;
+ Assert.IsNull(resp.NistAssuranceLevel);
+ resp.NistAssuranceLevel = NistAssuranceLevel.InsufficientForLevel1;
+ Assert.AreEqual(NistAssuranceLevel.InsufficientForLevel1, resp.NistAssuranceLevel);
+ }
+
+ [Test]
+ public void AssuranceLevels() {
+ PolicyResponse resp = new PolicyResponse();
+ Assert.AreEqual(0, resp.AssuranceLevels.Count);
+ resp.NistAssuranceLevel = NistAssuranceLevel.Level2;
+ Assert.AreEqual(1, resp.AssuranceLevels.Count);
+ Assert.AreEqual("2", resp.AssuranceLevels[Constants.AuthenticationLevels.NistTypeUri]);
+ resp.AssuranceLevels[Constants.AuthenticationLevels.NistTypeUri] = "3";
+ Assert.AreEqual(NistAssuranceLevel.Level3, resp.NistAssuranceLevel);
+ resp.AssuranceLevels.Clear();
+ Assert.IsNull(resp.NistAssuranceLevel);
+ }
+
+ [Test]
+ public void EqualsTest() {
+ PolicyResponse resp = new PolicyResponse();
+ PolicyResponse resp2 = new PolicyResponse();
+ Assert.AreEqual(resp, resp2);
+ Assert.AreNotEqual(resp, null);
+ Assert.AreNotEqual(null, resp);
+
+ // Test ActualPolicies list comparison
+ resp.ActualPolicies.Add(AuthenticationPolicies.PhishingResistant);
+ Assert.AreNotEqual(resp, resp2);
+ resp2.ActualPolicies.Add(AuthenticationPolicies.MultiFactor);
+ Assert.AreNotEqual(resp, resp2);
+ resp2.ActualPolicies.Clear();
+ resp2.ActualPolicies.Add(AuthenticationPolicies.PhishingResistant);
+ Assert.AreEqual(resp, resp2);
+
+ // Test ActualPolicies list comparison when that list is not in the same order.
+ resp.ActualPolicies.Add(AuthenticationPolicies.MultiFactor);
+ Assert.AreNotEqual(resp, resp2);
+ resp2.ActualPolicies.Insert(0, AuthenticationPolicies.MultiFactor);
+ Assert.AreEqual(resp, resp2);
+
+ // Test AuthenticationTimeUtc comparison.
+ resp.AuthenticationTimeUtc = DateTime.Now;
+ Assert.AreNotEqual(resp, resp2);
+ resp2.AuthenticationTimeUtc = resp.AuthenticationTimeUtc;
+ Assert.AreEqual(resp, resp2);
+ resp2.AuthenticationTimeUtc += TimeSpan.FromSeconds(1);
+ Assert.AreNotEqual(resp, resp2);
+ resp2.AuthenticationTimeUtc = resp.AuthenticationTimeUtc;
+ Assert.AreEqual(resp, resp2);
+
+ // Test NistAssuranceLevel comparison.
+ resp.NistAssuranceLevel = NistAssuranceLevel.InsufficientForLevel1;
+ Assert.AreNotEqual(resp, resp2);
+ resp2.NistAssuranceLevel = NistAssuranceLevel.InsufficientForLevel1;
+ Assert.AreEqual(resp, resp2);
+ resp.NistAssuranceLevel = NistAssuranceLevel.Level2;
+ Assert.AreNotEqual(resp, resp2);
+ resp2.NistAssuranceLevel = NistAssuranceLevel.Level2;
+ Assert.AreEqual(resp, resp2);
+
+ // Test AssuranceLevels comparison.
+ resp.AssuranceLevels.Add("custom", "b");
+ Assert.AreNotEqual(resp, resp2);
+ resp2.AssuranceLevels.Add("custom", "2");
+ Assert.AreNotEqual(resp, resp2);
+ resp2.AssuranceLevels["custom"] = "b";
+ Assert.AreEqual(resp, resp2);
+ resp.AssuranceLevels[Constants.AuthenticationLevels.NistTypeUri] = "1";
+ Assert.AreNotEqual(resp, resp2);
+ resp2.AssuranceLevels[Constants.AuthenticationLevels.NistTypeUri] = "1";
+ Assert.AreEqual(resp, resp2);
+ }
+
+ [Test]
+ public void DeserializeNull() {
+ PolicyResponse resp = new PolicyResponse();
+ Assert.IsFalse(((IExtensionResponse)resp).Deserialize(null, null, Constants.TypeUri));
+ }
+
+ [Test]
+ public void DeserializeEmpty() {
+ PolicyResponse resp = new PolicyResponse();
+ Assert.IsFalse(((IExtensionResponse)resp).Deserialize(new Dictionary<string, string>(), null, Constants.TypeUri));
+ }
+
+ [Test]
+ public void SerializeRoundTrip() {
+ // This test relies on the PolicyResponse.Equals method. If this and that test
+ // are failing, work on EqualsTest first.
+
+ // Most basic test
+ PolicyResponse resp = new PolicyResponse(), resp2 = new PolicyResponse();
+ var fields = ((IExtensionResponse)resp).Serialize(null);
+ Assert.IsTrue(((IExtensionResponse)resp2).Deserialize(fields, null, Constants.TypeUri));
+ Assert.AreEqual(resp, resp2);
+
+ // Test with all fields set
+ resp2 = new PolicyResponse();
+ resp.ActualPolicies.Add(AuthenticationPolicies.MultiFactor);
+ resp.AuthenticationTimeUtc = someUtcTime;
+ resp.NistAssuranceLevel = NistAssuranceLevel.Level2;
+ fields = ((IExtensionResponse)resp).Serialize(null);
+ Assert.IsTrue(((IExtensionResponse)resp2).Deserialize(fields, null, Constants.TypeUri));
+ Assert.AreEqual(resp, resp2);
+
+ // Test with an extra policy
+ resp2 = new PolicyResponse();
+ resp.ActualPolicies.Add(AuthenticationPolicies.PhishingResistant);
+ resp.AssuranceLevels.Add("customlevel", "ABC");
+ fields = ((IExtensionResponse)resp).Serialize(null);
+ Assert.IsTrue(((IExtensionResponse)resp2).Deserialize(fields, null, Constants.TypeUri));
+ Assert.AreEqual(resp, resp2);
+
+ // Test with a policy added twice. We should see it intelligently leave one of
+ // the doubled policies out.
+ resp2 = new PolicyResponse();
+ resp.ActualPolicies.Add(AuthenticationPolicies.PhishingResistant);
+ fields = ((IExtensionResponse)resp).Serialize(null);
+ Assert.IsTrue(((IExtensionResponse)resp2).Deserialize(fields, null, Constants.TypeUri));
+ Assert.AreNotEqual(resp, resp2);
+ // Now go ahead and add the doubled one so we can do our equality test.
+ resp2.ActualPolicies.Add(AuthenticationPolicies.PhishingResistant);
+ Assert.AreEqual(resp, resp2);
+ }
+
+ [Test]
+ public void Serialize() {
+ PolicyResponse resp = new PolicyResponse(), resp2 = new PolicyResponse();
+ var fields = ((IExtensionResponse)resp).Serialize(null);
+ Assert.AreEqual(1, fields.Count);
+ Assert.IsTrue(fields.ContainsKey("auth_policies"));
+ Assert.AreEqual(AuthenticationPolicies.None, fields["auth_policies"]);
+
+ resp.ActualPolicies.Add(AuthenticationPolicies.PhishingResistant);
+ fields = ((IExtensionResponse)resp).Serialize(null);
+ Assert.AreEqual(1, fields.Count);
+ Assert.AreEqual(AuthenticationPolicies.PhishingResistant, fields["auth_policies"]);
+
+ resp.ActualPolicies.Add(AuthenticationPolicies.PhysicalMultiFactor);
+ fields = ((IExtensionResponse)resp).Serialize(null);
+ Assert.AreEqual(1, fields.Count);
+ Assert.AreEqual(
+ AuthenticationPolicies.PhishingResistant + " " + AuthenticationPolicies.PhysicalMultiFactor,
+ fields["auth_policies"]);
+
+ resp.AuthenticationTimeUtc = DateTime.UtcNow;
+ fields = ((IExtensionResponse)resp).Serialize(null);
+ Assert.AreEqual(2, fields.Count);
+ Assert.IsTrue(fields.ContainsKey("auth_time"));
+
+ resp.NistAssuranceLevel = NistAssuranceLevel.Level3;
+ fields = ((IExtensionResponse)resp).Serialize(null);
+ Assert.AreEqual(4, fields.Count);
+ Assert.IsTrue(fields.ContainsKey("auth_level.ns.nist"));
+ Assert.AreEqual(Constants.AuthenticationLevels.NistTypeUri, fields["auth_level.ns.nist"]);
+ Assert.IsTrue(fields.ContainsKey("auth_level.nist"));
+ Assert.AreEqual("3", fields["auth_level.nist"]);
+
+ resp.AssuranceLevels.Add("custom", "CU");
+ fields = ((IExtensionResponse)resp).Serialize(null);
+ Assert.AreEqual(6, fields.Count);
+ Assert.IsTrue(fields.ContainsKey("auth_level.ns.alias2"));
+ Assert.AreEqual("custom", fields["auth_level.ns.alias2"]);
+ Assert.IsTrue(fields.ContainsKey("auth_level.alias2"));
+ Assert.AreEqual("CU", fields["auth_level.alias2"]);
+ // and make sure the NIST is still there.
+ Assert.IsTrue(fields.ContainsKey("auth_level.ns.nist"));
+ Assert.AreEqual(Constants.AuthenticationLevels.NistTypeUri, fields["auth_level.ns.nist"]);
+ Assert.IsTrue(fields.ContainsKey("auth_level.nist"));
+ Assert.AreEqual("3", fields["auth_level.nist"]);
+ }
+ }
+}
diff --git a/src/DotNetOpenId.Test/Hosting/AspNetHost.cs b/src/DotNetOpenId.Test/Hosting/AspNetHost.cs
index 8b92e23..f171f5a 100644
--- a/src/DotNetOpenId.Test/Hosting/AspNetHost.cs
+++ b/src/DotNetOpenId.Test/Hosting/AspNetHost.cs
@@ -19,7 +19,9 @@ namespace DotNetOpenId.Test.Hosting {
public AspNetHost() {
httpHost = HttpHost.CreateHost(this);
- DotNetOpenId.Provider.SigningEncoder.Signing += (s, e) => {
+ if (!UntrustedWebRequest.WhitelistHosts.Contains("localhost"))
+ UntrustedWebRequest.WhitelistHosts.Add("localhost");
+ DotNetOpenId.Provider.SigningMessageEncoder.Signing += (s, e) => {
if (MessageInterceptor != null) MessageInterceptor.OnSigningMessage(e.Message);
};
}
diff --git a/src/DotNetOpenId.Test/Provider/IAuthenticationRequestTest.cs b/src/DotNetOpenId.Test/Provider/IAuthenticationRequestTest.cs
index b574c97..3ea7d9c 100644
--- a/src/DotNetOpenId.Test/Provider/IAuthenticationRequestTest.cs
+++ b/src/DotNetOpenId.Test/Provider/IAuthenticationRequestTest.cs
@@ -9,14 +9,20 @@ using System.Net;
namespace DotNetOpenId.Test.Provider {
[TestFixture]
public class IAuthenticationRequestTest {
+ [SetUp]
+ public void Setup() {
+ if (!UntrustedWebRequest.WhitelistHosts.Contains("localhost"))
+ 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);
+ var consumer = new OpenIdRelyingParty(new ApplicationMemoryStore(), null, null);
var request = consumer.CreateRequest(TestSupport.GetIdentityUrl(TestSupport.Scenarios.AutoApproval, ProtocolVersion.V20), realm, returnTo);
- WebRequest.Create(request.RedirectToProviderUrl).GetResponse(); // the OP should return 500, causing exception here.
+ WebRequest.Create(request.RedirectingResponse.ExtractUrl()).GetResponse(); // the OP should return 500, causing exception here.
}
static void getUnverifiableRP(out Uri returnTo, out Realm realm) {
diff --git a/src/DotNetOpenId.Test/RelyingParty/AuthenticationRequestTests.cs b/src/DotNetOpenId.Test/RelyingParty/AuthenticationRequestTests.cs
new file mode 100644
index 0000000..399e4e7
--- /dev/null
+++ b/src/DotNetOpenId.Test/RelyingParty/AuthenticationRequestTests.cs
@@ -0,0 +1,32 @@
+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;
+
+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");
+ }
+
+ [Test]
+ public void Provider() {
+ OpenIdRelyingParty rp = new OpenIdRelyingParty(store, null, null);
+ Identifier id = TestSupport.GetFullUrl("xrdsdiscovery/xrds20.aspx");
+ 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 6cc85ab..7dcb792 100644
--- a/src/DotNetOpenId.Test/RelyingParty/AuthenticationResponseTests.cs
+++ b/src/DotNetOpenId.Test/RelyingParty/AuthenticationResponseTests.cs
@@ -29,14 +29,16 @@ namespace DotNetOpenId.Test.RelyingParty {
[SetUp]
public void SetUp() {
store = new ApplicationMemoryStore();
+ if (!UntrustedWebRequest.WhitelistHosts.Contains("localhost"))
+ UntrustedWebRequest.WhitelistHosts.Add("localhost");
}
Uri getPositiveAssertion(ProtocolVersion version) {
try {
- OpenIdRelyingParty rp = new OpenIdRelyingParty(store, null);
+ 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.RedirectToProviderUrl);
+ HttpWebRequest providerRequest = (HttpWebRequest)WebRequest.Create(request.RedirectingResponse.ExtractUrl());
providerRequest.AllowAutoRedirect = false;
Uri redirectUrl;
try {
@@ -104,7 +106,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).Response;
+ var response = new OpenIdRelyingParty(store, assertion, HttpUtility.ParseQueryString(assertion.Query)).Response;
Assert.AreEqual(AuthenticationStatus.Failed, response.Status);
Assert.IsNotNull(response.Exception);
}
@@ -138,7 +140,7 @@ 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).Response;
+ var response = new OpenIdRelyingParty(store, assertion, HttpUtility.ParseQueryString(assertion.Query)).Response;
Assert.AreEqual(AuthenticationStatus.Failed, response.Status);
Assert.IsNotNull(response.Exception);
}
diff --git a/src/DotNetOpenId.Test/RelyingParty/IProviderEndpointTests.cs b/src/DotNetOpenId.Test/RelyingParty/IProviderEndpointTests.cs
new file mode 100644
index 0000000..04f5873
--- /dev/null
+++ b/src/DotNetOpenId.Test/RelyingParty/IProviderEndpointTests.cs
@@ -0,0 +1,52 @@
+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;
+
+namespace DotNetOpenId.Test.RelyingParty {
+ [TestFixture]
+ public class IProviderEndpointTests {
+ 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");
+ }
+
+ [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);
+ IProviderEndpoint provider = request.Provider;
+ Assert.IsTrue(provider.IsExtensionSupported<ClaimsRequest>());
+ Assert.IsTrue(provider.IsExtensionSupported(typeof(ClaimsRequest)));
+ Assert.IsFalse(provider.IsExtensionSupported<FetchRequest>());
+ Assert.IsFalse(provider.IsExtensionSupported(typeof(FetchRequest)));
+
+ // 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");
+ request = rp.CreateRequest(id, realm, returnTo);
+ Assert.IsTrue(provider.IsExtensionSupported<ClaimsRequest>());
+ Assert.IsTrue(provider.IsExtensionSupported(typeof(ClaimsRequest)));
+ }
+
+ [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);
+ 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 4a31cbf..a2ca984 100644
--- a/src/DotNetOpenId.Test/RelyingParty/OpenIdRelyingPartyTest.cs
+++ b/src/DotNetOpenId.Test/RelyingParty/OpenIdRelyingPartyTest.cs
@@ -3,6 +3,7 @@ using DotNetOpenId.RelyingParty;
using NUnit.Framework;
using ProviderMemoryStore = DotNetOpenId.AssociationMemoryStore<DotNetOpenId.AssociationRelyingPartyType>;
using System.Web;
+using System.Collections.Specialized;
namespace DotNetOpenId.Test.RelyingParty {
[TestFixture]
@@ -16,6 +17,8 @@ namespace DotNetOpenId.Test.RelyingParty {
[SetUp]
public void Setup() {
store = new ApplicationMemoryStore();
+ if (!UntrustedWebRequest.WhitelistHosts.Contains("localhost"))
+ UntrustedWebRequest.WhitelistHosts.Add("localhost");
}
[Test]
@@ -26,25 +29,25 @@ namespace DotNetOpenId.Test.RelyingParty {
[Test]
public void CtorWithNullRequestUri() {
- new OpenIdRelyingParty(store, null);
+ new OpenIdRelyingParty(store, null, null);
}
[Test]
public void CtorWithNullStore() {
- var consumer = new OpenIdRelyingParty(null, simpleNonOpenIdRequest);
+ var consumer = new OpenIdRelyingParty(null, simpleNonOpenIdRequest, new NameValueCollection());
}
[Test]
[ExpectedException(typeof(InvalidOperationException))]
public void CreateRequestWithoutContext1() {
- var consumer = new OpenIdRelyingParty(store, simpleNonOpenIdRequest);
+ var consumer = new OpenIdRelyingParty(store, simpleNonOpenIdRequest, new NameValueCollection());
consumer.CreateRequest(simpleOpenId);
}
[Test]
[ExpectedException(typeof(InvalidOperationException))]
public void CreateRequestWithoutContext2() {
- var consumer = new OpenIdRelyingParty(store, simpleNonOpenIdRequest);
+ var consumer = new OpenIdRelyingParty(store, simpleNonOpenIdRequest, new NameValueCollection());
consumer.CreateRequest(simpleOpenId, realm);
}
@@ -52,7 +55,7 @@ namespace DotNetOpenId.Test.RelyingParty {
public void AssociationCreationWithStore() {
var providerStore = new ProviderMemoryStore();
- OpenIdRelyingParty rp = new OpenIdRelyingParty(new ApplicationMemoryStore(), null);
+ OpenIdRelyingParty rp = new OpenIdRelyingParty(new ApplicationMemoryStore(), null, null);
var idUrl = TestSupport.GetIdentityUrl(TestSupport.Scenarios.AutoApproval, ProtocolVersion.V20);
DotNetOpenId.RelyingParty.IAuthenticationRequest req;
@@ -70,7 +73,7 @@ namespace DotNetOpenId.Test.RelyingParty {
public void NoAssociationRequestWithoutStore() {
var providerStore = new ProviderMemoryStore();
- OpenIdRelyingParty rp = new OpenIdRelyingParty(null, null);
+ OpenIdRelyingParty rp = new OpenIdRelyingParty(null, null, null);
var idUrl = TestSupport.GetIdentityUrl(TestSupport.Scenarios.AutoApproval, ProtocolVersion.V20);
DotNetOpenId.RelyingParty.IAuthenticationRequest req;
@@ -111,10 +114,10 @@ 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);
+ var consumer = new OpenIdRelyingParty(null, null, null);
var request = consumer.CreateRequest(identityUrl, realm, returnTo);
- Protocol protocol = Protocol.Lookup(request.ProviderVersion);
- var nvc = HttpUtility.ParseQueryString(request.RedirectToProviderUrl.Query);
+ Protocol protocol = Protocol.Lookup(request.Provider.Version);
+ var nvc = HttpUtility.ParseQueryString(request.RedirectingResponse.ExtractUrl().Query);
string realmString = nvc[protocol.openid.Realm];
string returnToString = nvc[protocol.openid.return_to];
bool realmPortExplicitlyGiven = realmString.Contains(":80");
@@ -131,11 +134,11 @@ namespace DotNetOpenId.Test.RelyingParty {
public void ReturnToUrlEncodingTest() {
Uri origin = TestSupport.GetFullUrl(TestSupport.ConsumerPage);
var identityUrl = TestSupport.GetIdentityUrl(TestSupport.Scenarios.AutoApproval, ProtocolVersion.V20);
- var consumer = new OpenIdRelyingParty(null, null);
+ var consumer = new OpenIdRelyingParty(null, null, null);
var request = consumer.CreateRequest(identityUrl, origin, origin);
- Protocol protocol = Protocol.Lookup(request.ProviderVersion);
+ Protocol protocol = Protocol.Lookup(request.Provider.Version);
request.AddCallbackArguments("a+b", "c+d");
- var requestArgs = HttpUtility.ParseQueryString(request.RedirectToProviderUrl.Query);
+ var requestArgs = HttpUtility.ParseQueryString(request.RedirectingResponse.ExtractUrl().Query);
var returnToArgs = HttpUtility.ParseQueryString(requestArgs[protocol.openid.return_to]);
Assert.AreEqual("c+d", returnToArgs["a+b"]);
}
diff --git a/src/DotNetOpenId.Test/TestSupport.cs b/src/DotNetOpenId.Test/TestSupport.cs
index d45aad3..be17494 100644
--- a/src/DotNetOpenId.Test/TestSupport.cs
+++ b/src/DotNetOpenId.Test/TestSupport.cs
@@ -93,3 +93,16 @@ public class TestSupport {
nvc[protocol.openid.sig] = Convert.ToBase64String(assoc.Sign(subsetDictionary, signed));
}
}
+
+static class TestExtensions {
+ /// <summary>
+ /// Gets a URL that can be requested to send an indirect message.
+ /// </summary>
+ public static Uri ExtractUrl(this IResponse message) {
+ DotNetOpenId.Response response = (DotNetOpenId.Response)message;
+ IEncodable encodable = response.EncodableMessage;
+ UriBuilder builder = new UriBuilder(encodable.RedirectUrl);
+ UriUtil.AppendQueryArgs(builder, encodable.EncodedFields);
+ return builder.Uri;
+ }
+}
diff --git a/src/DotNetOpenId.Test/UntrustedWebRequestTests.cs b/src/DotNetOpenId.Test/UntrustedWebRequestTests.cs
new file mode 100644
index 0000000..c44354c
--- /dev/null
+++ b/src/DotNetOpenId.Test/UntrustedWebRequestTests.cs
@@ -0,0 +1,93 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using NUnit.Framework;
+using System.Text.RegularExpressions;
+using System.Net;
+
+namespace DotNetOpenId.Test {
+ [TestFixture]
+ public class UntrustedWebRequestTests {
+ TimeSpan timeoutDefault;
+ [SetUp]
+ public void SetUp() {
+ UntrustedWebRequest.WhitelistHosts.Clear();
+ UntrustedWebRequest.WhitelistHostsRegex.Clear();
+ UntrustedWebRequest.BlacklistHosts.Clear();
+ UntrustedWebRequest.BlacklistHostsRegex.Clear();
+ timeoutDefault = UntrustedWebRequest.Timeout;
+ }
+ [TearDown]
+ public void TearDown() {
+ UntrustedWebRequest.Timeout = timeoutDefault; // in case a test changed it
+ }
+
+ [Test]
+ public void DisallowUnsafeHosts() {
+ string[] unsafeHosts = new [] {
+ // IPv4 loopback representations
+ "http://127.0.0.1",
+ "http://127.100.0.1",
+ "http://127.0.0.100",
+ "http://2130706433", // 127.0.0.1 in decimal format
+ "http://0x7f000001", // 127.0.0.1 in hex format
+ // IPv6 loopback representation
+ "http://[::1]",
+ // disallowed schemes
+ "ftp://ftp.microsoft.com",
+ "xri://boo",
+ };
+ foreach (string unsafeHost in unsafeHosts) {
+ try {
+ UntrustedWebRequest.Request(new Uri(unsafeHost));
+ Assert.Fail("ArgumentException expected but none thrown.");
+ } catch (ArgumentException) {
+ // expected exception caught.
+ }
+ }
+ }
+
+ [Test]
+ public void Whitelist() {
+ UntrustedWebRequest.WhitelistHosts.Add("localhost");
+ // if this works, then we'll be waiting around for localhost to not respond
+ // for a while unless we take the timeout to zero.
+ UntrustedWebRequest.Timeout = TimeSpan.Zero; // will be reset in TearDown method
+ try {
+ UntrustedWebRequest.Request(new Uri("http://localhost:1234"));
+ // We're verifying that an ArgumentException is not thrown
+ // since we requested localhost to be allowed.
+ } catch (WebException) {
+ // It's ok, we're not expecting the request to succeed.
+ }
+ }
+
+ [Test]
+ public void WhitelistRegex() {
+ UntrustedWebRequest.WhitelistHostsRegex.Add(new Regex(@"^127\.\d+\.\d+\.\d+$"));
+ // if this works, then we'll be waiting around for localhost to not respond
+ // for a while unless we take the timeout to zero.
+ UntrustedWebRequest.Timeout = TimeSpan.Zero; // will be reset in TearDown method
+ try {
+ UntrustedWebRequest.Request(new Uri("http://127.0.0.1:1234"));
+ // We're verifying that an ArgumentException is not thrown
+ // since we requested localhost to be allowed.
+ } catch (WebException) {
+ // It's ok, we're not expecting the request to succeed.
+ }
+ }
+
+ [Test, ExpectedException(typeof(ArgumentException))]
+ public void Blacklist() {
+ UntrustedWebRequest.BlacklistHosts.Add("www.microsoft.com");
+ UntrustedWebRequest.Request(new Uri("http://WWW.MICROSOFT.COM"));
+ }
+
+ [Test, ExpectedException(typeof(ArgumentException))]
+ public void BlacklistRegex() {
+ UntrustedWebRequest.BlacklistHostsRegex.Add(new Regex(@"\Wmicrosoft.com$"));
+ UntrustedWebRequest.Request(new Uri("http://WWW.MICROSOFT.COM"));
+ }
+ }
+}
diff --git a/src/DotNetOpenId.Test/UriIdentifierTests.cs b/src/DotNetOpenId.Test/UriIdentifierTests.cs
index c8fcf6a..cd42d48 100644
--- a/src/DotNetOpenId.Test/UriIdentifierTests.cs
+++ b/src/DotNetOpenId.Test/UriIdentifierTests.cs
@@ -13,6 +13,12 @@ namespace DotNetOpenId.Test {
string relativeUri = "host/path";
string badUri = "som%-)830w8vf/?.<>,ewackedURI";
+ [SetUp]
+ public void SetUp() {
+ if (!UntrustedWebRequest.WhitelistHosts.Contains("localhost"))
+ UntrustedWebRequest.WhitelistHosts.Add("localhost");
+ }
+
[Test, ExpectedException(typeof(ArgumentNullException))]
public void CtorNullUri() {
new UriIdentifier((Uri)null);
@@ -84,8 +90,7 @@ namespace DotNetOpenId.Test {
Assert.AreEqual(expectedLocalId, se.ProviderLocalIdentifier);
Assert.AreEqual(expectSreg ? 2 : 1, se.ProviderSupportedServiceTypeUris.Length);
Assert.IsTrue(Array.IndexOf(se.ProviderSupportedServiceTypeUris, protocol.ClaimedIdentifierServiceTypeURI)>=0);
- if (expectSreg)
- Assert.IsTrue(Array.IndexOf(se.ProviderSupportedServiceTypeUris, Constants.TypeUri) >= 0);
+ Assert.AreEqual(expectSreg, se.IsExtensionSupported(new ClaimsRequest()));
}
void discoverXrds(string page, ProtocolVersion version, Identifier expectedLocalId) {
discover("/xrdsdiscovery/" + page + ".aspx", version, expectedLocalId, true, false);
diff --git a/src/DotNetOpenId.Test/UtilTest.cs b/src/DotNetOpenId.Test/UtilTest.cs
index 2f995c5..f6a3dbe 100644
--- a/src/DotNetOpenId.Test/UtilTest.cs
+++ b/src/DotNetOpenId.Test/UtilTest.cs
@@ -24,5 +24,15 @@ namespace DotNetOpenId.Test {
IDictionary<string, string> dict = Util.NameValueCollectionToDictionary(nvc);
Assert.IsTrue(dict["a"] == "b");
}
+
+ [Test]
+ public void NameValueCollectionToDictionaryNull() {
+ Assert.IsNull(Util.NameValueCollectionToDictionary(null));
+ }
+
+ [Test]
+ public void DictionaryToNameValueCollectionNull() {
+ Assert.IsNull(Util.DictionaryToNameValueCollection(null));
+ }
}
}
diff --git a/src/DotNetOpenId.TestWeb/ProviderEndpoint.aspx.cs b/src/DotNetOpenId.TestWeb/ProviderEndpoint.aspx.cs
index d75b923..098a812 100644
--- a/src/DotNetOpenId.TestWeb/ProviderEndpoint.aspx.cs
+++ b/src/DotNetOpenId.TestWeb/ProviderEndpoint.aspx.cs
@@ -1,18 +1,9 @@
using System;
-using System.Collections;
-using System.Configuration;
-using System.Data;
-using System.Web;
-using System.Web.Security;
-using System.Web.UI;
-using System.Web.UI.HtmlControls;
-using System.Web.UI.WebControls;
-using System.Web.UI.WebControls.WebParts;
-using System.Collections.Specialized;
using System.Collections.Generic;
using DotNetOpenId.Extensions.AttributeExchange;
using DotNetOpenId.Extensions.SimpleRegistration;
using SregDemandLevel = DotNetOpenId.Extensions.SimpleRegistration.DemandLevel;
+using DotNetOpenId.Extensions.ProviderAuthenticationPolicy;
using System.Globalization;
public partial class ProviderEndpoint : System.Web.UI.Page {
@@ -32,11 +23,13 @@ public partial class ProviderEndpoint : System.Web.UI.Page {
void respondToExtensions(DotNetOpenId.Provider.IRequest request, TestSupport.Scenarios scenario) {
var sregRequest = request.GetExtension<ClaimsRequest>();
- var sregResponse = new ClaimsResponse();
+ 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) {
@@ -61,6 +54,14 @@ public partial class ProviderEndpoint : System.Web.UI.Page {
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) {
@@ -96,6 +97,7 @@ public partial class ProviderEndpoint : System.Web.UI.Page {
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) {
diff --git a/src/DotNetOpenId.TestWeb/xrdsdiscovery/xrds10.aspx b/src/DotNetOpenId.TestWeb/xrdsdiscovery/xrds10.aspx
index 1b1af80..89cbd88 100644
--- a/src/DotNetOpenId.TestWeb/xrdsdiscovery/xrds10.aspx
+++ b/src/DotNetOpenId.TestWeb/xrdsdiscovery/xrds10.aspx
@@ -6,6 +6,7 @@
<XRD>
<Service priority="10">
<Type>http://openid.net/signon/1.0</Type>
+ <!-- this next sreg one is deliberately an unofficial (but supported) sreg/1.0 typeUri, so we test it. -->
<Type>http://openid.net/sreg/1.0</Type>
<URI>http://a/b</URI>
</Service>
diff --git a/src/DotNetOpenId.TestWeb/xrdsdiscovery/xrds1020.aspx b/src/DotNetOpenId.TestWeb/xrdsdiscovery/xrds1020.aspx
index 067aa8a..acd982a 100644
--- a/src/DotNetOpenId.TestWeb/xrdsdiscovery/xrds1020.aspx
+++ b/src/DotNetOpenId.TestWeb/xrdsdiscovery/xrds1020.aspx
@@ -6,12 +6,12 @@
<XRD>
<Service priority="10">
<Type>http://openid.net/signon/1.0</Type>
- <Type>http://openid.net/sreg/1.0</Type>
+ <Type>http://openid.net/extensions/sreg/1.1</Type>
<URI>http://a/b</URI>
</Service>
<Service priority="20">
<Type>http://specs.openid.net/auth/2.0/signon</Type>
- <Type>http://openid.net/sreg/1.0</Type>
+ <Type>http://openid.net/extensions/sreg/1.1</Type>
<URI>http://c/d</URI>
</Service>
</XRD>
diff --git a/src/DotNetOpenId.TestWeb/xrdsdiscovery/xrds11.aspx b/src/DotNetOpenId.TestWeb/xrdsdiscovery/xrds11.aspx
index fd522b4..7a757b2 100644
--- a/src/DotNetOpenId.TestWeb/xrdsdiscovery/xrds11.aspx
+++ b/src/DotNetOpenId.TestWeb/xrdsdiscovery/xrds11.aspx
@@ -6,7 +6,7 @@
<XRD>
<Service priority="10">
<Type>http://openid.net/signon/1.1</Type>
- <Type>http://openid.net/sreg/1.0</Type>
+ <Type>http://openid.net/extensions/sreg/1.1</Type>
<URI>http://a/b</URI>
</Service>
</XRD>
diff --git a/src/DotNetOpenId.TestWeb/xrdsdiscovery/xrds20.aspx b/src/DotNetOpenId.TestWeb/xrdsdiscovery/xrds20.aspx
index b31a47a..24a832b 100644
--- a/src/DotNetOpenId.TestWeb/xrdsdiscovery/xrds20.aspx
+++ b/src/DotNetOpenId.TestWeb/xrdsdiscovery/xrds20.aspx
@@ -6,7 +6,7 @@
<XRD>
<Service priority="10">
<Type>http://specs.openid.net/auth/2.0/signon</Type>
- <Type>http://openid.net/sreg/1.0</Type>
+ <Type>http://openid.net/extensions/sreg/1.1</Type>
<URI>http://a/b</URI>
</Service>
</XRD>
diff --git a/src/DotNetOpenId.TestWeb/xrdsdiscovery/xrds2010a.aspx b/src/DotNetOpenId.TestWeb/xrdsdiscovery/xrds2010a.aspx
index efac545..35f4de7 100644
--- a/src/DotNetOpenId.TestWeb/xrdsdiscovery/xrds2010a.aspx
+++ b/src/DotNetOpenId.TestWeb/xrdsdiscovery/xrds2010a.aspx
@@ -6,12 +6,12 @@
<XRD>
<Service priority="10">
<Type>http://specs.openid.net/auth/2.0/signon</Type>
- <Type>http://openid.net/sreg/1.0</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/sreg/1.0</Type>
+ <Type>http://openid.net/extensions/sreg/1.1</Type>
<URI>http://c/d</URI>
</Service>
</XRD>
diff --git a/src/DotNetOpenId.TestWeb/xrdsdiscovery/xrds2010b.aspx b/src/DotNetOpenId.TestWeb/xrdsdiscovery/xrds2010b.aspx
index c853092..aa8d20a 100644
--- a/src/DotNetOpenId.TestWeb/xrdsdiscovery/xrds2010b.aspx
+++ b/src/DotNetOpenId.TestWeb/xrdsdiscovery/xrds2010b.aspx
@@ -6,12 +6,12 @@
<XRD>
<Service priority="20">
<Type>http://openid.net/signon/1.0</Type>
- <Type>http://openid.net/sreg/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/sreg/1.0</Type>
+ <Type>http://openid.net/extensions/sreg/1.1</Type>
<URI>http://a/b</URI>
</Service>
</XRD>
diff --git a/src/DotNetOpenId.sln b/src/DotNetOpenId.sln
index a98a01d..e3371ce 100644
--- a/src/DotNetOpenId.sln
+++ b/src/DotNetOpenId.sln
@@ -3,10 +3,6 @@ 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\openid-attribute-exchange-1_0.html = ..\doc\openid-attribute-exchange-1_0.html
- ..\doc\openid-authentication-1_1.html = ..\doc\openid-authentication-1_1.html
- ..\doc\openid-authentication-2_0.html = ..\doc\openid-authentication-2_0.html
- ..\doc\openid-simple-registration-extension-1_0.html = ..\doc\openid-simple-registration-extension-1_0.html
..\doc\README.html = ..\doc\README.html
..\doc\WebFarms.htm = ..\doc\WebFarms.htm
EndProjectSection
diff --git a/src/DotNetOpenId/DotNetOpenId.csproj b/src/DotNetOpenId/DotNetOpenId.csproj
index 74c4a2c..15ab091 100644
--- a/src/DotNetOpenId/DotNetOpenId.csproj
+++ b/src/DotNetOpenId/DotNetOpenId.csproj
@@ -59,6 +59,8 @@
<Compile Include="Association.cs" />
<Compile Include="AssociationMemoryStore.cs" />
<Compile Include="Associations.cs" />
+ <Compile Include="Provider\SigningMessageEncoder.cs" />
+ <Compile Include="RelyingParty\IndirectMessageRequest.cs" />
<Compile Include="ExtensionArgumentsManager.cs" />
<Compile Include="Extensions\AliasManager.cs" />
<Compile Include="Extensions\AttributeExchange\Constants.cs" />
@@ -66,6 +68,11 @@
<Compile Include="Extensions\AttributeExchange\AttributeRequest.cs" />
<Compile Include="Extensions\AttributeExchange\AttributeValues.cs" />
<Compile Include="Extensions\AttributeExchange\WellKnownAttributes.cs" />
+ <Compile Include="Extensions\ProviderAuthenticationPolicy\AuthenticationPolicies.cs" />
+ <Compile Include="Extensions\ProviderAuthenticationPolicy\Constants.cs" />
+ <Compile Include="Extensions\ProviderAuthenticationPolicy\NistAssuranceLevel.cs" />
+ <Compile Include="Extensions\ProviderAuthenticationPolicy\PolicyRequest.cs" />
+ <Compile Include="Extensions\ProviderAuthenticationPolicy\PolicyResponse.cs" />
<Compile Include="Extensions\SimpleRegistration\Constants.cs" />
<Compile Include="Extensions\AttributeExchange\FetchRequest.cs" />
<Compile Include="Extensions\AttributeExchange\StoreRequest.cs" />
@@ -73,6 +80,7 @@
<Compile Include="Extensions\IExtension.cs" />
<Compile Include="HmacSha256Association.cs" />
<Compile Include="Identifier.cs" />
+ <Compile Include="IResponse.cs" />
<Compile Include="Loggers\TraceLogger.cs" />
<Compile Include="Loggers\ILog.cs" />
<Compile Include="Loggers\Log4NetLogger.cs" />
@@ -87,6 +95,7 @@
<Compile Include="RelyingParty\CheckAuthRequest.cs" />
<Compile Include="RelyingParty\CheckAuthResponse.cs" />
<Compile Include="RelyingParty\ApplicationMemoryStore.cs" />
+ <Compile Include="RelyingParty\IProviderEndpoint.cs" />
<Compile Include="RelyingParty\OpenIdMobileTextBox.cs" />
<Compile Include="RelyingParty\DirectRequest.cs" />
<Compile Include="RelyingParty\DirectResponse.cs" />
@@ -108,16 +117,15 @@
<Compile Include="OpenIdException.cs" />
<Compile Include="ProtocolMessages.cs" />
<Compile Include="Provider\EncodableResponse.cs" />
- <Compile Include="Provider\Encoder.cs" />
+ <Compile Include="MessageEncoder.cs" />
<Compile Include="Provider\FaultyRequest.cs" />
<Compile Include="Provider\IAuthenticationRequest.cs" />
<Compile Include="Provider\IdentityEndpoint.cs" />
<Compile Include="Provider\IRequest.cs" />
- <Compile Include="Provider\IResponse.cs" />
<Compile Include="Provider\OpenIdProvider.cs" />
<Compile Include="Provider\ProviderSession.cs" />
<Compile Include="Provider\Request.cs" />
- <Compile Include="Provider\Response.cs" />
+ <Compile Include="Response.cs" />
<Compile Include="Provider\ProviderEndpoint.cs" />
<Compile Include="Extensions\SimpleRegistration\Gender.cs" />
<Compile Include="Provider\AssociatedRequest.cs" />
@@ -142,7 +150,7 @@
<Compile Include="Provider\AssociateRequest.cs" />
<Compile Include="Provider\CheckAuthRequest.cs" />
<Compile Include="Provider\CheckIdRequest.cs" />
- <Compile Include="Provider\IEncodable.cs" />
+ <Compile Include="IEncodable.cs" />
<Compile Include="Provider\Signatory.cs" />
<Compile Include="XrdsPublisher.cs" />
<Compile Include="Strings.Designer.cs">
@@ -184,4 +192,4 @@
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<Import Project="..\..\tools\DotNetOpenId.Versioning.targets" />
-</Project> \ No newline at end of file
+</Project>
diff --git a/src/DotNetOpenId/ExtensionArgumentsManager.cs b/src/DotNetOpenId/ExtensionArgumentsManager.cs
index 9bcdb73..4f8cdc2 100644
--- a/src/DotNetOpenId/ExtensionArgumentsManager.cs
+++ b/src/DotNetOpenId/ExtensionArgumentsManager.cs
@@ -22,6 +22,7 @@ namespace DotNetOpenId {
/// </summary>
static readonly Dictionary<string, string> typeUriToAliasAffinity = new Dictionary<string, string> {
{ Extensions.SimpleRegistration.Constants.sreg_ns, Extensions.SimpleRegistration.Constants.sreg_compatibility_alias },
+ { Extensions.ProviderAuthenticationPolicy.Constants.TypeUri, Extensions.ProviderAuthenticationPolicy.Constants.pape_compatibility_alias },
};
private ExtensionArgumentsManager() { }
@@ -113,6 +114,10 @@ namespace DotNetOpenId {
}
}
+ /// <summary>
+ /// Gets the fields carried by a given OpenId extension.
+ /// </summary>
+ /// <returns>The fields included in the given extension, or null if the extension is not present.</returns>
public IDictionary<string, string> GetExtensionArguments(string extensionTypeUri) {
if (!isReadMode) throw new InvalidOperationException();
if (string.IsNullOrEmpty(extensionTypeUri)) throw new ArgumentNullException("extensionTypeUri");
diff --git a/src/DotNetOpenId/Extensions/AliasManager.cs b/src/DotNetOpenId/Extensions/AliasManager.cs
index 52308d5..f4bbb04 100644
--- a/src/DotNetOpenId/Extensions/AliasManager.cs
+++ b/src/DotNetOpenId/Extensions/AliasManager.cs
@@ -37,6 +37,61 @@ namespace DotNetOpenId.Extensions {
aliasToTypeUriMap.Add(alias, typeUri);
typeUriToAliasMap.Add(typeUri, alias);
}
+ /// <summary>
+ /// Takes a sequence of type URIs and assigns aliases for all of them.
+ /// </summary>
+ /// <param name="typeUris">The type URIs to create aliases for.</param>
+ /// <param name="preferredTypeUriToAliases">An optional dictionary of URI/alias pairs that suggest preferred aliases to use if available for certain type URIs.</param>
+ public void AssignAliases(IEnumerable<string> typeUris, IDictionary<string, string> preferredTypeUriToAliases) {
+ // First go through the actually used type URIs and see which ones have matching preferred aliases.
+ if (preferredTypeUriToAliases != null) {
+ foreach (string typeUri in typeUris) {
+ if (typeUriToAliasMap.ContainsKey(typeUri)) {
+ // this Type URI is already mapped to an alias.
+ continue;
+ }
+
+ string preferredAlias;
+ if (preferredTypeUriToAliases.TryGetValue(typeUri, out preferredAlias) && !IsAliasUsed(preferredAlias)) {
+ SetAlias(preferredAlias, typeUri);
+ }
+ }
+ }
+
+ // Now go through the whole list again and assign whatever is left now that the preferred ones
+ // have gotten their picks where available.
+ foreach (string typeUri in typeUris) {
+ if (typeUriToAliasMap.ContainsKey(typeUri)) {
+ // this Type URI is already mapped to an alias.
+ continue;
+ }
+
+ assignNewAlias(typeUri);
+ }
+ }
+ /// <summary>
+ /// Sets up aliases for any Type URIs in a dictionary that do not yet have aliases defined,
+ /// and where the given preferred alias is still available.
+ /// </summary>
+ /// <param name="preferredTypeUriToAliases">A dictionary of type URI keys and alias values.</param>
+ public void SetPreferredAliasesWhereNotSet(IDictionary<string, string> preferredTypeUriToAliases) {
+ if (preferredTypeUriToAliases == null) throw new ArgumentNullException("preferredTypeUriToAliases");
+
+ foreach (var pair in preferredTypeUriToAliases) {
+ if (typeUriToAliasMap.ContainsKey(pair.Key)) {
+ // type URI is already mapped
+ continue;
+ }
+
+ if (aliasToTypeUriMap.ContainsKey(pair.Value)) {
+ // alias is already mapped
+ continue;
+ }
+
+ // The type URI and alias are as yet unset, so go ahead and assign them.
+ SetAlias(pair.Value, pair.Key);
+ }
+ }
/// <summary>
/// Gets the Type Uri encoded by a given alias.
@@ -57,6 +112,11 @@ namespace DotNetOpenId.Extensions {
public IEnumerable<string> Aliases {
get { return aliasToTypeUriMap.Keys; }
}
+ /// <summary>
+ /// Returns a value indicating whether an alias has already been assigned to a type URI.
+ /// </summary>
+ /// <param name="alias">The alias in question.</param>
+ /// <returns>True if the alias has already been assigned. False otherwise.</returns>
public bool IsAliasUsed(string alias) {
if (string.IsNullOrEmpty(alias)) throw new ArgumentNullException("alias");
return aliasToTypeUriMap.ContainsKey(alias);
diff --git a/src/DotNetOpenId/Extensions/AttributeExchange/FetchRequest.cs b/src/DotNetOpenId/Extensions/AttributeExchange/FetchRequest.cs
index 6e09914..1b64868 100644
--- a/src/DotNetOpenId/Extensions/AttributeExchange/FetchRequest.cs
+++ b/src/DotNetOpenId/Extensions/AttributeExchange/FetchRequest.cs
@@ -53,6 +53,9 @@ namespace DotNetOpenId.Extensions.AttributeExchange {
#region IExtensionRequest Members
string IExtension.TypeUri { get { return Constants.TypeUri; } }
+ IEnumerable<string> IExtension.AdditionalSupportedTypeUris {
+ get { return new string[0]; }
+ }
IDictionary<string, string> IExtensionRequest.Serialize(RelyingParty.IAuthenticationRequest authenticationRequest) {
var fields = new Dictionary<string, string> {
@@ -84,7 +87,7 @@ namespace DotNetOpenId.Extensions.AttributeExchange {
return fields;
}
- bool IExtensionRequest.Deserialize(IDictionary<string, string> fields, DotNetOpenId.Provider.IRequest request) {
+ bool IExtensionRequest.Deserialize(IDictionary<string, string> fields, DotNetOpenId.Provider.IRequest request, string typeUri) {
if (fields == null) return false;
string mode;
fields.TryGetValue("mode", out mode);
@@ -111,11 +114,11 @@ namespace DotNetOpenId.Extensions.AttributeExchange {
}
AliasManager aliasManager = new AliasManager();
foreach (var alias in allAliases) {
- string typeUri;
- if (fields.TryGetValue("type." + alias, out typeUri)) {
- aliasManager.SetAlias(alias, typeUri);
+ string attributeTypeUri;
+ if (fields.TryGetValue("type." + alias, out attributeTypeUri)) {
+ aliasManager.SetAlias(alias, attributeTypeUri);
AttributeRequest att = new AttributeRequest {
- TypeUri = typeUri,
+ TypeUri = attributeTypeUri,
IsRequired = requiredAliases.Contains(alias),
};
string countString;
diff --git a/src/DotNetOpenId/Extensions/AttributeExchange/FetchResponse.cs b/src/DotNetOpenId/Extensions/AttributeExchange/FetchResponse.cs
index ddd3087..3878d2c 100644
--- a/src/DotNetOpenId/Extensions/AttributeExchange/FetchResponse.cs
+++ b/src/DotNetOpenId/Extensions/AttributeExchange/FetchResponse.cs
@@ -55,6 +55,9 @@ namespace DotNetOpenId.Extensions.AttributeExchange {
#region IExtensionResponse Members
string IExtension.TypeUri { get { return Constants.TypeUri; } }
+ IEnumerable<string> IExtension.AdditionalSupportedTypeUris {
+ get { return new string[0]; }
+ }
IDictionary<string, string> IExtensionResponse.Serialize(Provider.IRequest authenticationRequest) {
var fields = new Dictionary<string, string> {
@@ -87,7 +90,7 @@ namespace DotNetOpenId.Extensions.AttributeExchange {
}
}
- bool IExtensionResponse.Deserialize(IDictionary<string, string> fields, IAuthenticationResponse response) {
+ bool IExtensionResponse.Deserialize(IDictionary<string, string> fields, IAuthenticationResponse response, string typeUri) {
if (fields == null) return false;
string mode;
fields.TryGetValue("mode", out mode);
diff --git a/src/DotNetOpenId/Extensions/AttributeExchange/StoreRequest.cs b/src/DotNetOpenId/Extensions/AttributeExchange/StoreRequest.cs
index 3df74a0..6b5ce2b 100644
--- a/src/DotNetOpenId/Extensions/AttributeExchange/StoreRequest.cs
+++ b/src/DotNetOpenId/Extensions/AttributeExchange/StoreRequest.cs
@@ -53,6 +53,9 @@ namespace DotNetOpenId.Extensions.AttributeExchange {
#region IExtensionRequest Members
string IExtension.TypeUri { get { return Constants.TypeUri; } }
+ IEnumerable<string> IExtension.AdditionalSupportedTypeUris {
+ get { return new string[0]; }
+ }
IDictionary<string, string> IExtensionRequest.Serialize(RelyingParty.IAuthenticationRequest authenticationRequest) {
var fields = new Dictionary<string, string> {
@@ -64,7 +67,7 @@ namespace DotNetOpenId.Extensions.AttributeExchange {
return fields;
}
- bool IExtensionRequest.Deserialize(IDictionary<string, string> fields, DotNetOpenId.Provider.IRequest request) {
+ bool IExtensionRequest.Deserialize(IDictionary<string, string> fields, DotNetOpenId.Provider.IRequest request, string typeUri) {
if (fields == null) return false;
string mode;
fields.TryGetValue("mode", out mode);
diff --git a/src/DotNetOpenId/Extensions/AttributeExchange/StoreResponse.cs b/src/DotNetOpenId/Extensions/AttributeExchange/StoreResponse.cs
index a4e9cf1..acc0d6b 100644
--- a/src/DotNetOpenId/Extensions/AttributeExchange/StoreResponse.cs
+++ b/src/DotNetOpenId/Extensions/AttributeExchange/StoreResponse.cs
@@ -24,6 +24,9 @@ namespace DotNetOpenId.Extensions.AttributeExchange {
#region IExtensionResponse Members
string IExtension.TypeUri { get { return Constants.TypeUri; } }
+ IEnumerable<string> IExtension.AdditionalSupportedTypeUris {
+ get { return new string[0]; }
+ }
IDictionary<string, string> IExtensionResponse.Serialize(Provider.IRequest authenticationRequest) {
var fields = new Dictionary<string, string> {
@@ -35,7 +38,7 @@ namespace DotNetOpenId.Extensions.AttributeExchange {
return fields;
}
- bool IExtensionResponse.Deserialize(IDictionary<string, string> fields, IAuthenticationResponse response) {
+ bool IExtensionResponse.Deserialize(IDictionary<string, string> fields, IAuthenticationResponse response, string typeUri) {
if (fields == null) return false;
string mode;
if (!fields.TryGetValue("mode", out mode)) return false;
diff --git a/src/DotNetOpenId/Extensions/IExtension.cs b/src/DotNetOpenId/Extensions/IExtension.cs
index 2450a8a..e43565a 100644
--- a/src/DotNetOpenId/Extensions/IExtension.cs
+++ b/src/DotNetOpenId/Extensions/IExtension.cs
@@ -11,6 +11,22 @@ namespace DotNetOpenId.Extensions {
/// Gets the TypeURI the extension uses in the OpenID protocol and in XRDS advertisements.
/// </summary>
string TypeUri { get; }
+ /// <summary>
+ /// Additional TypeURIs that are supported by this extension, in preferred order.
+ /// May be empty if none other than <see cref="TypeUri"/> is supported, but
+ /// should not be null.
+ /// </summary>
+ /// <remarks>
+ /// Useful for reading in messages with an older version of an extension.
+ /// The value in the <see cref="TypeUri"/> property is always checked before
+ /// trying this list.
+ /// If you do support multiple versions of an extension using this method,
+ /// consider adding a CreateResponse method to your request extension class
+ /// so that the response can have the context it needs to remain compatible
+ /// given the version of the extension in the request message.
+ /// The <see cref="SimpleRegistration.ClaimsRequest.CreateResponse"/> for an example.
+ /// </remarks>
+ IEnumerable<string> AdditionalSupportedTypeUris { get; }
}
/// <summary>
@@ -26,8 +42,14 @@ namespace DotNetOpenId.Extensions {
/// <summary>
/// Reads the extension information on an authentication request to the provider.
/// </summary>
- /// <returns>True if the extension found any of its parameters in the request, false otherwise.</returns>
- bool Deserialize(IDictionary<string, string> fields, Provider.IRequest request);
+ /// <param name="fields">The fields belonging to the extension.</param>
+ /// <param name="request">The incoming OpenID request carrying the extension.</param>
+ /// <param name="typeUri">The actual extension TypeUri that was recognized in the message.</param>
+ /// <returns>
+ /// True if the extension found a valid set of recognized parameters in the request,
+ /// false otherwise.
+ /// </returns>
+ bool Deserialize(IDictionary<string, string> fields, Provider.IRequest request, string typeUri);
}
/// <summary>
@@ -43,7 +65,13 @@ namespace DotNetOpenId.Extensions {
/// <summary>
/// Reads a Provider's response for extension values.
/// </summary>
- /// <returns>True if the extension found any of its parameters in the response.</returns>
- bool Deserialize(IDictionary<string, string> fields, RelyingParty.IAuthenticationResponse response);
+ /// <param name="fields">The fields belonging to the extension.</param>
+ /// <param name="response">The incoming OpenID response carrying the extension.</param>
+ /// <param name="typeUri">The actual extension TypeUri that was recognized in the message.</param>
+ /// <returns>
+ /// True if the extension found a valid set of recognized parameters in the response,
+ /// false otherwise.
+ /// </returns>
+ bool Deserialize(IDictionary<string, string> fields, RelyingParty.IAuthenticationResponse response, string typeUri);
}
}
diff --git a/src/DotNetOpenId/Extensions/ProviderAuthenticationPolicy/AuthenticationPolicies.cs b/src/DotNetOpenId/Extensions/ProviderAuthenticationPolicy/AuthenticationPolicies.cs
new file mode 100644
index 0000000..f505a9b
--- /dev/null
+++ b/src/DotNetOpenId/Extensions/ProviderAuthenticationPolicy/AuthenticationPolicies.cs
@@ -0,0 +1,40 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace DotNetOpenId.Extensions.ProviderAuthenticationPolicy {
+ /// <summary>
+ /// Well-known authentication policies defined in the PAPE extension spec or by a recognized
+ /// standards body.
+ /// </summary>
+ /// <remarks>
+ /// This is a class of constants rather than a flags enum because policies may be
+ /// freely defined and used by anyone, just by using a new Uri.
+ /// </remarks>
+ public static class AuthenticationPolicies {
+ /// <summary>
+ /// Used in a PAPE response to indicate that no PAPE authentication policies could be satisfied.
+ /// </summary>
+ /// <remarks>
+ /// Used internally by the PAPE extension, so that users don't have to know about it.
+ /// </remarks>
+ internal const string None = "http://schemas.openid.net/pape/policies/2007/06/none";
+ /// <summary>
+ /// An authentication mechanism where the End User does not provide a shared secret to a party potentially under the control of the Relying Party. (Note that the potentially malicious Relying Party controls where the User-Agent is redirected to and thus may not send it to the End User's actual OpenID Provider).
+ /// </summary>
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Phishing")]
+ public const string PhishingResistant = "http://schemas.openid.net/pape/policies/2007/06/phishing-resistant";
+ /// <summary>
+ /// An authentication mechanism where the End User authenticates to the OpenID Provider by providing over one authentication factor. Common authentication factors are something you know, something you have, and something you are. An example would be authentication using a password and a software token or digital certificate.
+ /// </summary>
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Multi")]
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "MultiFactor")]
+ public const string MultiFactor = "http://schemas.openid.net/pape/policies/2007/06/multi-factor";
+ /// <summary>
+ /// An authentication mechanism where the End User authenticates to the OpenID Provider by providing over one authentication factor where at least one of the factors is a physical factor such as a hardware device or biometric. Common authentication factors are something you know, something you have, and something you are. This policy also implies the Multi-Factor Authentication policy (http://schemas.openid.net/pape/policies/2007/06/multi-factor) and both policies MAY BE specified in conjunction without conflict. An example would be authentication using a password and a hardware token.
+ /// </summary>
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "MultiFactor")]
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Multi")]
+ public const string PhysicalMultiFactor = "http://schemas.openid.net/pape/policies/2007/06/multi-factor-physical";
+ }
+}
diff --git a/src/DotNetOpenId/Extensions/ProviderAuthenticationPolicy/Constants.cs b/src/DotNetOpenId/Extensions/ProviderAuthenticationPolicy/Constants.cs
new file mode 100644
index 0000000..395ea36
--- /dev/null
+++ b/src/DotNetOpenId/Extensions/ProviderAuthenticationPolicy/Constants.cs
@@ -0,0 +1,90 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace DotNetOpenId.Extensions.ProviderAuthenticationPolicy {
+ /// <summary>
+ /// OpenID Provider Authentication Policy extension constants.
+ /// </summary>
+ static class Constants {
+ /// <summary>
+ /// The namespace used by this extension in messages.
+ /// </summary>
+ internal const string TypeUri = "http://specs.openid.net/extensions/pape/1.0";
+ /// <summary>
+ /// The namespace alias to use for OpenID 1.x interop, where aliases are not defined in the message.
+ /// </summary>
+ internal const string pape_compatibility_alias = "pape";
+ /// <summary>
+ /// The string to prepend on an Auth Level Type alias definition.
+ /// </summary>
+ internal const string AuthLevelNamespaceDeclarationPrefix = "auth_level.ns.";
+
+ internal static class AuthenticationLevels {
+ internal static readonly IDictionary<string, string> PreferredTypeUriToAliasMap = new Dictionary<string, string> {
+ { NistTypeUri, nist_compatibility_alias },
+ };
+
+ internal const string nist_compatibility_alias = "nist";
+ internal const string NistTypeUri = "http://csrc.nist.gov/publications/nistpubs/800-63/SP800-63V1_0_2.pdf";
+ }
+
+ /// <summary>
+ /// Parameters to be included with PAPE requests.
+ /// </summary>
+ internal static class RequestParameters {
+ /// <summary>
+ /// Optional. If the End User has not actively authenticated to the OP within the number of seconds specified in a manner fitting the requested policies, the OP SHOULD authenticate the End User for this request.
+ /// </summary>
+ /// <value>Integer value greater than or equal to zero in seconds.</value>
+ /// <remarks>
+ /// The OP should realize that not adhering to the request for re-authentication most likely means that the End User will not be allowed access to the services provided by the RP. If this parameter is absent in the request, the OP should authenticate the user at its own discretion.
+ /// </remarks>
+ internal const string MaxAuthAge = "max_auth_age";
+ /// <summary>
+ /// Zero or more authentication policy URIs that the OP SHOULD conform to when authenticating the user. If multiple policies are requested, the OP SHOULD satisfy as many as it can.
+ /// </summary>
+ /// <value>Space separated list of authentication policy URIs.</value>
+ /// <remarks>
+ /// If no policies are requested, the RP may be interested in other information such as the authentication age.
+ /// </remarks>
+ internal const string PreferredAuthPolicies = "preferred_auth_policies";
+ /// <summary>
+ /// The space separated list of the name spaces of the custom Assurance Level that RP requests, in the order of its preference.
+ /// </summary>
+ internal const string PreferredAuthLevelTypes = "preferred_auth_level_types";
+ }
+ /// <summary>
+ /// Parameters to be included with PAPE responses.
+ /// </summary>
+ internal static class ResponseParameters {
+ /// <summary>
+ /// One or more authentication policy URIs that the OP conformed to when authenticating the End User.
+ /// </summary>
+ /// <value>Space separated list of authentication policy URIs.</value>
+ /// <remarks>
+ /// If no policies were met though the OP wishes to convey other information in the response, this parameter MUST be included with the value of "none".
+ /// </remarks>
+ internal const string AuthPolicies = "auth_policies";
+ /// <summary>
+ /// Optional. The most recent timestamp when the End User has actively authenticated to the OP in a manner fitting the asserted policies.
+ /// </summary>
+ /// <value>
+ /// The timestamp MUST be formatted as specified in section 5.6 of [RFC3339] (Klyne, G. and C. Newman, “Date and Time on the Internet: Timestamps,” .), with the following restrictions:
+ /// * All times must be in the UTC timezone, indicated with a "Z".
+ /// * No fractional seconds are allowed
+ /// For example: 2005-05-15T17:11:51Z
+ /// </value>
+ /// <remarks>
+ /// If the RP's request included the "openid.max_auth_age" parameter then the OP MUST include "openid.auth_time" in its response. If "openid.max_auth_age" was not requested, the OP MAY choose to include "openid.auth_time" in its response.
+ /// </remarks>
+ internal const string AuthTime = "auth_time";
+ /// <summary>
+ /// The first part of a parameter name that gives the custom string value for
+ /// the assurance level. The second part of the parameter name is the alias for
+ /// that assurance level.
+ /// </summary>
+ internal const string AuthLevelAliasPrefix = "auth_level.";
+ }
+ }
+}
diff --git a/src/DotNetOpenId/Extensions/ProviderAuthenticationPolicy/NistAssuranceLevel.cs b/src/DotNetOpenId/Extensions/ProviderAuthenticationPolicy/NistAssuranceLevel.cs
new file mode 100644
index 0000000..6358294
--- /dev/null
+++ b/src/DotNetOpenId/Extensions/ProviderAuthenticationPolicy/NistAssuranceLevel.cs
@@ -0,0 +1,48 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace DotNetOpenId.Extensions.ProviderAuthenticationPolicy {
+ /// <summary>
+ /// Descriptions for NIST-defined levels of assurance that a credential
+ /// has not been compromised and therefore the extent to which an
+ /// authentication assertion can be trusted.
+ /// </summary>
+ /// <remarks>
+ /// One using this enum should review the following publication for details
+ /// before asserting or interpreting what these levels signify, notwithstanding
+ /// the brief summaries attached to each level in DotNetOpenId documentation.
+ /// http://csrc.nist.gov/publications/nistpubs/800-63/SP800-63V1_0_2.pdf
+ ///
+ /// See PAPE spec Appendix A.1.2 (NIST Assurance Levels) for high-level example classifications of authentication methods within the defined levels.
+ /// </remarks>
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Nist")]
+ public enum NistAssuranceLevel {
+ /// <summary>
+ /// Not an assurance level defined by NIST, but rather SHOULD be used to
+ /// signify that the OP recognizes the parameter and the End User
+ /// authentication did not meet the requirements of Level 1.
+ /// </summary>
+ InsufficientForLevel1 = 0,
+ /// <summary>
+ /// See this document for a thorough description:
+ /// http://csrc.nist.gov/publications/nistpubs/800-63/SP800-63V1_0_2.pdf
+ /// </summary>
+ Level1 = 1,
+ /// <summary>
+ /// See this document for a thorough description:
+ /// http://csrc.nist.gov/publications/nistpubs/800-63/SP800-63V1_0_2.pdf
+ /// </summary>
+ Level2 = 2,
+ /// <summary>
+ /// See this document for a thorough description:
+ /// http://csrc.nist.gov/publications/nistpubs/800-63/SP800-63V1_0_2.pdf
+ /// </summary>
+ Level3 = 3,
+ /// <summary>
+ /// See this document for a thorough description:
+ /// http://csrc.nist.gov/publications/nistpubs/800-63/SP800-63V1_0_2.pdf
+ /// </summary>
+ Level4 = 4,
+ }
+}
diff --git a/src/DotNetOpenId/Extensions/ProviderAuthenticationPolicy/PolicyRequest.cs b/src/DotNetOpenId/Extensions/ProviderAuthenticationPolicy/PolicyRequest.cs
new file mode 100644
index 0000000..eb78e1f
--- /dev/null
+++ b/src/DotNetOpenId/Extensions/ProviderAuthenticationPolicy/PolicyRequest.cs
@@ -0,0 +1,195 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Globalization;
+using System.Diagnostics;
+
+namespace DotNetOpenId.Extensions.ProviderAuthenticationPolicy {
+ /// <summary>
+ /// The PAPE request part of an OpenID Authentication request message.
+ /// </summary>
+ public sealed class PolicyRequest : IExtensionRequest {
+ /// <summary>
+ /// Instantiates a new <see cref="PolicyRequest"/>.
+ /// </summary>
+ public PolicyRequest() {
+ PreferredPolicies = new List<string>(1);
+ PreferredAuthLevelTypes = new List<string>(1);
+ }
+
+ /// <summary>
+ /// Optional. If the End User has not actively authenticated to the OP within the number of seconds specified in a manner fitting the requested policies, the OP SHOULD authenticate the End User for this request.
+ /// </summary>
+ /// <remarks>
+ /// The OP should realize that not adhering to the request for re-authentication most likely means that the End User will not be allowed access to the services provided by the RP. If this parameter is absent in the request, the OP should authenticate the user at its own discretion.
+ /// </remarks>
+ public TimeSpan? MaximumAuthenticationAge { get; set; }
+ /// <summary>
+ /// Zero or more authentication policy URIs that the OP SHOULD conform to when authenticating the user. If multiple policies are requested, the OP SHOULD satisfy as many as it can.
+ /// </summary>
+ /// <value>List of authentication policy URIs obtainable from the <see cref="AuthenticationPolicies"/> class or from a custom list.</value>
+ /// <remarks>
+ /// If no policies are requested, the RP may be interested in other information such as the authentication age.
+ /// </remarks>
+ public IList<string> PreferredPolicies { get; private set; }
+
+ /// <summary>
+ /// Zero or more name spaces of the custom Assurance Level the RP requests, in the order of its preference.
+ /// </summary>
+ public IList<string> PreferredAuthLevelTypes { get; private set; }
+
+ /// <summary>
+ /// Tests equality between two <see cref="PolicyRequest"/> instances.
+ /// </summary>
+ public override bool Equals(object obj) {
+ PolicyRequest other = obj as PolicyRequest;
+ if (other == null) return false;
+ if (MaximumAuthenticationAge != other.MaximumAuthenticationAge) return false;
+ if (PreferredPolicies.Count != other.PreferredPolicies.Count) return false;
+ foreach(string policy in PreferredPolicies) {
+ if (!other.PreferredPolicies.Contains(policy)) return false;
+ }
+ if (PreferredAuthLevelTypes.Count != other.PreferredAuthLevelTypes.Count) return false;
+ foreach (string authLevel in PreferredAuthLevelTypes) {
+ if (!other.PreferredAuthLevelTypes.Contains(authLevel)) return false;
+ }
+ return true;
+ }
+
+ /// <summary>
+ /// Gets a hash code for this object.
+ /// </summary>
+ public override int GetHashCode() {
+ return PreferredPolicies.GetHashCode();
+ }
+
+ #region IExtensionRequest Members
+
+ IDictionary<string, string> IExtensionRequest.Serialize(DotNetOpenId.RelyingParty.IAuthenticationRequest authenticationRequest) {
+ var fields = new Dictionary<string, string>();
+
+ if (MaximumAuthenticationAge.HasValue) {
+ fields.Add(Constants.RequestParameters.MaxAuthAge,
+ MaximumAuthenticationAge.Value.TotalSeconds.ToString(CultureInfo.InvariantCulture));
+ }
+
+ // Even if empty, this parameter is required as part of the request message.
+ fields.Add(Constants.RequestParameters.PreferredAuthPolicies, SerializePolicies(PreferredPolicies));
+
+ if (PreferredAuthLevelTypes.Count > 0) {
+ AliasManager authLevelAliases = new AliasManager();
+ authLevelAliases.AssignAliases(PreferredAuthLevelTypes, Constants.AuthenticationLevels.PreferredTypeUriToAliasMap);
+
+ // Add a definition for each Auth Level Type alias.
+ foreach (string alias in authLevelAliases.Aliases) {
+ fields.Add(Constants.AuthLevelNamespaceDeclarationPrefix + alias, authLevelAliases.ResolveAlias(alias));
+ }
+
+ // Now use the aliases for those type URIs to list a preferred order.
+ fields.Add(Constants.RequestParameters.PreferredAuthLevelTypes, SerializeAuthLevels(PreferredAuthLevelTypes, authLevelAliases));
+ }
+
+ return fields;
+ }
+
+ bool IExtensionRequest.Deserialize(IDictionary<string, string> fields, DotNetOpenId.Provider.IRequest request, string typeUri) {
+ if (fields == null) return false;
+ if (!fields.ContainsKey(Constants.RequestParameters.PreferredAuthPolicies)) return false;
+
+ string maxAuthAge;
+ MaximumAuthenticationAge = fields.TryGetValue(Constants.RequestParameters.MaxAuthAge, out maxAuthAge) ?
+ TimeSpan.FromSeconds(double.Parse(maxAuthAge, CultureInfo.InvariantCulture)) : (TimeSpan?)null;
+
+ PreferredPolicies.Clear();
+ string[] preferredPolicies = fields[Constants.RequestParameters.PreferredAuthPolicies].Split(' ');
+ foreach (string policy in preferredPolicies) {
+ if (policy.Length > 0)
+ PreferredPolicies.Add(policy);
+ }
+
+ PreferredAuthLevelTypes.Clear();
+ AliasManager authLevelAliases = FindIncomingAliases(fields);
+ string preferredAuthLevelAliases;
+ if (fields.TryGetValue(Constants.RequestParameters.PreferredAuthLevelTypes, out preferredAuthLevelAliases)) {
+ foreach (string authLevelAlias in preferredAuthLevelAliases.Split(' ')) {
+ if (authLevelAlias.Length == 0) continue;
+ PreferredAuthLevelTypes.Add(authLevelAliases.ResolveAlias(authLevelAlias));
+ }
+ }
+
+ return true;
+ }
+
+ #endregion
+
+ #region IExtension Members
+
+ string IExtension.TypeUri {
+ get { return Constants.TypeUri; }
+ }
+
+ IEnumerable<string> IExtension.AdditionalSupportedTypeUris {
+ get { return new string[0]; }
+ }
+
+ #endregion
+
+ static internal string SerializePolicies(IList<string> policies) {
+ return ConcatenateListOfElements(policies);
+ }
+
+ private static string SerializeAuthLevels(IList<string> preferredAuthLevelTypes, AliasManager aliases) {
+ var aliasList = new List<string>();
+ foreach (string typeUri in preferredAuthLevelTypes) {
+ aliasList.Add(aliases.GetAlias(typeUri));
+ }
+
+ return ConcatenateListOfElements(aliasList);
+ }
+
+ /// <summary>
+ /// Looks at the incoming fields and figures out what the aliases and name spaces for auth level types are.
+ /// </summary>
+ internal static AliasManager FindIncomingAliases(IDictionary<string, string> fields) {
+ AliasManager aliasManager = new AliasManager();
+
+ foreach (var pair in fields) {
+ if (!pair.Key.StartsWith(Constants.AuthLevelNamespaceDeclarationPrefix, StringComparison.Ordinal)) {
+ continue;
+ }
+
+ string alias = pair.Key.Substring(Constants.AuthLevelNamespaceDeclarationPrefix.Length);
+ aliasManager.SetAlias(alias, pair.Value);
+ }
+
+ aliasManager.SetPreferredAliasesWhereNotSet(Constants.AuthenticationLevels.PreferredTypeUriToAliasMap);
+
+ return aliasManager;
+ }
+
+ internal static string ConcatenateListOfElements(IList<string> values) {
+ Debug.Assert(values != null);
+ StringBuilder valuesList = new StringBuilder();
+ foreach (string value in GetUniqueItems(values)) {
+ if (value.Contains(" ")) {
+ throw new FormatException(string.Format(CultureInfo.CurrentCulture,
+ Strings.InvalidUri, value));
+ }
+ valuesList.Append(value);
+ valuesList.Append(" ");
+ }
+ if (valuesList.Length > 0)
+ valuesList.Length -= 1; // remove trailing space
+ return valuesList.ToString();
+ }
+
+ static internal IEnumerable<T> GetUniqueItems<T>(IList<T> list) {
+ List<T> itemsSeen = new List<T>(list.Count);
+ foreach (T item in list) {
+ if (itemsSeen.Contains(item)) continue;
+ itemsSeen.Add(item);
+ yield return item;
+ }
+ }
+ }
+}
diff --git a/src/DotNetOpenId/Extensions/ProviderAuthenticationPolicy/PolicyResponse.cs b/src/DotNetOpenId/Extensions/ProviderAuthenticationPolicy/PolicyResponse.cs
new file mode 100644
index 0000000..0f5922f
--- /dev/null
+++ b/src/DotNetOpenId/Extensions/ProviderAuthenticationPolicy/PolicyResponse.cs
@@ -0,0 +1,194 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Diagnostics;
+using System.Globalization;
+
+namespace DotNetOpenId.Extensions.ProviderAuthenticationPolicy {
+ /// <summary>
+ /// The PAPE response part of an OpenID Authentication response message.
+ /// </summary>
+ public sealed class PolicyResponse : IExtensionResponse {
+ // This array of formats is not yet a complete list.
+ static readonly string[] PermissibleDateTimeFormats = { "yyyy-MM-ddTHH:mm:ssZ" };
+
+ /// <summary>
+ /// Instantiates a <see cref="PolicyResponse"/>.
+ /// </summary>
+ public PolicyResponse() {
+ ActualPolicies = new List<string>(1);
+ AssuranceLevels = new Dictionary<string, string>(1);
+ }
+
+ /// <summary>
+ /// One or more authentication policy URIs that the OP conformed to when authenticating the End User.
+ /// </summary>
+ /// <remarks>
+ /// If no policies were met though the OP wishes to convey other information in the response, this parameter MUST be included with the value of "none".
+ /// </remarks>
+ public IList<string> ActualPolicies { get; private set; }
+ DateTime? authenticationTimeUtc;
+ /// <summary>
+ /// Optional. The most recent timestamp when the End User has actively authenticated to the OP in a manner fitting the asserted policies.
+ /// </summary>
+ /// <remarks>
+ /// If the RP's request included the "openid.max_auth_age" parameter then the OP MUST include "openid.auth_time" in its response. If "openid.max_auth_age" was not requested, the OP MAY choose to include "openid.auth_time" in its response.
+ /// </remarks>
+ public DateTime? AuthenticationTimeUtc {
+ get { return authenticationTimeUtc; }
+ set {
+ // Make sure that whatever is set here, it becomes UTC time.
+ if (value.HasValue) {
+ if (value.Value.Kind == DateTimeKind.Unspecified)
+ throw new ArgumentException(Strings.UnspecifiedDateTimeKindNotAllowed, "value");
+ authenticationTimeUtc = value.Value.ToUniversalTime();
+ } else {
+ authenticationTimeUtc = null;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Optional. The Assurance Level as defined by the National Institute of Standards and Technology (NIST) in Special Publication 800-63 (Burr, W., Dodson, D., and W. Polk, Ed., “Electronic Authentication Guideline,” April 2006.) [NIST_SP800‑63] corresponding to the authentication method and policies employed by the OP when authenticating the End User. /// </summary>
+ /// <remarks> /// See PAPE spec Appendix A.1.2 (NIST Assurance Levels) for high-level example classifications of authentication methods within the defined levels. /// </remarks>
+ public NistAssuranceLevel? NistAssuranceLevel {
+ get {
+ string levelString;
+ if (AssuranceLevels.TryGetValue(Constants.AuthenticationLevels.NistTypeUri, out levelString)) {
+ return (NistAssuranceLevel)Enum.Parse(typeof(NistAssuranceLevel), levelString);
+ } else {
+ return null;
+ }
+ }
+ set {
+ if (value != null) {
+ AssuranceLevels[Constants.AuthenticationLevels.NistTypeUri] = ((int)value).ToString(CultureInfo.InvariantCulture);
+ } else {
+ AssuranceLevels.Remove(Constants.AuthenticationLevels.NistTypeUri);
+ }
+ }
+ }
+
+ /// <summary>
+ /// Gets a dictionary where keys are the authentication level type URIs and
+ /// the values are the per authentication level defined custom value.
+ /// </summary>
+ /// <remarks>
+ /// A very common key is <see cref="Constants.AuthenticationLevels.NistTypeUri"/>
+ /// and values for this key are available in <see cref="NistAssuranceLevel"/>.
+ /// </remarks>
+ public IDictionary<string, string> AssuranceLevels { get; private set; }
+
+ /// <summary>
+ /// Tests equality between two <see cref="PolicyResponse"/> instances.
+ /// </summary>
+ public override bool Equals(object obj) {
+ PolicyResponse other = obj as PolicyResponse;
+ if (other == null) return false;
+ if (AuthenticationTimeUtc != other.AuthenticationTimeUtc) return false;
+ if (AssuranceLevels.Count != other.AssuranceLevels.Count) return false;
+ foreach (var pair in AssuranceLevels) {
+ if (!other.AssuranceLevels.Contains(pair)) return false;
+ }
+ if (ActualPolicies.Count != other.ActualPolicies.Count) return false;
+ foreach (string policy in ActualPolicies) {
+ if (!other.ActualPolicies.Contains(policy)) return false;
+ }
+ return true;
+ }
+
+ /// <summary>
+ /// Gets a hash code for this object.
+ /// </summary>
+ public override int GetHashCode() {
+ return ActualPolicies.GetHashCode();
+ }
+
+ #region IExtensionResponse Members
+
+ IDictionary<string, string> IExtensionResponse.Serialize(DotNetOpenId.Provider.IRequest authenticationRequest) {
+ var fields = new Dictionary<string, string>();
+
+ fields.Add(Constants.ResponseParameters.AuthPolicies, SerializePolicies(ActualPolicies));
+ if (AuthenticationTimeUtc.HasValue) {
+ fields.Add(Constants.ResponseParameters.AuthTime, AuthenticationTimeUtc.Value.ToUniversalTime().ToString(PermissibleDateTimeFormats[0], CultureInfo.InvariantCulture));
+ }
+
+ if (AssuranceLevels.Count > 0) {
+ AliasManager aliases = new AliasManager();
+ aliases.AssignAliases(AssuranceLevels.Keys, Constants.AuthenticationLevels.PreferredTypeUriToAliasMap);
+
+ // Add a definition for each Auth Level Type alias.
+ foreach (string alias in aliases.Aliases) {
+ fields.Add(Constants.AuthLevelNamespaceDeclarationPrefix + alias, aliases.ResolveAlias(alias));
+ }
+
+ // Now use the aliases for those type URIs to list the individual values.
+ foreach (var pair in AssuranceLevels) {
+ fields.Add(Constants.ResponseParameters.AuthLevelAliasPrefix + aliases.GetAlias(pair.Key), pair.Value);
+ }
+ }
+
+ return fields;
+ }
+
+ bool IExtensionResponse.Deserialize(IDictionary<string, string> fields, DotNetOpenId.RelyingParty.IAuthenticationResponse response, string typeUri) {
+ if (fields == null) return false;
+ if (!fields.ContainsKey(Constants.ResponseParameters.AuthPolicies)) return false;
+
+ ActualPolicies.Clear();
+ string[] actualPolicies = fields[Constants.ResponseParameters.AuthPolicies].Split(' ');
+ foreach (string policy in actualPolicies) {
+ if (policy.Length > 0 && policy != AuthenticationPolicies.None)
+ ActualPolicies.Add(policy);
+ }
+
+ AuthenticationTimeUtc = null;
+ string authTime;
+ if (fields.TryGetValue(Constants.ResponseParameters.AuthTime, out authTime)) {
+ DateTime authDateTime;
+ if (DateTime.TryParse(authTime, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal, out authDateTime) &&
+ authDateTime.Kind == DateTimeKind.Utc) { // may be unspecified per our option above
+ AuthenticationTimeUtc = authDateTime;
+ } else {
+ Logger.ErrorFormat("Invalid format for {0} parameter: {1}",
+ Constants.ResponseParameters.AuthTime, authTime);
+ }
+ }
+
+ AssuranceLevels.Clear();
+ AliasManager authLevelAliases = PolicyRequest.FindIncomingAliases(fields);
+ foreach (string authLevelAlias in authLevelAliases.Aliases) {
+ string authValue;
+ if (fields.TryGetValue(Constants.ResponseParameters.AuthLevelAliasPrefix + authLevelAlias, out authValue)) {
+ string authLevelType = authLevelAliases.ResolveAlias(authLevelAlias);
+ AssuranceLevels[authLevelType] = authValue;
+ }
+ }
+
+ return true;
+ }
+
+ #endregion
+
+ #region IExtension Members
+
+ string IExtension.TypeUri {
+ get { return Constants.TypeUri; }
+ }
+
+ IEnumerable<string> IExtension.AdditionalSupportedTypeUris {
+ get { return new string[0]; }
+ }
+
+ #endregion
+
+ static internal string SerializePolicies(IList<string> policies) {
+ if (policies.Count == 0) {
+ return AuthenticationPolicies.None;
+ } else {
+ return PolicyRequest.ConcatenateListOfElements(policies);
+ }
+ }
+ }
+}
diff --git a/src/DotNetOpenId/Extensions/SimpleRegistration/ClaimsRequest.cs b/src/DotNetOpenId/Extensions/SimpleRegistration/ClaimsRequest.cs
index e3cca83..e5d282e 100644
--- a/src/DotNetOpenId/Extensions/SimpleRegistration/ClaimsRequest.cs
+++ b/src/DotNetOpenId/Extensions/SimpleRegistration/ClaimsRequest.cs
@@ -128,9 +128,18 @@ namespace DotNetOpenId.Extensions.SimpleRegistration {
#region IExtensionRequest Members
string IExtension.TypeUri { get { return Constants.sreg_ns; } }
+ static readonly string[] additionalTypeUris = new string[] {
+ Constants.sreg_ns10,
+ Constants.sreg_ns11other,
+ };
+ IEnumerable<string> IExtension.AdditionalSupportedTypeUris {
+ get { return additionalTypeUris; }
+ }
- bool IExtensionRequest.Deserialize(IDictionary<string, string> args, IRequest request) {
+ bool IExtensionRequest.Deserialize(IDictionary<string, string> args, IRequest request, string typeUri) {
if (args == null) return false;
+ Debug.Assert(!string.IsNullOrEmpty(typeUri));
+ typeUriDeserializedFrom = typeUri;
string policyUrl;
if (args.TryGetValue(Constants.policy_url, out policyUrl)
@@ -168,10 +177,18 @@ namespace DotNetOpenId.Extensions.SimpleRegistration {
}
#endregion
+ string typeUriDeserializedFrom;
+ /// <summary>
+ /// Prepares a Simple Registration response extension that is compatible with the
+ /// version of Simple Registration used in the request message.
+ /// </summary>
+ public ClaimsResponse CreateResponse() {
+ return new ClaimsResponse(typeUriDeserializedFrom);
+ }
+
/// <summary>
/// Renders the requested information as a string.
/// </summary>
- /// <returns></returns>
public override string ToString() {
return string.Format(CultureInfo.CurrentCulture, @"Nickname = '{0}'
Email = '{1}'
diff --git a/src/DotNetOpenId/Extensions/SimpleRegistration/ClaimsResponse.cs b/src/DotNetOpenId/Extensions/SimpleRegistration/ClaimsResponse.cs
index 88f0b35..0203bce 100644
--- a/src/DotNetOpenId/Extensions/SimpleRegistration/ClaimsResponse.cs
+++ b/src/DotNetOpenId/Extensions/SimpleRegistration/ClaimsResponse.cs
@@ -24,6 +24,20 @@ namespace DotNetOpenId.Extensions.SimpleRegistration
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2218:OverrideGetHashCodeOnOverridingEquals"), Serializable()]
public sealed class ClaimsResponse : IExtensionResponse
{
+ string typeUriToUse;
+
+ /// <summary>
+ /// Creates an instance of the <see cref="ClaimsResponse"/> class.
+ /// </summary>
+ [Obsolete("Use ClaimsRequest.CreateResponse() instead.")]
+ public ClaimsResponse() : this(Constants.sreg_ns) {
+ }
+
+ internal ClaimsResponse(string typeUriToUse) {
+ if (string.IsNullOrEmpty(typeUriToUse)) throw new ArgumentNullException("typeUriToUse");
+ this.typeUriToUse = typeUriToUse;
+ }
+
/// <summary>
/// The nickname the user goes by.
/// </summary>
@@ -39,7 +53,7 @@ namespace DotNetOpenId.Extensions.SimpleRegistration
{
get
{
- if (Email == null) return null;
+ if (string.IsNullOrEmpty(Email)) return null;
if (string.IsNullOrEmpty(FullName))
return new MailAddress(Email);
else
@@ -104,7 +118,10 @@ namespace DotNetOpenId.Extensions.SimpleRegistration
public string TimeZone { get; set; }
#region IExtensionResponse Members
- string IExtension.TypeUri { get { return Constants.sreg_ns; } }
+ string IExtension.TypeUri { get { return typeUriToUse; } }
+ IEnumerable<string> IExtension.AdditionalSupportedTypeUris {
+ get { return new string[0]; }
+ }
/// <summary>
/// Adds the values of this struct to an authentication response being prepared
@@ -148,7 +165,7 @@ namespace DotNetOpenId.Extensions.SimpleRegistration
return fields;
}
- bool IExtensionResponse.Deserialize(IDictionary<string, string> sreg, IAuthenticationResponse response) {
+ bool IExtensionResponse.Deserialize(IDictionary<string, string> sreg, IAuthenticationResponse response, string typeUri) {
if (sreg == null) return false;
string nickname, email, fullName, dob, genderString, postalCode, country, language, timeZone;
BirthDate = null;
diff --git a/src/DotNetOpenId/Extensions/SimpleRegistration/Constants.cs b/src/DotNetOpenId/Extensions/SimpleRegistration/Constants.cs
index fc4066e..8aa9591 100644
--- a/src/DotNetOpenId/Extensions/SimpleRegistration/Constants.cs
+++ b/src/DotNetOpenId/Extensions/SimpleRegistration/Constants.cs
@@ -7,8 +7,9 @@ namespace DotNetOpenId.Extensions.SimpleRegistration {
/// Simple Registration constants
/// </summary>
internal static class Constants {
- internal const string TypeUri = "http://openid.net/sreg/1.0";
internal const string sreg_ns = "http://openid.net/extensions/sreg/1.1";
+ internal const string sreg_ns10 = "http://openid.net/sreg/1.0";
+ internal const string sreg_ns11other = "http://openid.net/sreg/1.1";
internal const string sreg_compatibility_alias = "sreg";
internal const string policy_url = "policy_url";
internal const string optional = "optional";
diff --git a/src/DotNetOpenId/GlobalSuppressions.cs b/src/DotNetOpenId/GlobalSuppressions.cs
index 8ee927f..a3b36f7 100644
--- a/src/DotNetOpenId/GlobalSuppressions.cs
+++ b/src/DotNetOpenId/GlobalSuppressions.cs
@@ -75,3 +75,8 @@
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Xrds", Scope = "type", Target = "DotNetOpenId.XrdsPublisher")]
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1020:AvoidNamespacesWithFewTypes", Scope = "namespace", Target = "DotNetOpenId.Extensions.SimpleRegistration")]
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA2210:AssembliesShouldHaveValidStrongNames")]
+[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Nist", Scope = "member", Target = "DotNetOpenId.Extensions.ProviderAuthenticationPolicy.PolicyResponse.#NistAssuranceLevel")]
+[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1020:AvoidNamespacesWithFewTypes", Scope = "namespace", Target = "DotNetOpenId.Extensions.ProviderAuthenticationPolicy")]
+[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Whitelist", Scope = "member", Target = "DotNetOpenId.UntrustedWebRequest.#WhitelistHostsRegEx")]
+[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Whitelist", Scope = "member", Target = "DotNetOpenId.UntrustedWebRequest.#WhitelistHostsRegex")]
+[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Whitelist", Scope = "member", Target = "DotNetOpenId.UntrustedWebRequest.#WhitelistHosts")]
diff --git a/src/DotNetOpenId/IEncodable.cs b/src/DotNetOpenId/IEncodable.cs
new file mode 100644
index 0000000..4dd830b
--- /dev/null
+++ b/src/DotNetOpenId/IEncodable.cs
@@ -0,0 +1,38 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace DotNetOpenId {
+ internal enum EncodingType {
+ None,
+ /// <summary>
+ /// Data to be sent to the OP or RP site by telling the user agent to
+ /// redirect GET or form POST to a special URL with a payload of arguments.
+ /// </summary>
+ IndirectMessage,
+ /// <summary>
+ /// Provider response data to be sent directly to the Relying Party site,
+ /// in response to a direct request initiated by the RP
+ /// (not indirect via the user agent).
+ /// Key-Value Form encoding will be used.
+ /// </summary>
+ DirectResponse
+ }
+
+ /// <remarks>
+ /// Classes that implement IEncodable should be either [Serializable] or
+ /// derive from <see cref="MarshalByRefObject"/> so that testing can
+ /// remote across app-domain boundaries to sniff/tamper with messages.
+ /// </remarks>
+ internal interface IEncodable {
+ EncodingType EncodingType { get; }
+ IDictionary<string, string> EncodedFields { get; }
+ /// <summary>
+ /// The URL that the user agent should be redirected to
+ /// in the case of <see cref="DotNetOpenId.EncodingType.IndirectMessage"/>.
+ /// Does not apply to <see cref="DotNetOpenId.EncodingType.DirectResponse"/>.
+ /// </summary>
+ Uri RedirectUrl { get; }
+ Protocol Protocol { get; }
+ }
+}
diff --git a/src/DotNetOpenId/Provider/IResponse.cs b/src/DotNetOpenId/IResponse.cs
index 29a70ce..1c40401 100644
--- a/src/DotNetOpenId/Provider/IResponse.cs
+++ b/src/DotNetOpenId/IResponse.cs
@@ -1,12 +1,8 @@
-using System;
-using System.Collections.Generic;
-using System.Text;
-using System.Net;
-using System.Collections.Specialized;
+using System.Net;
-namespace DotNetOpenId.Provider {
+namespace DotNetOpenId {
/// <summary>
- /// Represents a Provider's response to an OpenId authentication request.
+ /// Represents an indirect message passed between Relying Party and Provider.
/// </summary>
public interface IResponse {
/// <summary>
@@ -26,7 +22,7 @@ namespace DotNetOpenId.Provider {
/// Sends the response to the browser.
/// </summary>
/// <remarks>
- /// This requires an ASP.NET hosted web site.
+ /// This requires an ASP.NET HttpContext.
/// </remarks>
void Send();
}
diff --git a/src/DotNetOpenId/MessageEncoder.cs b/src/DotNetOpenId/MessageEncoder.cs
new file mode 100644
index 0000000..bbcbdf0
--- /dev/null
+++ b/src/DotNetOpenId/MessageEncoder.cs
@@ -0,0 +1,111 @@
+using System;
+using System.Collections.Specialized;
+using System.Text;
+using System.Net;
+using System.Diagnostics;
+using DotNetOpenId.Provider;
+using System.IO;
+using System.Web;
+
+namespace DotNetOpenId {
+ /// <summary>
+ /// Encodes <see cref="IEncodable"/> messages into <see cref="Response"/> instances
+ /// that can be interpreted by the host web site.
+ /// </summary>
+ internal class MessageEncoder {
+ /// <summary>
+ /// The maximum allowable size for a 301 Redirect response before we send
+ /// a 200 OK response with a scripted form POST with the parameters instead
+ /// in order to ensure successfully sending a large payload to another server
+ /// that might have a maximum allowable size restriction on its GET request.
+ /// </summary>
+ internal static int GetToPostThreshold = 2 * 1024; // 2KB, recommended by OpenID group
+ // We are intentionally using " instead of the html single quote ' below because
+ // the HtmlEncode'd values that we inject will only escape the double quote, so
+ // only the double-quote used around these values is safe.
+ const string FormPostFormat = @"
+<html>
+<body onload=""var btn = document.getElementById('submit_button'); btn.disabled = true; btn.value = 'Login in progress'; document.getElementById('openid_message').submit()"">
+<form id=""openid_message"" action=""{0}"" method=""post"" accept-charset=""UTF-8"" enctype=""application/x-www-form-urlencoded"" onSubmit=""var btn = document.getElementById('submit_button'); btn.disabled = true; btn.value = 'Login in progress'; return true;"">
+{1}
+ <input id=""submit_button"" type=""submit"" value=""Continue"" />
+</form>
+</body>
+</html>
+";
+ /// <summary>
+ /// Encodes messages into <see cref="Response"/> instances.
+ /// </summary>
+ public virtual Response Encode(IEncodable message) {
+ if (message == null) throw new ArgumentNullException("message");
+
+ EncodingType encode_as = message.EncodingType;
+ Response wr;
+
+ WebHeaderCollection headers = new WebHeaderCollection();
+ switch (encode_as) {
+ case EncodingType.DirectResponse:
+ Logger.DebugFormat("Sending direct message response:{0}{1}",
+ Environment.NewLine, Util.ToString(message.EncodedFields));
+ HttpStatusCode code = (message is Exception) ?
+ HttpStatusCode.BadRequest : HttpStatusCode.OK;
+ wr = new Response(code, headers, ProtocolMessages.KeyValueForm.GetBytes(message.EncodedFields), message);
+ break;
+ case EncodingType.IndirectMessage:
+ Logger.DebugFormat("Sending indirect message:{0}{1}",
+ Environment.NewLine, Util.ToString(message.EncodedFields));
+ // TODO: either redirect or do a form POST depending on payload size.
+ Debug.Assert(message.RedirectUrl != null);
+ if (getSizeOfPayload(message) <= GetToPostThreshold)
+ wr = Create301RedirectResponse(message);
+ else
+ wr = CreateFormPostResponse(message);
+ break;
+ default:
+ Logger.ErrorFormat("Cannot encode response: {0}", message);
+ wr = new Response(HttpStatusCode.BadRequest, headers, new byte[0], message);
+ break;
+ }
+ return wr;
+ }
+
+ static int getSizeOfPayload(IEncodable message) {
+ Debug.Assert(message != null);
+ int size = 0;
+ foreach (var field in message.EncodedFields) {
+ size += field.Key.Length;
+ size += field.Value.Length;
+ }
+ return size;
+ }
+ protected virtual Response Create301RedirectResponse(IEncodable message) {
+ WebHeaderCollection headers = new WebHeaderCollection();
+ UriBuilder builder = new UriBuilder(message.RedirectUrl);
+ UriUtil.AppendQueryArgs(builder, message.EncodedFields);
+ headers.Add(HttpResponseHeader.Location, builder.Uri.AbsoluteUri);
+ Logger.DebugFormat("Redirecting to {0}", builder.Uri.AbsoluteUri);
+ return new Response(HttpStatusCode.Redirect, headers, new byte[0], message);
+ }
+ protected virtual Response CreateFormPostResponse(IEncodable message) {
+ WebHeaderCollection headers = new WebHeaderCollection();
+ MemoryStream body = new MemoryStream();
+ StreamWriter bodyWriter = new StreamWriter(body);
+ StringBuilder hiddenFields = new StringBuilder();
+ foreach (var field in message.EncodedFields) {
+ hiddenFields.AppendFormat("\t<input type=\"hidden\" name=\"{0}\" value=\"{1}\" />\r\n",
+ HttpUtility.HtmlEncode(field.Key), HttpUtility.HtmlEncode(field.Value));
+ }
+ bodyWriter.WriteLine(FormPostFormat,
+ HttpUtility.HtmlEncode(message.RedirectUrl.AbsoluteUri), hiddenFields);
+ bodyWriter.Flush();
+ return new Response(HttpStatusCode.OK, headers, body.ToArray(), message);
+ }
+ }
+
+ internal class EncodeEventArgs : EventArgs {
+ public EncodeEventArgs(IEncodable encodable) {
+ Message = encodable;
+ }
+ public IEncodable Message { get; private set; }
+ }
+}
diff --git a/src/DotNetOpenId/OpenIdException.cs b/src/DotNetOpenId/OpenIdException.cs
index bbdbfbc..3285494 100644
--- a/src/DotNetOpenId/OpenIdException.cs
+++ b/src/DotNetOpenId/OpenIdException.cs
@@ -113,10 +113,10 @@ namespace DotNetOpenId {
EncodingType IEncodable.EncodingType {
get {
if (IsDirectMessage)
- return EncodingType.ResponseBody;
+ return EncodingType.DirectResponse;
if (HasReturnTo)
- return EncodingType.RedirectBrowserUrl;
+ return EncodingType.IndirectMessage;
Debug.Fail("Somehow we cannot tell whether this is a direct message or indirect message. Did we construct an exception without a Query parameter?");
return EncodingType.None;
diff --git a/src/DotNetOpenId/Provider/EncodableResponse.cs b/src/DotNetOpenId/Provider/EncodableResponse.cs
index c7ebb4e..47bc1cd 100644
--- a/src/DotNetOpenId/Provider/EncodableResponse.cs
+++ b/src/DotNetOpenId/Provider/EncodableResponse.cs
@@ -45,7 +45,7 @@ namespace DotNetOpenId.Provider {
#region IEncodable Members
public EncodingType EncodingType {
- get { return RedirectUrl != null ? EncodingType.RedirectBrowserUrl : EncodingType.ResponseBody; }
+ get { return RedirectUrl != null ? EncodingType.IndirectMessage : EncodingType.DirectResponse; }
}
public IDictionary<string, string> EncodedFields {
@@ -53,7 +53,7 @@ namespace DotNetOpenId.Provider {
var nvc = new Dictionary<string, string>();
foreach (var pair in Fields) {
- if (EncodingType == EncodingType.RedirectBrowserUrl) {
+ if (EncodingType == EncodingType.IndirectMessage) {
nvc.Add(Protocol.openid.Prefix + pair.Key, pair.Value);
} else {
nvc.Add(pair.Key, pair.Value);
diff --git a/src/DotNetOpenId/Provider/Encoder.cs b/src/DotNetOpenId/Provider/Encoder.cs
deleted file mode 100644
index 3b9f115..0000000
--- a/src/DotNetOpenId/Provider/Encoder.cs
+++ /dev/null
@@ -1,91 +0,0 @@
-using System;
-using System.Collections.Specialized;
-using System.Text;
-using System.Net;
-using System.Diagnostics;
-
-namespace DotNetOpenId.Provider {
- /// <summary>
- /// Encodes responses in to <see cref="WebResponse"/>.
- /// </summary>
- internal class Encoder {
- /// <summary>
- /// Encodes responses in to WebResponses.
- /// </summary>
- public virtual Response Encode(IEncodable response) {
- EncodingType encode_as = response.EncodingType;
- Response wr;
-
- switch (encode_as) {
- case EncodingType.ResponseBody:
- Logger.DebugFormat("Sending direct message response:{0}{1}",
- Environment.NewLine, Util.ToString(response.EncodedFields));
- HttpStatusCode code = (response is Exception) ?
- HttpStatusCode.BadRequest : HttpStatusCode.OK;
- wr = new Response(code, null, ProtocolMessages.KeyValueForm.GetBytes(response.EncodedFields));
- break;
- case EncodingType.RedirectBrowserUrl:
- Logger.DebugFormat("Sending indirect message response:{0}{1}",
- Environment.NewLine, Util.ToString(response.EncodedFields));
- Debug.Assert(response.RedirectUrl != null);
- WebHeaderCollection headers = new WebHeaderCollection();
-
- UriBuilder builder = new UriBuilder(response.RedirectUrl);
- UriUtil.AppendQueryArgs(builder, response.EncodedFields);
- headers.Add(HttpResponseHeader.Location, builder.Uri.AbsoluteUri);
-
- Logger.DebugFormat("Redirecting to {0}", builder.Uri.AbsoluteUri);
- wr = new Response(HttpStatusCode.Redirect, headers, new byte[0]);
- break;
- default:
- Logger.ErrorFormat("Cannot encode response: {0}", response);
- wr = new Response(HttpStatusCode.BadRequest, null, new byte[0]);
- break;
- }
- return wr;
- }
- }
-
- /// <summary>
- /// Encodes responses in to <see cref="WebResponse"/>, signing them when required.
- /// </summary>
- internal class SigningEncoder : Encoder {
- Signatory signatory;
-
- public SigningEncoder(Signatory signatory) {
- if (signatory == null)
- throw new ArgumentNullException("signatory", "Must have a store to sign this request");
-
- this.signatory = signatory;
- }
-
- 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));
- }
- }
-
- internal class EncodeEventArgs : EventArgs {
- public EncodeEventArgs(IEncodable encodable) {
- Message = encodable;
- }
- public IEncodable Message { get; private set;}
- }
-}
diff --git a/src/DotNetOpenId/Provider/IEncodable.cs b/src/DotNetOpenId/Provider/IEncodable.cs
deleted file mode 100644
index 3a7a60d..0000000
--- a/src/DotNetOpenId/Provider/IEncodable.cs
+++ /dev/null
@@ -1,33 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Text;
-
-namespace DotNetOpenId.Provider {
- internal enum EncodingType {
- None,
- /// <summary>
- /// Response data to be sent to the consumer web site by telling the
- /// browser to redirect back to the consumer web site with a querystring
- /// that contains our data.
- /// </summary>
- RedirectBrowserUrl,
- /// <summary>
- /// Response data to be sent directly to the consumer site,
- /// in response to a direct request initiated by the consumer site
- /// (not the client browser).
- /// </summary>
- ResponseBody
- }
-
- /// <remarks>
- /// Classes that implement IEncodable should be either [Serializable] or
- /// derive from <see cref="MarshalByRefObject"/> so that testing can
- /// remote across app-domain boundaries to sniff/tamper with messages.
- /// </remarks>
- internal interface IEncodable {
- EncodingType EncodingType { get; }
- IDictionary<string, string> EncodedFields { get; }
- Uri RedirectUrl { get; }
- Protocol Protocol { get; }
- }
-}
diff --git a/src/DotNetOpenId/Provider/OpenIdProvider.cs b/src/DotNetOpenId/Provider/OpenIdProvider.cs
index 48424b9..b493fda 100644
--- a/src/DotNetOpenId/Provider/OpenIdProvider.cs
+++ b/src/DotNetOpenId/Provider/OpenIdProvider.cs
@@ -15,7 +15,7 @@ namespace DotNetOpenId.Provider {
[DebuggerDisplay("Endpoint: {Endpoint}, OpenId Request: {Query.ContainsKey(\"openid.mode\")}")]
public class OpenIdProvider {
internal Signatory Signatory { get; private set; }
- internal Encoder Encoder;
+ internal MessageEncoder Encoder;
/// <summary>
/// The incoming request's Url.
/// </summary>
@@ -55,8 +55,11 @@ namespace DotNetOpenId.Provider {
/// The Internet-facing URL that responds to OpenID requests.
/// </param>
/// <param name="requestUrl">The incoming request URL.</param>
- /// <param name="query">The name/value pairs that came in on the
- /// QueryString of a GET request or in the entity of a POST request.</param>
+ /// <param name="query">
+ /// The name/value pairs that came in on the
+ /// QueryString of a GET request or in the entity of a POST request.
+ /// For example: (Request.HttpMethod == "GET" ? Request.QueryString : Request.Form).
+ /// </param>
public OpenIdProvider(IProviderAssociationStore store, Uri providerEndpoint, Uri requestUrl, NameValueCollection query)
: this(store, providerEndpoint, requestUrl, Util.NameValueCollectionToDictionary(query)) { }
OpenIdProvider(IProviderAssociationStore store, Uri providerEndpoint, Uri requestUrl, IDictionary<string, string> query) {
@@ -68,7 +71,7 @@ namespace DotNetOpenId.Provider {
RequestUrl = requestUrl;
Query = query;
Signatory = new Signatory(store);
- Encoder = new SigningEncoder(Signatory);
+ Encoder = new SigningMessageEncoder(Signatory);
store.ClearExpiredAssociations(); // every so often we should do this.
}
diff --git a/src/DotNetOpenId/Provider/Request.cs b/src/DotNetOpenId/Provider/Request.cs
index 9257e99..7813a2b 100644
--- a/src/DotNetOpenId/Provider/Request.cs
+++ b/src/DotNetOpenId/Provider/Request.cs
@@ -123,9 +123,36 @@ namespace DotNetOpenId.Provider
OutgoingExtensions.AddExtensionArguments(extension.TypeUri, extension.Serialize(this));
}
+ /// <summary>
+ /// Attempts to load an extension from an OpenId message.
+ /// </summary>
+ /// <param name="extension">The extension to attempt to load.</param>
+ /// <returns>
+ /// True if the extension was found in the message and successfully loaded.
+ /// False otherwise.
+ /// </returns>
+ bool getExtension(DotNetOpenId.Extensions.IExtensionRequest extension) {
+ var fields = IncomingExtensions.GetExtensionArguments(extension.TypeUri);
+ if (fields != null) {
+ // The extension was found using the preferred TypeUri.
+ return extension.Deserialize(fields, this, extension.TypeUri);
+ } else {
+ // The extension may still be found using secondary TypeUris.
+ if (extension.AdditionalSupportedTypeUris != null) {
+ foreach (string typeUri in extension.AdditionalSupportedTypeUris) {
+ fields = IncomingExtensions.GetExtensionArguments(typeUri);
+ if (fields != null) {
+ // We found one of the older ones.
+ return extension.Deserialize(fields, this, typeUri);
+ }
+ }
+ }
+ }
+ return false;
+ }
public T GetExtension<T>() where T : DotNetOpenId.Extensions.IExtensionRequest, new() {
T extension = new T();
- return extension.Deserialize(IncomingExtensions.GetExtensionArguments(extension.TypeUri), this) ? extension : default(T);
+ return getExtension(extension) ? (T)extension : default(T);
}
public DotNetOpenId.Extensions.IExtensionRequest GetExtension(Type extensionType) {
@@ -135,7 +162,7 @@ namespace DotNetOpenId.Provider
Strings.TypeMustImplementX, typeof(DotNetOpenId.Extensions.IExtensionRequest).FullName),
"extensionType");
var extension = (DotNetOpenId.Extensions.IExtensionRequest)Activator.CreateInstance(extensionType);
- return extension.Deserialize(IncomingExtensions.GetExtensionArguments(extension.TypeUri), this) ? extension : null;
+ return getExtension(extension) ? extension : null;
}
public override string ToString() {
diff --git a/src/DotNetOpenId/Provider/SigningMessageEncoder.cs b/src/DotNetOpenId/Provider/SigningMessageEncoder.cs
new file mode 100644
index 0000000..ea5a522
--- /dev/null
+++ b/src/DotNetOpenId/Provider/SigningMessageEncoder.cs
@@ -0,0 +1,41 @@
+using System;
+using System.Diagnostics;
+
+namespace DotNetOpenId.Provider {
+ /// <summary>
+ /// Encodes responses in to <see cref="Response"/>, signing them when required.
+ /// </summary>
+ internal class SigningMessageEncoder : MessageEncoder {
+ Signatory signatory;
+
+ public SigningMessageEncoder(Signatory signatory) {
+ if (signatory == null)
+ throw new ArgumentNullException("signatory", "Must have a store to sign this request");
+
+ this.signatory = signatory;
+ }
+
+ 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/RelyingParty/AuthenticationRequest.cs b/src/DotNetOpenId/RelyingParty/AuthenticationRequest.cs
index 2800716..2ce5a0e 100644
--- a/src/DotNetOpenId/RelyingParty/AuthenticationRequest.cs
+++ b/src/DotNetOpenId/RelyingParty/AuthenticationRequest.cs
@@ -6,6 +6,7 @@ using System.Collections.Specialized;
using System.Globalization;
using System.Web;
using System.Diagnostics;
+using DotNetOpenId.Extensions;
namespace DotNetOpenId.RelyingParty {
/// <summary>
@@ -30,15 +31,18 @@ namespace DotNetOpenId.RelyingParty {
class AuthenticationRequest : IAuthenticationRequest {
Association assoc;
ServiceEndpoint endpoint;
+ MessageEncoder encoder;
Protocol protocol { get { return endpoint.Protocol; } }
AuthenticationRequest(string token, Association assoc, ServiceEndpoint endpoint,
- Realm realm, Uri returnToUrl) {
+ Realm realm, Uri returnToUrl, MessageEncoder encoder) {
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");
this.assoc = assoc;
this.endpoint = endpoint;
+ this.encoder = encoder;
Realm = realm;
ReturnToUrl = returnToUrl;
@@ -49,7 +53,7 @@ namespace DotNetOpenId.RelyingParty {
AddCallbackArguments(DotNetOpenId.RelyingParty.Token.TokenKey, token);
}
internal static AuthenticationRequest Create(Identifier userSuppliedIdentifier,
- Realm realm, Uri returnToUrl, IRelyingPartyApplicationStore store) {
+ Realm realm, Uri returnToUrl, IRelyingPartyApplicationStore store, MessageEncoder encoder) {
if (userSuppliedIdentifier == null) throw new ArgumentNullException("userSuppliedIdentifier");
if (realm == null) throw new ArgumentNullException("realm");
@@ -83,7 +87,7 @@ namespace DotNetOpenId.RelyingParty {
return new AuthenticationRequest(
new Token(endpoint).Serialize(store),
store != null ? getAssociation(endpoint, store) : null,
- endpoint, realm, returnToUrl);
+ endpoint, realm, returnToUrl, encoder);
}
static Association getAssociation(ServiceEndpoint provider, IRelyingPartyApplicationStore store) {
if (provider == null) throw new ArgumentNullException("provider");
@@ -124,16 +128,27 @@ namespace DotNetOpenId.RelyingParty {
public AuthenticationRequestMode Mode { get; set; }
public Realm Realm { get; private set; }
public Uri ReturnToUrl { get; private set; }
- public Identifier ClaimedIdentifier { get { return endpoint.ClaimedIdentifier; } }
+ public Identifier ClaimedIdentifier {
+ get { return IsDirectedIdentity ? null : endpoint.ClaimedIdentifier; }
+ }
+ public bool IsDirectedIdentity {
+ get { return endpoint.ClaimedIdentifier == endpoint.Protocol.ClaimedIdentifierForOPIdentifier; }
+ }
/// <summary>
/// The detected version of OpenID implemented by the Provider.
/// </summary>
public Version ProviderVersion { get { return protocol.Version; } }
/// <summary>
- /// Gets the URL the user agent should be redirected to to begin the
+ /// Gets information about the OpenId Provider, as advertised by the
+ /// OpenId discovery documents found at the <see cref="ClaimedIdentifier"/>
+ /// location.
+ /// </summary>
+ IProviderEndpoint IAuthenticationRequest.Provider { get { return endpoint; } }
+ /// <summary>
+ /// Gets the response to send to the user agent to begin the
/// OpenID authentication process.
/// </summary>
- public Uri RedirectToProviderUrl {
+ public IResponse RedirectingResponse {
get {
UriBuilder returnToBuilder = new UriBuilder(ReturnToUrl);
UriUtil.AppendQueryArgs(returnToBuilder, this.ReturnToArgs);
@@ -154,21 +169,17 @@ namespace DotNetOpenId.RelyingParty {
if (this.assoc != null)
qsArgs.Add(protocol.openid.assoc_handle, this.assoc.Handle);
- var extensionArgs = OutgoingExtensions.GetArgumentsToSend(true);
-
- Logger.DebugFormat("Preparing indirect message:{0}{1}{2}", Environment.NewLine,
- Util.ToString(qsArgs), Util.ToString(extensionArgs));
-
- UriBuilder redir = new UriBuilder(this.endpoint.ProviderEndpoint);
-
- UriUtil.AppendQueryArgs(redir, qsArgs);
- UriUtil.AppendQueryArgs(redir, extensionArgs);
+ // Add on extension arguments
+ foreach(var pair in OutgoingExtensions.GetArgumentsToSend(true))
+ qsArgs.Add(pair.Key, pair.Value);
- return redir.Uri;
+ var request = new IndirectMessageRequest(this.endpoint.ProviderEndpoint, qsArgs);
+ return this.encoder.Encode(request);
}
}
public void AddExtension(DotNetOpenId.Extensions.IExtensionRequest extension) {
+ if (extension == null) throw new ArgumentNullException("extension");
OutgoingExtensions.AddExtensionArguments(extension.TypeUri, extension.Serialize(this));
}
@@ -195,28 +206,15 @@ namespace DotNetOpenId.RelyingParty {
/// <summary>
/// Redirects the user agent to the provider for authentication.
+ /// Execution of the current page terminates after this call.
/// </summary>
/// <remarks>
/// This method requires an ASP.NET HttpContext.
/// </remarks>
public void RedirectToProvider() {
- RedirectToProvider(false);
- }
- /// <summary>
- /// Redirects the user agent to the provider for authentication.
- /// </summary>
- /// <param name="endResponse">
- /// Whether execution of this response should cease after this call.
- /// </param>
- /// <remarks>
- /// This method requires an ASP.NET HttpContext.
- /// </remarks>
- public void RedirectToProvider(bool endResponse) {
if (HttpContext.Current == null || HttpContext.Current.Response == null)
throw new InvalidOperationException(Strings.CurrentHttpContextRequired);
- Uri redirectUri = RedirectToProviderUrl;
- Logger.InfoFormat("Redirecting for authentication to {0}", redirectUri.AbsoluteUri);
- HttpContext.Current.Response.Redirect(redirectUri.AbsoluteUri, endResponse);
+ RedirectingResponse.Send();
}
}
}
diff --git a/src/DotNetOpenId/RelyingParty/AuthenticationResponse.cs b/src/DotNetOpenId/RelyingParty/AuthenticationResponse.cs
index 6af7faf..3ccfc97 100644
--- a/src/DotNetOpenId/RelyingParty/AuthenticationResponse.cs
+++ b/src/DotNetOpenId/RelyingParty/AuthenticationResponse.cs
@@ -93,6 +93,26 @@ namespace DotNetOpenId.RelyingParty {
get { return new Uri(Util.GetRequiredArg(signedArguments, Provider.Protocol.openid.return_to)); }
}
+ bool getExtension(IExtensionResponse extension) {
+ var fields = IncomingExtensions.GetExtensionArguments(extension.TypeUri);
+ if (fields != null) {
+ // The extension was found using the preferred TypeUri.
+ return extension.Deserialize(fields, this, extension.TypeUri);
+ } else {
+ // The extension may still be found using secondary TypeUris.
+ if (extension.AdditionalSupportedTypeUris != null) {
+ foreach (string typeUri in extension.AdditionalSupportedTypeUris) {
+ fields = IncomingExtensions.GetExtensionArguments(typeUri);
+ if (fields != null) {
+ // We found one of the older ones.
+ return extension.Deserialize(fields, this, typeUri);
+ }
+ }
+ }
+ }
+ return false;
+ }
+
/// <summary>
/// Tries to get an OpenID extension that may be present in the response.
/// </summary>
@@ -100,7 +120,7 @@ namespace DotNetOpenId.RelyingParty {
/// <returns>The extension, if it is found. Null otherwise.</returns>
public T GetExtension<T>() where T : IExtensionResponse, new() {
T extension = new T();
- return extension.Deserialize(IncomingExtensions.GetExtensionArguments(extension.TypeUri), this) ? extension : default(T);
+ return getExtension(extension) ? extension : default(T);
}
public IExtensionResponse GetExtension(Type extensionType) {
@@ -110,7 +130,7 @@ namespace DotNetOpenId.RelyingParty {
Strings.TypeMustImplementX, typeof(IExtensionResponse).FullName),
"extensionType");
var extension = (IExtensionResponse)Activator.CreateInstance(extensionType);
- return extension.Deserialize(IncomingExtensions.GetExtensionArguments(extension.TypeUri), this) ? extension : null;
+ return getExtension(extension) ? extension : null;
}
internal static AuthenticationResponse Parse(IDictionary<string, string> query,
@@ -121,7 +141,12 @@ namespace DotNetOpenId.RelyingParty {
Logger.DebugFormat("OpenID authentication response received:{0}{1}", Environment.NewLine, Util.ToString(query));
ServiceEndpoint tokenEndpoint = null;
- string token = Util.GetOptionalArg(query, Token.TokenKey);
+ // The query parameter may be the POST query or the GET query,
+ // but the token parameter will always be in the GET query because
+ // it was added to the return_to parameter list.
+ IDictionary<string, string> requestUrlQuery = Util.NameValueCollectionToDictionary(
+ HttpUtility.ParseQueryString(requestUrl.Query));
+ string token = Util.GetOptionalArg(requestUrlQuery, Token.TokenKey);
if (token != null) {
tokenEndpoint = Token.Deserialize(token, store).Endpoint;
}
diff --git a/src/DotNetOpenId/RelyingParty/DirectRequest.cs b/src/DotNetOpenId/RelyingParty/DirectRequest.cs
index 3645e0a..dbc91f8 100644
--- a/src/DotNetOpenId/RelyingParty/DirectRequest.cs
+++ b/src/DotNetOpenId/RelyingParty/DirectRequest.cs
@@ -4,6 +4,7 @@ using System.Text;
using System.Net;
using System.Diagnostics;
using System.Globalization;
+using System.IO;
namespace DotNetOpenId.RelyingParty {
[DebuggerDisplay("OpenId: {Protocol.Version}")]
@@ -29,6 +30,14 @@ namespace DotNetOpenId.RelyingParty {
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));
diff --git a/src/DotNetOpenId/RelyingParty/IAuthenticationRequest.cs b/src/DotNetOpenId/RelyingParty/IAuthenticationRequest.cs
index 3297b64..8773454 100644
--- a/src/DotNetOpenId/RelyingParty/IAuthenticationRequest.cs
+++ b/src/DotNetOpenId/RelyingParty/IAuthenticationRequest.cs
@@ -26,30 +26,21 @@ namespace DotNetOpenId.RelyingParty {
void AddExtension(IExtensionRequest extension);
/// <summary>
/// Redirects the user agent to the provider for authentication.
+ /// Execution of the current page terminates after this call.
/// </summary>
/// <remarks>
/// This method requires an ASP.NET HttpContext.
/// </remarks>
void RedirectToProvider();
/// <summary>
- /// Redirects the user agent to the provider for authentication.
- /// </summary>
- /// <param name="endResponse">
- /// Whether execution of this response should cease after this call.
- /// </param>
- /// <remarks>
- /// This method requires an ASP.NET HttpContext.
- /// </remarks>
- void RedirectToProvider(bool endResponse);
- /// <summary>
/// Gets/sets the mode the Provider should use during authentication.
/// </summary>
AuthenticationRequestMode Mode { get; set; }
/// <summary>
- /// Gets the URL the user agent should be redirected to to begin the
- /// OpenID authentication process.
+ /// Gets the HTTP response the relying party should send to the user agent
+ /// to redirect it to the OpenID Provider to start the OpenID authentication process.
/// </summary>
- Uri RedirectToProviderUrl { get; }
+ IResponse RedirectingResponse { get; }
/// <summary>
/// Gets the URL that the user agent will return to after authentication
/// completes or fails at the Provider.
@@ -62,12 +53,34 @@ namespace DotNetOpenId.RelyingParty {
Realm Realm { get; }
/// <summary>
/// Gets the Claimed Identifier that the User Supplied Identifier
- /// resolved to.
+ /// resolved to. Null if the user provided an OP Identifier
+ /// (directed identity).
/// </summary>
+ /// <remarks>
+ /// Null is returned if the user is using the directed identity feature
+ /// of OpenID 2.0 to make it nearly impossible for a relying party site
+ /// to improperly store the reserved OpenID URL used for directed identity
+ /// as a user's own Identifier.
+ /// However, to test for the Directed Identity feature, please test the
+ /// <see cref="IsDirectedIdentity"/> property rather than testing this
+ /// property for a null value.
+ /// </remarks>
Identifier ClaimedIdentifier { get; }
/// <summary>
+ /// Gets whether the authenticating user has chosen to let the Provider
+ /// determine and send the ClaimedIdentifier after authentication.
+ /// </summary>
+ bool IsDirectedIdentity { get; }
+ /// <summary>
+ /// Gets information about the OpenId Provider, as advertised by the
+ /// OpenId discovery documents found at the <see cref="ClaimedIdentifier"/>
+ /// location.
+ /// </summary>
+ IProviderEndpoint Provider { get; }
+ /// <summary>
/// The detected version of OpenID implemented by the Provider.
/// </summary>
+ [Obsolete("Use Provider.Version instead.")]
Version ProviderVersion { get; }
}
}
diff --git a/src/DotNetOpenId/RelyingParty/IProviderEndpoint.cs b/src/DotNetOpenId/RelyingParty/IProviderEndpoint.cs
new file mode 100644
index 0000000..322158f
--- /dev/null
+++ b/src/DotNetOpenId/RelyingParty/IProviderEndpoint.cs
@@ -0,0 +1,51 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace DotNetOpenId.RelyingParty {
+ /// <summary>
+ /// Information published about an OpenId Provider by the
+ /// OpenId discovery documents found at a user's Claimed Identifier.
+ /// </summary>
+ /// <remarks>
+ /// Because information provided by this interface is suppplied by a
+ /// user's individually published documents, it may be incomplete or inaccurate.
+ /// </remarks>
+ public interface IProviderEndpoint {
+ /// <summary>
+ /// Checks whether the OpenId Identifier claims support for a given extension.
+ /// </summary>
+ /// <typeparam name="T">The extension whose support is being queried.</typeparam>
+ /// <returns>True if support for the extension is advertised. False otherwise.</returns>
+ /// <remarks>
+ /// Note that a true or false return value is no guarantee of a Provider's
+ /// support for or lack of support for an extension. The return value is
+ /// determined by how the authenticating user filled out his/her XRDS document only.
+ /// The only way to be sure of support for a given extension is to include
+ /// the extension in the request and see if a response comes back for that extension.
+ /// </remarks>
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter")]
+ bool IsExtensionSupported<T>() where T : Extensions.IExtension, new();
+ /// <summary>
+ /// Checks whether the OpenId Identifier claims support for a given extension.
+ /// </summary>
+ /// <param name="extensionType">The extension whose support is being queried.</param>
+ /// <returns>True if support for the extension is advertised. False otherwise.</returns>
+ /// <remarks>
+ /// Note that a true or false return value is no guarantee of a Provider's
+ /// support for or lack of support for an extension. The return value is
+ /// determined by how the authenticating user filled out his/her XRDS document only.
+ /// The only way to be sure of support for a given extension is to include
+ /// the extension in the request and see if a response comes back for that extension.
+ /// </remarks>
+ bool IsExtensionSupported(Type extensionType);
+ /// <summary>
+ /// The detected version of OpenID implemented by the Provider.
+ /// </summary>
+ Version Version { get; }
+ /// <summary>
+ /// The URL that the OpenID Provider receives authentication requests at.
+ /// </summary>
+ Uri Uri { get; }
+ }
+}
diff --git a/src/DotNetOpenId/RelyingParty/IndirectMessageRequest.cs b/src/DotNetOpenId/RelyingParty/IndirectMessageRequest.cs
new file mode 100644
index 0000000..8348374
--- /dev/null
+++ b/src/DotNetOpenId/RelyingParty/IndirectMessageRequest.cs
@@ -0,0 +1,25 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace DotNetOpenId.RelyingParty {
+ internal class IndirectMessageRequest : IEncodable {
+ public IndirectMessageRequest(Uri receivingUrl, IDictionary<string, string> fields) {
+ if (receivingUrl == null) throw new ArgumentNullException("receivingUrl");
+ if (fields == null) throw new ArgumentNullException("fields");
+ RedirectUrl = receivingUrl;
+ EncodedFields = fields;
+ }
+
+ #region IEncodable Members
+
+ 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/OpenIdRelyingParty.cs b/src/DotNetOpenId/RelyingParty/OpenIdRelyingParty.cs
index cfc09af..1729de2 100644
--- a/src/DotNetOpenId/RelyingParty/OpenIdRelyingParty.cs
+++ b/src/DotNetOpenId/RelyingParty/OpenIdRelyingParty.cs
@@ -1,22 +1,92 @@
using System;
-using System.Collections.Specialized;
-using System.Web.SessionState;
-using DotNetOpenId;
-using System.Web;
using System.Collections.Generic;
-using DotNetOpenId.Provider;
-using System.Globalization;
+using System.Collections.Specialized;
using System.Diagnostics;
+using System.Web;
namespace DotNetOpenId.RelyingParty {
/// <summary>
/// Provides the programmatic facilities to act as an OpenId consumer.
/// </summary>
+ /// <remarks>
+ /// For easier, ASP.NET designer drop-in support for adding OpenID login support,
+ /// see the <see cref="OpenIdLogin"/> or <see cref="OpenIdTextBox"/> controls.
+ /// </remarks>
+ /// <example>
+ /// <code language="ASP.NET">
+ ///&lt;h2&gt;Login Page &lt;/h2&gt;
+ ///&lt;asp:Label ID="Label1" runat="server" Text="OpenID Login" /&gt;
+ ///&lt;asp:TextBox ID="openIdBox" runat="server" /&gt;
+ ///&lt;asp:Button ID="loginButton" runat="server" Text="Login" OnClick="loginButton_Click" /&gt;
+ ///&lt;asp:CustomValidator runat="server" ID="openidValidator" ErrorMessage="Invalid OpenID Identifier"
+ /// ControlToValidate="openIdBox" EnableViewState="false" OnServerValidate="openidValidator_ServerValidate" /&gt;
+ ///&lt;br /&gt;
+ ///&lt;asp:Label ID="loginFailedLabel" runat="server" EnableViewState="False" Text="Login failed"
+ /// Visible="False" /&gt;
+ ///&lt;asp:Label ID="loginCanceledLabel" runat="server" EnableViewState="False" Text="Login canceled"
+ /// Visible="False" /&gt;
+ /// </code>
+ /// <code language="c#">
+ ///protected void openidValidator_ServerValidate(object source, ServerValidateEventArgs args) {
+ /// // This catches common typos that result in an invalid OpenID Identifier.
+ /// args.IsValid = Identifier.IsValid(args.Value);
+ ///}
+ ///
+ ///protected void loginButton_Click(object sender, EventArgs e) {
+ /// if (!Page.IsValid) return; // don't login if custom validation failed.
+ /// OpenIdRelyingParty openid = new OpenIdRelyingParty();
+ /// try {
+ /// IAuthenticationRequest request = openid.CreateRequest(openIdBox.Text);
+ /// // This is where you would add any OpenID extensions you wanted
+ /// // to include in the authentication request.
+ /// // request.AddExtension(someExtensionRequestInstance);
+ ///
+ /// // Send your visitor to their Provider for authentication.
+ /// request.RedirectToProvider();
+ /// } catch (OpenIdException 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();
+ ///
+ /// OpenIdRelyingParty openid = new OpenIdRelyingParty();
+ /// if (openid.Response != null) {
+ /// switch (openid.Response.Status) {
+ /// case AuthenticationStatus.Authenticated:
+ /// // This is where you would look for any OpenID extension responses included
+ /// // in the authentication assertion.
+ /// // var extension = openid.Response.GetExtension&lt;SomeExtensionResponseType&gt;();
+ ///
+ /// // Use FormsAuthentication to tell ASP.NET that the user is now logged in,
+ /// // with the OpenID Claimed Identifier as their username.
+ /// FormsAuthentication.RedirectFromLoginPage(openid.Response.ClaimedIdentifier, false);
+ /// break;
+ /// case AuthenticationStatus.Canceled:
+ /// loginCanceledLabel.Visible = true;
+ /// break;
+ /// case AuthenticationStatus.Failed:
+ /// loginFailedLabel.Visible = true;
+ /// break;
+ /// // We don't need to handle SetupRequired because we're not setting
+ /// // IAuthenticationRequest.Mode to immediate mode.
+ /// //case AuthenticationStatus.SetupRequired:
+ /// // break;
+ /// }
+ /// }
+ ///}
+ /// </code>
+ /// </example>
[DebuggerDisplay("isAuthenticationResponseReady: {isAuthenticationResponseReady}, stateless: {store == null}")]
public class OpenIdRelyingParty {
IRelyingPartyApplicationStore store;
Uri request;
IDictionary<string, string> query;
+ MessageEncoder encoder;
/// <summary>
/// Constructs an OpenId consumer that uses the current HttpContext request
@@ -25,7 +95,9 @@ namespace DotNetOpenId.RelyingParty {
/// <remarks>
/// This method requires a current ASP.NET HttpContext.
/// </remarks>
- public OpenIdRelyingParty() : this(HttpApplicationStore, Util.GetRequestUrlFromContext()) { }
+ public OpenIdRelyingParty()
+ : this(HttpApplicationStore,
+ Util.GetRequestUrlFromContext(), Util.GetQueryFromContext()) { }
/// <summary>
/// Constructs an OpenId consumer that uses a given querystring and IAssociationStore.
/// </summary>
@@ -39,6 +111,12 @@ namespace DotNetOpenId.RelyingParty {
/// Optional. The current incoming HTTP request that may contain an OpenId assertion.
/// If not included, any OpenId authentication assertions will not be processed.
/// </param>
+ /// <param name="query">
+ /// The name/value pairs that came in on the
+ /// QueryString of a GET request or in the entity of a POST request.
+ /// For example: (Request.HttpMethod == "GET" ? Request.QueryString : Request.Form).
+ /// This must be supplied if <paramref name="requestUrl"/> is supplied.
+ /// </param>
/// <remarks>
/// The IRelyingPartyApplicationStore must be shared across an entire web farm
/// because of the design of how nonces are stored/retrieved. Even if
@@ -47,15 +125,20 @@ namespace DotNetOpenId.RelyingParty {
/// which must therefore share the nonce information in the application
/// state store in order to stop the intruder.
/// </remarks>
- public OpenIdRelyingParty(IRelyingPartyApplicationStore store, Uri requestUrl) {
+ public OpenIdRelyingParty(IRelyingPartyApplicationStore store, Uri requestUrl, NameValueCollection query) :
+ this(store, requestUrl, Util.NameValueCollectionToDictionary(query)) {
+ }
+ OpenIdRelyingParty(IRelyingPartyApplicationStore store, Uri requestUrl, IDictionary<string, string> query) {
this.store = store;
if (store != null) {
store.ClearExpiredAssociations(); // every so often we should do this.
}
if (requestUrl != null) {
+ if (query == null) throw new ArgumentNullException("query");
this.request = requestUrl;
- this.query = Util.NameValueCollectionToDictionary(HttpUtility.ParseQueryString(requestUrl.Query));
+ this.query = query;
}
+ this.encoder = new MessageEncoder();
}
/// <summary>
@@ -79,9 +162,25 @@ 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, realm, returnToUrl, store);
+ return AuthenticationRequest.Create(userSuppliedIdentifier, realm, returnToUrl, store, encoder);
}
+ /// <summary>
+ /// Creates an authentication request to verify that a user controls
+ /// some given Identifier.
+ /// </summary>
+ /// <param name="userSuppliedIdentifier">
+ /// The Identifier supplied by the user. This may be a URL, an XRI or i-name.
+ /// </param>
+ /// <param name="realm">
+ /// The shorest URL that describes this relying party web site's address.
+ /// For example, if your login page is found at https://www.example.com/login.aspx,
+ /// your realm would typically be https://www.example.com/.
+ /// </param>
+ /// <returns>
+ /// An authentication request object that describes the HTTP response to
+ /// send to the user agent to initiate the authentication.
+ /// </returns>
/// <remarks>
/// This method requires an ASP.NET HttpContext.
/// </remarks>
@@ -117,6 +216,17 @@ namespace DotNetOpenId.RelyingParty {
|| parameterName == Token.TokenKey;
}
+ /// <summary>
+ /// Creates an authentication request to verify that a user controls
+ /// some given Identifier.
+ /// </summary>
+ /// <param name="userSuppliedIdentifier">
+ /// The Identifier supplied by the user. This may be a URL, an XRI or i-name.
+ /// </param>
+ /// <returns>
+ /// An authentication request object that describes the HTTP response to
+ /// send to the user agent to initiate the authentication.
+ /// </returns>
/// <remarks>
/// This method requires an ASP.NET HttpContext.
/// </remarks>
@@ -150,9 +260,6 @@ namespace DotNetOpenId.RelyingParty {
if (!query.ContainsKey(protocol.openid.mode))
return false;
- if (HttpContext.Current != null && !HttpContext.Current.Request.RequestType.Equals("GET", StringComparison.Ordinal))
- return false;
-
return true;
}
}
diff --git a/src/DotNetOpenId/RelyingParty/OpenIdTextBox.cs b/src/DotNetOpenId/RelyingParty/OpenIdTextBox.cs
index 0fc7c7d..2bd136a 100644
--- a/src/DotNetOpenId/RelyingParty/OpenIdTextBox.cs
+++ b/src/DotNetOpenId/RelyingParty/OpenIdTextBox.cs
@@ -553,7 +553,7 @@ namespace DotNetOpenId.RelyingParty
protected override void OnLoad(EventArgs e) {
base.OnLoad(e);
- if (!Enabled) return;
+ if (!Enabled || Page.IsPostBack) return;
var consumer = new OpenIdRelyingParty();
if (consumer.Response != null) {
string persistentString = Page.Request.QueryString[usePersistentCookieCallbackKey];
@@ -805,6 +805,7 @@ namespace DotNetOpenId.RelyingParty
if (request == null) throw new ArgumentNullException("request");
Request = request;
ClaimedIdentifier = request.ClaimedIdentifier;
+ IsDirectedIdentity = request.IsDirectedIdentity;
}
/// <summary>
/// Constructs an object with information on a completed authentication attempt
@@ -821,9 +822,15 @@ namespace DotNetOpenId.RelyingParty
/// </summary>
public bool Cancel { get; set; }
/// <summary>
- /// The Identifier the user is claiming to own.
+ /// The Identifier the user is claiming to own. Or null if the user
+ /// is using Directed Identity.
/// </summary>
public Identifier ClaimedIdentifier { get; private set; }
+ /// <summary>
+ /// Whether the user has selected to let his Provider determine
+ /// the ClaimedIdentifier to use as part of successful authentication.
+ /// </summary>
+ public bool IsDirectedIdentity { get; private set; }
/// <summary>
/// Gets the details of the OpenID authentication request,
diff --git a/src/DotNetOpenId/RelyingParty/ServiceEndpoint.cs b/src/DotNetOpenId/RelyingParty/ServiceEndpoint.cs
index 98c2e9a..cbc5c07 100644
--- a/src/DotNetOpenId/RelyingParty/ServiceEndpoint.cs
+++ b/src/DotNetOpenId/RelyingParty/ServiceEndpoint.cs
@@ -7,13 +7,15 @@ using System.Xml.XPath;
using System.IO;
using DotNetOpenId.Yadis;
using System.Diagnostics;
+using DotNetOpenId.Extensions;
+using System.Globalization;
namespace DotNetOpenId.RelyingParty {
/// <summary>
/// Represents information discovered about a user-supplied Identifier.
/// </summary>
[DebuggerDisplay("ClaimedIdentifier: {ClaimedIdentifier}, ProviderEndpoint: {ProviderEndpoint}, OpenId: {Protocol.Version}")]
- internal class ServiceEndpoint {
+ internal class ServiceEndpoint : IProviderEndpoint {
/// <summary>
/// The URL which accepts OpenID Authentication protocol messages.
/// </summary>
@@ -22,6 +24,7 @@ namespace DotNetOpenId.RelyingParty {
/// This value MUST be an absolute HTTP or HTTPS URL.
/// </remarks>
public Uri ProviderEndpoint { get; private set; }
+ Uri IProviderEndpoint.Uri { get { return ProviderEndpoint; } }
/*
/// <summary>
/// An Identifier for an OpenID Provider.
@@ -92,6 +95,41 @@ namespace DotNetOpenId.RelyingParty {
return Array.IndexOf(ProviderSupportedServiceTypeUris, extensionUri) >= 0;
}
+ public bool IsExtensionSupported(IExtension extension) {
+ if (extension == null) throw new ArgumentNullException("extension");
+
+ // Consider the primary case.
+ if (IsExtensionSupported(extension.TypeUri)) {
+ return true;
+ }
+ // Consider the secondary cases.
+ if (extension.AdditionalSupportedTypeUris != null) {
+ foreach (string extensionTypeUri in extension.AdditionalSupportedTypeUris) {
+ if (IsExtensionSupported(extensionTypeUri)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ public bool IsExtensionSupported<T>() where T : Extensions.IExtension, new() {
+ T extension = new T();
+ return IsExtensionSupported(extension);
+ }
+
+ public bool IsExtensionSupported(Type extensionType) {
+ if (extensionType == null) throw new ArgumentNullException("extensionType");
+ if (!typeof(Extensions.IExtension).IsAssignableFrom(extensionType))
+ throw new ArgumentException(string.Format(CultureInfo.CurrentCulture,
+ Strings.TypeMustImplementX, typeof(Extensions.IExtension).FullName),
+ "extensionType");
+ var extension = (Extensions.IExtension)Activator.CreateInstance(extensionType);
+ return IsExtensionSupported(extension);
+ }
+
+ Version IProviderEndpoint.Version { get { return Protocol.Version; } }
+
/// <summary>
/// Saves the discovered information about this endpoint
/// for later comparison to validate assertions.
diff --git a/src/DotNetOpenId/Provider/Response.cs b/src/DotNetOpenId/Response.cs
index c8ca8b9..d4469ec 100644
--- a/src/DotNetOpenId/Provider/Response.cs
+++ b/src/DotNetOpenId/Response.cs
@@ -3,24 +3,37 @@ using System.Collections.Specialized;
using System.Text;
using System.Net;
using System.Web;
+using System.Diagnostics;
-namespace DotNetOpenId.Provider {
+namespace DotNetOpenId {
/// <summary>
- /// A response to an OpenID request in terms a web server understands.
+ /// A response to an OpenID request in terms the host web site can forward to the user agent.
/// </summary>
class Response : IResponse {
- internal Response(HttpStatusCode code, WebHeaderCollection headers, byte[] body) {
+ /// <param name="code">The HTTP status code.</param>
+ /// <param name="headers">The collection of any HTTP headers that should be included. Cannot be null, but can be an empty collection.</param>
+ /// <param name="body">The payload of the response, if any. Cannot be null, but can be an empty array.</param>
+ /// <param name="encodableMessage">
+ /// Used to assist testing to decipher the field contents of a Response.
+ /// </param>
+ internal Response(HttpStatusCode code, WebHeaderCollection headers, byte[] body, IEncodable encodableMessage) {
+ if (headers == null) throw new ArgumentNullException("headers");
+ if (body == null) throw new ArgumentNullException("body");
+ Debug.Assert(encodableMessage != null, "For testing, this is useful to have.");
Code = code;
Headers = headers ?? new WebHeaderCollection();
Body = body;
+ EncodableMessage = encodableMessage;
}
public HttpStatusCode Code { get; private set; }
public WebHeaderCollection Headers { get; private set; }
public byte[] Body { get; private set; }
+ internal IEncodable EncodableMessage { get; private set; }
/// <summary>
/// Sends this response to the user agent or OpenId consumer.
+ /// Execution of the current page terminates after this call.
/// </summary>
/// <remarks>
/// This method requires a current ASP.NET HttpContext.
@@ -36,6 +49,7 @@ namespace DotNetOpenId.Provider {
HttpContext.Current.Response.OutputStream.Flush();
}
HttpContext.Current.Response.OutputStream.Close();
+ HttpContext.Current.Response.End();
}
}
}
diff --git a/src/DotNetOpenId/Strings.Designer.cs b/src/DotNetOpenId/Strings.Designer.cs
index 485a2b2..8a500ef 100644
--- a/src/DotNetOpenId/Strings.Designer.cs
+++ b/src/DotNetOpenId/Strings.Designer.cs
@@ -277,6 +277,15 @@ namespace DotNetOpenId {
}
/// <summary>
+ /// Looks up a localized string similar to The value &apos;{0}&apos; is not a valid URI..
+ /// </summary>
+ internal static string InvalidUri {
+ get {
+ return ResourceManager.GetString("InvalidUri", resourceCulture);
+ }
+ }
+
+ /// <summary>
/// Looks up a localized string similar to Failure parsing XRDS document..
/// </summary>
internal static string InvalidXRDSDocument {
@@ -500,5 +509,23 @@ namespace DotNetOpenId {
return ResourceManager.GetString("TypeMustImplementX", resourceCulture);
}
}
+
+ /// <summary>
+ /// Looks up a localized string similar to The URL &apos;{0}&apos; is rated unsafe and cannot be requested this way..
+ /// </summary>
+ internal static string UnsafeWebRequestDetected {
+ get {
+ return ResourceManager.GetString("UnsafeWebRequestDetected", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to Providing a DateTime whose Kind is Unspecified is not allowed..
+ /// </summary>
+ internal static string UnspecifiedDateTimeKindNotAllowed {
+ get {
+ return ResourceManager.GetString("UnspecifiedDateTimeKindNotAllowed", resourceCulture);
+ }
+ }
}
}
diff --git a/src/DotNetOpenId/Strings.resx b/src/DotNetOpenId/Strings.resx
index e225899..6455819 100644
--- a/src/DotNetOpenId/Strings.resx
+++ b/src/DotNetOpenId/Strings.resx
@@ -189,6 +189,9 @@
<data name="InvalidSignature" xml:space="preserve">
<value>The signature verification failed.</value>
</data>
+ <data name="InvalidUri" xml:space="preserve">
+ <value>The value '{0}' is not a valid URI.</value>
+ </data>
<data name="InvalidXRDSDocument" xml:space="preserve">
<value>Failure parsing XRDS document.</value>
</data>
@@ -264,4 +267,10 @@
<data name="TypeMustImplementX" xml:space="preserve">
<value>The type must implement {0}.</value>
</data>
+ <data name="UnsafeWebRequestDetected" xml:space="preserve">
+ <value>The URL '{0}' is rated unsafe and cannot be requested this way.</value>
+ </data>
+ <data name="UnspecifiedDateTimeKindNotAllowed" xml:space="preserve">
+ <value>Providing a DateTime whose Kind is Unspecified is not allowed.</value>
+ </data>
</root> \ No newline at end of file
diff --git a/src/DotNetOpenId/UntrustedWebRequest.cs b/src/DotNetOpenId/UntrustedWebRequest.cs
index 4a70e1d..852f21f 100644
--- a/src/DotNetOpenId/UntrustedWebRequest.cs
+++ b/src/DotNetOpenId/UntrustedWebRequest.cs
@@ -3,14 +3,26 @@
#endif
namespace DotNetOpenId {
using System;
+ using System.Collections.Generic;
using System.Diagnostics;
+ using System.Globalization;
using System.IO;
using System.Net;
-
+ using System.Text.RegularExpressions;
/// <summary>
/// A paranoid HTTP get/post request engine. It helps to protect against attacks from remote
- /// server leaving dangling connections, sending too much data, etc.
+ /// server leaving dangling connections, sending too much data, causing requests against
+ /// internal servers, etc.
/// </summary>
+ /// <remarks>
+ /// Protections include:
+ /// * Conservative maximum time to receive the complete response.
+ /// * Only HTTP and HTTPS schemes are permitted.
+ /// * Internal IP address ranges are not permitted: 127.*.*.*, 1::*
+ /// * Internal host names are not permitted (periods must be found in the host name)
+ /// If a particular host would be permitted but is in the blacklist, it is not allowed.
+ /// If a particular host would not be permitted but is in the whitelist, it is allowed.
+ /// </remarks>
public static class UntrustedWebRequest {
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
static int maximumBytesToRead = 1024 * 1024;
@@ -62,13 +74,116 @@ namespace DotNetOpenId {
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline")]
static UntrustedWebRequest() {
ReadWriteTimeout = TimeSpan.FromMilliseconds(800);
- Timeout = TimeSpan.FromSeconds(8);
+ Timeout = TimeSpan.FromSeconds(10);
#if LONGTIMEOUT
ReadWriteTimeout = TimeSpan.FromHours(1);
Timeout = TimeSpan.FromHours(1);
#endif
}
+ static bool isIPv6Loopback(IPAddress ip) {
+ Debug.Assert(ip != null);
+ byte[] addressBytes = ip.GetAddressBytes();
+ for (int i = 0; i < addressBytes.Length - 1; i++)
+ if (addressBytes[i] != 0) return false;
+ if (addressBytes[addressBytes.Length - 1] != 1) return false;
+ return true;
+ }
+ static ICollection<string> allowableSchemes = new List<string> { "http", "https" };
+ static ICollection<string> whitelistHosts = new List<string>();
+ /// <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>();
+ /// <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>();
+ /// <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>();
+ /// <summary>
+ /// A collection of host name regular expressions that indicate hosts that should
+ /// be rjected even if they pass standard security checks.
+ /// </summary>
+ public static ICollection<Regex> BlacklistHostsRegex { get { return blacklistHostsRegex; } }
+ static bool isHostWhitelisted(string host) {
+ return isHostInList(host, WhitelistHosts, WhitelistHostsRegex);
+ }
+ static bool isHostBlacklisted(string host) {
+ return isHostInList(host, BlacklistHosts, BlacklistHostsRegex);
+ }
+ static bool isHostInList(string host, ICollection<string> stringList, ICollection<Regex> regexList) {
+ Debug.Assert(!string.IsNullOrEmpty(host));
+ Debug.Assert(stringList != null);
+ Debug.Assert(regexList != null);
+ foreach (string testHost in stringList) {
+ if (string.Equals(host, testHost, StringComparison.OrdinalIgnoreCase))
+ return true;
+ }
+ foreach (Regex regex in regexList) {
+ if (regex.IsMatch(host))
+ return true;
+ }
+ return false;
+ }
+ static bool isUriAllowable(Uri uri) {
+ Debug.Assert(uri != null);
+ if (!allowableSchemes.Contains(uri.Scheme)) {
+ Logger.WarnFormat("Rejecting URL {0} because it uses a disallowed scheme.", uri);
+ return false;
+ }
+
+ // Allow for whitelist or blacklist to override our detection.
+ DotNetOpenId.Util.Func<string, bool> failsUnlessWhitelisted = (string reason) => {
+ if (isHostWhitelisted(uri.DnsSafeHost)) return true;
+ Logger.WarnFormat("Rejecting URL {0} because {1}.", uri, reason);
+ return false;
+ };
+
+ // Try to interpret the hostname as an IP address so we can test for internal
+ // IP address ranges. Note that IP addresses can appear in many forms
+ // (e.g. http://127.0.0.1, http://2130706433, http://0x0100007f, http://::1
+ // So we convert them to a canonical IPAddress instance, and test for all
+ // non-routable IP ranges: 10.*.*.*, 127.*.*.*, ::1
+ // Note that Uri.IsLoopback is very unreliable, not catching many of these variants.
+ IPAddress hostIPAddress;
+ if (IPAddress.TryParse(uri.DnsSafeHost, out hostIPAddress)) {
+ byte[] addressBytes = hostIPAddress.GetAddressBytes();
+ // The host is actually an IP address.
+ switch (hostIPAddress.AddressFamily) {
+ case System.Net.Sockets.AddressFamily.InterNetwork:
+ if (addressBytes[0] == 127 || addressBytes[0] == 10)
+ return failsUnlessWhitelisted("it is a loopback address.");
+ break;
+ case System.Net.Sockets.AddressFamily.InterNetworkV6:
+ if (isIPv6Loopback(hostIPAddress))
+ return failsUnlessWhitelisted("it is a loopback address.");
+ break;
+ default:
+ return failsUnlessWhitelisted("it does not use an IPv4 or IPv6 address.");
+ }
+ } else {
+ // The host is given by name. We require names to contain periods to
+ // help make sure it's not an internal address.
+ if (!uri.Host.Contains(".")) {
+ return failsUnlessWhitelisted("it does not contain a period in the host name.");
+ }
+ }
+ if (isHostBlacklisted(uri.DnsSafeHost)) {
+ Logger.WarnFormat("Rejected URL {0} because it is blacklisted.", uri);
+ return false;
+ }
+ return true;
+ }
+
/// <summary>
/// Reads a maximum number of bytes from a response stream.
/// </summary>
@@ -111,6 +226,8 @@ namespace DotNetOpenId {
static UntrustedWebResponse Request(Uri uri, byte[] body, string[] acceptTypes,
bool avoidSendingExpect100Continue) {
if (uri == null) throw new ArgumentNullException("uri");
+ if (!isUriAllowable(uri)) throw new ArgumentException(string.Format(CultureInfo.CurrentCulture,
+ Strings.UnsafeWebRequestDetected, uri), "uri");
// mock the request if a hosting unit test has configured it.
if (MockRequests != null) {
diff --git a/src/DotNetOpenId/Util.cs b/src/DotNetOpenId/Util.cs
index de6d727..61986c6 100644
--- a/src/DotNetOpenId/Util.cs
+++ b/src/DotNetOpenId/Util.cs
@@ -102,7 +102,7 @@ namespace DotNetOpenId {
}
public static IDictionary<string, string> NameValueCollectionToDictionary(NameValueCollection nvc) {
- if (nvc == null) throw new ArgumentNullException("nvc");
+ if (nvc == null) return null;
var dict = new Dictionary<string, string>(nvc.Count);
for (int i = 0; i < nvc.Count; i++) {
string key = nvc.GetKey(i);
@@ -117,6 +117,7 @@ namespace DotNetOpenId {
return dict;
}
public static NameValueCollection DictionaryToNameValueCollection(IDictionary<string, string> dict) {
+ if (dict == null) return null;
NameValueCollection nvc = new NameValueCollection(dict.Count);
foreach (var pair in dict) {
nvc.Add(pair.Key, pair.Value);
diff --git a/src/DotNetOpenId/Yadis/Yadis.cs b/src/DotNetOpenId/Yadis/Yadis.cs
index a50adff..1ae477d 100644
--- a/src/DotNetOpenId/Yadis/Yadis.cs
+++ b/src/DotNetOpenId/Yadis/Yadis.cs
@@ -7,15 +7,23 @@ using System.Xml;
using System.Xml.Serialization;
using System.Net.Mime;
using System.Web.UI.HtmlControls;
+using System.Diagnostics;
namespace DotNetOpenId.Yadis {
class Yadis {
internal const string HeaderName = "X-XRDS-Location";
public static DiscoveryResult Discover(UriIdentifier uri) {
- var response = UntrustedWebRequest.Request(uri, null,
+ UntrustedWebResponse response;
+ try {
+ response = UntrustedWebRequest.Request(uri, null,
new[] { ContentTypes.Html, ContentTypes.XHtml, ContentTypes.Xrds });
- if (response.StatusCode != System.Net.HttpStatusCode.OK) {
+ if (response.StatusCode != System.Net.HttpStatusCode.OK) {
+ return null;
+ }
+ } catch (ArgumentException ex) {
+ // Unsafe URLs generate this
+ Logger.WarnFormat("Unsafe OpenId URL detected ({0}). Request aborted. {1}", uri, ex);
return null;
}
UntrustedWebResponse response2 = null;
diff --git a/src/version.txt b/src/version.txt
index ebf14b4..21bb5e1 100644
--- a/src/version.txt
+++ b/src/version.txt
@@ -1 +1 @@
-2.1.8
+2.2.5
diff --git a/tools/decode_token.ps1 b/tools/decode_token.ps1
new file mode 100644
index 0000000..9796415
--- /dev/null
+++ b/tools/decode_token.ps1
@@ -0,0 +1,20 @@
+# This is a useful diagnostic tool for when users send in failed positive assertions
+# and you want to read what's in the token parameter.
+
+param (
+ $token = { throw "token or urlToken parameter required" },
+ $urlToken
+ )
+
+[void] [Reflection.Assembly]::LoadWithPartialName('System.Web')
+
+if ($urlToken) {
+ $token = [Web.HttpUtility]::UrlDecode($urlToken)
+}
+
+$tokenBytes = [Convert]::FromBase64String($token)
+$siglength = 32
+$decodedToken = [Text.Encoding]::UTF8.GetString($tokenBytes, $siglength, $tokenBytes.length - $siglength)
+
+Write-Host "Decoded token:"
+Write-Host $decodedToken \ No newline at end of file