diff options
-rw-r--r-- | samples/OpenIdProviderMvc/Controllers/OpenIdController.cs | 191 | ||||
-rw-r--r-- | samples/OpenIdProviderMvc/OpenIdProviderMvc.csproj | 1 | ||||
-rw-r--r-- | samples/OpenIdProviderMvc/Views/OpenId/AskUser.aspx | 38 | ||||
-rw-r--r-- | samples/OpenIdProviderMvc/Web.config | 1 |
4 files changed, 200 insertions, 31 deletions
diff --git a/samples/OpenIdProviderMvc/Controllers/OpenIdController.cs b/samples/OpenIdProviderMvc/Controllers/OpenIdController.cs index bd0fdbf..0ef9680 100644 --- a/samples/OpenIdProviderMvc/Controllers/OpenIdController.cs +++ b/samples/OpenIdProviderMvc/Controllers/OpenIdController.cs @@ -9,63 +9,192 @@ namespace OpenIdProviderMvc.Controllers { using DotNetOpenAuth.OpenId; using DotNetOpenAuth.OpenId.Behaviors; using DotNetOpenAuth.OpenId.Extensions.ProviderAuthenticationPolicy; + using DotNetOpenAuth.OpenId.Extensions.SimpleRegistration; using DotNetOpenAuth.OpenId.Provider; using OpenIdProviderMvc.Code; public class OpenIdController : Controller { internal static OpenIdProvider OpenIdProvider = new OpenIdProvider(); - internal static IAuthenticationRequest PendingAuthenticationRequest { - get { return ProviderEndpoint.PendingAuthenticationRequest; } - set { ProviderEndpoint.PendingAuthenticationRequest = value; } - } - [ValidateInput(false)] public ActionResult Provider() { IRequest request = OpenIdProvider.GetRequest(); if (request != null) { - var authRequest = request as IAuthenticationRequest; - if (authRequest != null) { - PendingAuthenticationRequest = authRequest; - if (authRequest.IsReturnUrlDiscoverable(OpenIdProvider) == RelyingPartyDiscoveryResult.Success && - User.Identity.IsAuthenticated && - (authRequest.IsDirectedIdentity || this.UserControlsIdentifier(authRequest))) { - return this.SendAssertion(); - } else { - return RedirectToAction("LogOn", "Account", new { returnUrl = Url.Action("SendAssertion") }); - } - } - + // Some requests are automatically handled by DotNetOpenAuth. If this is one, go ahead and let it go. if (request.IsResponseReady) { return OpenIdProvider.PrepareResponse(request).AsActionResult(); - } else { - return RedirectToAction("LogOn", "Account"); } + + // This is apparently one that the host (the web site itself) has to respond to. + ProviderEndpoint.PendingRequest = (IHostProcessedRequest)request; + + // Try responding immediately if possible. + ActionResult response; + if (this.AutoRespondIfPossible(out response)) { + return response; + } + + // We can't respond immediately with a positive result. But if we still have to respond immediately... + if (ProviderEndpoint.PendingRequest.Immediate) { + // We can't stop to prompt the user -- we must just return a negative response. + return this.SendAssertion(); + } + + return this.RedirectToAction("AskUser"); } else { - return View(); + // No OpenID request was recognized. This may be a user that stumbled on the OP Endpoint. + return this.View(); } } + /// <summary> + /// Displays a confirmation page. + /// </summary> [Authorize] + public ActionResult AskUser() { + if (ProviderEndpoint.PendingRequest == null) { + // Oops... precious little we can confirm without a pending OpenID request. + return this.RedirectToAction("Index", "Home"); + } + + // The user MAY have just logged in. Try again to respond automatically to the RP if appropriate. + ActionResult response; + if (this.AutoRespondIfPossible(out response)) { + return response; + } + + this.ViewData["Realm"] = ProviderEndpoint.PendingRequest.Realm; + + return this.View(); + } + + [HttpPost, Authorize, ValidateAntiForgeryToken] + public ActionResult AskUserResponse(bool confirmed) { + if (ProviderEndpoint.PendingAnonymousRequest != null) { + ProviderEndpoint.PendingAnonymousRequest.IsApproved = confirmed; + } else if (ProviderEndpoint.PendingAuthenticationRequest != null) { + ProviderEndpoint.PendingAuthenticationRequest.IsAuthenticated = confirmed; + } else { + throw new InvalidOperationException("There's no pending authentication request!"); + } + + return this.SendAssertion(); + } + + /// <summary> + /// Sends a positive or a negative assertion, based on how the pending request is currently marked. + /// </summary> + /// <returns>An MVC redirect result.</returns> public ActionResult SendAssertion() { - IAuthenticationRequest authReq = PendingAuthenticationRequest; - PendingAuthenticationRequest = null; // clear session static so we don't do this again - if (authReq == null) { + var pendingRequest = ProviderEndpoint.PendingRequest; + var authReq = pendingRequest as IAuthenticationRequest; + var anonReq = pendingRequest as IAnonymousRequest; + ProviderEndpoint.PendingRequest = null; // clear session static so we don't do this again + if (pendingRequest == null) { throw new InvalidOperationException("There's no pending authentication request!"); } - if (authReq.IsDirectedIdentity) { - authReq.LocalIdentifier = Models.User.GetClaimedIdentifierForUser(User.Identity.Name); + // Set safe defaults if somehow the user ended up (perhaps through XSRF) here before electing to send data to the RP. + if (anonReq != null && !anonReq.IsApproved.HasValue) { + anonReq.IsApproved = false; } - if (!authReq.IsDelegatedIdentifier) { - authReq.ClaimedIdentifier = authReq.LocalIdentifier; + + if (authReq != null && !authReq.IsAuthenticated.HasValue) { + authReq.IsAuthenticated = false; } - // Respond to AX/sreg extension requests. - //// Real web sites would have code here + if (authReq != null && authReq.IsAuthenticated.Value) { + if (authReq.IsDirectedIdentity) { + authReq.LocalIdentifier = Models.User.GetClaimedIdentifierForUser(User.Identity.Name); + } + + if (!authReq.IsDelegatedIdentifier) { + authReq.ClaimedIdentifier = authReq.LocalIdentifier; + } + } + + // Respond to AX/sreg extension requests only on a positive result. + if ((authReq != null && authReq.IsAuthenticated.Value) || + (anonReq != null && anonReq.IsApproved.Value)) { + // Look for a Simple Registration request. When the AXFetchAsSregTransform behavior is turned on + // in the web.config file as it is in this sample, AX requests will come in as SReg requests. + var claimsRequest = pendingRequest.GetExtension<ClaimsRequest>(); + if (claimsRequest != null) { + var claimsResponse = claimsRequest.CreateResponse(); + + // This simple respond to a request check may be enhanced to only respond to an individual attribute + // request if the user consents to it explicitly, in which case this response extension creation can take + // place in the confirmation page action rather than here. + if (claimsRequest.Email != DemandLevel.NoRequest) { + claimsResponse.Email = User.Identity.Name + "@dotnetopenauth.net"; + } + + pendingRequest.AddResponseExtension(claimsResponse); + } + } + + return OpenIdProvider.PrepareResponse(pendingRequest).AsActionResult(); + } + + /// <summary> + /// Attempts to formulate an automatic response to the RP if the user's profile allows it. + /// </summary> + /// <param name="response">Receives the ActionResult for the caller to return, or <c>null</c> if no automatic response can be made.</param> + /// <returns>A value indicating whether an automatic response is possible.</returns> + private bool AutoRespondIfPossible(out ActionResult response) { + // If the odds are good we can respond to this one immediately (without prompting the user)... + if (ProviderEndpoint.PendingRequest.IsReturnUrlDiscoverable(OpenIdProvider) == RelyingPartyDiscoveryResult.Success + && User.Identity.IsAuthenticated + && this.HasUserAuthorizedAutoLogin(ProviderEndpoint.PendingRequest)) { + // Is this is an identity authentication request? (as opposed to an anonymous request)... + if (ProviderEndpoint.PendingAuthenticationRequest != null) { + // If this is directed identity, or if the claimed identifier being checked is controlled by the current user... + if (ProviderEndpoint.PendingAuthenticationRequest.IsDirectedIdentity + || this.UserControlsIdentifier(ProviderEndpoint.PendingAuthenticationRequest)) { + ProviderEndpoint.PendingAuthenticationRequest.IsAuthenticated = true; + response = this.SendAssertion(); + return true; + } + } + + // If this is an anonymous request, we can respond to that too. + if (ProviderEndpoint.PendingAnonymousRequest != null) { + ProviderEndpoint.PendingAnonymousRequest.IsApproved = true; + response = this.SendAssertion(); + return true; + } + } + + response = null; + return false; + } + + /// <summary> + /// Determines whether the currently logged in user has authorized auto login to the requesting relying party. + /// </summary> + /// <param name="request">The incoming request.</param> + /// <returns> + /// <c>true</c> if it is safe to respond affirmatively to this request and all extensions + /// without further user confirmation; otherwise, <c>false</c>. + /// </returns> + private bool HasUserAuthorizedAutoLogin(IHostProcessedRequest request) { + // TODO: host should implement this method meaningfully, consulting their user database. + // Make sure the user likes the RP + if (true/*User.UserLikesRP(request.Realm))*/) { + // And make sure the RP is only asking for information about the user that the user has granted before. + if (true/*User.HasGrantedExtensions(request)*/) { + // For now for the purposes of the sample, we'll disallow auto-logins when an sreg request is present. + if (request.GetExtension<ClaimsRequest>() != null) { + return false; + } + + return true; + } + } - authReq.IsAuthenticated = this.UserControlsIdentifier(authReq); - return OpenIdProvider.PrepareResponse(authReq).AsActionResult(); + // If we aren't sure the user likes this site and is willing to disclose the requested info, return false + // so the user has the opportunity to explicity choose whether to share his/her info. + return false; } /// <summary> diff --git a/samples/OpenIdProviderMvc/OpenIdProviderMvc.csproj b/samples/OpenIdProviderMvc/OpenIdProviderMvc.csproj index 794a91e..9dc060e 100644 --- a/samples/OpenIdProviderMvc/OpenIdProviderMvc.csproj +++ b/samples/OpenIdProviderMvc/OpenIdProviderMvc.csproj @@ -92,6 +92,7 @@ <Content Include="Views\Account\ChangePassword.aspx" /> <Content Include="Views\Account\ChangePasswordSuccess.aspx" /> <Content Include="Views\Account\Register.aspx" /> + <Content Include="Views\OpenId\AskUser.aspx" /> <Content Include="Views\Shared\Xrds.aspx" /> <Content Include="Views\OpenId\Provider.aspx" /> <Content Include="Views\User\Identity.aspx" /> diff --git a/samples/OpenIdProviderMvc/Views/OpenId/AskUser.aspx b/samples/OpenIdProviderMvc/Views/OpenId/AskUser.aspx new file mode 100644 index 0000000..5098325 --- /dev/null +++ b/samples/OpenIdProviderMvc/Views/OpenId/AskUser.aspx @@ -0,0 +1,38 @@ +<%@ Page Title="Do you want to log into another web site?" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" + Inherits="System.Web.Mvc.ViewPage" %> + +<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server"> + <h2> + Logging in somewhere? + </h2> + <p> + Are you trying to log into + <b><%= Html.Encode(ViewData["Realm"]) %></b>? + </p> + <% using (Html.BeginForm("AskUserResponse", "OpenId")) { %> + <%= Html.AntiForgeryToken() %> + <%= Html.Hidden("confirmed", "false") %> + <div style="display: none" id="responseButtonsDiv"> + <input type="submit" value="yes" onclick="document.getElementsByName('confirmed')[0].value = 'true'; return true;" /> + <input type="submit" value="no" /> + </div> + <div id="javascriptDisabled"> + <b>Javascript appears to be disabled in your browser. </b>This page requires Javascript + to be enabled to better protect your security. + </div> + <script language="javascript" type="text/javascript"> + //<![CDATA[ + // we use HTML to hide the action buttons and Javascript to show them + // to protect against click-jacking in an iframe whose javascript is disabled. + document.getElementById('responseButtonsDiv').style.display = 'block'; + document.getElementById('javascriptDisabled').style.display = 'none'; + + // Frame busting code (to protect us from being hosted in an iframe). + // This protects us from click-jacking. + if (document.location !== window.top.location) { + window.top.location = document.location; + } + //]]> + </script> + <% } %> +</asp:Content> diff --git a/samples/OpenIdProviderMvc/Web.config b/samples/OpenIdProviderMvc/Web.config index cc30638..9b1eb90 100644 --- a/samples/OpenIdProviderMvc/Web.config +++ b/samples/OpenIdProviderMvc/Web.config @@ -46,6 +46,7 @@ profile matches, the default behavior is assumed. --> <!--<add type="DotNetOpenAuth.OpenId.Behaviors.GsaIcamProfile, DotNetOpenAuth" />--> <add type="DotNetOpenAuth.OpenId.Behaviors.PpidGeneration, DotNetOpenAuth" /> + <add type="DotNetOpenAuth.OpenId.Behaviors.AXFetchAsSregTransform, DotNetOpenAuth" /> </behaviors> <!-- Uncomment the following to activate the sample custom store. --> <!--<store type="RelyingPartyWebForms.CustomStore, RelyingPartyWebForms" />--> |