diff options
37 files changed, 1120 insertions, 402 deletions
diff --git a/samples/OpenIdProviderWebForms/Code/Util.cs b/samples/OpenIdProviderWebForms/Code/Util.cs index 84d3c63..8700dbd 100644 --- a/samples/OpenIdProviderWebForms/Code/Util.cs +++ b/samples/OpenIdProviderWebForms/Code/Util.cs @@ -6,10 +6,6 @@ namespace OpenIdProviderWebForms.Code { using System; - using System.Collections.Generic; - using System.Diagnostics; - using System.Net; - using System.Text; using System.Web; using DotNetOpenAuth.OpenId; using DotNetOpenAuth.OpenId.Provider; @@ -51,6 +47,26 @@ namespace OpenIdProviderWebForms.Code { // to know the answer. idrequest.IsAuthenticated = userOwningOpenIdUrl == HttpContext.Current.User.Identity.Name; } + + if (idrequest.IsAuthenticated.Value) { + // add extension responses here. + } + } else { + HttpContext.Current.Response.Redirect("~/decide.aspx", true); + } + } + + internal static void ProcessAnonymousRequest(IAnonymousRequest request) { + if (request.Immediate) { + // NOTE: in a production provider site, you may want to only + // respond affirmatively if the user has already authorized this consumer + // to know the answer. + request.IsApproved = HttpContext.Current.User.Identity.IsAuthenticated; + + if (request.IsApproved.Value) { + // Add extension responses here. + // These would typically be filled in from a user database + } } else { HttpContext.Current.Response.Redirect("~/decide.aspx", true); } diff --git a/samples/OpenIdProviderWebForms/Provider.ashx.cs b/samples/OpenIdProviderWebForms/Provider.ashx.cs index 40acc04..c8441cf 100644 --- a/samples/OpenIdProviderWebForms/Provider.ashx.cs +++ b/samples/OpenIdProviderWebForms/Provider.ashx.cs @@ -24,17 +24,20 @@ // But authentication requests cannot be responded to until something on // this site decides whether to approve or disapprove the authentication. if (!request.IsResponseReady) { - var idrequest = (IAuthenticationRequest)request; - - // We store the authentication request in the user's session so that + // We store the request in the user's session so that // redirects and user prompts can appear and eventually some page can decide // to respond to the OpenID authentication request either affirmatively or // negatively. - ProviderEndpoint.PendingAuthenticationRequest = idrequest; + ProviderEndpoint.PendingAnonymousRequest = request as IAnonymousRequest; + ProviderEndpoint.PendingAuthenticationRequest = request as IAuthenticationRequest; // We delegate that approval process to our utility method that we share // with our other Provider sample page server.aspx. - Code.Util.ProcessAuthenticationChallenge(idrequest); + if (ProviderEndpoint.PendingAuthenticationRequest != null) { + Code.Util.ProcessAuthenticationChallenge(ProviderEndpoint.PendingAuthenticationRequest); + } else if (ProviderEndpoint.PendingAnonymousRequest != null) { + Code.Util.ProcessAnonymousRequest(ProviderEndpoint.PendingAnonymousRequest); + } // As part of authentication approval, the user may need to authenticate // to this Provider and/or decide whether to allow the requesting RP site @@ -52,7 +55,7 @@ ProviderEndpoint.Provider.SendResponse(request); // Make sure that any PendingAuthenticationRequest that MAY be set is cleared. - ProviderEndpoint.PendingAuthenticationRequest = null; + ProviderEndpoint.PendingRequest = null; } } } diff --git a/samples/OpenIdProviderWebForms/decide.aspx b/samples/OpenIdProviderWebForms/decide.aspx index 54c2f01..4a6e2d8 100644 --- a/samples/OpenIdProviderWebForms/decide.aspx +++ b/samples/OpenIdProviderWebForms/decide.aspx @@ -1,34 +1,24 @@ -<%@ Page Language="C#" AutoEventWireup="true" Inherits="OpenIdProviderWebForms.decide" CodeBehind="decide.aspx.cs" MasterPageFile="~/Site.Master" %> +<%@ Page Language="C#" AutoEventWireup="true" Inherits="OpenIdProviderWebForms.decide" + CodeBehind="decide.aspx.cs" MasterPageFile="~/Site.Master" %> <%@ Register Src="ProfileFields.ascx" TagName="ProfileFields" TagPrefix="uc1" %> <asp:Content runat="server" ContentPlaceHolderID="Main"> - <p> - A site has asked to authenticate that you own the identifier below. You should - only do this if you wish to log in to the site given by the Realm.</p> - <p> - This site - <asp:Label ID="relyingPartyVerificationResultLabel" runat="server" - Font-Bold="True" Text="failed" /> verification. </p> + <p><asp:Label ID="siteRequestLabel" runat="server" Text="A site has asked to authenticate that you own the identifier below." /> + You should only do this if you wish to log in to the site given by the Realm.</p> + <p>This site <asp:Label ID="relyingPartyVerificationResultLabel" runat="server" Font-Bold="True" + Text="failed" /> verification. </p> <table> <tr> - <td> - Identifier: </td> - <td> - <asp:Label runat="server" ID='identityUrlLabel' /> - </td> + <td>Identifier: </td> + <td><asp:Label runat="server" ID='identityUrlLabel' /> </td> </tr> <tr> - <td> - Realm: </td> - <td> - <asp:Label runat="server" ID='realmLabel' /> - </td> + <td>Realm: </td> + <td><asp:Label runat="server" ID='realmLabel' /> </td> </tr> </table> - <p> - Allow this authentication to proceed? - </p> + <p>Allow this to proceed? </p> <uc1:ProfileFields ID="profileFields" runat="server" Visible="false" /> <asp:Button ID="yes_button" OnClick="Yes_Click" Text=" yes " runat="Server" /> <asp:Button ID="no_button" OnClick="No_Click" Text=" no " runat="Server" /> -</asp:Content>
\ No newline at end of file +</asp:Content> diff --git a/samples/OpenIdProviderWebForms/decide.aspx.cs b/samples/OpenIdProviderWebForms/decide.aspx.cs index 777a688..13f997f 100644 --- a/samples/OpenIdProviderWebForms/decide.aspx.cs +++ b/samples/OpenIdProviderWebForms/decide.aspx.cs @@ -12,61 +12,75 @@ namespace OpenIdProviderWebForms { /// </summary> public partial class decide : Page { protected void Page_Load(object src, EventArgs e) { - if (ProviderEndpoint.PendingAuthenticationRequest == null) { + if (ProviderEndpoint.PendingRequest == null) { Response.Redirect("~/"); } - if (ProviderEndpoint.PendingAuthenticationRequest.IsDirectedIdentity) { - ProviderEndpoint.PendingAuthenticationRequest.LocalIdentifier = Code.Util.BuildIdentityUrl(); - } this.relyingPartyVerificationResultLabel.Text = - ProviderEndpoint.PendingAuthenticationRequest.IsReturnUrlDiscoverable(ProviderEndpoint.Provider.Channel.WebRequestHandler) ? "passed" : "failed"; + ProviderEndpoint.PendingRequest.IsReturnUrlDiscoverable(ProviderEndpoint.Provider.Channel.WebRequestHandler) ? "passed" : "failed"; + + this.realmLabel.Text = ProviderEndpoint.PendingRequest.Realm.ToString(); - this.identityUrlLabel.Text = ProviderEndpoint.PendingAuthenticationRequest.LocalIdentifier.ToString(); - this.realmLabel.Text = ProviderEndpoint.PendingAuthenticationRequest.Realm.ToString(); + if (ProviderEndpoint.PendingAuthenticationRequest != null) { + if (ProviderEndpoint.PendingAuthenticationRequest.IsDirectedIdentity) { + ProviderEndpoint.PendingAuthenticationRequest.LocalIdentifier = Code.Util.BuildIdentityUrl(); + } + this.identityUrlLabel.Text = ProviderEndpoint.PendingAuthenticationRequest.LocalIdentifier.ToString(); - // check that the logged in user is the same as the user requesting authentication to the consumer. If not, then log them out. - if (string.Equals(User.Identity.Name, Code.Util.ExtractUserName(ProviderEndpoint.PendingAuthenticationRequest.LocalIdentifier), StringComparison.OrdinalIgnoreCase)) { - // if simple registration fields were used, then prompt the user for them - var requestedFields = ProviderEndpoint.PendingAuthenticationRequest.GetExtension<ClaimsRequest>(); - if (requestedFields != null) { - this.profileFields.Visible = true; - this.profileFields.SetRequiredFieldsFromRequest(requestedFields); - if (!IsPostBack) { - var sregResponse = requestedFields.CreateResponse(); - sregResponse.Email = Membership.GetUser().Email; - this.profileFields.SetOpenIdProfileFields(sregResponse); - } + // check that the logged in user is the same as the user requesting authentication to the consumer. If not, then log them out. + if (!string.Equals(User.Identity.Name, Code.Util.ExtractUserName(ProviderEndpoint.PendingAuthenticationRequest.LocalIdentifier), StringComparison.OrdinalIgnoreCase)) { + FormsAuthentication.SignOut(); + Response.Redirect(Request.Url.AbsoluteUri); } } else { - FormsAuthentication.SignOut(); - Response.Redirect(Request.Url.AbsoluteUri); + this.identityUrlLabel.Text = "(not applicable)"; + this.siteRequestLabel.Text = "A site has asked for information about you."; + } + + // if simple registration fields were used, then prompt the user for them + var requestedFields = ProviderEndpoint.PendingRequest.GetExtension<ClaimsRequest>(); + if (requestedFields != null) { + this.profileFields.Visible = true; + this.profileFields.SetRequiredFieldsFromRequest(requestedFields); + if (!IsPostBack) { + var sregResponse = requestedFields.CreateResponse(); + sregResponse.Email = Membership.GetUser().Email; + this.profileFields.SetOpenIdProfileFields(sregResponse); + } } } protected void Yes_Click(object sender, EventArgs e) { - var sregRequest = ProviderEndpoint.PendingAuthenticationRequest.GetExtension<ClaimsRequest>(); + var sregRequest = ProviderEndpoint.PendingRequest.GetExtension<ClaimsRequest>(); ClaimsResponse sregResponse = null; if (sregRequest != null) { sregResponse = this.profileFields.GetOpenIdProfileFields(sregRequest); - ProviderEndpoint.PendingAuthenticationRequest.AddResponseExtension(sregResponse); + ProviderEndpoint.PendingRequest.AddResponseExtension(sregResponse); } - var papeRequest = ProviderEndpoint.PendingAuthenticationRequest.GetExtension<PolicyRequest>(); + var papeRequest = ProviderEndpoint.PendingRequest.GetExtension<PolicyRequest>(); PolicyResponse papeResponse = null; if (papeRequest != null) { papeResponse = new PolicyResponse(); papeResponse.NistAssuranceLevel = NistAssuranceLevel.InsufficientForLevel1; - ProviderEndpoint.PendingAuthenticationRequest.AddResponseExtension(papeResponse); + ProviderEndpoint.PendingRequest.AddResponseExtension(papeResponse); } - ProviderEndpoint.PendingAuthenticationRequest.IsAuthenticated = true; - Debug.Assert(ProviderEndpoint.PendingAuthenticationRequest.IsResponseReady, "Setting authentication should be all that's necessary."); + if (ProviderEndpoint.PendingAuthenticationRequest != null) { + ProviderEndpoint.PendingAuthenticationRequest.IsAuthenticated = true; + } else { + ProviderEndpoint.PendingAnonymousRequest.IsApproved = true; + } + Debug.Assert(ProviderEndpoint.PendingRequest.IsResponseReady, "Setting authentication should be all that's necessary."); ProviderEndpoint.SendResponse(); } protected void No_Click(object sender, EventArgs e) { - ProviderEndpoint.PendingAuthenticationRequest.IsAuthenticated = false; - Debug.Assert(ProviderEndpoint.PendingAuthenticationRequest.IsResponseReady, "Setting authentication should be all that's necessary."); + if (ProviderEndpoint.PendingAuthenticationRequest != null) { + ProviderEndpoint.PendingAuthenticationRequest.IsAuthenticated = false; + } else { + ProviderEndpoint.PendingAnonymousRequest.IsApproved = false; + } + Debug.Assert(ProviderEndpoint.PendingRequest.IsResponseReady, "Setting authentication should be all that's necessary."); ProviderEndpoint.SendResponse(); } } diff --git a/samples/OpenIdProviderWebForms/decide.aspx.designer.cs b/samples/OpenIdProviderWebForms/decide.aspx.designer.cs index 795d1c7..05386cd 100644 --- a/samples/OpenIdProviderWebForms/decide.aspx.designer.cs +++ b/samples/OpenIdProviderWebForms/decide.aspx.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.4918 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. @@ -14,6 +14,15 @@ namespace OpenIdProviderWebForms { public partial class decide { /// <summary> + /// siteRequestLabel control. + /// </summary> + /// <remarks> + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// </remarks> + protected global::System.Web.UI.WebControls.Label siteRequestLabel; + + /// <summary> /// relyingPartyVerificationResultLabel control. /// </summary> /// <remarks> diff --git a/samples/OpenIdProviderWebForms/server.aspx b/samples/OpenIdProviderWebForms/server.aspx index 10030a6..d3ce78d 100644 --- a/samples/OpenIdProviderWebForms/server.aspx +++ b/samples/OpenIdProviderWebForms/server.aspx @@ -14,7 +14,7 @@ This server.aspx page is the default provider endpoint to use. To switch to the .ashx handler, change the user_xrds.aspx and op_xrds.aspx files to point to provider.ashx instead of server.aspx. --%> - <openid:ProviderEndpoint runat="server" OnAuthenticationChallenge="provider_AuthenticationChallenge" /> + <openid:ProviderEndpoint runat="server" OnAuthenticationChallenge="provider_AuthenticationChallenge" OnAnonymousRequest="provider_AnonymousRequest" /> <p> <asp:Label ID="serverEndpointUrl" runat="server" EnableViewState="false" /> is an OpenID server endpoint. diff --git a/samples/OpenIdProviderWebForms/server.aspx.cs b/samples/OpenIdProviderWebForms/server.aspx.cs index c0af0b4..89e14f4 100644 --- a/samples/OpenIdProviderWebForms/server.aspx.cs +++ b/samples/OpenIdProviderWebForms/server.aspx.cs @@ -14,5 +14,9 @@ namespace OpenIdProviderWebForms { protected void provider_AuthenticationChallenge(object sender, AuthenticationChallengeEventArgs e) { Code.Util.ProcessAuthenticationChallenge(e.Request); } + + protected void provider_AnonymousRequest(object sender, AnonymousRequestEventArgs e) { + Code.Util.ProcessAnonymousRequest(e.Request); + } } }
\ No newline at end of file diff --git a/samples/OpenIdRelyingPartyWebForms/NoIdentityOpenId.aspx b/samples/OpenIdRelyingPartyWebForms/NoIdentityOpenId.aspx new file mode 100644 index 0000000..dc0c2bb --- /dev/null +++ b/samples/OpenIdRelyingPartyWebForms/NoIdentityOpenId.aspx @@ -0,0 +1,41 @@ +<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="NoIdentityOpenId.aspx.cs" + MasterPageFile="~/Site.Master" Inherits="OpenIdRelyingPartyWebForms.NoIdentityOpenId" %> + +<asp:Content runat="server" ContentPlaceHolderID="Main"> + <h2>No-login OpenID extension Page </h2> + <p>This demonstrates an RP sending an extension-only request to an OP that carries + extensions that request anonymous information about you. In this scenario, the OP + would still authenticate the user, but would not assert any OpenID Identifier back + to the RP, but might provide information regarding the user such as age or membership + in an organization. </p> + <p><b>Note: </b>At time of this writing, most OPs do not support this feature, although + it is documented in the OpenID 2.0 spec. </p> + <asp:Label ID="Label1" runat="server" Text="OpenID Identifier" /> <asp:TextBox ID="openIdBox" + runat="server" /> + <asp:Button ID="beginButton" runat="server" Text="Begin" OnClick="beginButton_Click" /> + <asp:CustomValidator runat="server" ID="openidValidator" ErrorMessage="Invalid OpenID Identifier" + ControlToValidate="openIdBox" EnableViewState="false" Display="Dynamic" OnServerValidate="openidValidator_ServerValidate" /> + <asp:Label runat="server" EnableViewState="false" ID="resultMessage" /> + <asp:Panel runat="server" ID="ExtensionResponsesPanel" EnableViewState="false" Visible="false"> + <p>We have received a reasonable response from the Provider. Below is the Simple Registration + response we received, if any: </p> + <table id="profileFieldsTable" runat="server"> + <tr> + <td>Gender </td> + <td><asp:Label runat="server" ID="genderLabel" /> </td> + </tr> + <tr> + <td>Post Code </td> + <td><asp:Label runat="server" ID="postalCodeLabel" /> </td> + </tr> + <tr> + <td>Country </td> + <td><asp:Label runat="server" ID="countryLabel" /> </td> + </tr> + <tr> + <td>Timezone </td> + <td><asp:Label runat="server" ID="timeZoneLabel" /> </td> + </tr> + </table> + </asp:Panel> +</asp:Content> diff --git a/samples/OpenIdRelyingPartyWebForms/NoIdentityOpenId.aspx.cs b/samples/OpenIdRelyingPartyWebForms/NoIdentityOpenId.aspx.cs new file mode 100644 index 0000000..8b9ea78 --- /dev/null +++ b/samples/OpenIdRelyingPartyWebForms/NoIdentityOpenId.aspx.cs @@ -0,0 +1,79 @@ +namespace OpenIdRelyingPartyWebForms { + using System; + using System.Web.UI.WebControls; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OpenId; + using DotNetOpenAuth.OpenId.Extensions.SimpleRegistration; + using DotNetOpenAuth.OpenId.RelyingParty; + + public partial class NoIdentityOpenId : System.Web.UI.Page { + protected void Page_Load(object sender, EventArgs e) { + openIdBox.Focus(); + using (OpenIdRelyingParty rp = new OpenIdRelyingParty()) { + IAuthenticationResponse response = rp.GetResponse(); + if (response != null) { + switch (response.Status) { + case AuthenticationStatus.ExtensionsOnly: + ExtensionResponsesPanel.Visible = true; + + // This is the "success" status we get when no authentication was requested. + var sreg = response.GetExtension<ClaimsResponse>(); + if (sreg != null) { + timeZoneLabel.Text = sreg.TimeZone; + postalCodeLabel.Text = sreg.PostalCode; + countryLabel.Text = sreg.Country; + if (sreg.Gender.HasValue) { + genderLabel.Text = sreg.Gender.Value.ToString(); + } + } + break; + case AuthenticationStatus.Canceled: + resultMessage.Text = "Canceled at OP. This may be a sign that the OP doesn't support this message."; + break; + case AuthenticationStatus.Failed: + resultMessage.Text = "OP returned a failure: " + response.Exception; + break; + case AuthenticationStatus.SetupRequired: + case AuthenticationStatus.Authenticated: + default: + resultMessage.Text = "OP returned an unexpected response."; + break; + } + } + } + } + + protected void beginButton_Click(object sender, EventArgs e) { + if (!this.Page.IsValid) { + return; // don't login if custom validation failed. + } + try { + using (OpenIdRelyingParty rp = new OpenIdRelyingParty()) { + var request = rp.CreateRequest(openIdBox.Text); + request.IsExtensionOnly = true; + + // This is where you would add any OpenID extensions you wanted + // to include in the request. + request.AddExtension(new ClaimsRequest { + Country = DemandLevel.Request, + Gender = DemandLevel.Require, + PostalCode = DemandLevel.Require, + TimeZone = DemandLevel.Require, + }); + + request.RedirectToProvider(); + } + } catch (ProtocolException ex) { + // The user probably entered an Identifier that + // was not a valid OpenID endpoint. + this.openidValidator.Text = ex.Message; + this.openidValidator.IsValid = false; + } + } + + 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); + } + } +} diff --git a/samples/OpenIdRelyingPartyWebForms/NoIdentityOpenId.aspx.designer.cs b/samples/OpenIdRelyingPartyWebForms/NoIdentityOpenId.aspx.designer.cs new file mode 100644 index 0000000..fb959a3 --- /dev/null +++ b/samples/OpenIdRelyingPartyWebForms/NoIdentityOpenId.aspx.designer.cs @@ -0,0 +1,115 @@ +//------------------------------------------------------------------------------ +// <auto-generated> +// This code was generated by a tool. +// Runtime Version:2.0.50727.4918 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// </auto-generated> +//------------------------------------------------------------------------------ + +namespace OpenIdRelyingPartyWebForms { + + + public partial class NoIdentityOpenId { + + /// <summary> + /// Label1 control. + /// </summary> + /// <remarks> + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// </remarks> + protected global::System.Web.UI.WebControls.Label Label1; + + /// <summary> + /// openIdBox control. + /// </summary> + /// <remarks> + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// </remarks> + protected global::System.Web.UI.WebControls.TextBox openIdBox; + + /// <summary> + /// beginButton control. + /// </summary> + /// <remarks> + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// </remarks> + protected global::System.Web.UI.WebControls.Button beginButton; + + /// <summary> + /// openidValidator control. + /// </summary> + /// <remarks> + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// </remarks> + protected global::System.Web.UI.WebControls.CustomValidator openidValidator; + + /// <summary> + /// resultMessage control. + /// </summary> + /// <remarks> + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// </remarks> + protected global::System.Web.UI.WebControls.Label resultMessage; + + /// <summary> + /// ExtensionResponsesPanel control. + /// </summary> + /// <remarks> + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// </remarks> + protected global::System.Web.UI.WebControls.Panel ExtensionResponsesPanel; + + /// <summary> + /// profileFieldsTable control. + /// </summary> + /// <remarks> + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// </remarks> + protected global::System.Web.UI.HtmlControls.HtmlTable profileFieldsTable; + + /// <summary> + /// genderLabel control. + /// </summary> + /// <remarks> + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// </remarks> + protected global::System.Web.UI.WebControls.Label genderLabel; + + /// <summary> + /// postalCodeLabel control. + /// </summary> + /// <remarks> + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// </remarks> + protected global::System.Web.UI.WebControls.Label postalCodeLabel; + + /// <summary> + /// countryLabel control. + /// </summary> + /// <remarks> + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// </remarks> + protected global::System.Web.UI.WebControls.Label countryLabel; + + /// <summary> + /// timeZoneLabel control. + /// </summary> + /// <remarks> + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// </remarks> + protected global::System.Web.UI.WebControls.Label timeZoneLabel; + } +} diff --git a/samples/OpenIdRelyingPartyWebForms/OpenIdRelyingPartyWebForms.csproj b/samples/OpenIdRelyingPartyWebForms/OpenIdRelyingPartyWebForms.csproj index c45f007..556dadf 100644 --- a/samples/OpenIdRelyingPartyWebForms/OpenIdRelyingPartyWebForms.csproj +++ b/samples/OpenIdRelyingPartyWebForms/OpenIdRelyingPartyWebForms.csproj @@ -139,6 +139,13 @@ <Compile Include="m\Login.aspx.designer.cs"> <DependentUpon>Login.aspx</DependentUpon> </Compile> + <Compile Include="NoIdentityOpenId.aspx.cs"> + <DependentUpon>NoIdentityOpenId.aspx</DependentUpon> + <SubType>ASPXCodeBehind</SubType> + </Compile> + <Compile Include="NoIdentityOpenId.aspx.designer.cs"> + <DependentUpon>NoIdentityOpenId.aspx</DependentUpon> + </Compile> <Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="TracePage.aspx.cs"> <DependentUpon>TracePage.aspx</DependentUpon> @@ -163,6 +170,7 @@ <Content Include="MembersOnly\DisplayGoogleContacts.aspx" /> <Content Include="MembersOnly\Web.config" /> <Content Include="m\Login.aspx" /> + <Content Include="NoIdentityOpenId.aspx" /> </ItemGroup> <ItemGroup> <None Include="Code\CustomStoreDataSet.xsc"> diff --git a/samples/OpenIdRelyingPartyWebForms/login.aspx.designer.cs b/samples/OpenIdRelyingPartyWebForms/login.aspx.designer.cs index 436ef7b..d175fc8 100644 --- a/samples/OpenIdRelyingPartyWebForms/login.aspx.designer.cs +++ b/samples/OpenIdRelyingPartyWebForms/login.aspx.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.4918 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. diff --git a/samples/OpenIdRelyingPartyWebForms/loginProgrammatic.aspx b/samples/OpenIdRelyingPartyWebForms/loginProgrammatic.aspx index a00eccd..78179f7 100644 --- a/samples/OpenIdRelyingPartyWebForms/loginProgrammatic.aspx +++ b/samples/OpenIdRelyingPartyWebForms/loginProgrammatic.aspx @@ -12,4 +12,5 @@ Visible="False" /> <asp:Label ID="loginCanceledLabel" runat="server" EnableViewState="False" Text="Login canceled" Visible="False" /> + <asp:CheckBox ID="noLoginCheckBox" runat="server" Text="Extensions only (no login) -- most OPs don't yet support this" /> </asp:Content>
\ No newline at end of file diff --git a/samples/OpenIdRelyingPartyWebForms/loginProgrammatic.aspx.cs b/samples/OpenIdRelyingPartyWebForms/loginProgrammatic.aspx.cs index fe73b7e..ed11148 100644 --- a/samples/OpenIdRelyingPartyWebForms/loginProgrammatic.aspx.cs +++ b/samples/OpenIdRelyingPartyWebForms/loginProgrammatic.aspx.cs @@ -41,11 +41,6 @@ // was not a valid OpenID endpoint. this.openidValidator.Text = ex.Message; this.openidValidator.IsValid = false; - } catch (WebException ex) { - // The user probably entered an Identifier that - // was not a valid OpenID endpoint. - this.openidValidator.Text = ex.Message; - this.openidValidator.IsValid = false; } } diff --git a/samples/OpenIdRelyingPartyWebForms/loginProgrammatic.aspx.designer.cs b/samples/OpenIdRelyingPartyWebForms/loginProgrammatic.aspx.designer.cs index 0363be7..239d7b8 100644 --- a/samples/OpenIdRelyingPartyWebForms/loginProgrammatic.aspx.designer.cs +++ b/samples/OpenIdRelyingPartyWebForms/loginProgrammatic.aspx.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.4918 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. @@ -66,5 +66,14 @@ namespace OpenIdRelyingPartyWebForms { /// To modify move field declaration from designer file to code-behind file. /// </remarks> protected global::System.Web.UI.WebControls.Label loginCanceledLabel; + + /// <summary> + /// noLoginCheckBox control. + /// </summary> + /// <remarks> + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// </remarks> + protected global::System.Web.UI.WebControls.CheckBox noLoginCheckBox; } } diff --git a/samples/OpenIdRelyingPartyWebForms/xrds.aspx b/samples/OpenIdRelyingPartyWebForms/xrds.aspx index e169bc7..9e201d0 100644 --- a/samples/OpenIdRelyingPartyWebForms/xrds.aspx +++ b/samples/OpenIdRelyingPartyWebForms/xrds.aspx @@ -17,6 +17,7 @@ is default.aspx. <URI priority="1"><%=new Uri(Request.Url, Response.ApplyAppPathModifier("~/login.aspx"))%></URI> <URI priority="2"><%=new Uri(Request.Url, Response.ApplyAppPathModifier("~/loginProgrammatic.aspx"))%></URI> <URI priority="3"><%=new Uri(Request.Url, Response.ApplyAppPathModifier("~/ajaxlogin.aspx"))%></URI> + <URI priority="3"><%=new Uri(Request.Url, Response.ApplyAppPathModifier("~/NoIdentityOpenId.aspx"))%></URI> </Service> </XRD> </xrds:XRDS> diff --git a/src/DotNetOpenAuth/DotNetOpenAuth.csproj b/src/DotNetOpenAuth/DotNetOpenAuth.csproj index a6069b4..1f3df12 100644 --- a/src/DotNetOpenAuth/DotNetOpenAuth.csproj +++ b/src/DotNetOpenAuth/DotNetOpenAuth.csproj @@ -368,10 +368,15 @@ <Compile Include="OpenId\Messages\SignedResponseRequest.cs" /> <Compile Include="OpenId\NoDiscoveryIdentifier.cs" /> <Compile Include="OpenId\OpenIdUtilities.cs" /> + <Compile Include="OpenId\Provider\AnonymousRequest.cs" /> + <Compile Include="OpenId\Provider\AnonymousRequestEventArgs.cs" /> <Compile Include="OpenId\Provider\AuthenticationChallengeEventArgs.cs" /> <Compile Include="OpenId\Provider\AuthenticationRequest.cs" /> <Compile Include="OpenId\Provider\AutoResponsiveRequest.cs" /> + <Compile Include="OpenId\Provider\HostProcessedRequest.cs" /> + <Compile Include="OpenId\Provider\IAnonymousRequest.cs" /> <Compile Include="OpenId\Provider\IAuthenticationRequest.cs" /> + <Compile Include="OpenId\Provider\IHostProcessedRequest.cs" /> <Compile Include="OpenId\Provider\IdentityEndpoint.cs" /> <Compile Include="OpenId\Provider\IdentityEndpointNormalizationEventArgs.cs" /> <Compile Include="OpenId\Provider\IErrorReporting.cs" /> @@ -418,6 +423,7 @@ <Compile Include="OpenId\RelyingParty\OpenIdLogin.cs" /> <Compile Include="OpenId\RelyingParty\OpenIdMobileTextBox.cs" /> <Compile Include="OpenId\RelyingParty\OpenIdTextBox.cs" /> + <Compile Include="OpenId\RelyingParty\PositiveAnonymousResponse.cs" /> <Compile Include="OpenId\RelyingParty\PositiveAuthenticationResponse.cs" /> <Compile Include="OpenId\RelyingParty\AuthenticationStatus.cs" /> <Compile Include="OpenId\RelyingParty\FailedAuthenticationResponse.cs" /> diff --git a/src/DotNetOpenAuth/OpenId/ChannelElements/OpenIdMessageFactory.cs b/src/DotNetOpenAuth/OpenId/ChannelElements/OpenIdMessageFactory.cs index c8ffae8..31a2da5 100644 --- a/src/DotNetOpenAuth/OpenId/ChannelElements/OpenIdMessageFactory.cs +++ b/src/DotNetOpenAuth/OpenId/ChannelElements/OpenIdMessageFactory.cs @@ -57,6 +57,7 @@ namespace DotNetOpenAuth.OpenId.ChannelElements { if (fields.ContainsKey(protocol.openid.identity)) { message = new CheckIdRequest(protocol.Version, recipient.Location, authMode); } else { + ErrorUtilities.VerifyProtocol(!fields.ContainsKey(protocol.openid.claimed_id), OpenIdStrings.IdentityAndClaimedIdentifierMustBeBothPresentOrAbsent); message = new SignedResponseRequest(protocol.Version, recipient.Location, authMode); } } else if (string.Equals(mode, protocol.Args.Mode.cancel) || @@ -66,6 +67,7 @@ namespace DotNetOpenAuth.OpenId.ChannelElements { if (fields.ContainsKey(protocol.openid.identity)) { message = new PositiveAssertionResponse(protocol.Version, recipient.Location); } else { + ErrorUtilities.VerifyProtocol(!fields.ContainsKey(protocol.openid.claimed_id), OpenIdStrings.IdentityAndClaimedIdentifierMustBeBothPresentOrAbsent); message = new IndirectSignedResponse(protocol.Version, recipient.Location); } } else if (string.Equals(mode, protocol.Args.Mode.check_authentication)) { diff --git a/src/DotNetOpenAuth/OpenId/Messages/NegativeAssertionResponse.cs b/src/DotNetOpenAuth/OpenId/Messages/NegativeAssertionResponse.cs index f03422c..99a7c5a 100644 --- a/src/DotNetOpenAuth/OpenId/Messages/NegativeAssertionResponse.cs +++ b/src/DotNetOpenAuth/OpenId/Messages/NegativeAssertionResponse.cs @@ -31,12 +31,13 @@ namespace DotNetOpenAuth.OpenId.Messages { /// </summary> /// <param name="request">The request that the relying party sent.</param> /// <param name="channel">The channel to use to simulate construction of the user_setup_url, if applicable. May be null, but the user_setup_url will not be constructed.</param> - internal NegativeAssertionResponse(CheckIdRequest request, Channel channel) + internal NegativeAssertionResponse(SignedResponseRequest request, Channel channel) : base(request, GetMode(request)) { // If appropriate, and when we're provided with a channel to do it, // go ahead and construct the user_setup_url if (this.Version.Major < 2 && request.Immediate && channel != null) { - this.UserSetupUrl = ConstructUserSetupUrl(request, channel); + // All requests are CheckIdRequests in OpenID 1.x, so this cast should be safe. + this.UserSetupUrl = ConstructUserSetupUrl((CheckIdRequest)request, channel); } } @@ -130,7 +131,7 @@ namespace DotNetOpenAuth.OpenId.Messages { /// </summary> /// <param name="request">The request that we're responding to.</param> /// <returns>The value of the openid.mode parameter to use.</returns> - private static string GetMode(CheckIdRequest request) { + private static string GetMode(SignedResponseRequest request) { ErrorUtilities.VerifyArgumentNotNull(request, "request"); Protocol protocol = Protocol.Lookup(request.Version); diff --git a/src/DotNetOpenAuth/OpenId/OpenIdStrings.Designer.cs b/src/DotNetOpenAuth/OpenId/OpenIdStrings.Designer.cs index a6e02fe..033ea54 100644 --- a/src/DotNetOpenAuth/OpenId/OpenIdStrings.Designer.cs +++ b/src/DotNetOpenAuth/OpenId/OpenIdStrings.Designer.cs @@ -259,6 +259,15 @@ namespace DotNetOpenAuth.OpenId { } /// <summary> + /// Looks up a localized string similar to The openid.identity and openid.claimed_id parameters must either be both present or both absent from the message.. + /// </summary> + internal static string IdentityAndClaimedIdentifierMustBeBothPresentOrAbsent { + get { + return ResourceManager.GetString("IdentityAndClaimedIdentifierMustBeBothPresentOrAbsent", resourceCulture); + } + } + + /// <summary> /// Looks up a localized string similar to The Provider requested association type '{0}' and session type '{1}', which are not compatible with each other.. /// </summary> internal static string IncompatibleAssociationAndSessionTypes { diff --git a/src/DotNetOpenAuth/OpenId/OpenIdStrings.resx b/src/DotNetOpenAuth/OpenId/OpenIdStrings.resx index 95fe655..2b934f5 100644 --- a/src/DotNetOpenAuth/OpenId/OpenIdStrings.resx +++ b/src/DotNetOpenAuth/OpenId/OpenIdStrings.resx @@ -301,4 +301,7 @@ Discovered endpoint info: <data name="UnsupportedChannelConfiguration" xml:space="preserve"> <value>This feature is unavailable due to an unrecognized channel configuration.</value> </data> + <data name="IdentityAndClaimedIdentifierMustBeBothPresentOrAbsent" xml:space="preserve"> + <value>The openid.identity and openid.claimed_id parameters must either be both present or both absent from the message.</value> + </data> </root>
\ No newline at end of file diff --git a/src/DotNetOpenAuth/OpenId/Provider/AnonymousRequest.cs b/src/DotNetOpenAuth/OpenId/Provider/AnonymousRequest.cs new file mode 100644 index 0000000..546db9f --- /dev/null +++ b/src/DotNetOpenAuth/OpenId/Provider/AnonymousRequest.cs @@ -0,0 +1,74 @@ +//----------------------------------------------------------------------- +// <copyright file="AnonymousRequest.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.Provider { + using System.Diagnostics.Contracts; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OpenId.Messages; + + /// <summary> + /// Provides access to a host Provider to read an incoming extension-only checkid request message, + /// and supply extension responses or a cancellation message to the RP. + /// </summary> + internal class AnonymousRequest : HostProcessedRequest, IAnonymousRequest { + /// <summary> + /// The extension-response message to send, if the host site chooses to send it. + /// </summary> + private readonly IndirectSignedResponse positiveResponse; + + /// <summary> + /// Initializes a new instance of the <see cref="AnonymousRequest"/> class. + /// </summary> + /// <param name="provider">The provider that received the request.</param> + /// <param name="request">The incoming authentication request message.</param> + internal AnonymousRequest(OpenIdProvider provider, SignedResponseRequest request) + : base(provider, request) { + Contract.Requires(provider != null); + Contract.Requires(!(request is CheckIdRequest), "Instantiate " + typeof(AuthenticationRequest).Name + " to handle this kind of message."); + ErrorUtilities.VerifyInternal(!(request is CheckIdRequest), "Instantiate {0} to handle this kind of message.", typeof(AuthenticationRequest).Name); + + this.positiveResponse = new IndirectSignedResponse(request); + } + + #region IAnonymousRequest Members + + /// <summary> + /// Gets or sets a value indicating whether the user approved sending any data to the relying party. + /// </summary> + /// <value><c>true</c> if approved; otherwise, <c>false</c>.</value> + public bool? IsApproved { get; set; } + + #endregion + + #region Request members + + /// <summary> + /// Gets a value indicating whether the response is ready to be sent to the user agent. + /// </summary> + /// <remarks> + /// This property returns false if there are properties that must be set on this + /// request instance before the response can be sent. + /// </remarks> + public override bool IsResponseReady { + get { return this.IsApproved.HasValue; } + } + + /// <summary> + /// Gets the response message, once <see cref="IsResponseReady"/> is <c>true</c>. + /// </summary> + protected override IProtocolMessage ResponseMessage { + get { + if (this.IsApproved.HasValue) { + return this.IsApproved.Value ? (IProtocolMessage)this.positiveResponse : this.NegativeResponse; + } else { + return null; + } + } + } + + #endregion + } +} diff --git a/src/DotNetOpenAuth/OpenId/Provider/AnonymousRequestEventArgs.cs b/src/DotNetOpenAuth/OpenId/Provider/AnonymousRequestEventArgs.cs new file mode 100644 index 0000000..cdd5311 --- /dev/null +++ b/src/DotNetOpenAuth/OpenId/Provider/AnonymousRequestEventArgs.cs @@ -0,0 +1,32 @@ +//----------------------------------------------------------------------- +// <copyright file="AnonymousRequestEventArgs.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.Provider { + using System; + using System.Diagnostics.Contracts; + using DotNetOpenAuth.Messaging; + + /// <summary> + /// The event arguments that include details of the incoming request. + /// </summary> + public class AnonymousRequestEventArgs : EventArgs { + /// <summary> + /// Initializes a new instance of the <see cref="AnonymousRequestEventArgs"/> class. + /// </summary> + /// <param name="request">The incoming OpenID request.</param> + internal AnonymousRequestEventArgs(IAnonymousRequest request) { + Contract.Requires(request != null); + ErrorUtilities.VerifyArgumentNotNull(request, "request"); + + this.Request = request; + } + + /// <summary> + /// Gets the incoming OpenID request. + /// </summary> + public IAnonymousRequest Request { get; private set; } + } +} diff --git a/src/DotNetOpenAuth/OpenId/Provider/AuthenticationRequest.cs b/src/DotNetOpenAuth/OpenId/Provider/AuthenticationRequest.cs index 34ade49..7a547dd 100644 --- a/src/DotNetOpenAuth/OpenId/Provider/AuthenticationRequest.cs +++ b/src/DotNetOpenAuth/OpenId/Provider/AuthenticationRequest.cs @@ -6,10 +6,7 @@ namespace DotNetOpenAuth.OpenId.Provider { using System; - using System.Collections.Generic; - using System.Linq; - using System.Net; - using System.Text; + using System.Diagnostics.Contracts; using DotNetOpenAuth.Messaging; using DotNetOpenAuth.OpenId.Messages; @@ -19,28 +16,23 @@ namespace DotNetOpenAuth.OpenId.Provider { /// requests. /// </summary> [Serializable] - internal class AuthenticationRequest : Request, IAuthenticationRequest { + internal class AuthenticationRequest : HostProcessedRequest, IAuthenticationRequest { /// <summary> /// The positive assertion to send, if the host site chooses to send it. /// </summary> private readonly PositiveAssertionResponse positiveResponse; /// <summary> - /// The negative assertion to send, if the host site chooses to send it. - /// </summary> - private readonly NegativeAssertionResponse negativeResponse; - - /// <summary> /// Initializes a new instance of the <see cref="AuthenticationRequest"/> class. /// </summary> /// <param name="provider">The provider that received the request.</param> /// <param name="request">The incoming authentication request message.</param> internal AuthenticationRequest(OpenIdProvider provider, CheckIdRequest request) - : base(request) { + : base(provider, request) { + Contract.Requires(provider != null); ErrorUtilities.VerifyArgumentNotNull(provider, "provider"); this.positiveResponse = new PositiveAssertionResponse(request); - this.negativeResponse = new NegativeAssertionResponse(request, provider.Channel); if (this.ClaimedIdentifier == Protocol.ClaimedIdentifierForOPIdentifier && Protocol.ClaimedIdentifierForOPIdentifier != null) { @@ -71,29 +63,6 @@ namespace DotNetOpenAuth.OpenId.Provider { #region IAuthenticationRequest Properties /// <summary> - /// Gets the version of OpenID being used by the relying party that sent the request. - /// </summary> - public ProtocolVersion RelyingPartyVersion { - get { return Protocol.Lookup(this.RequestMessage.Version).ProtocolVersion; } - } - - /// <summary> - /// Gets a value indicating whether the consumer demands an immediate response. - /// If false, the consumer is willing to wait for the identity provider - /// to authenticate the user. - /// </summary> - public bool Immediate { - get { return this.RequestMessage.Immediate; } - } - - /// <summary> - /// Gets the URL the consumer site claims to use as its 'base' address. - /// </summary> - public Realm Realm { - get { return this.RequestMessage.Realm; } - } - - /// <summary> /// Gets a value indicating whether the Provider should help the user /// select a Claimed Identifier to send back to the relying party. /// </summary> @@ -197,7 +166,7 @@ namespace DotNetOpenAuth.OpenId.Provider { protected override IProtocolMessage ResponseMessage { get { if (this.IsAuthenticated.HasValue) { - return this.IsAuthenticated.Value ? (IProtocolMessage)this.positiveResponse : this.negativeResponse; + return this.IsAuthenticated.Value ? (IProtocolMessage)this.positiveResponse : this.NegativeResponse; } else { return null; } @@ -231,53 +200,6 @@ namespace DotNetOpenAuth.OpenId.Provider { this.positiveResponse.ClaimedIdentifier = builder.Uri; } - /// <summary> - /// Gets a value indicating whether verification of the return URL claimed by the Relying Party - /// succeeded. - /// </summary> - /// <param name="requestHandler">The request handler to use to perform relying party discovery.</param> - /// <returns> - /// <c>true</c> if the Relying Party passed discovery verification; <c>false</c> otherwise. - /// </returns> - /// <remarks> - /// Return URL verification is only attempted if this property is queried. - /// The result of the verification is cached per request so calling this - /// property getter multiple times in one request is not a performance hit. - /// See OpenID Authentication 2.0 spec section 9.2.1. - /// </remarks> - public bool IsReturnUrlDiscoverable(IDirectWebRequestHandler requestHandler) { - ErrorUtilities.VerifyArgumentNotNull(requestHandler, "requestHandler"); - - ErrorUtilities.VerifyInternal(this.Realm != null, "Realm should have been read or derived by now."); - try { - foreach (var returnUrl in Realm.Discover(requestHandler, false)) { - Realm discoveredReturnToUrl = returnUrl.ReturnToEndpoint; - - // The spec requires that the return_to URLs given in an RPs XRDS doc - // do not contain wildcards. - if (discoveredReturnToUrl.DomainWildcard) { - Logger.Yadis.WarnFormat("Realm {0} contained return_to URL {1} which contains a wildcard, which is not allowed.", Realm, discoveredReturnToUrl); - continue; - } - - // Use the same rules as return_to/realm matching to check whether this - // URL fits the return_to URL we were given. - if (discoveredReturnToUrl.Contains(this.RequestMessage.ReturnTo)) { - // no need to keep looking after we find a match - return true; - } - } - } catch (ProtocolException ex) { - // Don't do anything else. We quietly fail at return_to verification and return false. - Logger.Yadis.InfoFormat("Relying party discovery at URL {0} failed. {1}", Realm, ex); - } catch (WebException ex) { - // Don't do anything else. We quietly fail at return_to verification and return false. - Logger.Yadis.InfoFormat("Relying party discovery at URL {0} failed. {1}", Realm, ex); - } - - return false; - } - #endregion } } diff --git a/src/DotNetOpenAuth/OpenId/Provider/HostProcessedRequest.cs b/src/DotNetOpenAuth/OpenId/Provider/HostProcessedRequest.cs new file mode 100644 index 0000000..f4f42a4 --- /dev/null +++ b/src/DotNetOpenAuth/OpenId/Provider/HostProcessedRequest.cs @@ -0,0 +1,131 @@ +//----------------------------------------------------------------------- +// <copyright file="HostProcessedRequest.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.Provider { + using System; + using System.Collections.Generic; + using System.Diagnostics.Contracts; + using System.Linq; + using System.Net; + using System.Text; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OpenId.Messages; + + /// <summary> + /// A base class from which identity and non-identity RP requests can derive. + /// </summary> + internal abstract class HostProcessedRequest : Request, IHostProcessedRequest { + /// <summary> + /// The negative assertion to send, if the host site chooses to send it. + /// </summary> + private readonly NegativeAssertionResponse negativeResponse; + + /// <summary> + /// Initializes a new instance of the <see cref="HostProcessedRequest"/> class. + /// </summary> + /// <param name="provider">The provider that received the request.</param> + /// <param name="request">The incoming request message.</param> + protected HostProcessedRequest(OpenIdProvider provider, SignedResponseRequest request) + : base(request) { + Contract.Requires(provider != null); + + this.negativeResponse = new NegativeAssertionResponse(request, provider.Channel); + } + + #region IHostProcessedRequest Properties + + /// <summary> + /// Gets the version of OpenID being used by the relying party that sent the request. + /// </summary> + public ProtocolVersion RelyingPartyVersion { + get { return Protocol.Lookup(this.RequestMessage.Version).ProtocolVersion; } + } + + /// <summary> + /// Gets a value indicating whether the consumer demands an immediate response. + /// If false, the consumer is willing to wait for the identity provider + /// to authenticate the user. + /// </summary> + public bool Immediate { + get { return this.RequestMessage.Immediate; } + } + + /// <summary> + /// Gets the URL the consumer site claims to use as its 'base' address. + /// </summary> + public Realm Realm { + get { return this.RequestMessage.Realm; } + } + + #endregion + + /// <summary> + /// Gets the negative response. + /// </summary> + protected NegativeAssertionResponse NegativeResponse { + get { return this.negativeResponse; } + } + + /// <summary> + /// Gets the original request message. + /// </summary> + /// <value>This may be null in the case of an unrecognizable message.</value> + protected new SignedResponseRequest RequestMessage { + get { return (SignedResponseRequest)base.RequestMessage; } + } + + #region IHostProcessedRequest Methods + + /// <summary> + /// Gets a value indicating whether verification of the return URL claimed by the Relying Party + /// succeeded. + /// </summary> + /// <param name="requestHandler">The request handler to use to perform relying party discovery.</param> + /// <returns> + /// <c>true</c> if the Relying Party passed discovery verification; <c>false</c> otherwise. + /// </returns> + /// <remarks> + /// Return URL verification is only attempted if this property is queried. + /// The result of the verification is cached per request so calling this + /// property getter multiple times in one request is not a performance hit. + /// See OpenID Authentication 2.0 spec section 9.2.1. + /// </remarks> + public bool IsReturnUrlDiscoverable(IDirectWebRequestHandler requestHandler) { + ErrorUtilities.VerifyArgumentNotNull(requestHandler, "requestHandler"); + + ErrorUtilities.VerifyInternal(this.Realm != null, "Realm should have been read or derived by now."); + try { + foreach (var returnUrl in Realm.Discover(requestHandler, false)) { + Realm discoveredReturnToUrl = returnUrl.ReturnToEndpoint; + + // The spec requires that the return_to URLs given in an RPs XRDS doc + // do not contain wildcards. + if (discoveredReturnToUrl.DomainWildcard) { + Logger.Yadis.WarnFormat("Realm {0} contained return_to URL {1} which contains a wildcard, which is not allowed.", Realm, discoveredReturnToUrl); + continue; + } + + // Use the same rules as return_to/realm matching to check whether this + // URL fits the return_to URL we were given. + if (discoveredReturnToUrl.Contains(this.RequestMessage.ReturnTo)) { + // no need to keep looking after we find a match + return true; + } + } + } catch (ProtocolException ex) { + // Don't do anything else. We quietly fail at return_to verification and return false. + Logger.Yadis.InfoFormat("Relying party discovery at URL {0} failed. {1}", Realm, ex); + } catch (WebException ex) { + // Don't do anything else. We quietly fail at return_to verification and return false. + Logger.Yadis.InfoFormat("Relying party discovery at URL {0} failed. {1}", Realm, ex); + } + + return false; + } + + #endregion + } +} diff --git a/src/DotNetOpenAuth/OpenId/Provider/IAnonymousRequest.cs b/src/DotNetOpenAuth/OpenId/Provider/IAnonymousRequest.cs new file mode 100644 index 0000000..ec2c175 --- /dev/null +++ b/src/DotNetOpenAuth/OpenId/Provider/IAnonymousRequest.cs @@ -0,0 +1,25 @@ +//----------------------------------------------------------------------- +// <copyright file="IAnonymousRequest.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.Provider { + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + + /// <summary> + /// Instances of this interface represent incoming extension-only requests. + /// This interface provides the details of the request and allows setting + /// the response. + /// </summary> + public interface IAnonymousRequest : IHostProcessedRequest { + /// <summary> + /// Gets or sets a value indicating whether the user approved sending any data to the relying party. + /// </summary> + /// <value><c>true</c> if approved; otherwise, <c>false</c>.</value> + bool? IsApproved { get; set; } + } +} diff --git a/src/DotNetOpenAuth/OpenId/Provider/IAuthenticationRequest.cs b/src/DotNetOpenAuth/OpenId/Provider/IAuthenticationRequest.cs index b1ef269..bb837b5 100644 --- a/src/DotNetOpenAuth/OpenId/Provider/IAuthenticationRequest.cs +++ b/src/DotNetOpenAuth/OpenId/Provider/IAuthenticationRequest.cs @@ -15,24 +15,7 @@ namespace DotNetOpenAuth.OpenId.Provider { /// This interface provides the details of the request and allows setting /// the response. /// </summary> - public interface IAuthenticationRequest : IRequest { - /// <summary> - /// Gets the version of OpenID being used by the relying party that sent the request. - /// </summary> - ProtocolVersion RelyingPartyVersion { get; } - - /// <summary> - /// Gets a value indicating whether the consumer demands an immediate response. - /// If false, the consumer is willing to wait for the identity provider - /// to authenticate the user. - /// </summary> - bool Immediate { get; } - - /// <summary> - /// Gets the URL the consumer site claims to use as its 'base' address. - /// </summary> - Realm Realm { get; } - + public interface IAuthenticationRequest : IHostProcessedRequest { /// <summary> /// Gets a value indicating whether the Provider should help the user /// select a Claimed Identifier to send back to the relying party. @@ -109,18 +92,5 @@ namespace DotNetOpenAuth.OpenId.Provider { /// request before the <see cref="ClaimedIdentifier"/> property is set. /// </exception> void SetClaimedIdentifierFragment(string fragment); - - /// <summary> - /// Attempts to perform relying party discovery of the return URL claimed by the Relying Party. - /// </summary> - /// <param name="requestHandler">The request handler to use to perform relying party discovery.</param> - /// <returns> - /// <c>true</c> if the Relying Party passed discovery verification; <c>false</c> otherwise. - /// </returns> - /// <remarks> - /// <para>Return URL verification is only attempted if this method is called.</para> - /// <para>See OpenID Authentication 2.0 spec section 9.2.1.</para> - /// </remarks> - bool IsReturnUrlDiscoverable(IDirectWebRequestHandler requestHandler); } } diff --git a/src/DotNetOpenAuth/OpenId/Provider/IHostProcessedRequest.cs b/src/DotNetOpenAuth/OpenId/Provider/IHostProcessedRequest.cs new file mode 100644 index 0000000..31479a1 --- /dev/null +++ b/src/DotNetOpenAuth/OpenId/Provider/IHostProcessedRequest.cs @@ -0,0 +1,45 @@ +//----------------------------------------------------------------------- +// <copyright file="IHostProcessedRequest.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.Provider { + using DotNetOpenAuth.Messaging; + + /// <summary> + /// Interface exposing incoming messages to the OpenID Provider that + /// require interaction with the host site. + /// </summary> + public interface IHostProcessedRequest : IRequest { + /// <summary> + /// Gets the version of OpenID being used by the relying party that sent the request. + /// </summary> + ProtocolVersion RelyingPartyVersion { get; } + + /// <summary> + /// Gets the URL the consumer site claims to use as its 'base' address. + /// </summary> + Realm Realm { get; } + + /// <summary> + /// Gets a value indicating whether the consumer demands an immediate response. + /// If false, the consumer is willing to wait for the identity provider + /// to authenticate the user. + /// </summary> + bool Immediate { get; } + + /// <summary> + /// Attempts to perform relying party discovery of the return URL claimed by the Relying Party. + /// </summary> + /// <param name="requestHandler">The request handler to use to perform relying party discovery.</param> + /// <returns> + /// <c>true</c> if the Relying Party passed discovery verification; <c>false</c> otherwise. + /// </returns> + /// <remarks> + /// <para>Return URL verification is only attempted if this method is called.</para> + /// <para>See OpenID Authentication 2.0 spec section 9.2.1.</para> + /// </remarks> + bool IsReturnUrlDiscoverable(IDirectWebRequestHandler requestHandler); + } +} diff --git a/src/DotNetOpenAuth/OpenId/Provider/OpenIdProvider.cs b/src/DotNetOpenAuth/OpenId/Provider/OpenIdProvider.cs index c3780a9..7bb404c 100644 --- a/src/DotNetOpenAuth/OpenId/Provider/OpenIdProvider.cs +++ b/src/DotNetOpenAuth/OpenId/Provider/OpenIdProvider.cs @@ -204,6 +204,11 @@ namespace DotNetOpenAuth.OpenId.Provider { return new AuthenticationRequest(this, checkIdMessage); } + var extensionOnlyRequest = incomingMessage as SignedResponseRequest; + if (extensionOnlyRequest != null) { + return new AnonymousRequest(this, extensionOnlyRequest); + } + var checkAuthMessage = incomingMessage as CheckAuthenticationRequest; if (checkAuthMessage != null) { return new AutoResponsiveRequest(incomingMessage, new CheckAuthenticationResponse(checkAuthMessage, this)); diff --git a/src/DotNetOpenAuth/OpenId/Provider/ProviderEndpoint.cs b/src/DotNetOpenAuth/OpenId/Provider/ProviderEndpoint.cs index bec510b..91f10e6 100644 --- a/src/DotNetOpenAuth/OpenId/Provider/ProviderEndpoint.cs +++ b/src/DotNetOpenAuth/OpenId/Provider/ProviderEndpoint.cs @@ -27,7 +27,7 @@ namespace DotNetOpenAuth.OpenId.Provider { /// <summary> /// The key used to store the pending authentication request in the ASP.NET session. /// </summary> - private const string PendingAuthenticationRequestKey = "pendingAuthenticationRequestKey"; + private const string PendingRequestKey = "pendingRequest"; /// <summary> /// The default value for the <see cref="Enabled"/> property. @@ -52,6 +52,12 @@ namespace DotNetOpenAuth.OpenId.Provider { public event EventHandler<AuthenticationChallengeEventArgs> AuthenticationChallenge; /// <summary> + /// Fired when an incoming OpenID message carries extension requests + /// but is not regarding any OpenID identifier. + /// </summary> + public event EventHandler<AnonymousRequestEventArgs> AnonymousRequest; + + /// <summary> /// Gets or sets the <see cref="OpenIdProvider"/> instance to use for all instances of this control. /// </summary> /// <value>The default value is an <see cref="OpenIdProvider"/> instance initialized according to the web.config file.</value> @@ -76,8 +82,36 @@ namespace DotNetOpenAuth.OpenId.Provider { /// before responding to the relying party's authentication request. /// </remarks> public static IAuthenticationRequest PendingAuthenticationRequest { - get { return HttpContext.Current.Session[PendingAuthenticationRequestKey] as IAuthenticationRequest; } - set { HttpContext.Current.Session[PendingAuthenticationRequestKey] = value; } + get { return HttpContext.Current.Session[PendingRequestKey] as IAuthenticationRequest; } + set { HttpContext.Current.Session[PendingRequestKey] = value; } + } + + /// <summary> + /// Gets or sets an incoming OpenID anonymous request that has not yet been responded to. + /// </summary> + /// <remarks> + /// This request is stored in the ASP.NET Session state, so it will survive across + /// redirects, postbacks, and transfers. This allows you to authenticate the user + /// yourself, and confirm his/her desire to provide data to the relying party site + /// before responding to the relying party's request. + /// </remarks> + public static IAnonymousRequest PendingAnonymousRequest { + get { return HttpContext.Current.Session[PendingRequestKey] as IAnonymousRequest; } + set { HttpContext.Current.Session[PendingRequestKey] = value; } + } + + /// <summary> + /// Gets or sets an incoming OpenID request that has not yet been responded to. + /// </summary> + /// <remarks> + /// This request is stored in the ASP.NET Session state, so it will survive across + /// redirects, postbacks, and transfers. This allows you to authenticate the user + /// yourself, and confirm his/her desire to provide data to the relying party site + /// before responding to the relying party's request. + /// </remarks> + public static IHostProcessedRequest PendingRequest { + get { return HttpContext.Current.Session[PendingRequestKey] as IHostProcessedRequest; } + set { HttpContext.Current.Session[PendingRequestKey] = value; } } /// <summary> @@ -100,8 +134,8 @@ namespace DotNetOpenAuth.OpenId.Provider { /// Sends the response for the <see cref="PendingAuthenticationRequest"/> and clears the property. /// </summary> public static void SendResponse() { - Provider.SendResponse(PendingAuthenticationRequest); - PendingAuthenticationRequest = null; + Provider.SendResponse(PendingRequest); + PendingRequest = null; } /// <summary> @@ -125,13 +159,22 @@ namespace DotNetOpenAuth.OpenId.Provider { // determine what incoming message was received IRequest request = provider.GetRequest(); if (request != null) { + PendingRequest = null; + // process the incoming message appropriately and send the response - if (!request.IsResponseReady) { - var idrequest = (IAuthenticationRequest)request; + IAuthenticationRequest idrequest; + IAnonymousRequest anonRequest; + if ((idrequest = request as IAuthenticationRequest) != null) { PendingAuthenticationRequest = idrequest; this.OnAuthenticationChallenge(idrequest); - } else { - PendingAuthenticationRequest = null; + } else if ((anonRequest = request as IAnonymousRequest) != null) { + PendingAnonymousRequest = anonRequest; + if (!this.OnAnonymousRequest(anonRequest)) { + // This is a feature not supported by the OP, so + // go ahead and set disapproved so we can send a response. + Logger.OpenId.Warn("An incoming anonymous OpenID request message was detected, but the ProviderEndpoint.AnonymousRequest event is not handled, so returning cancellation message to relying party."); + anonRequest.IsApproved = false; + } } if (request.IsResponseReady) { provider.SendResponse(request); @@ -154,6 +197,21 @@ namespace DotNetOpenAuth.OpenId.Provider { } /// <summary> + /// Fires the <see cref="AnonymousRequest"/> event. + /// </summary> + /// <param name="request">The request to include in the event args.</param> + /// <returns><c>true</c> if there were any anonymous request handlers.</returns> + protected virtual bool OnAnonymousRequest(IAnonymousRequest request) { + var anonymousRequest = this.AnonymousRequest; + if (anonymousRequest != null) { + anonymousRequest(this, new AnonymousRequestEventArgs(request)); + return true; + } else { + return false; + } + } + + /// <summary> /// Creates the default OpenIdProvider to use. /// </summary> /// <returns>The new instance of OpenIdProvider.</returns> diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/AuthenticationRequest.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/AuthenticationRequest.cs index 2e22aec..1f470b2 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/AuthenticationRequest.cs +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/AuthenticationRequest.cs @@ -133,6 +133,16 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { } /// <summary> + /// Gets or sets a value indicating whether this request only carries extensions + /// and is not a request to verify that the user controls some identifier. + /// </summary> + /// <value> + /// <c>true</c> if this request is merely a carrier of extensions and is not + /// about an OpenID identifier; otherwise, <c>false</c>. + /// </value> + public bool IsExtensionOnly { get; set; } + + /// <summary> /// Gets information about the OpenId Provider, as advertised by the /// OpenId discovery documents found at the <see cref="ClaimedIdentifier"/> /// location. @@ -401,16 +411,22 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { } /// <summary> - /// Creates the authentication request message to send to the Provider, + /// Creates the request message to send to the Provider, /// based on the properties in this instance. /// </summary> /// <returns>The message to send to the Provider.</returns> - private CheckIdRequest CreateRequestMessage() { + private SignedResponseRequest CreateRequestMessage() { Association association = this.GetAssociation(); - CheckIdRequest request = new CheckIdRequest(this.endpoint.Protocol.Version, this.endpoint.ProviderEndpoint, this.Mode); - request.ClaimedIdentifier = this.endpoint.ClaimedIdentifier; - request.LocalIdentifier = this.endpoint.ProviderLocalIdentifier; + SignedResponseRequest request; + if (!this.IsExtensionOnly) { + CheckIdRequest authRequest = new CheckIdRequest(this.endpoint.Protocol.Version, this.endpoint.ProviderEndpoint, this.Mode); + authRequest.ClaimedIdentifier = this.endpoint.ClaimedIdentifier; + authRequest.LocalIdentifier = this.endpoint.ProviderLocalIdentifier; + request = authRequest; + } else { + request = new SignedResponseRequest(this.endpoint.Protocol.Version, this.endpoint.ProviderEndpoint, this.Mode); + } request.Realm = this.Realm; request.ReturnTo = this.ReturnToUrl; request.AssociationHandle = association != null ? association.Handle : null; diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/AuthenticationStatus.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/AuthenticationStatus.cs index c67355d..d9e5d0a 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/AuthenticationStatus.cs +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/AuthenticationStatus.cs @@ -33,5 +33,11 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// Authentication is completed successfully. /// </summary> Authenticated, + + /// <summary> + /// The Provider sent a message that did not contain an identity assertion, + /// but may carry OpenID extensions. + /// </summary> + ExtensionsOnly, } } diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/IAuthenticationRequest.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/IAuthenticationRequest.cs index 4892092..0bfcaf5 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/IAuthenticationRequest.cs +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/IAuthenticationRequest.cs @@ -63,6 +63,30 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { bool IsDirectedIdentity { get; } /// <summary> + /// Gets or sets a value indicating whether this request only carries extensions + /// and is not a request to verify that the user controls some identifier. + /// </summary> + /// <value> + /// <c>true</c> if this request is merely a carrier of extensions and is not + /// about an OpenID identifier; otherwise, <c>false</c>. + /// </value> + /// <remarks> + /// <para>Although OpenID is first and primarily an authentication protocol, its extensions + /// can be interesting all by themselves. For instance, a relying party might want + /// to know that its user is over 21 years old, or perhaps a member of some organization. + /// OpenID extensions can provide this, without any need for asserting the identity of the user.</para> + /// <para>Constructing an OpenID request for only extensions can be done by calling + /// <see cref="OpenIdRelyingParty.CreateRequest(Identifier)"/> with any valid OpenID identifier + /// (claimed identifier or OP identifier). But once this property is set to <c>true</c>, + /// the claimed identifier value in the request is not included in the transmitted message.</para> + /// <para>It is anticipated that an RP would only issue these types of requests to OPs that + /// trusts to make assertions regarding the individual holding an account at that OP, so it + /// is not likely that the RP would allow the user to type in an arbitrary claimed identifier + /// without checking that it resolved to an OP endpoint the RP has on a trust whitelist.</para> + /// </remarks> + bool IsExtensionOnly { get; set; } + + /// <summary> /// Gets information about the OpenId Provider, as advertised by the /// OpenId discovery documents found at the <see cref="ClaimedIdentifier"/> /// location. diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingParty.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingParty.cs index 4bcaf10..3762602 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingParty.cs +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingParty.cs @@ -342,8 +342,11 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { var message = this.Channel.ReadFromRequest(httpRequestInfo); PositiveAssertionResponse positiveAssertion; NegativeAssertionResponse negativeAssertion; + IndirectSignedResponse positiveExtensionOnly; if ((positiveAssertion = message as PositiveAssertionResponse) != null) { return new PositiveAuthenticationResponse(positiveAssertion, this); + } else if ((positiveExtensionOnly = message as IndirectSignedResponse) != null) { + return new PositiveAnonymousResponse(positiveExtensionOnly); } else if ((negativeAssertion = message as NegativeAssertionResponse) != null) { return new NegativeAuthenticationResponse(negativeAssertion); } else if (message != null) { diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdTextBox.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdTextBox.cs index c86647a..1eea315 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdTextBox.cs +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdTextBox.cs @@ -1033,8 +1033,13 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { case AuthenticationStatus.Failed: this.OnFailed(response); break; + case AuthenticationStatus.ExtensionsOnly: default: - throw new InvalidOperationException("Unexpected response status code."); + // The NotApplicable (extension-only assertion) is NOT one that we support + // in this control because that scenario is primarily interesting to RPs + // that are asking a specific OP, and it is not user-initiated as this textbox + // is designed for. + throw new InvalidOperationException(MessagingStrings.UnexpectedMessageReceivedOfMany); } } } diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/PositiveAnonymousResponse.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/PositiveAnonymousResponse.cs new file mode 100644 index 0000000..a28de12 --- /dev/null +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/PositiveAnonymousResponse.cs @@ -0,0 +1,273 @@ +//----------------------------------------------------------------------- +// <copyright file="PositiveAnonymousResponse.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.RelyingParty { + using System; + using System.Collections.Generic; + using System.Diagnostics.Contracts; + using System.Linq; + using System.Web; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OpenId.Messages; + + /// <summary> + /// Wraps an extension-only response from the OP in an <see cref="IAuthenticationResponse"/> instance + /// for public consumption by the host web site. + /// </summary> + internal class PositiveAnonymousResponse : IAuthenticationResponse { + /// <summary> + /// Backin field for the <see cref="Response"/> property. + /// </summary> + private readonly IndirectSignedResponse response; + + /// <summary> + /// Initializes a new instance of the <see cref="PositiveAnonymousResponse"/> class. + /// </summary> + /// <param name="response">The response message.</param> + protected internal PositiveAnonymousResponse(IndirectSignedResponse response) { + Contract.Requires(response != null); + ErrorUtilities.VerifyArgumentNotNull(response, "response"); + + this.response = response; + } + + #region IAuthenticationResponse Properties + + /// <summary> + /// Gets the Identifier that the end user claims to own. For use with user database storage and lookup. + /// May be null for some failed authentications (i.e. failed directed identity authentications). + /// </summary> + /// <remarks> + /// <para> + /// This is the secure identifier that should be used for database storage and lookup. + /// It is not always friendly (i.e. =Arnott becomes =!9B72.7DD1.50A9.5CCD), but it protects + /// user identities against spoofing and other attacks. + /// </para> + /// <para> + /// For user-friendly identifiers to display, use the + /// <see cref="FriendlyIdentifierForDisplay"/> property. + /// </para> + /// </remarks> + public virtual Identifier ClaimedIdentifier { + get { return null; } + } + + /// <summary> + /// Gets a user-friendly OpenID Identifier for display purposes ONLY. + /// </summary> + /// <value></value> + /// <remarks> + /// <para> + /// This <i>should</i> be put through <see cref="HttpUtility.HtmlEncode(string)"/> before + /// sending to a browser to secure against javascript injection attacks. + /// </para> + /// <para> + /// This property retains some aspects of the user-supplied identifier that get lost + /// in the <see cref="ClaimedIdentifier"/>. For example, XRIs used as user-supplied + /// identifiers (i.e. =Arnott) become unfriendly unique strings (i.e. =!9B72.7DD1.50A9.5CCD). + /// For display purposes, such as text on a web page that says "You're logged in as ...", + /// this property serves to provide the =Arnott string, or whatever else is the most friendly + /// string close to what the user originally typed in. + /// </para> + /// <para> + /// If the user-supplied identifier is a URI, this property will be the URI after all + /// redirects, and with the protocol and fragment trimmed off. + /// If the user-supplied identifier is an XRI, this property will be the original XRI. + /// If the user-supplied identifier is an OpenID Provider identifier (i.e. yahoo.com), + /// this property will be the Claimed Identifier, with the protocol stripped if it is a URI. + /// </para> + /// <para> + /// It is <b>very</b> important that this property <i>never</i> be used for database storage + /// or lookup to avoid identity spoofing and other security risks. For database storage + /// and lookup please use the <see cref="ClaimedIdentifier"/> property. + /// </para> + /// </remarks> + public virtual string FriendlyIdentifierForDisplay { + get { return null; } + } + + /// <summary> + /// Gets the detailed success or failure status of the authentication attempt. + /// </summary> + public virtual AuthenticationStatus Status { + get { return AuthenticationStatus.ExtensionsOnly; } + } + + /// <summary> + /// Gets the details regarding a failed authentication attempt, if available. + /// This will be set if and only if <see cref="Status"/> is <see cref="AuthenticationStatus.Failed"/>. + /// </summary> + /// <value></value> + public Exception Exception { + get { return null; } + } + + #endregion + + /// <summary> + /// Gets the positive extension-only message the Relying Party received that this instance wraps. + /// </summary> + protected internal IndirectSignedResponse Response { + get { return this.response; } + } + + #region IAuthenticationResponse methods + + /// <summary> + /// Gets a callback argument's value that was previously added using + /// <see cref="IAuthenticationRequest.AddCallbackArguments(string, string)"/>. + /// </summary> + /// <param name="key">The name of the parameter whose value is sought.</param> + /// <returns> + /// The value of the argument, or null if the named parameter could not be found. + /// </returns> + /// <remarks> + /// Callback parameters are only available if they are complete and untampered with + /// since the original request message (as proven by a signature). + /// If the relying party is operating in stateless mode <c>null</c> is always + /// returned since the callback arguments could not be signed to protect against + /// tampering. + /// </remarks> + public string GetCallbackArgument(string key) { + if (this.response.ReturnToParametersSignatureValidated) { + return this.response.GetReturnToArgument(key); + } else { + return null; + } + } + + /// <summary> + /// Gets all the callback arguments that were previously added using + /// <see cref="IAuthenticationRequest.AddCallbackArguments(string, string)"/> or as a natural part + /// of the return_to URL. + /// </summary> + /// <returns>A name-value dictionary. Never null.</returns> + /// <remarks> + /// Callback parameters are only available if they are complete and untampered with + /// since the original request message (as proven by a signature). + /// If the relying party is operating in stateless mode an empty dictionary is always + /// returned since the callback arguments could not be signed to protect against + /// tampering. + /// </remarks> + public IDictionary<string, string> GetCallbackArguments() { + if (this.response.ReturnToParametersSignatureValidated) { + var args = new Dictionary<string, string>(); + + // Return all the return_to arguments, except for the OpenID-supporting ones. + // The only arguments that should be returned here are the ones that the host + // web site adds explicitly. + foreach (string key in this.response.GetReturnToParameterNames().Where(key => !OpenIdRelyingParty.IsOpenIdSupportingParameter(key))) { + args[key] = this.response.GetReturnToArgument(key); + } + + return args; + } else { + return EmptyDictionary<string, string>.Instance; + } + } + + /// <summary> + /// Tries to get an OpenID extension that may be present in the response. + /// </summary> + /// <typeparam name="T">The type of extension to look for in the response message.</typeparam> + /// <returns> + /// The extension, if it is found. Null otherwise. + /// </returns> + /// <remarks> + /// <para>Extensions are returned only if the Provider signed them. + /// Relying parties that do not care if the values were modified in + /// transit should use the <see cref="GetUntrustedExtension<T>"/> method + /// in order to allow the Provider to not sign the extension. </para> + /// <para>Unsigned extensions are completely unreliable and should be + /// used only to prefill user forms since the user or any other third + /// party may have tampered with the data carried by the extension.</para> + /// <para>Signed extensions are only reliable if the relying party + /// trusts the OpenID Provider that signed them. Signing does not mean + /// the relying party can trust the values -- it only means that the values + /// have not been tampered with since the Provider sent the message.</para> + /// </remarks> + public T GetExtension<T>() where T : IOpenIdMessageExtension { + return this.response.SignedExtensions.OfType<T>().FirstOrDefault(); + } + + /// <summary> + /// Tries to get an OpenID extension that may be present in the response. + /// </summary> + /// <param name="extensionType">Type of the extension to look for in the response.</param> + /// <returns> + /// The extension, if it is found. Null otherwise. + /// </returns> + /// <remarks> + /// <para>Extensions are returned only if the Provider signed them. + /// Relying parties that do not care if the values were modified in + /// transit should use the <see cref="GetUntrustedExtension"/> method + /// in order to allow the Provider to not sign the extension. </para> + /// <para>Unsigned extensions are completely unreliable and should be + /// used only to prefill user forms since the user or any other third + /// party may have tampered with the data carried by the extension.</para> + /// <para>Signed extensions are only reliable if the relying party + /// trusts the OpenID Provider that signed them. Signing does not mean + /// the relying party can trust the values -- it only means that the values + /// have not been tampered with since the Provider sent the message.</para> + /// </remarks> + public IOpenIdMessageExtension GetExtension(Type extensionType) { + ErrorUtilities.VerifyArgumentNotNull(extensionType, "extensionType"); + return this.response.SignedExtensions.OfType<IOpenIdMessageExtension>().Where(ext => extensionType.IsInstanceOfType(ext)).FirstOrDefault(); + } + + /// <summary> + /// Tries to get an OpenID extension that may be present in the response, without + /// requiring it to be signed by the Provider. + /// </summary> + /// <typeparam name="T">The type of extension to look for in the response message.</typeparam> + /// <returns> + /// The extension, if it is found. Null otherwise. + /// </returns> + /// <remarks> + /// <para>Extensions are returned whether they are signed or not. + /// Use the <see cref="GetExtension<T>"/> method to retrieve + /// extension responses only if they are signed by the Provider to + /// protect against tampering. </para> + /// <para>Unsigned extensions are completely unreliable and should be + /// used only to prefill user forms since the user or any other third + /// party may have tampered with the data carried by the extension.</para> + /// <para>Signed extensions are only reliable if the relying party + /// trusts the OpenID Provider that signed them. Signing does not mean + /// the relying party can trust the values -- it only means that the values + /// have not been tampered with since the Provider sent the message.</para> + /// </remarks> + public T GetUntrustedExtension<T>() where T : IOpenIdMessageExtension { + return this.response.Extensions.OfType<T>().FirstOrDefault(); + } + + /// <summary> + /// Tries to get an OpenID extension that may be present in the response. + /// </summary> + /// <param name="extensionType">Type of the extension to look for in the response.</param> + /// <returns> + /// The extension, if it is found. Null otherwise. + /// </returns> + /// <remarks> + /// <para>Extensions are returned whether they are signed or not. + /// Use the <see cref="GetExtension"/> method to retrieve + /// extension responses only if they are signed by the Provider to + /// protect against tampering. </para> + /// <para>Unsigned extensions are completely unreliable and should be + /// used only to prefill user forms since the user or any other third + /// party may have tampered with the data carried by the extension.</para> + /// <para>Signed extensions are only reliable if the relying party + /// trusts the OpenID Provider that signed them. Signing does not mean + /// the relying party can trust the values -- it only means that the values + /// have not been tampered with since the Provider sent the message.</para> + /// </remarks> + public IOpenIdMessageExtension GetUntrustedExtension(Type extensionType) { + ErrorUtilities.VerifyArgumentNotNull(extensionType, "extensionType"); + return this.response.Extensions.OfType<IOpenIdMessageExtension>().Where(ext => extensionType.IsInstanceOfType(ext)).FirstOrDefault(); + } + + #endregion + } +} diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/PositiveAuthenticationResponse.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/PositiveAuthenticationResponse.cs index 32980f5..e3740db 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/PositiveAuthenticationResponse.cs +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/PositiveAuthenticationResponse.cs @@ -5,14 +5,11 @@ //----------------------------------------------------------------------- namespace DotNetOpenAuth.OpenId.RelyingParty { - using System; - using System.Collections.Generic; using System.Diagnostics; + using System.Diagnostics.Contracts; using System.Linq; - using System.Text; using System.Web; using DotNetOpenAuth.Messaging; - using DotNetOpenAuth.OpenId.Extensions; using DotNetOpenAuth.OpenId.Messages; /// <summary> @@ -20,17 +17,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// for public consumption by the host web site. /// </summary> [DebuggerDisplay("Status: {Status}, ClaimedIdentifier: {ClaimedIdentifier}")] - internal class PositiveAuthenticationResponse : IAuthenticationResponse { - /// <summary> - /// The positive assertion message the Relying Party received that this instance wraps. - /// </summary> - private readonly PositiveAssertionResponse response; - - /// <summary> - /// The relying party that created this request object. - /// </summary> - private readonly OpenIdRelyingParty relyingParty; - + internal class PositiveAuthenticationResponse : PositiveAnonymousResponse { /// <summary> /// The OpenID service endpoint reconstructed from the assertion message. /// </summary> @@ -47,22 +34,20 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// </summary> /// <param name="response">The positive assertion response that was just received by the Relying Party.</param> /// <param name="relyingParty">The relying party.</param> - internal PositiveAuthenticationResponse(PositiveAssertionResponse response, OpenIdRelyingParty relyingParty) { - ErrorUtilities.VerifyArgumentNotNull(response, "response"); + internal PositiveAuthenticationResponse(PositiveAssertionResponse response, OpenIdRelyingParty relyingParty) + : base(response) { + Contract.Requires(relyingParty != null); ErrorUtilities.VerifyArgumentNotNull(relyingParty, "relyingParty"); - this.response = response; - this.relyingParty = relyingParty; - this.endpoint = ServiceEndpoint.CreateForClaimedIdentifier( - this.response.ClaimedIdentifier, - this.response.GetReturnToArgument(AuthenticationRequest.UserSuppliedIdentifierParameterName), - this.response.LocalIdentifier, - new ProviderEndpointDescription(this.response.ProviderEndpoint, this.response.Version), + this.Response.ClaimedIdentifier, + this.Response.GetReturnToArgument(AuthenticationRequest.UserSuppliedIdentifierParameterName), + this.Response.LocalIdentifier, + new ProviderEndpointDescription(this.Response.ProviderEndpoint, this.Response.Version), null, null); - this.VerifyDiscoveryMatchesAssertion(); + this.VerifyDiscoveryMatchesAssertion(relyingParty); } #region IAuthenticationResponse Properties @@ -83,14 +68,13 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// <see cref="FriendlyIdentifierForDisplay"/> property. /// </para> /// </remarks> - public Identifier ClaimedIdentifier { + public override Identifier ClaimedIdentifier { get { return this.endpoint.ClaimedIdentifier; } } /// <summary> /// Gets a user-friendly OpenID Identifier for display purposes ONLY. /// </summary> - /// <value></value> /// <remarks> /// <para> /// This <i>should</i> be put through <see cref="HttpUtility.HtmlEncode(string)"/> before @@ -117,203 +101,38 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// and lookup please use the <see cref="ClaimedIdentifier"/> property. /// </para> /// </remarks> - public string FriendlyIdentifierForDisplay { + public override string FriendlyIdentifierForDisplay { get { return this.endpoint.FriendlyIdentifierForDisplay; } } /// <summary> /// Gets the detailed success or failure status of the authentication attempt. /// </summary> - /// <value></value> - public AuthenticationStatus Status { + public override AuthenticationStatus Status { get { return AuthenticationStatus.Authenticated; } } - /// <summary> - /// Gets the details regarding a failed authentication attempt, if available. - /// This will be set if and only if <see cref="Status"/> is <see cref="AuthenticationStatus.Failed"/>. - /// </summary> - /// <value></value> - public Exception Exception { - get { return null; } - } - #endregion /// <summary> /// Gets the positive assertion response message. /// </summary> - internal PositiveAssertionResponse Response { - get { return this.response; } - } - - #region IAuthenticationResponse methods - - /// <summary> - /// Gets a callback argument's value that was previously added using - /// <see cref="IAuthenticationRequest.AddCallbackArguments(string, string)"/>. - /// </summary> - /// <param name="key">The name of the parameter whose value is sought.</param> - /// <returns> - /// The value of the argument, or null if the named parameter could not be found. - /// </returns> - /// <remarks> - /// Callback parameters are only available if they are complete and untampered with - /// since the original request message (as proven by a signature). - /// If the relying party is operating in stateless mode <c>null</c> is always - /// returned since the callback arguments could not be signed to protect against - /// tampering. - /// </remarks> - public string GetCallbackArgument(string key) { - if (this.response.ReturnToParametersSignatureValidated) { - return this.response.GetReturnToArgument(key); - } else { - return null; - } - } - - /// <summary> - /// Gets all the callback arguments that were previously added using - /// <see cref="IAuthenticationRequest.AddCallbackArguments(string, string)"/> or as a natural part - /// of the return_to URL. - /// </summary> - /// <returns>A name-value dictionary. Never null.</returns> - /// <remarks> - /// Callback parameters are only available if they are complete and untampered with - /// since the original request message (as proven by a signature). - /// If the relying party is operating in stateless mode an empty dictionary is always - /// returned since the callback arguments could not be signed to protect against - /// tampering. - /// </remarks> - public IDictionary<string, string> GetCallbackArguments() { - if (this.response.ReturnToParametersSignatureValidated) { - var args = new Dictionary<string, string>(); - - // Return all the return_to arguments, except for the OpenID-supporting ones. - // The only arguments that should be returned here are the ones that the host - // web site adds explicitly. - foreach (string key in this.response.GetReturnToParameterNames().Where(key => !OpenIdRelyingParty.IsOpenIdSupportingParameter(key))) { - args[key] = this.response.GetReturnToArgument(key); - } - - return args; - } else { - return EmptyDictionary<string, string>.Instance; - } - } - - /// <summary> - /// Tries to get an OpenID extension that may be present in the response. - /// </summary> - /// <typeparam name="T">The type of extension to look for in the response message.</typeparam> - /// <returns> - /// The extension, if it is found. Null otherwise. - /// </returns> - /// <remarks> - /// <para>Extensions are returned only if the Provider signed them. - /// Relying parties that do not care if the values were modified in - /// transit should use the <see cref="GetUntrustedExtension<T>"/> method - /// in order to allow the Provider to not sign the extension. </para> - /// <para>Unsigned extensions are completely unreliable and should be - /// used only to prefill user forms since the user or any other third - /// party may have tampered with the data carried by the extension.</para> - /// <para>Signed extensions are only reliable if the relying party - /// trusts the OpenID Provider that signed them. Signing does not mean - /// the relying party can trust the values -- it only means that the values - /// have not been tampered with since the Provider sent the message.</para> - /// </remarks> - public T GetExtension<T>() where T : IOpenIdMessageExtension { - return this.response.SignedExtensions.OfType<T>().FirstOrDefault(); - } - - /// <summary> - /// Tries to get an OpenID extension that may be present in the response. - /// </summary> - /// <param name="extensionType">Type of the extension to look for in the response.</param> - /// <returns> - /// The extension, if it is found. Null otherwise. - /// </returns> - /// <remarks> - /// <para>Extensions are returned only if the Provider signed them. - /// Relying parties that do not care if the values were modified in - /// transit should use the <see cref="GetUntrustedExtension"/> method - /// in order to allow the Provider to not sign the extension. </para> - /// <para>Unsigned extensions are completely unreliable and should be - /// used only to prefill user forms since the user or any other third - /// party may have tampered with the data carried by the extension.</para> - /// <para>Signed extensions are only reliable if the relying party - /// trusts the OpenID Provider that signed them. Signing does not mean - /// the relying party can trust the values -- it only means that the values - /// have not been tampered with since the Provider sent the message.</para> - /// </remarks> - public IOpenIdMessageExtension GetExtension(Type extensionType) { - ErrorUtilities.VerifyArgumentNotNull(extensionType, "extensionType"); - return this.response.SignedExtensions.OfType<IOpenIdMessageExtension>().Where(ext => extensionType.IsInstanceOfType(ext)).FirstOrDefault(); - } - - /// <summary> - /// Tries to get an OpenID extension that may be present in the response, without - /// requiring it to be signed by the Provider. - /// </summary> - /// <typeparam name="T">The type of extension to look for in the response message.</typeparam> - /// <returns> - /// The extension, if it is found. Null otherwise. - /// </returns> - /// <remarks> - /// <para>Extensions are returned whether they are signed or not. - /// Use the <see cref="GetExtension<T>"/> method to retrieve - /// extension responses only if they are signed by the Provider to - /// protect against tampering. </para> - /// <para>Unsigned extensions are completely unreliable and should be - /// used only to prefill user forms since the user or any other third - /// party may have tampered with the data carried by the extension.</para> - /// <para>Signed extensions are only reliable if the relying party - /// trusts the OpenID Provider that signed them. Signing does not mean - /// the relying party can trust the values -- it only means that the values - /// have not been tampered with since the Provider sent the message.</para> - /// </remarks> - public T GetUntrustedExtension<T>() where T : IOpenIdMessageExtension { - return this.response.Extensions.OfType<T>().FirstOrDefault(); - } - - /// <summary> - /// Tries to get an OpenID extension that may be present in the response. - /// </summary> - /// <param name="extensionType">Type of the extension to look for in the response.</param> - /// <returns> - /// The extension, if it is found. Null otherwise. - /// </returns> - /// <remarks> - /// <para>Extensions are returned whether they are signed or not. - /// Use the <see cref="GetExtension"/> method to retrieve - /// extension responses only if they are signed by the Provider to - /// protect against tampering. </para> - /// <para>Unsigned extensions are completely unreliable and should be - /// used only to prefill user forms since the user or any other third - /// party may have tampered with the data carried by the extension.</para> - /// <para>Signed extensions are only reliable if the relying party - /// trusts the OpenID Provider that signed them. Signing does not mean - /// the relying party can trust the values -- it only means that the values - /// have not been tampered with since the Provider sent the message.</para> - /// </remarks> - public IOpenIdMessageExtension GetUntrustedExtension(Type extensionType) { - ErrorUtilities.VerifyArgumentNotNull(extensionType, "extensionType"); - return this.response.Extensions.OfType<IOpenIdMessageExtension>().Where(ext => extensionType.IsInstanceOfType(ext)).FirstOrDefault(); + protected internal new PositiveAssertionResponse Response { + get { return (PositiveAssertionResponse)base.Response; } } - #endregion - /// <summary> /// Verifies that the positive assertion data matches the results of /// discovery on the Claimed Identifier. /// </summary> + /// <param name="relyingParty">The relying party.</param> /// <exception cref="ProtocolException"> /// Thrown when the Provider is asserting that a user controls an Identifier /// when discovery on that Identifier contradicts what the Provider says. /// This would be an indication of either a misconfigured Provider or /// an attempt by someone to spoof another user's identity with a rogue Provider. /// </exception> - private void VerifyDiscoveryMatchesAssertion() { + private void VerifyDiscoveryMatchesAssertion(OpenIdRelyingParty relyingParty) { Logger.OpenId.Debug("Verifying assertion matches identifier discovery results..."); // While it LOOKS like we're performing discovery over HTTP again @@ -325,8 +144,12 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { // is signed by the RP before it's considered reliable. In 1.x stateless mode, this RP // doesn't (and can't) sign its own return_to URL, so its cached discovery information // is merely a hint that must be verified by performing discovery again here. - var discoveryResults = this.response.ClaimedIdentifier.Discover(this.relyingParty.WebRequestHandler); - ErrorUtilities.VerifyProtocol(discoveryResults.Contains(this.endpoint), OpenIdStrings.IssuedAssertionFailsIdentifierDiscovery, this.endpoint, discoveryResults.ToStringDeferred(true)); + var discoveryResults = this.Response.ClaimedIdentifier.Discover(relyingParty.WebRequestHandler); + ErrorUtilities.VerifyProtocol( + discoveryResults.Contains(this.endpoint), + OpenIdStrings.IssuedAssertionFailsIdentifierDiscovery, + this.endpoint, + discoveryResults.ToStringDeferred(true)); } } } |