diff options
45 files changed, 462 insertions, 167 deletions
diff --git a/samples/OpenIdOfflineProvider/HttpHost.cs b/samples/OpenIdOfflineProvider/HttpHost.cs index 390275a..a2558f4 100644 --- a/samples/OpenIdOfflineProvider/HttpHost.cs +++ b/samples/OpenIdOfflineProvider/HttpHost.cs @@ -121,14 +121,19 @@ namespace DotNetOpenAuth.OpenIdOfflineProvider { private void ProcessRequests() { Contract.Requires(this.listener != null); - try { - while (true) { + while (true) { + try { HttpListenerContext context = this.listener.GetContext(); this.handler(context); + } catch (HttpListenerException ex) { + if (this.listener.IsListening) { + App.Logger.Error("Unexpected exception.", ex); + } else { + // the listener is probably being shut down + App.Logger.Warn("HTTP listener is closing down.", ex); + break; + } } - } catch (HttpListenerException ex) { - // the listener is probably being shut down - App.Logger.Warn("HTTP listener is closing down.", ex); } } } diff --git a/samples/OpenIdProviderMvc/Code/ReadOnlyXmlMembershipProvider.cs b/samples/OpenIdProviderMvc/Code/ReadOnlyXmlMembershipProvider.cs index cc5a321..d66573f 100644 --- a/samples/OpenIdProviderMvc/Code/ReadOnlyXmlMembershipProvider.cs +++ b/samples/OpenIdProviderMvc/Code/ReadOnlyXmlMembershipProvider.cs @@ -237,6 +237,8 @@ } internal string GetSalt(string userName) { + // This is just a sample with no database... a real web app MUST return + // a reasonable salt here and have that salt be persistent for each user. this.ReadMembershipDataStore(); return this.users[userName].Email; } diff --git a/samples/OpenIdProviderMvc/Controllers/HomeController.cs b/samples/OpenIdProviderMvc/Controllers/HomeController.cs index 5ba08b3..fb03ce2 100644 --- a/samples/OpenIdProviderMvc/Controllers/HomeController.cs +++ b/samples/OpenIdProviderMvc/Controllers/HomeController.cs @@ -9,6 +9,7 @@ public class HomeController : Controller { public ActionResult Index() { if (Request.AcceptTypes.Contains("application/xrds+xml")) { + ViewData["OPIdentifier"] = true; return View("Xrds"); } @@ -21,6 +22,7 @@ } public ActionResult Xrds() { + ViewData["OPIdentifier"] = true; return View(); } } diff --git a/samples/OpenIdProviderMvc/Controllers/UserController.cs b/samples/OpenIdProviderMvc/Controllers/UserController.cs index 4fc2f9f..5e0c21f 100644 --- a/samples/OpenIdProviderMvc/Controllers/UserController.cs +++ b/samples/OpenIdProviderMvc/Controllers/UserController.cs @@ -7,25 +7,28 @@ namespace OpenIdProviderMvc.Controllers { using System.Web.Mvc.Ajax; public class UserController : Controller { - public ActionResult PpidIdentity() { - if (Request.AcceptTypes.Contains("application/xrds+xml")) { - return View("Xrds"); - } - - return View(); - } - - public ActionResult Identity(string id) { - var redirect = this.RedirectIfNotNormalizedRequestUri(); - if (redirect != null) { - return redirect; + /// <summary> + /// Identities the specified id. + /// </summary> + /// <param name="id">The username or anonymous identifier.</param> + /// <param name="anon">if set to <c>true</c> then <paramref name="id"/> represents an anonymous identifier rather than a username.</param> + /// <returns>The view to display.</returns> + public ActionResult Identity(string id, bool anon) { + if (!anon) { + var redirect = this.RedirectIfNotNormalizedRequestUri(id); + if (redirect != null) { + return redirect; + } } if (Request.AcceptTypes != null && Request.AcceptTypes.Contains("application/xrds+xml")) { return View("Xrds"); } - this.ViewData["username"] = id; + if (!anon) { + this.ViewData["username"] = id; + } + return View(); } @@ -33,8 +36,8 @@ namespace OpenIdProviderMvc.Controllers { return View(); } - private ActionResult RedirectIfNotNormalizedRequestUri() { - Uri normalized = Models.User.GetNormalizedClaimedIdentifier(Request.Url); + private ActionResult RedirectIfNotNormalizedRequestUri(string user) { + Uri normalized = Models.User.GetClaimedIdentifierForUser(user); if (Request.Url != normalized) { return Redirect(normalized.AbsoluteUri); } diff --git a/samples/OpenIdProviderMvc/Global.asax.cs b/samples/OpenIdProviderMvc/Global.asax.cs index 3ca6104..8390c46 100644 --- a/samples/OpenIdProviderMvc/Global.asax.cs +++ b/samples/OpenIdProviderMvc/Global.asax.cs @@ -14,17 +14,19 @@ /// visit http://go.microsoft.com/?LinkId=9394801 /// </remarks> public class MvcApplication : System.Web.HttpApplication { + private static object behaviorInitializationSyncObject = new object(); + public static void RegisterRoutes(RouteCollection routes) { routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); routes.MapRoute( "User identities", "user/{id}/{action}", - new { controller = "User", action = "Identity", id = string.Empty }); + new { controller = "User", action = "Identity", id = string.Empty, anon = false }); routes.MapRoute( "PPID identifiers", "anon", - new { controller = "User", action = "PpidIdentity", id = string.Empty }); + new { controller = "User", action = "Identity", id = string.Empty, anon = true }); routes.MapRoute( "Default", // Route name "{controller}/{action}/{id}", // URL with parameters @@ -33,8 +35,21 @@ protected void Application_Start() { RegisterRoutes(RouteTable.Routes); - DotNetOpenAuth.OpenId.Behaviors.PpidGeneration.PpidIdentifierProvider = new Code.AnonymousIdentifierProvider(); - DotNetOpenAuth.OpenId.Behaviors.GsaIcamProfile.PpidIdentifierProvider = new Code.AnonymousIdentifierProvider(); + } + + protected void Application_BeginRequest(object sender, EventArgs e) { + InitializeBehaviors(); + } + + private static void InitializeBehaviors() { + if (DotNetOpenAuth.OpenId.Behaviors.PpidGeneration.PpidIdentifierProvider == null) { + lock (behaviorInitializationSyncObject) { + if (DotNetOpenAuth.OpenId.Behaviors.PpidGeneration.PpidIdentifierProvider == null) { + DotNetOpenAuth.OpenId.Behaviors.PpidGeneration.PpidIdentifierProvider = new Code.AnonymousIdentifierProvider(); + DotNetOpenAuth.OpenId.Behaviors.GsaIcamProfile.PpidIdentifierProvider = new Code.AnonymousIdentifierProvider(); + } + } + } } } }
\ No newline at end of file diff --git a/samples/OpenIdProviderMvc/Models/User.cs b/samples/OpenIdProviderMvc/Models/User.cs index 443c004..198b8fa 100644 --- a/samples/OpenIdProviderMvc/Models/User.cs +++ b/samples/OpenIdProviderMvc/Models/User.cs @@ -8,10 +8,6 @@ using OpenIdProviderMvc.Code; internal class User { - internal static Uri PpidClaimedIdentifierBaseUri { - get { return Util.GetAppPathRootedUri("anon?id="); } - } - internal static Uri ClaimedIdentifierBaseUri { get { return Util.GetAppPathRootedUri("user/"); } } diff --git a/samples/OpenIdProviderMvc/OpenIdProviderMvc.csproj b/samples/OpenIdProviderMvc/OpenIdProviderMvc.csproj index f568538..0c01c64 100644 --- a/samples/OpenIdProviderMvc/OpenIdProviderMvc.csproj +++ b/samples/OpenIdProviderMvc/OpenIdProviderMvc.csproj @@ -92,11 +92,9 @@ <Content Include="Views\Account\ChangePassword.aspx" /> <Content Include="Views\Account\ChangePasswordSuccess.aspx" /> <Content Include="Views\Account\Register.aspx" /> - <Content Include="Views\Home\Xrds.aspx" /> + <Content Include="Views\Shared\Xrds.aspx" /> <Content Include="Views\OpenId\Provider.aspx" /> - <Content Include="Views\User\PpidIdentity.aspx" /> <Content Include="Views\User\Identity.aspx" /> - <Content Include="Views\User\Xrds.aspx" /> <Content Include="Web.config" /> <Content Include="Content\Site.css" /> <Content Include="Scripts\jquery-1.3.1.js" /> diff --git a/samples/OpenIdProviderMvc/Views/Home/Xrds.aspx b/samples/OpenIdProviderMvc/Views/Home/Xrds.aspx deleted file mode 100644 index 7b0c417..0000000 --- a/samples/OpenIdProviderMvc/Views/Home/Xrds.aspx +++ /dev/null @@ -1,19 +0,0 @@ -<%@ Page Language="C#" AutoEventWireup="true" ContentType="application/xrds+xml" %><?xml version="1.0" encoding="UTF-8"?> -<%-- -This page is a required as part of the service discovery phase of the openid -protocol (step 1). It simply renders the xml for doing service discovery of -server.aspx using the xrds mechanism. -This XRDS doc is discovered via the user.aspx page. ---%> -<xrds:XRDS - xmlns:xrds="xri://$xrds" - xmlns:openid="http://openid.net/xmlns/1.0" - xmlns="xri://$xrd*($v*2.0)"> - <XRD> - <Service priority="10"> - <Type>http://specs.openid.net/auth/2.0/server</Type> - <Type>http://openid.net/extensions/sreg/1.1</Type> - <URI><%=new Uri(Request.Url, Response.ApplyAppPathModifier("~/OpenId/Provider"))%></URI> - </Service> - </XRD> -</xrds:XRDS> diff --git a/samples/OpenIdProviderMvc/Views/Shared/Xrds.aspx b/samples/OpenIdProviderMvc/Views/Shared/Xrds.aspx new file mode 100644 index 0000000..7aad102 --- /dev/null +++ b/samples/OpenIdProviderMvc/Views/Shared/Xrds.aspx @@ -0,0 +1,31 @@ +<%@ Page Language="C#" AutoEventWireup="true" ContentType="application/xrds+xml" %> +<%@ OutputCache Duration="86400" VaryByParam="none" Location="Any" %><?xml version="1.0" encoding="UTF-8"?> +<%-- +This XRDS view is used for both the OP identifier and the user identity pages. +Only a couple of conditional checks are required to share the view, but sharing the view +makes it very easy to ensure that all the Type URIs that this server supports are included +for all XRDS discovery. +--%> +<xrds:XRDS + xmlns:xrds="xri://$xrds" + xmlns:openid="http://openid.net/xmlns/1.0" + xmlns="xri://$xrd*($v*2.0)"> + <XRD> + <Service priority="10"> +<% if (ViewData["OPIdentifier"] != null) { %> + <Type>http://specs.openid.net/auth/2.0/server</Type> +<% } else { %> + <Type>http://specs.openid.net/auth/2.0/signon</Type> +<% } %> + <Type>http://openid.net/extensions/sreg/1.1</Type> + <Type>http://axschema.org/contact/email</Type> + + <%-- + Add these types when and if the Provider supports the respective aspects of the UI extension. + <Type>http://specs.openid.net/extensions/ui/1.0/mode/popup</Type> + <Type>http://specs.openid.net/extensions/ui/1.0/lang-pref</Type> + <Type>http://specs.openid.net/extensions/ui/1.0/icon</Type>--%> + <URI><%=new Uri(Request.Url, Response.ApplyAppPathModifier("~/OpenId/Provider"))%></URI> + </Service> + </XRD> +</xrds:XRDS> diff --git a/samples/OpenIdProviderMvc/Views/User/Identity.aspx b/samples/OpenIdProviderMvc/Views/User/Identity.aspx index bb50899..51233a3 100644 --- a/samples/OpenIdProviderMvc/Views/User/Identity.aspx +++ b/samples/OpenIdProviderMvc/Views/User/Identity.aspx @@ -3,18 +3,26 @@ <%@ Register Assembly="DotNetOpenAuth" Namespace="DotNetOpenAuth.OpenId.Provider" TagPrefix="op" %> <asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server"> - <%=Html.Encode(ViewData["username"])%> - identity page + <%=Html.Encode(ViewData["username"] ?? string.Empty)%> + Identity page </asp:Content> <asp:Content runat="server" ContentPlaceHolderID="HeadContent"> - <op:IdentityEndpoint ID="IdentityEndpoint11" runat="server" ProviderEndpointUrl="~/OpenId/Provider" ProviderVersion="V11" /> - <op:IdentityEndpoint ID="IdentityEndpoint20" runat="server" ProviderEndpointUrl="~/OpenId/Provider" XrdsUrl="~/User/all/xrds" XrdsAutoAnswer="false" /> + <op:IdentityEndpoint ID="IdentityEndpoint11" runat="server" ProviderEndpointUrl="~/OpenId/Provider" + ProviderVersion="V11" /> + <op:IdentityEndpoint ID="IdentityEndpoint20" runat="server" ProviderEndpointUrl="~/OpenId/Provider" + XrdsUrl="~/User/all/xrds" XrdsAutoAnswer="false" XrdsAdvertisement="Both" /> </asp:Content> <asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server"> - <h2>This is - <%=Html.Encode(ViewData["username"])%>'s OpenID identity page </h2> - + <h2> + <% if (!string.IsNullOrEmpty(ViewData["username"] as string)) { %> + This is + <%=Html.Encode(ViewData["username"])%>'s + <% } %> + OpenID identity page + </h2> <% if (string.Equals(User.Identity.Name, ViewData["username"])) { %> - <p>This is <b>your</b> identity page. </p> + <p> + This is <b>your</b> identity page. + </p> <% } %> </asp:Content> diff --git a/samples/OpenIdProviderMvc/Views/User/PpidIdentity.aspx b/samples/OpenIdProviderMvc/Views/User/PpidIdentity.aspx deleted file mode 100644 index 655e5d6..0000000 --- a/samples/OpenIdProviderMvc/Views/User/PpidIdentity.aspx +++ /dev/null @@ -1,16 +0,0 @@ -<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage" %> - -<%@ Register Assembly="DotNetOpenAuth" Namespace="DotNetOpenAuth.OpenId.Provider" - TagPrefix="op" %> -<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server"> - Identity page -</asp:Content> -<asp:Content runat="server" ContentPlaceHolderID="HeadContent"> - <op:IdentityEndpoint ID="IdentityEndpoint11" runat="server" ProviderEndpointUrl="~/OpenId/PpidProvider" - ProviderVersion="V11" /> - <op:IdentityEndpoint ID="IdentityEndpoint20" runat="server" ProviderEndpointUrl="~/OpenId/PpidProvider" - XrdsUrl="~/User/all/xrds" XrdsAutoAnswer="false" /> -</asp:Content> -<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server"> - <h2>OpenID identity page </h2> -</asp:Content> diff --git a/samples/OpenIdProviderMvc/Views/User/Xrds.aspx b/samples/OpenIdProviderMvc/Views/User/Xrds.aspx deleted file mode 100644 index 452742c..0000000 --- a/samples/OpenIdProviderMvc/Views/User/Xrds.aspx +++ /dev/null @@ -1,15 +0,0 @@ -<%@ Page Language="C#" AutoEventWireup="true" ContentType="application/xrds+xml" %><?xml version="1.0" encoding="UTF-8"?> -<XRDS xmlns="xri://$xrds" xmlns:openid="http://openid.net/xmlns/1.0"> - <XRD xmlns="xri://$xrd*($v*2.0)"> - <Service priority="10"> - <Type>http://specs.openid.net/auth/2.0/signon</Type> - <Type>http://openid.net/extensions/sreg/1.1</Type> - <URI><%=new Uri(Request.Url, Response.ApplyAppPathModifier("~/OpenId/Provider"))%></URI> - </Service> - <Service priority="20"> - <Type>http://openid.net/signon/1.0</Type> - <Type>http://openid.net/extensions/sreg/1.1</Type> - <URI><%=new Uri(Request.Url, Response.ApplyAppPathModifier("~/OpenId/Provider"))%></URI> - </Service> - </XRD> -</XRDS> diff --git a/samples/OpenIdProviderWebForms/Code/CustomStore.cs b/samples/OpenIdProviderWebForms/Code/CustomStore.cs index d8181fe..7face0b 100644 --- a/samples/OpenIdProviderWebForms/Code/CustomStore.cs +++ b/samples/OpenIdProviderWebForms/Code/CustomStore.cs @@ -109,13 +109,14 @@ namespace OpenIdProviderWebForms.Code { // at you in the result of a race condition somewhere in your web site UI code // and display some message to have the user try to log in again, and possibly // warn them about a replay attack. + timestamp = timestamp.ToLocalTime(); lock (this) { - if (dataSet.Nonce.FindByCodeContext(nonce, context) != null) { + if (dataSet.Nonce.FindByIssuedCodeContext(timestamp, nonce, context) != null) { return false; } TimeSpan maxMessageAge = DotNetOpenAuth.Configuration.DotNetOpenAuthSection.Configuration.Messaging.MaximumMessageLifetime; - dataSet.Nonce.AddNonceRow(context, nonce, timestamp.ToLocalTime(), (timestamp + maxMessageAge).ToLocalTime()); + dataSet.Nonce.AddNonceRow(context, nonce, timestamp, timestamp + maxMessageAge); return true; } } diff --git a/samples/OpenIdProviderWebForms/Code/CustomStoreDataSet.Designer.cs b/samples/OpenIdProviderWebForms/Code/CustomStoreDataSet.Designer.cs index d836261..813ff62 100644 --- a/samples/OpenIdProviderWebForms/Code/CustomStoreDataSet.Designer.cs +++ b/samples/OpenIdProviderWebForms/Code/CustomStoreDataSet.Designer.cs @@ -1,7 +1,7 @@ //------------------------------------------------------------------------------ // <auto-generated> // This code was generated by a tool. -// Runtime Version:2.0.50727.3521 +// Runtime Version:2.0.50727.4927 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. @@ -678,8 +678,9 @@ namespace OpenIdProviderWebForms.Code { } [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - public NonceRow FindByCodeContext(string Code, string Context) { + public NonceRow FindByIssuedCodeContext(System.DateTime Issued, string Code, string Context) { return ((NonceRow)(this.Rows.Find(new object[] { + Issued, Code, Context}))); } @@ -715,6 +716,7 @@ namespace OpenIdProviderWebForms.Code { this.columnExpires = new global::System.Data.DataColumn("Expires", typeof(global::System.DateTime), null, global::System.Data.MappingType.Element); base.Columns.Add(this.columnExpires); this.Constraints.Add(new global::System.Data.UniqueConstraint("Constraint1", new global::System.Data.DataColumn[] { + this.columnIssued, this.columnCode, this.columnContext}, true)); this.columnContext.AllowDBNull = false; diff --git a/samples/OpenIdProviderWebForms/Code/CustomStoreDataSet.xsd b/samples/OpenIdProviderWebForms/Code/CustomStoreDataSet.xsd index 295fe74..04a96eb 100644 --- a/samples/OpenIdProviderWebForms/Code/CustomStoreDataSet.xsd +++ b/samples/OpenIdProviderWebForms/Code/CustomStoreDataSet.xsd @@ -15,20 +15,20 @@ <xs:element name="Association" msprop:Generator_UserTableName="Association" msprop:Generator_RowDeletedName="AssociationRowDeleted" msprop:Generator_RowChangedName="AssociationRowChanged" msprop:Generator_RowClassName="AssociationRow" msprop:Generator_RowChangingName="AssociationRowChanging" msprop:Generator_RowEvArgName="AssociationRowChangeEvent" msprop:Generator_RowEvHandlerName="AssociationRowChangeEventHandler" msprop:Generator_TableClassName="AssociationDataTable" msprop:Generator_TableVarName="tableAssociation" msprop:Generator_RowDeletingName="AssociationRowDeleting" msprop:Generator_TablePropName="Association"> <xs:complexType> <xs:sequence> - <xs:element name="DistinguishingFactor" msprop:Generator_UserColumnName="DistinguishingFactor" msprop:Generator_ColumnPropNameInRow="DistinguishingFactor" msprop:Generator_ColumnVarNameInTable="columnDistinguishingFactor" msprop:Generator_ColumnPropNameInTable="DistinguishingFactorColumn" type="xs:string" /> - <xs:element name="Handle" msprop:Generator_UserColumnName="Handle" msprop:Generator_ColumnPropNameInRow="Handle" msprop:Generator_ColumnVarNameInTable="columnHandle" msprop:Generator_ColumnPropNameInTable="HandleColumn" type="xs:string" /> - <xs:element name="Expires" msprop:Generator_UserColumnName="Expires" msprop:Generator_ColumnPropNameInRow="Expires" msprop:Generator_ColumnVarNameInTable="columnExpires" msprop:Generator_ColumnPropNameInTable="ExpiresColumn" type="xs:dateTime" /> - <xs:element name="PrivateData" msprop:Generator_UserColumnName="PrivateData" msprop:Generator_ColumnPropNameInRow="PrivateData" msprop:Generator_ColumnVarNameInTable="columnPrivateData" msprop:Generator_ColumnPropNameInTable="PrivateDataColumn" type="xs:base64Binary" /> + <xs:element name="DistinguishingFactor" msprop:Generator_UserColumnName="DistinguishingFactor" msprop:Generator_ColumnVarNameInTable="columnDistinguishingFactor" msprop:Generator_ColumnPropNameInRow="DistinguishingFactor" msprop:Generator_ColumnPropNameInTable="DistinguishingFactorColumn" type="xs:string" /> + <xs:element name="Handle" msprop:Generator_UserColumnName="Handle" msprop:Generator_ColumnVarNameInTable="columnHandle" msprop:Generator_ColumnPropNameInRow="Handle" msprop:Generator_ColumnPropNameInTable="HandleColumn" type="xs:string" /> + <xs:element name="Expires" msprop:Generator_UserColumnName="Expires" msprop:Generator_ColumnVarNameInTable="columnExpires" msprop:Generator_ColumnPropNameInRow="Expires" msprop:Generator_ColumnPropNameInTable="ExpiresColumn" type="xs:dateTime" /> + <xs:element name="PrivateData" msprop:Generator_UserColumnName="PrivateData" msprop:Generator_ColumnVarNameInTable="columnPrivateData" msprop:Generator_ColumnPropNameInRow="PrivateData" msprop:Generator_ColumnPropNameInTable="PrivateDataColumn" type="xs:base64Binary" /> </xs:sequence> </xs:complexType> </xs:element> <xs:element name="Nonce" msprop:Generator_UserTableName="Nonce" msprop:Generator_RowDeletedName="NonceRowDeleted" msprop:Generator_RowChangedName="NonceRowChanged" msprop:Generator_RowClassName="NonceRow" msprop:Generator_RowChangingName="NonceRowChanging" msprop:Generator_RowEvArgName="NonceRowChangeEvent" msprop:Generator_RowEvHandlerName="NonceRowChangeEventHandler" msprop:Generator_TableClassName="NonceDataTable" msprop:Generator_TableVarName="tableNonce" msprop:Generator_RowDeletingName="NonceRowDeleting" msprop:Generator_TablePropName="Nonce"> <xs:complexType> <xs:sequence> - <xs:element name="Context" msprop:Generator_UserColumnName="Context" msprop:Generator_ColumnPropNameInRow="Context" msprop:Generator_ColumnVarNameInTable="columnContext" msprop:Generator_ColumnPropNameInTable="ContextColumn" type="xs:string" /> - <xs:element name="Code" msprop:Generator_UserColumnName="Code" msprop:Generator_ColumnVarNameInTable="columnCode" msprop:Generator_ColumnPropNameInRow="Code" msprop:Generator_ColumnPropNameInTable="CodeColumn" type="xs:string" /> - <xs:element name="Issued" msprop:Generator_UserColumnName="Issued" msprop:Generator_ColumnVarNameInTable="columnIssued" msprop:Generator_ColumnPropNameInRow="Issued" msprop:Generator_ColumnPropNameInTable="IssuedColumn" type="xs:dateTime" /> - <xs:element name="Expires" msprop:Generator_UserColumnName="Expires" msprop:Generator_ColumnVarNameInTable="columnExpires" msprop:Generator_ColumnPropNameInRow="Expires" msprop:Generator_ColumnPropNameInTable="ExpiresColumn" type="xs:dateTime" /> + <xs:element name="Context" msprop:Generator_UserColumnName="Context" msprop:Generator_ColumnVarNameInTable="columnContext" msprop:Generator_ColumnPropNameInRow="Context" msprop:Generator_ColumnPropNameInTable="ContextColumn" type="xs:string" /> + <xs:element name="Code" msprop:Generator_UserColumnName="Code" msprop:Generator_ColumnPropNameInRow="Code" msprop:Generator_ColumnVarNameInTable="columnCode" msprop:Generator_ColumnPropNameInTable="CodeColumn" type="xs:string" /> + <xs:element name="Issued" msprop:Generator_UserColumnName="Issued" msprop:Generator_ColumnPropNameInRow="Issued" msprop:Generator_ColumnVarNameInTable="columnIssued" msprop:Generator_ColumnPropNameInTable="IssuedColumn" type="xs:dateTime" /> + <xs:element name="Expires" msprop:Generator_UserColumnName="Expires" msprop:Generator_ColumnPropNameInRow="Expires" msprop:Generator_ColumnVarNameInTable="columnExpires" msprop:Generator_ColumnPropNameInTable="ExpiresColumn" type="xs:dateTime" /> </xs:sequence> </xs:complexType> </xs:element> @@ -41,6 +41,7 @@ </xs:unique> <xs:unique name="Constraint1" msdata:PrimaryKey="true"> <xs:selector xpath=".//mstns:Nonce" /> + <xs:field xpath="mstns:Issued" /> <xs:field xpath="mstns:Code" /> <xs:field xpath="mstns:Context" /> </xs:unique> diff --git a/samples/OpenIdProviderWebForms/Code/CustomStoreDataSet.xss b/samples/OpenIdProviderWebForms/Code/CustomStoreDataSet.xss index ede3b42..b19f728 100644 --- a/samples/OpenIdProviderWebForms/Code/CustomStoreDataSet.xss +++ b/samples/OpenIdProviderWebForms/Code/CustomStoreDataSet.xss @@ -7,7 +7,7 @@ <DiagramLayout xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" ex:showrelationlabel="False" ViewPortX="-10" ViewPortY="-10" xmlns:ex="urn:schemas-microsoft-com:xml-msdatasource-layout-extended" xmlns="urn:schemas-microsoft-com:xml-msdatasource-layout"> <Shapes> <Shape ID="DesignTable:Association" ZOrder="2" X="349" Y="83" Height="105" Width="154" AdapterExpanded="true" DataTableExpanded="true" OldAdapterHeight="0" OldDataTableHeight="0" SplitterPosition="101" /> - <Shape ID="DesignTable:Nonce" ZOrder="1" X="567" Y="77" Height="86" Width="150" AdapterExpanded="true" DataTableExpanded="true" OldAdapterHeight="0" OldDataTableHeight="0" SplitterPosition="86" /> + <Shape ID="DesignTable:Nonce" ZOrder="1" X="567" Y="77" Height="125" Width="150" AdapterExpanded="true" DataTableExpanded="true" OldAdapterHeight="0" OldDataTableHeight="0" SplitterPosition="121" /> </Shapes> <Connectors /> </DiagramLayout>
\ No newline at end of file diff --git a/samples/OpenIdRelyingPartyWebForms/Code/CustomStore.cs b/samples/OpenIdRelyingPartyWebForms/Code/CustomStore.cs index 2363aec..07f209b 100644 --- a/samples/OpenIdRelyingPartyWebForms/Code/CustomStore.cs +++ b/samples/OpenIdRelyingPartyWebForms/Code/CustomStore.cs @@ -64,13 +64,14 @@ // at you in the result of a race condition somewhere in your web site UI code // and display some message to have the user try to log in again, and possibly // warn them about a replay attack. + timestamp = timestamp.ToLocalTime(); lock (this) { - if (dataSet.Nonce.FindByCodeContext(nonce, context) != null) { + if (dataSet.Nonce.FindByIssuedCodeContext(timestamp, nonce, context) != null) { return false; } TimeSpan maxMessageAge = DotNetOpenAuth.Configuration.DotNetOpenAuthSection.Configuration.Messaging.MaximumMessageLifetime; - dataSet.Nonce.AddNonceRow(context, nonce, timestamp.ToLocalTime(), (timestamp + maxMessageAge).ToLocalTime()); + dataSet.Nonce.AddNonceRow(context, nonce, timestamp, timestamp + maxMessageAge); return true; } } diff --git a/samples/OpenIdRelyingPartyWebForms/Code/CustomStoreDataSet.xsd b/samples/OpenIdRelyingPartyWebForms/Code/CustomStoreDataSet.xsd index b80310e..fa161fd 100644 --- a/samples/OpenIdRelyingPartyWebForms/Code/CustomStoreDataSet.xsd +++ b/samples/OpenIdRelyingPartyWebForms/Code/CustomStoreDataSet.xsd @@ -15,20 +15,20 @@ <xs:element name="Association" msprop:Generator_UserTableName="Association" msprop:Generator_RowDeletedName="AssociationRowDeleted" msprop:Generator_RowChangedName="AssociationRowChanged" msprop:Generator_RowClassName="AssociationRow" msprop:Generator_RowChangingName="AssociationRowChanging" msprop:Generator_RowEvArgName="AssociationRowChangeEvent" msprop:Generator_RowEvHandlerName="AssociationRowChangeEventHandler" msprop:Generator_TableClassName="AssociationDataTable" msprop:Generator_TableVarName="tableAssociation" msprop:Generator_RowDeletingName="AssociationRowDeleting" msprop:Generator_TablePropName="Association"> <xs:complexType> <xs:sequence> - <xs:element name="DistinguishingFactor" msprop:Generator_UserColumnName="DistinguishingFactor" msprop:Generator_ColumnVarNameInTable="columnDistinguishingFactor" msprop:Generator_ColumnPropNameInRow="DistinguishingFactor" msprop:Generator_ColumnPropNameInTable="DistinguishingFactorColumn" type="xs:string" /> - <xs:element name="Handle" msprop:Generator_UserColumnName="Handle" msprop:Generator_ColumnVarNameInTable="columnHandle" msprop:Generator_ColumnPropNameInRow="Handle" msprop:Generator_ColumnPropNameInTable="HandleColumn" type="xs:string" /> - <xs:element name="Expires" msprop:Generator_UserColumnName="Expires" msprop:Generator_ColumnVarNameInTable="columnExpires" msprop:Generator_ColumnPropNameInRow="Expires" msprop:Generator_ColumnPropNameInTable="ExpiresColumn" type="xs:dateTime" /> - <xs:element name="PrivateData" msprop:Generator_UserColumnName="PrivateData" msprop:Generator_ColumnVarNameInTable="columnPrivateData" msprop:Generator_ColumnPropNameInRow="PrivateData" msprop:Generator_ColumnPropNameInTable="PrivateDataColumn" type="xs:base64Binary" /> + <xs:element name="DistinguishingFactor" msprop:Generator_UserColumnName="DistinguishingFactor" msprop:Generator_ColumnPropNameInRow="DistinguishingFactor" msprop:Generator_ColumnVarNameInTable="columnDistinguishingFactor" msprop:Generator_ColumnPropNameInTable="DistinguishingFactorColumn" type="xs:string" /> + <xs:element name="Handle" msprop:Generator_UserColumnName="Handle" msprop:Generator_ColumnPropNameInRow="Handle" msprop:Generator_ColumnVarNameInTable="columnHandle" msprop:Generator_ColumnPropNameInTable="HandleColumn" type="xs:string" /> + <xs:element name="Expires" msprop:Generator_UserColumnName="Expires" msprop:Generator_ColumnPropNameInRow="Expires" msprop:Generator_ColumnVarNameInTable="columnExpires" msprop:Generator_ColumnPropNameInTable="ExpiresColumn" type="xs:dateTime" /> + <xs:element name="PrivateData" msprop:Generator_UserColumnName="PrivateData" msprop:Generator_ColumnPropNameInRow="PrivateData" msprop:Generator_ColumnVarNameInTable="columnPrivateData" msprop:Generator_ColumnPropNameInTable="PrivateDataColumn" type="xs:base64Binary" /> </xs:sequence> </xs:complexType> </xs:element> <xs:element name="Nonce" msprop:Generator_UserTableName="Nonce" msprop:Generator_RowDeletedName="NonceRowDeleted" msprop:Generator_RowChangedName="NonceRowChanged" msprop:Generator_RowClassName="NonceRow" msprop:Generator_RowChangingName="NonceRowChanging" msprop:Generator_RowEvArgName="NonceRowChangeEvent" msprop:Generator_RowEvHandlerName="NonceRowChangeEventHandler" msprop:Generator_TableClassName="NonceDataTable" msprop:Generator_TableVarName="tableNonce" msprop:Generator_RowDeletingName="NonceRowDeleting" msprop:Generator_TablePropName="Nonce"> <xs:complexType> <xs:sequence> - <xs:element name="Context" msprop:Generator_UserColumnName="Context" msprop:Generator_ColumnPropNameInRow="Context" msprop:Generator_ColumnVarNameInTable="columnContext" msprop:Generator_ColumnPropNameInTable="ContextColumn" type="xs:string" /> - <xs:element name="Code" msprop:Generator_UserColumnName="Code" msprop:Generator_ColumnPropNameInRow="Code" msprop:Generator_ColumnVarNameInTable="columnCode" msprop:Generator_ColumnPropNameInTable="CodeColumn" type="xs:string" /> - <xs:element name="Issued" msprop:Generator_UserColumnName="Issued" msprop:Generator_ColumnPropNameInRow="Issued" msprop:Generator_ColumnVarNameInTable="columnIssued" msprop:Generator_ColumnPropNameInTable="IssuedColumn" type="xs:dateTime" /> - <xs:element name="Expires" msprop:Generator_UserColumnName="Expires" msprop:Generator_ColumnVarNameInTable="columnExpires" msprop:Generator_ColumnPropNameInRow="Expires" msprop:Generator_ColumnPropNameInTable="ExpiresColumn" type="xs:dateTime" /> + <xs:element name="Context" msprop:Generator_UserColumnName="Context" msprop:Generator_ColumnVarNameInTable="columnContext" msprop:Generator_ColumnPropNameInRow="Context" msprop:Generator_ColumnPropNameInTable="ContextColumn" type="xs:string" /> + <xs:element name="Code" msprop:Generator_UserColumnName="Code" msprop:Generator_ColumnVarNameInTable="columnCode" msprop:Generator_ColumnPropNameInRow="Code" msprop:Generator_ColumnPropNameInTable="CodeColumn" type="xs:string" /> + <xs:element name="Issued" msprop:Generator_UserColumnName="Issued" msprop:Generator_ColumnVarNameInTable="columnIssued" msprop:Generator_ColumnPropNameInRow="Issued" msprop:Generator_ColumnPropNameInTable="IssuedColumn" type="xs:dateTime" /> + <xs:element name="Expires" msprop:Generator_UserColumnName="Expires" msprop:Generator_ColumnPropNameInRow="Expires" msprop:Generator_ColumnVarNameInTable="columnExpires" msprop:Generator_ColumnPropNameInTable="ExpiresColumn" type="xs:dateTime" /> </xs:sequence> </xs:complexType> </xs:element> @@ -41,6 +41,7 @@ </xs:unique> <xs:unique name="Constraint1" msdata:PrimaryKey="true"> <xs:selector xpath=".//mstns:Nonce" /> + <xs:field xpath="mstns:Issued" /> <xs:field xpath="mstns:Code" /> <xs:field xpath="mstns:Context" /> </xs:unique> diff --git a/samples/OpenIdRelyingPartyWebForms/Code/CustomStoreDataSet.xss b/samples/OpenIdRelyingPartyWebForms/Code/CustomStoreDataSet.xss index 483a137..ab8f226 100644 --- a/samples/OpenIdRelyingPartyWebForms/Code/CustomStoreDataSet.xss +++ b/samples/OpenIdRelyingPartyWebForms/Code/CustomStoreDataSet.xss @@ -7,7 +7,7 @@ <DiagramLayout xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" ex:showrelationlabel="False" ViewPortX="0" ViewPortY="0" xmlns:ex="urn:schemas-microsoft-com:xml-msdatasource-layout-extended" xmlns="urn:schemas-microsoft-com:xml-msdatasource-layout"> <Shapes> <Shape ID="DesignTable:Association" ZOrder="2" X="349" Y="83" Height="105" Width="154" AdapterExpanded="true" DataTableExpanded="true" OldAdapterHeight="0" OldDataTableHeight="0" SplitterPosition="101" /> - <Shape ID="DesignTable:Nonce" ZOrder="1" X="604" Y="86" Height="86" Width="150" AdapterExpanded="true" DataTableExpanded="true" OldAdapterHeight="0" OldDataTableHeight="0" SplitterPosition="82" /> + <Shape ID="DesignTable:Nonce" ZOrder="1" X="604" Y="86" Height="125" Width="150" AdapterExpanded="true" DataTableExpanded="true" OldAdapterHeight="0" OldDataTableHeight="0" SplitterPosition="121" /> </Shapes> <Connectors /> </DiagramLayout>
\ No newline at end of file diff --git a/samples/OpenIdRelyingPartyWebForms/Code/CustomStoreDataSet1.Designer.cs b/samples/OpenIdRelyingPartyWebForms/Code/CustomStoreDataSet1.Designer.cs index 580b1fa..0c0e194 100644 --- a/samples/OpenIdRelyingPartyWebForms/Code/CustomStoreDataSet1.Designer.cs +++ b/samples/OpenIdRelyingPartyWebForms/Code/CustomStoreDataSet1.Designer.cs @@ -1,7 +1,7 @@ //------------------------------------------------------------------------------ // <auto-generated> // This code was generated by a tool. -// Runtime Version:2.0.50727.4912 +// Runtime Version:2.0.50727.4927 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. @@ -678,8 +678,9 @@ namespace OpenIdRelyingPartyWebForms.Code { } [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - public NonceRow FindByCodeContext(string Code, string Context) { + public NonceRow FindByIssuedCodeContext(System.DateTime Issued, string Code, string Context) { return ((NonceRow)(this.Rows.Find(new object[] { + Issued, Code, Context}))); } @@ -715,6 +716,7 @@ namespace OpenIdRelyingPartyWebForms.Code { this.columnExpires = new global::System.Data.DataColumn("Expires", typeof(global::System.DateTime), null, global::System.Data.MappingType.Element); base.Columns.Add(this.columnExpires); this.Constraints.Add(new global::System.Data.UniqueConstraint("Constraint1", new global::System.Data.DataColumn[] { + this.columnIssued, this.columnCode, this.columnContext}, true)); this.columnContext.AllowDBNull = false; diff --git a/samples/OpenIdRelyingPartyWebForms/xrds.aspx b/samples/OpenIdRelyingPartyWebForms/xrds.aspx index 99a535c..92983fd 100644 --- a/samples/OpenIdRelyingPartyWebForms/xrds.aspx +++ b/samples/OpenIdRelyingPartyWebForms/xrds.aspx @@ -21,5 +21,9 @@ is default.aspx. <URI priority="5"><%=new Uri(Request.Url, Response.ApplyAppPathModifier("~/loginPlusOAuth.aspx"))%></URI> <URI priority="6"><%=new Uri(Request.Url, Response.ApplyAppPathModifier("~/loginPlusOAuthSampleOP.aspx"))%></URI> </Service> + <Service> + <Type>http://specs.openid.net/extensions/ui/icon</Type> + <URI><%=new Uri(Request.Url, Response.ApplyAppPathModifier("~/images/dotnetopenid_tiny.gif"))%></URI> + </Service> </XRD> </xrds:XRDS> diff --git a/src/DotNetOpenAuth.Test/DotNetOpenAuth.Test.csproj b/src/DotNetOpenAuth.Test/DotNetOpenAuth.Test.csproj index b01eea5..27ea955 100644 --- a/src/DotNetOpenAuth.Test/DotNetOpenAuth.Test.csproj +++ b/src/DotNetOpenAuth.Test/DotNetOpenAuth.Test.csproj @@ -169,6 +169,7 @@ <Compile Include="Messaging\ErrorUtilitiesTests.cs" /> <Compile Include="Messaging\MessageSerializerTests.cs" /> <Compile Include="Messaging\MultipartPostPartTests.cs" /> + <Compile Include="Messaging\OutgoingWebResponseTests.cs" /> <Compile Include="Messaging\Reflection\MessageDescriptionTests.cs" /> <Compile Include="Messaging\Reflection\MessageDictionaryTests.cs" /> <Compile Include="Messaging\MessagingTestBase.cs" /> @@ -330,4 +331,4 @@ </ItemGroup> <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" /> <Import Project="..\..\tools\DotNetOpenAuth.Versioning.targets" /> -</Project>
\ No newline at end of file +</Project> diff --git a/src/DotNetOpenAuth.Test/Messaging/OutgoingWebResponseTests.cs b/src/DotNetOpenAuth.Test/Messaging/OutgoingWebResponseTests.cs new file mode 100644 index 0000000..35f9259 --- /dev/null +++ b/src/DotNetOpenAuth.Test/Messaging/OutgoingWebResponseTests.cs @@ -0,0 +1,36 @@ +//----------------------------------------------------------------------- +// <copyright file="OutgoingWebResponseTests.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Test.Messaging { + using System.Net; + using System.Text; + using DotNetOpenAuth.Messaging; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + [TestClass] + public class OutgoingWebResponseTests { + /// <summary> + /// Verifies that setting the Body property correctly converts to a byte stream. + /// </summary> + [TestMethod] + public void SetBodyToByteStream() { + var response = new OutgoingWebResponse(); + string stringValue = "abc"; + response.Body = stringValue; + Assert.AreEqual(stringValue.Length, response.ResponseStream.Length); + + // Verify that the actual bytes are correct. + Encoding encoding = new UTF8Encoding(false); // avoid emitting a byte-order mark + var expectedBuffer = encoding.GetBytes(stringValue); + var actualBuffer = new byte[stringValue.Length]; + Assert.AreEqual(stringValue.Length, response.ResponseStream.Read(actualBuffer, 0, stringValue.Length)); + CollectionAssert.AreEqual(expectedBuffer, actualBuffer); + + // Verify that the header was set correctly. + Assert.AreEqual(encoding.HeaderName, response.Headers[HttpResponseHeader.ContentEncoding]); + } + } +} diff --git a/src/DotNetOpenAuth.Test/Mocks/MockRealm.cs b/src/DotNetOpenAuth.Test/Mocks/MockRealm.cs index f89f119..dd17735 100644 --- a/src/DotNetOpenAuth.Test/Mocks/MockRealm.cs +++ b/src/DotNetOpenAuth.Test/Mocks/MockRealm.cs @@ -37,7 +37,7 @@ namespace DotNetOpenAuth.Test.Mocks { /// <returns> /// The details of the endpoints if found, otherwise null. /// </returns> - internal override IEnumerable<RelyingPartyEndpointDescription> Discover(IDirectWebRequestHandler requestHandler, bool allowRedirects) { + internal override IEnumerable<RelyingPartyEndpointDescription> DiscoverReturnToEndpoints(IDirectWebRequestHandler requestHandler, bool allowRedirects) { return this.relyingPartyDescriptions; } } diff --git a/src/DotNetOpenAuth.Test/OpenId/Extensions/UI/UIRequestTests.cs b/src/DotNetOpenAuth.Test/OpenId/Extensions/UI/UIRequestTests.cs index f69fc8b..7a60a32 100644 --- a/src/DotNetOpenAuth.Test/OpenId/Extensions/UI/UIRequestTests.cs +++ b/src/DotNetOpenAuth.Test/OpenId/Extensions/UI/UIRequestTests.cs @@ -16,15 +16,30 @@ namespace DotNetOpenAuth.Test.OpenId.Extensions.UI { public void Defaults() { UIRequest request = new UIRequest(); Assert.AreEqual("popup", request.Mode); - Assert.AreEqual(CultureInfo.CurrentUICulture, request.LanguagePreference); + Assert.AreEqual(1, request.LanguagePreference.Length); + Assert.AreEqual(CultureInfo.CurrentUICulture, request.LanguagePreference[0]); } [TestMethod] - public void LanguagePreferenceEncoding() { + public void LanguagePreferenceEncodingDecoding() { var request = new UIRequest(); - request.LanguagePreference = new CultureInfo("en-US"); MessageDictionary dictionary = this.MessageDescriptions.GetAccessor(request); + + request.LanguagePreference = new[] { new CultureInfo("en-US") }; Assert.AreEqual("en-US", dictionary["lang"]); + + request.LanguagePreference = new[] { new CultureInfo("en-US"), new CultureInfo("es-ES") }; + Assert.AreEqual("en-US,es-ES", dictionary["lang"]); + + // Now test decoding + dictionary["lang"] = "en-US"; + Assert.AreEqual(1, request.LanguagePreference.Length); + Assert.AreEqual(new CultureInfo("en-US"), request.LanguagePreference[0]); + + dictionary["lang"] = "en-US,es-ES"; + Assert.AreEqual(2, request.LanguagePreference.Length); + Assert.AreEqual(new CultureInfo("en-US"), request.LanguagePreference[0]); + Assert.AreEqual(new CultureInfo("es-ES"), request.LanguagePreference[1]); } [TestMethod] diff --git a/src/DotNetOpenAuth.sln b/src/DotNetOpenAuth.sln index 54b81f3..2c15c50 100644 --- a/src/DotNetOpenAuth.sln +++ b/src/DotNetOpenAuth.sln @@ -7,7 +7,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotNetOpenAuth.Test", "DotN EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{20B5E173-C3C4-49F8-BD25-E69044075B4D}" ProjectSection(SolutionItems) = preProject - dotnetopenauth.vsmdi = dotnetopenauth.vsmdi + DotNetOpenAuth.vsmdi = DotNetOpenAuth.vsmdi + ..\LICENSE.txt = ..\LICENSE.txt LocalTestRun.testrunconfig = LocalTestRun.testrunconfig ..\doc\README.Bin.html = ..\doc\README.Bin.html ..\doc\README.html = ..\doc\README.html diff --git a/src/DotNetOpenAuth/Configuration/DotNetOpenAuth.xsd b/src/DotNetOpenAuth/Configuration/DotNetOpenAuth.xsd index ea32f4c..a214053 100644 --- a/src/DotNetOpenAuth/Configuration/DotNetOpenAuth.xsd +++ b/src/DotNetOpenAuth/Configuration/DotNetOpenAuth.xsd @@ -197,6 +197,15 @@ </xs:choice> <xs:attribute name="requireSsl" type="xs:boolean" default="false" /> <xs:attribute name="protectDownlevelReplayAttacks" type="xs:boolean" /> + <xs:attribute name="unsolicitedAssertionVerification"> + <xs:simpleType> + <xs:restriction base="xs:NMTOKEN"> + <xs:enumeration value="RequireSuccess" /> + <xs:enumeration value="LogWarningOnFailure" /> + <xs:enumeration value="NeverVerify" /> + </xs:restriction> + </xs:simpleType> + </xs:attribute> <xs:attribute name="minimumHashBitLength" type="xs:int" /> <xs:attribute name="maximumHashBitLength" type="xs:int" /> </xs:complexType> diff --git a/src/DotNetOpenAuth/Configuration/OpenIdProviderSecuritySettingsElement.cs b/src/DotNetOpenAuth/Configuration/OpenIdProviderSecuritySettingsElement.cs index 457955c..cd927f2 100644 --- a/src/DotNetOpenAuth/Configuration/OpenIdProviderSecuritySettingsElement.cs +++ b/src/DotNetOpenAuth/Configuration/OpenIdProviderSecuritySettingsElement.cs @@ -41,6 +41,11 @@ namespace DotNetOpenAuth.Configuration { private const string RequireSslConfigName = "requireSsl"; /// <summary> + /// Gets the name of the @unsolicitedAssertionVerification attribute. + /// </summary> + private const string UnsolicitedAssertionVerificationConfigName = "unsolicitedAssertionVerification"; + + /// <summary> /// Initializes a new instance of the <see cref="OpenIdProviderSecuritySettingsElement"/> class. /// </summary> public OpenIdProviderSecuritySettingsElement() { @@ -84,6 +89,17 @@ namespace DotNetOpenAuth.Configuration { } /// <summary> + /// Gets or sets the level of verification a Provider performs on an identifier before + /// sending an unsolicited assertion for it. + /// </summary> + /// <value>The default value is <see cref="UnsolicitedAssertionVerificationLevel.Always"/>.</value> + [ConfigurationProperty(UnsolicitedAssertionVerificationConfigName, DefaultValue = ProviderSecuritySettings.UnsolicitedAssertionVerificationDefault)] + public ProviderSecuritySettings.UnsolicitedAssertionVerificationLevel UnsolicitedAssertionVerification { + get { return (ProviderSecuritySettings.UnsolicitedAssertionVerificationLevel)this[UnsolicitedAssertionVerificationConfigName]; } + set { this[UnsolicitedAssertionVerificationConfigName] = value; } + } + + /// <summary> /// Gets or sets the configured lifetimes of the various association types. /// </summary> [ConfigurationProperty(AssociationsConfigName, IsDefaultCollection = false)] @@ -109,6 +125,7 @@ namespace DotNetOpenAuth.Configuration { settings.MinimumHashBitLength = this.MinimumHashBitLength; settings.MaximumHashBitLength = this.MaximumHashBitLength; settings.ProtectDownlevelReplayAttacks = this.ProtectDownlevelReplayAttacks; + settings.UnsolicitedAssertionVerification = this.UnsolicitedAssertionVerification; foreach (AssociationTypeElement element in this.AssociationLifetimes) { Contract.Assume(element != null); settings.AssociationLifetimes.Add(element.AssociationType, element.MaximumLifetime); diff --git a/src/DotNetOpenAuth/Messaging/HttpRequestInfo.cs b/src/DotNetOpenAuth/Messaging/HttpRequestInfo.cs index f4b1633..16b4546 100644 --- a/src/DotNetOpenAuth/Messaging/HttpRequestInfo.cs +++ b/src/DotNetOpenAuth/Messaging/HttpRequestInfo.cs @@ -374,7 +374,16 @@ namespace DotNetOpenAuth.Messaging { WebHeaderCollection headers = new WebHeaderCollection(); foreach (string key in pairs) { - headers.Add(key, pairs[key]); + try { + headers.Add(key, pairs[key]); + } catch (ArgumentException ex) { + Logger.Messaging.WarnFormat( + "{0} thrown when trying to add web header \"{1}: {2}\". {3}", + ex.GetType().Name, + key, + pairs[key], + ex.Message); + } } return headers; diff --git a/src/DotNetOpenAuth/Messaging/IncomingWebResponse.cs b/src/DotNetOpenAuth/Messaging/IncomingWebResponse.cs index 9ad228e..70b1032 100644 --- a/src/DotNetOpenAuth/Messaging/IncomingWebResponse.cs +++ b/src/DotNetOpenAuth/Messaging/IncomingWebResponse.cs @@ -104,7 +104,7 @@ namespace DotNetOpenAuth.Messaging { /// This can be different from the <see cref="RequestUri"/> in cases of /// redirection during the request. /// </remarks> - public Uri FinalUri { get; private set; } + public Uri FinalUri { get; internal set; } /// <summary> /// Gets the headers that must be included in the response to the user agent. diff --git a/src/DotNetOpenAuth/Messaging/OutgoingWebResponse.cs b/src/DotNetOpenAuth/Messaging/OutgoingWebResponse.cs index 91853fe..f6a930c 100644 --- a/src/DotNetOpenAuth/Messaging/OutgoingWebResponse.cs +++ b/src/DotNetOpenAuth/Messaging/OutgoingWebResponse.cs @@ -30,6 +30,11 @@ namespace DotNetOpenAuth.Messaging { /// </remarks> public class OutgoingWebResponse { /// <summary> + /// The encoder to use for serializing the response body. + /// </summary> + private static Encoding bodyStringEncoder = new UTF8Encoding(false); + + /// <summary> /// Initializes a new instance of the <see cref="OutgoingWebResponse"/> class. /// </summary> internal OutgoingWebResponse() { @@ -206,10 +211,9 @@ namespace DotNetOpenAuth.Messaging { return; } - Encoding encoding = Encoding.UTF8; - this.Headers[HttpResponseHeader.ContentEncoding] = encoding.HeaderName; + this.Headers[HttpResponseHeader.ContentEncoding] = bodyStringEncoder.HeaderName; this.ResponseStream = new MemoryStream(); - StreamWriter writer = new StreamWriter(this.ResponseStream, encoding); + StreamWriter writer = new StreamWriter(this.ResponseStream, bodyStringEncoder); writer.Write(body); writer.Flush(); this.ResponseStream.Seek(0, SeekOrigin.Begin); diff --git a/src/DotNetOpenAuth/Messaging/Reflection/MessagePart.cs b/src/DotNetOpenAuth/Messaging/Reflection/MessagePart.cs index 7fe8222..32d2fec 100644 --- a/src/DotNetOpenAuth/Messaging/Reflection/MessagePart.cs +++ b/src/DotNetOpenAuth/Messaging/Reflection/MessagePart.cs @@ -11,6 +11,7 @@ namespace DotNetOpenAuth.Messaging.Reflection { using System.Diagnostics.CodeAnalysis; using System.Diagnostics.Contracts; using System.Globalization; + using System.Linq; using System.Net.Security; using System.Reflection; using System.Xml; @@ -96,6 +97,7 @@ namespace DotNetOpenAuth.Messaging.Reflection { Map<Identifier>(id => id.ToString(), safeIdentfier); Map<bool>(value => value.ToString().ToLowerInvariant(), safeBool); Map<CultureInfo>(c => c.Name, str => new CultureInfo(str)); + Map<CultureInfo[]>(cs => string.Join(",", cs.Select(c => c.Name).ToArray()), str => str.Split(',').Select(s => new CultureInfo(s)).ToArray()); } /// <summary> diff --git a/src/DotNetOpenAuth/Messaging/UntrustedWebRequestHandler.cs b/src/DotNetOpenAuth/Messaging/UntrustedWebRequestHandler.cs index a5cbdab..3ea1bf2 100644 --- a/src/DotNetOpenAuth/Messaging/UntrustedWebRequestHandler.cs +++ b/src/DotNetOpenAuth/Messaging/UntrustedWebRequestHandler.cs @@ -258,6 +258,15 @@ namespace DotNetOpenAuth.Messaging { Uri redirectUri = new Uri(response.FinalUri, response.Headers[HttpResponseHeader.Location]); request = request.Clone(redirectUri); } else { + if (response.FinalUri != request.RequestUri) { + // Since we don't automatically follow redirects, there's only one scenario where this + // can happen: when the server sends a (non-redirecting) Content-Location header in the response. + // It's imperative that we do not trust that header though, so coerce the FinalUri to be + // what we just requested. + Logger.Http.WarnFormat("The response from {0} included an HTTP header indicating it's the same as {1}, but it's not a redirect so we won't trust that.", request.RequestUri, response.FinalUri); + response.FinalUri = request.RequestUri; + } + return response; } } @@ -454,12 +463,14 @@ namespace DotNetOpenAuth.Messaging { request.ReadWriteTimeout = (int)this.ReadWriteTimeout.TotalMilliseconds; request.Timeout = (int)this.Timeout.TotalMilliseconds; request.KeepAlive = false; - - // If SSL is required throughout, we cannot allow auto redirects because - // it may include a pass through an unprotected HTTP request. - // We have to follow redirects manually. - request.AllowAutoRedirect = false; } + + // If SSL is required throughout, we cannot allow auto redirects because + // it may include a pass through an unprotected HTTP request. + // We have to follow redirects manually. + // It also allows us to ignore HttpWebResponse.FinalUri since that can be affected by + // the Content-Location header and open security holes. + request.AllowAutoRedirect = false; } } } diff --git a/src/DotNetOpenAuth/OpenId/Extensions/UI/UIConstants.cs b/src/DotNetOpenAuth/OpenId/Extensions/UI/UIConstants.cs index 963b301..1cc920a 100644 --- a/src/DotNetOpenAuth/OpenId/Extensions/UI/UIConstants.cs +++ b/src/DotNetOpenAuth/OpenId/Extensions/UI/UIConstants.cs @@ -24,5 +24,11 @@ namespace DotNetOpenAuth.OpenId.Extensions.UI { /// specifying the user's preferred language through the UI extension. /// </summary> internal const string LangPrefSupported = "http://specs.openid.net/extensions/ui/1.0/lang-pref"; + + /// <summary> + /// The Type URI that appears in the XRDS document when the OP supports the RP + /// specifying the icon for the OP to display during authentication through the UI extension. + /// </summary> + internal const string IconSupported = "http://specs.openid.net/extensions/ui/1.0/icon"; } } diff --git a/src/DotNetOpenAuth/OpenId/Extensions/UI/UIRequest.cs b/src/DotNetOpenAuth/OpenId/Extensions/UI/UIRequest.cs index d67d932..bee675d 100644 --- a/src/DotNetOpenAuth/OpenId/Extensions/UI/UIRequest.cs +++ b/src/DotNetOpenAuth/OpenId/Extensions/UI/UIRequest.cs @@ -8,11 +8,14 @@ namespace DotNetOpenAuth.OpenId.Extensions.UI { using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; + using System.Diagnostics.Contracts; using System.Globalization; using System.Linq; using DotNetOpenAuth.Messaging; using DotNetOpenAuth.OpenId.Messages; + using DotNetOpenAuth.OpenId.Provider; using DotNetOpenAuth.OpenId.RelyingParty; + using DotNetOpenAuth.Xrds; /// <summary> /// OpenID User Interface extension 1.0 request message. @@ -25,7 +28,7 @@ namespace DotNetOpenAuth.OpenId.Extensions.UI { /// <see cref="UIModes.Popup"/>. </para> /// <para>An RP may determine whether an arbitrary OP supports this extension (and thereby determine /// whether to use a standard full window redirect or a popup) via the - /// <see cref="IProviderEndpoint.IsExtensionSupported"/> method on the <see cref="IAuthenticationRequest.Provider"/> + /// <see cref="IProviderEndpoint.IsExtensionSupported"/> method on the <see cref="DotNetOpenAuth.OpenId.RelyingParty.IAuthenticationRequest.Provider"/> /// object.</para> /// </remarks> public sealed class UIRequest : IOpenIdMessageExtension, IMessageWithEvents { @@ -46,6 +49,7 @@ namespace DotNetOpenAuth.OpenId.Extensions.UI { private static readonly string[] additionalTypeUris = new string[] { UIConstants.LangPrefSupported, UIConstants.PopupSupported, + UIConstants.IconSupported, }; /// <summary> @@ -57,18 +61,18 @@ namespace DotNetOpenAuth.OpenId.Extensions.UI { /// Initializes a new instance of the <see cref="UIRequest"/> class. /// </summary> public UIRequest() { - this.LanguagePreference = CultureInfo.CurrentUICulture; + this.LanguagePreference = new[] { CultureInfo.CurrentUICulture }; } /// <summary> - /// Gets or sets the user's preferred language. + /// Gets or sets the list of user's preferred languages, sorted in decreasing preferred order. /// </summary> /// <value>The default is the <see cref="CultureInfo.CurrentUICulture"/> of the thread that created this instance.</value> /// <remarks> - /// The user's preferred language, reusing the Language Tag format used by the [Language Preference Attribute] (axschema.org, “Language Preference Attribute,” .) for [OpenID Attribute Exchange] (Hardt, D., Bufu, J., and J. Hoyt, “OpenID Attribute Exchange 1.0,” .) and defined in [RFC4646] (Phillips, A. and M. Davis, “Tags for Identifying Languages,” .). For example "en-US" represents the English language as spoken in the United States, and "fr-CA" represents the French language spoken in Canada. + /// The user's preferred languages as a [BCP 47] language priority list, represented as a comma-separated list of BCP 47 basic language ranges in descending priority order. For instance, the value "fr-CA,fr-FR,en-CA" represents the preference for French spoken in Canada, French spoken in France, followed by English spoken in Canada. /// </remarks> [MessagePart("lang", AllowEmpty = false)] - public CultureInfo LanguagePreference { get; set; } + public CultureInfo[] LanguagePreference { get; set; } /// <summary> /// Gets the style of UI that the RP is hosting the OP's authentication page in. @@ -87,7 +91,7 @@ namespace DotNetOpenAuth.OpenId.Extensions.UI { /// <remarks> /// By default, the Provider displays the relying party's favicon.ico. /// </remarks> - [MessagePart("popup", AllowEmpty = false, IsRequired = false)] + [MessagePart("icon", AllowEmpty = false, IsRequired = false)] public bool? Icon { get; set; } #region IOpenIdMessageExtension Members @@ -126,7 +130,7 @@ namespace DotNetOpenAuth.OpenId.Extensions.UI { #endregion - #region IMessage Members + #region IMessage Properties /// <summary> /// Gets the version of the protocol or extension this message is prepared to implement. @@ -149,6 +153,72 @@ namespace DotNetOpenAuth.OpenId.Extensions.UI { get { return this.extraData; } } + #endregion + + /// <summary> + /// Gets the URL of the RP icon for the OP to display. + /// </summary> + /// <param name="realm">The realm of the RP where the authentication request originated.</param> + /// <param name="webRequestHandler">The web request handler to use for discovery. + /// Usually available via <see cref="Channel.WebRequestHandler">OpenIdProvider.Channel.WebRequestHandler</see>.</param> + /// <returns> + /// A sequence of the RP's icons it has available for the Provider to display, in decreasing preferred order. + /// </returns> + /// <value>The icon URL.</value> + /// <remarks> + /// This property is automatically set for the OP with the result of RP discovery. + /// RPs should set this value by including an entry such as this in their XRDS document. + /// <example> + /// <Service xmlns="xri://$xrd*($v*2.0)"> + /// <Type>http://specs.openid.net/extensions/ui/icon</Type> + /// <URI>http://consumer.example.com/images/image.jpg</URI> + /// </Service> + /// </example> + /// </remarks> + public static IEnumerable<Uri> GetRelyingPartyIconUrls(Realm realm, IDirectWebRequestHandler webRequestHandler) { + Contract.Requires(realm != null); + Contract.Requires(webRequestHandler != null); + ErrorUtilities.VerifyArgumentNotNull(realm, "realm"); + ErrorUtilities.VerifyArgumentNotNull(webRequestHandler, "webRequestHandler"); + + XrdsDocument xrds = realm.Discover(webRequestHandler, false); + if (xrds == null) { + return Enumerable.Empty<Uri>(); + } else { + return xrds.FindRelyingPartyIcons(); + } + } + + /// <summary> + /// Gets the URL of the RP icon for the OP to display. + /// </summary> + /// <param name="realm">The realm of the RP where the authentication request originated.</param> + /// <param name="provider">The Provider instance used to obtain the authentication request.</param> + /// <returns> + /// A sequence of the RP's icons it has available for the Provider to display, in decreasing preferred order. + /// </returns> + /// <value>The icon URL.</value> + /// <remarks> + /// This property is automatically set for the OP with the result of RP discovery. + /// RPs should set this value by including an entry such as this in their XRDS document. + /// <example> + /// <Service xmlns="xri://$xrd*($v*2.0)"> + /// <Type>http://specs.openid.net/extensions/ui/icon</Type> + /// <URI>http://consumer.example.com/images/image.jpg</URI> + /// </Service> + /// </example> + /// </remarks> + public static IEnumerable<Uri> GetRelyingPartyIconUrls(Realm realm, OpenIdProvider provider) { + Contract.Requires(realm != null); + Contract.Requires(provider != null); + ErrorUtilities.VerifyArgumentNotNull(realm, "realm"); + ErrorUtilities.VerifyArgumentNotNull(provider, "provider"); + + return GetRelyingPartyIconUrls(realm, provider.Channel.WebRequestHandler); + } + + #region IMessage methods + /// <summary> /// Checks the message state for conformity to the protocol specification /// and throws an exception if the message is invalid. diff --git a/src/DotNetOpenAuth/OpenId/OpenIdXrdsHelper.cs b/src/DotNetOpenAuth/OpenId/OpenIdXrdsHelper.cs index fd9e8d8..664a127 100644 --- a/src/DotNetOpenAuth/OpenId/OpenIdXrdsHelper.cs +++ b/src/DotNetOpenAuth/OpenId/OpenIdXrdsHelper.cs @@ -33,6 +33,19 @@ namespace DotNetOpenAuth.OpenId { } /// <summary> + /// Finds the icons the relying party wants an OP to display as part of authentication, + /// per the UI extension spec. + /// </summary> + /// <param name="xrds">The XrdsDocument to search.</param> + /// <returns>A sequence of the icon URLs in preferred order.</returns> + internal static IEnumerable<Uri> FindRelyingPartyIcons(this XrdsDocument xrds) { + return from xrd in xrds.XrdElements + from service in xrd.OpenIdRelyingPartyIcons + from uri in service.UriElements + select uri.Uri; + } + + /// <summary> /// Creates the service endpoints described in this document, useful for requesting /// authentication of one of the OpenID Providers that result from it. /// </summary> diff --git a/src/DotNetOpenAuth/OpenId/Provider/HostProcessedRequest.cs b/src/DotNetOpenAuth/OpenId/Provider/HostProcessedRequest.cs index 7c6c4c1..38d1094 100644 --- a/src/DotNetOpenAuth/OpenId/Provider/HostProcessedRequest.cs +++ b/src/DotNetOpenAuth/OpenId/Provider/HostProcessedRequest.cs @@ -137,7 +137,7 @@ namespace DotNetOpenAuth.OpenId.Provider { return RelyingPartyDiscoveryResult.NoServiceDocument; } - var returnToEndpoints = this.Realm.Discover(provider.Channel.WebRequestHandler, false); + var returnToEndpoints = this.Realm.DiscoverReturnToEndpoints(provider.Channel.WebRequestHandler, false); if (returnToEndpoints == null) { return RelyingPartyDiscoveryResult.NoServiceDocument; } diff --git a/src/DotNetOpenAuth/OpenId/Provider/IProviderBehavior.cs b/src/DotNetOpenAuth/OpenId/Provider/IProviderBehavior.cs index ade0d8e..01b4ac8 100644 --- a/src/DotNetOpenAuth/OpenId/Provider/IProviderBehavior.cs +++ b/src/DotNetOpenAuth/OpenId/Provider/IProviderBehavior.cs @@ -12,12 +12,8 @@ namespace DotNetOpenAuth.OpenId.Provider { /// <summary> /// Applies a custom security policy to certain OpenID security settings and behaviors. /// </summary> - /// <remarks> - /// BEFORE MARKING THIS INTERFACE PUBLIC: it's very important that we shift the methods to be channel-level - /// rather than facade class level and for the OpenIdChannel to be the one to invoke these methods. - /// </remarks> [ContractClass(typeof(IProviderBehaviorContract))] - internal interface IProviderBehavior { + public interface IProviderBehavior { /// <summary> /// Applies a well known set of security requirements to a default set of security settings. /// </summary> diff --git a/src/DotNetOpenAuth/OpenId/Provider/OpenIdProvider.cs b/src/DotNetOpenAuth/OpenId/Provider/OpenIdProvider.cs index 2af6830..f141834 100644 --- a/src/DotNetOpenAuth/OpenId/Provider/OpenIdProvider.cs +++ b/src/DotNetOpenAuth/OpenId/Provider/OpenIdProvider.cs @@ -149,7 +149,11 @@ namespace DotNetOpenAuth.OpenId.Provider { /// <summary> /// Gets a list of custom behaviors to apply to OpenID actions. /// </summary> - internal ICollection<IProviderBehavior> Behaviors { + /// <remarks> + /// Adding behaviors can impact the security settings of the <see cref="OpenIdProvider"/> + /// in ways that subsequently removing the behaviors will not reverse. + /// </remarks> + public ICollection<IProviderBehavior> Behaviors { get { return this.behaviors; } } @@ -359,21 +363,27 @@ namespace DotNetOpenAuth.OpenId.Provider { // is authorized to send an assertion for the given claimed identifier, // do due diligence by performing our own discovery on the claimed identifier // and make sure that it is tied to this OP and OP local identifier. - var serviceEndpoint = DotNetOpenAuth.OpenId.RelyingParty.ServiceEndpoint.CreateForClaimedIdentifier(claimedIdentifier, localIdentifier, new ProviderEndpointDescription(providerEndpoint, Protocol.Default.Version), null, null); - var discoveredEndpoints = claimedIdentifier.Discover(this.WebRequestHandler); - if (!discoveredEndpoints.Contains(serviceEndpoint)) { - Logger.OpenId.DebugFormat( - "Failed to send unsolicited assertion for {0} because its discovered services did not include this endpoint: {1}{2}{1}Discovered endpoints: {1}{3}", - claimedIdentifier, - Environment.NewLine, - serviceEndpoint, - discoveredEndpoints.ToStringDeferred(true)); - ErrorUtilities.ThrowProtocol(OpenIdStrings.UnsolicitedAssertionForUnrelatedClaimedIdentifier, claimedIdentifier); + if (this.SecuritySettings.UnsolicitedAssertionVerification != ProviderSecuritySettings.UnsolicitedAssertionVerificationLevel.NeverVerify) { + var serviceEndpoint = DotNetOpenAuth.OpenId.RelyingParty.ServiceEndpoint.CreateForClaimedIdentifier(claimedIdentifier, localIdentifier, new ProviderEndpointDescription(providerEndpoint, Protocol.Default.Version), null, null); + var discoveredEndpoints = claimedIdentifier.Discover(this.WebRequestHandler); + if (!discoveredEndpoints.Contains(serviceEndpoint)) { + Logger.OpenId.WarnFormat( + "Failed to send unsolicited assertion for {0} because its discovered services did not include this endpoint: {1}{2}{1}Discovered endpoints: {1}{3}", + claimedIdentifier, + Environment.NewLine, + serviceEndpoint, + discoveredEndpoints.ToStringDeferred(true)); + + // Only FAIL if the setting is set for it. + if (this.securitySettings.UnsolicitedAssertionVerification == ProviderSecuritySettings.UnsolicitedAssertionVerificationLevel.RequireSuccess) { + ErrorUtilities.ThrowProtocol(OpenIdStrings.UnsolicitedAssertionForUnrelatedClaimedIdentifier, claimedIdentifier); + } + } } Logger.OpenId.InfoFormat("Preparing unsolicited assertion for {0}", claimedIdentifier); RelyingPartyEndpointDescription returnToEndpoint = null; - var returnToEndpoints = relyingParty.Discover(this.WebRequestHandler, true); + var returnToEndpoints = relyingParty.DiscoverReturnToEndpoints(this.WebRequestHandler, true); if (returnToEndpoints != null) { returnToEndpoint = returnToEndpoints.FirstOrDefault(); } diff --git a/src/DotNetOpenAuth/OpenId/Provider/ProviderSecuritySettings.cs b/src/DotNetOpenAuth/OpenId/Provider/ProviderSecuritySettings.cs index 876e412..ddc10d2 100644 --- a/src/DotNetOpenAuth/OpenId/Provider/ProviderSecuritySettings.cs +++ b/src/DotNetOpenAuth/OpenId/Provider/ProviderSecuritySettings.cs @@ -28,6 +28,11 @@ namespace DotNetOpenAuth.OpenId.Provider { internal const bool SignOutgoingExtensionsDefault = true; /// <summary> + /// The default value for the <see cref="UnsolicitedAssertionVerification"/> property. + /// </summary> + internal const UnsolicitedAssertionVerificationLevel UnsolicitedAssertionVerificationDefault = UnsolicitedAssertionVerificationLevel.RequireSuccess; + + /// <summary> /// The subset of association types and their customized lifetimes. /// </summary> private IDictionary<string, TimeSpan> associationLifetimes = new Dictionary<string, TimeSpan>(); @@ -39,6 +44,37 @@ namespace DotNetOpenAuth.OpenId.Provider { : base(true) { this.SignOutgoingExtensions = SignOutgoingExtensionsDefault; this.ProtectDownlevelReplayAttacks = ProtectDownlevelReplayAttacksDefault; + this.UnsolicitedAssertionVerification = UnsolicitedAssertionVerificationDefault; + } + + /// <summary> + /// The behavior a Provider takes when verifying that it is authoritative for an + /// identifier it is about to send an unsolicited assertion for. + /// </summary> + public enum UnsolicitedAssertionVerificationLevel { + /// <summary> + /// Always verify that the Provider is authoritative for an identifier before + /// sending an unsolicited assertion for it and fail if it is not. + /// </summary> + RequireSuccess, + + /// <summary> + /// Always check that the Provider is authoritative for an identifier before + /// sending an unsolicited assertion for it, but only log failures, and proceed + /// to send the unsolicited assertion. + /// </summary> + LogWarningOnFailure, + + /// <summary> + /// Never verify that the Provider is authoritative for an identifier before + /// sending an unsolicited assertion for it. + /// </summary> + /// <remarks> + /// This setting is useful for web servers that refuse to allow a Provider to + /// introspectively perform an HTTP GET on itself, when sending unsolicited assertions + /// for identifiers that the OP controls. + /// </remarks> + NeverVerify, } /// <summary> @@ -57,6 +93,13 @@ namespace DotNetOpenAuth.OpenId.Provider { public bool RequireSsl { get; set; } /// <summary> + /// Gets or sets the level of verification a Provider performs on an identifier before + /// sending an unsolicited assertion for it. + /// </summary> + /// <value>The default value is <see cref="UnsolicitedAssertionVerificationLevel.Always"/>.</value> + public UnsolicitedAssertionVerificationLevel UnsolicitedAssertionVerification { get; set; } + + /// <summary> /// Gets or sets a value indicating whether OpenID 1.x relying parties that may not be /// protecting their users from replay attacks are protected from /// replay attacks by this provider. @@ -101,6 +144,7 @@ namespace DotNetOpenAuth.OpenId.Provider { securitySettings.ProtectDownlevelReplayAttacks = this.ProtectDownlevelReplayAttacks; securitySettings.RequireSsl = this.RequireSsl; securitySettings.SignOutgoingExtensions = this.SignOutgoingExtensions; + securitySettings.UnsolicitedAssertionVerification = this.UnsolicitedAssertionVerification; return securitySettings; } diff --git a/src/DotNetOpenAuth/OpenId/Realm.cs b/src/DotNetOpenAuth/OpenId/Realm.cs index 770aca0..fb0fbfb 100644 --- a/src/DotNetOpenAuth/OpenId/Realm.cs +++ b/src/DotNetOpenAuth/OpenId/Realm.cs @@ -381,7 +381,26 @@ namespace DotNetOpenAuth.OpenId { /// <returns> /// The details of the endpoints if found; or <c>null</c> if no service document was discovered. /// </returns> - internal virtual IEnumerable<RelyingPartyEndpointDescription> Discover(IDirectWebRequestHandler requestHandler, bool allowRedirects) { + internal virtual IEnumerable<RelyingPartyEndpointDescription> DiscoverReturnToEndpoints(IDirectWebRequestHandler requestHandler, bool allowRedirects) { + XrdsDocument xrds = this.Discover(requestHandler, allowRedirects); + if (xrds != null) { + return xrds.FindRelyingPartyReceivingEndpoints(); + } + + return null; + } + + /// <summary> + /// Searches for an XRDS document at the realm URL. + /// </summary> + /// <param name="requestHandler">The mechanism to use for sending HTTP requests.</param> + /// <param name="allowRedirects">Whether redirects may be followed when discovering the Realm. + /// This may be true when creating an unsolicited assertion, but must be + /// false when performing return URL verification per 2.0 spec section 9.2.1.</param> + /// <returns> + /// The XRDS document if found; or <c>null</c> if no service document was discovered. + /// </returns> + internal virtual XrdsDocument Discover(IDirectWebRequestHandler requestHandler, bool allowRedirects) { // Attempt YADIS discovery DiscoveryResult yadisResult = Yadis.Discover(requestHandler, this.UriWithWildcardChangedToWww, false); if (yadisResult != null) { @@ -389,8 +408,7 @@ namespace DotNetOpenAuth.OpenId { ErrorUtilities.VerifyProtocol(allowRedirects || yadisResult.NormalizedUri == yadisResult.RequestUri, OpenIdStrings.RealmCausedRedirectUponDiscovery, yadisResult.RequestUri); if (yadisResult.IsXrds) { try { - XrdsDocument xrds = new XrdsDocument(yadisResult.ResponseText); - return xrds.FindRelyingPartyReceivingEndpoints(); + return new XrdsDocument(yadisResult.ResponseText); } catch (XmlException ex) { throw ErrorUtilities.Wrap(ex, XrdsStrings.InvalidXRDSDocument); } diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/IRelyingPartyBehavior.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/IRelyingPartyBehavior.cs index 199368f..300a15f 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/IRelyingPartyBehavior.cs +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/IRelyingPartyBehavior.cs @@ -11,12 +11,8 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// <summary> /// Applies a custom security policy to certain OpenID security settings and behaviors. /// </summary> - /// <remarks> - /// BEFORE MARKING THIS INTERFACE PUBLIC: it's very important that we shift the methods to be channel-level - /// rather than facade class level and for the OpenIdChannel to be the one to invoke these methods. - /// </remarks> [ContractClass(typeof(IRelyingPartyBehaviorContract))] - internal interface IRelyingPartyBehavior { + public interface IRelyingPartyBehavior { /// <summary> /// Applies a well known set of security requirements to a default set of security settings. /// </summary> diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdButton.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdButton.cs index 13d02a3..dbf6944 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdButton.cs +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdButton.cs @@ -116,8 +116,12 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// <param name="eventArgument">A <see cref="T:System.String"/> that represents an optional event argument to be passed to the event handler.</param> protected override void RaisePostBackEvent(string eventArgument) { if (!this.PrecreateRequest) { - IAuthenticationRequest request = this.CreateRequests().FirstOrDefault(); - request.RedirectToProvider(); + try { + IAuthenticationRequest request = this.CreateRequests().First(); + request.RedirectToProvider(); + } catch (InvalidOperationException ex) { + throw ErrorUtilities.Wrap(ex, OpenIdStrings.OpenIdEndpointNotFound); + } } } diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingParty.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingParty.cs index f78420e..2808d39 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingParty.cs +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingParty.cs @@ -218,7 +218,11 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// <summary> /// Gets a list of custom behaviors to apply to OpenID actions. /// </summary> - internal ICollection<IRelyingPartyBehavior> Behaviors { + /// <remarks> + /// Adding behaviors can impact the security settings of this <see cref="OpenIdRelyingParty"/> + /// instance in ways that subsequently removing the behaviors will not reverse. + /// </remarks> + public ICollection<IRelyingPartyBehavior> Behaviors { get { return this.behaviors; } } diff --git a/src/DotNetOpenAuth/Xrds/XrdElement.cs b/src/DotNetOpenAuth/Xrds/XrdElement.cs index 72c5078..a8cc145 100644 --- a/src/DotNetOpenAuth/Xrds/XrdElement.cs +++ b/src/DotNetOpenAuth/Xrds/XrdElement.cs @@ -96,6 +96,13 @@ namespace DotNetOpenAuth.Xrds { } /// <summary> + /// Gets the services that would be discoverable at an RP for the UI extension icon. + /// </summary> + public IEnumerable<ServiceElement> OpenIdRelyingPartyIcons { + get { return this.SearchForServiceTypeUris(p => "http://specs.openid.net/extensions/ui/icon"); } + } + + /// <summary> /// Gets an enumeration of all Service/URI elements, sorted in priority order. /// </summary> public IEnumerable<UriElement> ServiceUris { |