diff options
13 files changed, 268 insertions, 22 deletions
diff --git a/samples/OpenIdWebRingSsoProvider/Code/Util.cs b/samples/OpenIdWebRingSsoProvider/Code/Util.cs index ea01c9f..07064a2 100644 --- a/samples/OpenIdWebRingSsoProvider/Code/Util.cs +++ b/samples/OpenIdWebRingSsoProvider/Code/Util.cs @@ -9,9 +9,12 @@ namespace OpenIdWebRingSsoProvider.Code { using System.Configuration; using System.Web; using DotNetOpenAuth.OpenId; + using DotNetOpenAuth.OpenId.Extensions.AttributeExchange; using DotNetOpenAuth.OpenId.Provider; public class Util { + private const string RolesAttribute = "http://samples.dotnetopenauth.net/sso/roles"; + public static string ExtractUserName(Uri url) { return url.Segments[url.Segments.Length - 1]; } @@ -68,6 +71,16 @@ namespace OpenIdWebRingSsoProvider.Code { if (idrequest.IsAuthenticated.Value) { // add extension responses here. + var fetchRequest = idrequest.GetExtension<FetchRequest>(); + if (fetchRequest != null) { + var fetchResponse = new FetchResponse(); + if (fetchRequest.Attributes.Contains(RolesAttribute)) { + // Inform the RP what roles this user should fill + // These roles would normally come out of the user database. + fetchResponse.Attributes.Add(RolesAttribute, "Member", "Admin"); + } + idrequest.AddResponseExtension(fetchResponse); + } } } } diff --git a/samples/OpenIdWebRingSsoProvider/Default.aspx b/samples/OpenIdWebRingSsoProvider/Default.aspx index 5b74ffb..9bddc98 100644 --- a/samples/OpenIdWebRingSsoProvider/Default.aspx +++ b/samples/OpenIdWebRingSsoProvider/Default.aspx @@ -9,9 +9,17 @@ </head> <body> <form id="form1" runat="server"> - <div> - Provider SSO home page. - </div> + <p> + This sample is of an OpenID Provider that acts within a controlled set of web + sites (perhaps all belonging to the same organization). It authenticates + the user in its own way (Windows Auth, username/password, InfoCard, X.509, + anything), and then sends an automatically OpenID assertion to a limited set of + whitelisted RPs without prompting the user. + </p> + <p> + This particular sample uses Windows Authentication so that when the user visits + an RP and the RP sends the user to this OP for authentication, the process is + completely implicit -- the user never sees the OP.</p> </form> </body> </html> diff --git a/samples/OpenIdWebRingSsoRelyingParty/Admin/Default.aspx b/samples/OpenIdWebRingSsoRelyingParty/Admin/Default.aspx new file mode 100644 index 0000000..d3653e7 --- /dev/null +++ b/samples/OpenIdWebRingSsoRelyingParty/Admin/Default.aspx @@ -0,0 +1,19 @@ +<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="OpenIdWebRingSsoRelyingParty.Admin.Default" %> + +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml"> +<head runat="server"> + <title></title> +</head> +<body> + <form id="form1" runat="server"> + <div> + You must be an admin! + </div> + <p> + The roles you're assigned come from the trusted Provider's identity assertion. The + sample OP comes hard-wired to assert membership in the Admin and Member roles. + </p> + </form> +</body> +</html> diff --git a/samples/OpenIdWebRingSsoRelyingParty/Admin/Default.aspx.cs b/samples/OpenIdWebRingSsoRelyingParty/Admin/Default.aspx.cs new file mode 100644 index 0000000..94da1f7 --- /dev/null +++ b/samples/OpenIdWebRingSsoRelyingParty/Admin/Default.aspx.cs @@ -0,0 +1,13 @@ +namespace OpenIdWebRingSsoRelyingParty.Admin { + using System; + using System.Collections.Generic; + using System.Linq; + using System.Web; + using System.Web.UI; + using System.Web.UI.WebControls; + + public partial class Default : System.Web.UI.Page { + protected void Page_Load(object sender, EventArgs e) { + } + } +} diff --git a/samples/OpenIdWebRingSsoRelyingParty/Admin/Default.aspx.designer.cs b/samples/OpenIdWebRingSsoRelyingParty/Admin/Default.aspx.designer.cs new file mode 100644 index 0000000..9519fc3 --- /dev/null +++ b/samples/OpenIdWebRingSsoRelyingParty/Admin/Default.aspx.designer.cs @@ -0,0 +1,25 @@ +//------------------------------------------------------------------------------ +// <auto-generated> +// This code was generated by a tool. +// Runtime Version:2.0.50727.4927 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// </auto-generated> +//------------------------------------------------------------------------------ + +namespace OpenIdWebRingSsoRelyingParty.Admin { + + + public partial class Default { + + /// <summary> + /// form1 control. + /// </summary> + /// <remarks> + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// </remarks> + protected global::System.Web.UI.HtmlControls.HtmlForm form1; + } +} diff --git a/samples/OpenIdWebRingSsoRelyingParty/Admin/Web.config b/samples/OpenIdWebRingSsoRelyingParty/Admin/Web.config new file mode 100644 index 0000000..52a5faf --- /dev/null +++ b/samples/OpenIdWebRingSsoRelyingParty/Admin/Web.config @@ -0,0 +1,9 @@ +<?xml version="1.0"?> +<configuration> + <system.web> + <authorization> + <allow roles="Admin"/> + <deny users="*"/> + </authorization> + </system.web> +</configuration> diff --git a/samples/OpenIdWebRingSsoRelyingParty/AuthTicketRoles.cs b/samples/OpenIdWebRingSsoRelyingParty/AuthTicketRoles.cs new file mode 100644 index 0000000..06783bd --- /dev/null +++ b/samples/OpenIdWebRingSsoRelyingParty/AuthTicketRoles.cs @@ -0,0 +1,57 @@ +//----------------------------------------------------------------------- +// <copyright file="AuthTicketRoles.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace OpenIdWebRingSsoRelyingParty { + using System; + using System.Collections.Generic; + using System.Linq; + using System.Security.Principal; + using System.Web; + using System.Web.Security; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OAuth; + using DotNetOpenAuth.OAuth.ChannelElements; + using DotNetOpenAuth.OAuth.Messages; + + /// <summary> + /// An authentication module that utilizes the forms auth ticket cookie + /// as a cache for the users' roles, since those roles are determined by + /// the OpenID Provider and we don't have a local user-roles cache at this + /// RP since those relationships are always managed by the Provider. + /// </summary> + public class AuthTicketRoles : IHttpModule { + #region IHttpModule Members + + /// <summary> + /// Initializes a module and prepares it to handle requests. + /// </summary> + /// <param name="context">An <see cref="T:System.Web.HttpApplication"/> that provides access to the methods, properties, and events common to all application objects within an ASP.NET application</param> + public void Init(HttpApplication context) { + context.AuthenticateRequest += this.application_AuthenticateRequest; + } + + /// <summary> + /// Disposes of the resources (other than memory) used by the module that implements <see cref="T:System.Web.IHttpModule"/>. + /// </summary> + public void Dispose() { + } + + #endregion + + private void application_AuthenticateRequest(object sender, EventArgs e) { + if (HttpContext.Current.User != null) { + var cookie = HttpContext.Current.Request.Cookies[FormsAuthentication.FormsCookieName]; + if (cookie != null) { + var ticket = FormsAuthentication.Decrypt(cookie.Value); + if (!string.IsNullOrEmpty(ticket.UserData)) { + string[] roles = ticket.UserData.Split(';'); + HttpContext.Current.User = new GenericPrincipal(HttpContext.Current.User.Identity, roles); + } + } + } + } + } +} diff --git a/samples/OpenIdWebRingSsoRelyingParty/Default.aspx b/samples/OpenIdWebRingSsoRelyingParty/Default.aspx index 017ff8d..00efb08 100644 --- a/samples/OpenIdWebRingSsoRelyingParty/Default.aspx +++ b/samples/OpenIdWebRingSsoRelyingParty/Default.aspx @@ -8,8 +8,22 @@ <body> <form id="form1" runat="server"> <div> + We've recognized you (via the SSO OP) as: <asp:LoginName ID="LoginName1" runat="server" /> + <p>Try visiting the <a href="Admin/Default.aspx">Admin area</a></p> </div> + <p>This sample is of an OpenID Relying Party that acts within a controlled set of + web sites (perhaps all belonging to the same organization). This + particular RP is configured to require authentication for all web pages, and to + always use just one (trusted) OP (the OpenIdWebRingSsoProvider) without ever + prompting the user.</p> + <p>Although the sample OP uses Windows Authentication, and so this RP could easily + do the same, the idea is that the OP and RP may exist on different network + topologies, or the OP may be the only site with access to the user credential + database, or any number of other scenarios where the RP doesn't have the freedom + to authenticate the user the way the OP has, yet this set of web sites want to + have the users only authenticate themselves to one site with one set of + credentials.</p> </form> </body> </html> diff --git a/samples/OpenIdWebRingSsoRelyingParty/Login.aspx b/samples/OpenIdWebRingSsoRelyingParty/Login.aspx index ab97e6a..2e7df2e 100644 --- a/samples/OpenIdWebRingSsoRelyingParty/Login.aspx +++ b/samples/OpenIdWebRingSsoRelyingParty/Login.aspx @@ -7,13 +7,20 @@ </head> <body> <form id="form1" runat="server"> - <div> - Sorry. We couldn't log you in. - </div> - <asp:Label runat="server" ID="errorLabel" /> - <p> - <asp:Button ID="retryButton" runat="server" Text="Try Again" OnClick="retryButton_Click" /> - </p> + <asp:MultiView ID="MultiView1" runat="server" ActiveViewIndex="0"> + <asp:View ID="View1" runat="server"> + <div> + Sorry. We couldn't log you in. + </div> + <asp:Label runat="server" ID="errorLabel" /> + <p> + <asp:Button ID="retryButton" runat="server" Text="Try Again" OnClick="retryButton_Click" /> + </p> + </asp:View> + <asp:View ID="View2" runat="server"> + You don't have permission to visit <%=HttpUtility.HtmlEncode(Request.QueryString["ReturnUrl"]) %>. + </asp:View> + </asp:MultiView> </form> </body> </html> diff --git a/samples/OpenIdWebRingSsoRelyingParty/Login.aspx.cs b/samples/OpenIdWebRingSsoRelyingParty/Login.aspx.cs index e955b31..ec5af08 100644 --- a/samples/OpenIdWebRingSsoRelyingParty/Login.aspx.cs +++ b/samples/OpenIdWebRingSsoRelyingParty/Login.aspx.cs @@ -8,11 +8,19 @@ using System.Web.UI; using System.Web.UI.WebControls; using DotNetOpenAuth.OpenId; + using DotNetOpenAuth.OpenId.Extensions.AttributeExchange; using DotNetOpenAuth.OpenId.RelyingParty; public partial class Login : System.Web.UI.Page { + private const string RolesAttribute = "http://samples.dotnetopenauth.net/sso/roles"; + private static OpenIdRelyingParty relyingParty = new OpenIdRelyingParty(); + static Login() { + // Configure the RP to only allow assertions from our trusted OP endpoint. + relyingParty.EndpointFilter = ep => ep.Uri.AbsoluteUri == ConfigurationManager.AppSettings["SsoProviderOPEndpoint"]; + } + protected void Page_Load(object sender, EventArgs e) { UriBuilder returnToBuilder = new UriBuilder(Request.Url); returnToBuilder.Path = "/login.aspx"; @@ -24,24 +32,56 @@ var response = relyingParty.GetResponse(); if (response == null) { - // Because this is a sample of a controlled SSO environment, - // we don't ask the user which Provider to use... we just send - // them straight off to the one Provider we trust. - var request = relyingParty.CreateRequest( - ConfigurationManager.AppSettings["SsoProvider"], - realm, - returnTo); - request.RedirectToProvider(); + if (Request.QueryString["ReturnUrl"] != null && User.Identity.IsAuthenticated) { + // The user must have been directed here because he has insufficient + // permissions to access something. + MultiView1.ActiveViewIndex = 1; + } else { + // Because this is a sample of a controlled SSO environment, + // we don't ask the user which Provider to use... we just send + // them straight off to the one Provider we trust. + var request = relyingParty.CreateRequest( + ConfigurationManager.AppSettings["SsoProviderOPIdentifier"], + realm, + returnTo); + var fetchRequest = new FetchRequest(); + fetchRequest.Attributes.AddOptional(RolesAttribute); + request.AddExtension(fetchRequest); + request.RedirectToProvider(); + } } else { switch (response.Status) { case AuthenticationStatus.Canceled: - errorLabel.Text = "Login canceled."; + this.errorLabel.Text = "Login canceled."; break; case AuthenticationStatus.Failed: - errorLabel.Text = HttpUtility.HtmlEncode(response.Exception.Message); + this.errorLabel.Text = HttpUtility.HtmlEncode(response.Exception.Message); break; case AuthenticationStatus.Authenticated: - FormsAuthentication.RedirectFromLoginPage(response.ClaimedIdentifier, false); + IList<string> roles = null; + var fetchResponse = response.GetExtension<FetchResponse>(); + if (fetchResponse != null) { + if (fetchResponse.Attributes.Contains(RolesAttribute)) { + roles = fetchResponse.Attributes[RolesAttribute].Values; + } + } + if (roles == null) { + roles = new List<string>(0); + } + + // Apply the roles to this auth ticket + const int TimeoutInMinutes = 100; // TODO: look up the right value from the web.config file + var ticket = new FormsAuthenticationTicket( + 2, + response.ClaimedIdentifier, + DateTime.Now, + DateTime.Now.AddMinutes(TimeoutInMinutes), + false, // non-persistent, since login is automatic and we wanted updated roles + string.Join(";", roles.ToArray())); + + HttpCookie cookie = new HttpCookie(FormsAuthentication.FormsCookieName, FormsAuthentication.Encrypt(ticket)); + Response.SetCookie(cookie); + Response.Redirect(Request.QueryString["ReturnUrl"] ?? FormsAuthentication.DefaultUrl); break; default: break; diff --git a/samples/OpenIdWebRingSsoRelyingParty/Login.aspx.designer.cs b/samples/OpenIdWebRingSsoRelyingParty/Login.aspx.designer.cs index a413966..7ed2669 100644 --- a/samples/OpenIdWebRingSsoRelyingParty/Login.aspx.designer.cs +++ b/samples/OpenIdWebRingSsoRelyingParty/Login.aspx.designer.cs @@ -23,6 +23,24 @@ namespace OpenIdWebRingSsoRelyingParty { protected global::System.Web.UI.HtmlControls.HtmlForm form1; /// <summary> + /// MultiView1 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.MultiView MultiView1; + + /// <summary> + /// View1 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.View View1; + + /// <summary> /// errorLabel control. /// </summary> /// <remarks> @@ -39,5 +57,14 @@ namespace OpenIdWebRingSsoRelyingParty { /// To modify move field declaration from designer file to code-behind file. /// </remarks> protected global::System.Web.UI.WebControls.Button retryButton; + + /// <summary> + /// View2 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.View View2; } } diff --git a/samples/OpenIdWebRingSsoRelyingParty/OpenIdWebRingSsoRelyingParty.csproj b/samples/OpenIdWebRingSsoRelyingParty/OpenIdWebRingSsoRelyingParty.csproj index 3cc9d44..978a1a57 100644 --- a/samples/OpenIdWebRingSsoRelyingParty/OpenIdWebRingSsoRelyingParty.csproj +++ b/samples/OpenIdWebRingSsoRelyingParty/OpenIdWebRingSsoRelyingParty.csproj @@ -59,6 +59,13 @@ <Content Include="xrds.aspx" /> </ItemGroup> <ItemGroup> + <Compile Include="Admin\Default.aspx.cs"> + <DependentUpon>Default.aspx</DependentUpon> + <SubType>ASPXCodeBehind</SubType> + </Compile> + <Compile Include="Admin\Default.aspx.designer.cs"> + <DependentUpon>Default.aspx</DependentUpon> + </Compile> <Compile Include="Default.aspx.cs"> <SubType>ASPXCodeBehind</SubType> <DependentUpon>Default.aspx</DependentUpon> @@ -73,6 +80,7 @@ <Compile Include="Login.aspx.designer.cs"> <DependentUpon>Login.aspx</DependentUpon> </Compile> + <Compile Include="AuthTicketRoles.cs" /> <Compile Include="Properties\AssemblyInfo.cs" /> </ItemGroup> <ItemGroup> @@ -82,6 +90,10 @@ </ProjectReference> </ItemGroup> <ItemGroup> + <Content Include="Admin\Default.aspx" /> + <Content Include="Admin\Web.config" /> + </ItemGroup> + <ItemGroup> <Folder Include="App_Data\" /> </ItemGroup> <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" /> diff --git a/samples/OpenIdWebRingSsoRelyingParty/Web.config b/samples/OpenIdWebRingSsoRelyingParty/Web.config index 6c5ca43..94ef60c 100644 --- a/samples/OpenIdWebRingSsoRelyingParty/Web.config +++ b/samples/OpenIdWebRingSsoRelyingParty/Web.config @@ -64,7 +64,8 @@ </dotNetOpenAuth> <appSettings> - <add key="SsoProvider" value="http://localhost:39167/" /> + <add key="SsoProviderOPIdentifier" value="http://localhost:39167/" /> + <add key="SsoProviderOPEndpoint" value="http://localhost:39167/server.aspx" /> </appSettings> <connectionStrings/> @@ -124,6 +125,7 @@ </httpHandlers> <httpModules> <add name="ScriptModule" type="System.Web.Handlers.ScriptModule, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/> + <add name="AuthTicketRoles" type="OpenIdWebRingSsoRelyingParty.AuthTicketRoles, OpenIdWebRingSsoRelyingParty"/> </httpModules> </system.web> |