diff options
author | Andrew Arnott <andrewarnott@gmail.com> | 2009-04-22 23:14:41 -0700 |
---|---|---|
committer | Andrew Arnott <andrewarnott@gmail.com> | 2009-04-22 23:14:41 -0700 |
commit | 7323ea7a7b17e6fc0c6636f4c5784d5bfe2179e9 (patch) | |
tree | 58bd5d97392da0f279fa1338ba628bd2da4790d6 | |
parent | 8f173adba793c6ef4efccb4ee21c17e24a442783 (diff) | |
download | DotNetOpenAuth-7323ea7a7b17e6fc0c6636f4c5784d5bfe2179e9.zip DotNetOpenAuth-7323ea7a7b17e6fc0c6636f4c5784d5bfe2179e9.tar.gz DotNetOpenAuth-7323ea7a7b17e6fc0c6636f4c5784d5bfe2179e9.tar.bz2 |
Initial stab at PPID identifiers to protect privacy.
21 files changed, 430 insertions, 20 deletions
diff --git a/samples/OpenIdOfflineProvider/OpenIdOfflineProvider.csproj b/samples/OpenIdOfflineProvider/OpenIdOfflineProvider.csproj index 3e9970c..fd1ca1d 100644 --- a/samples/OpenIdOfflineProvider/OpenIdOfflineProvider.csproj +++ b/samples/OpenIdOfflineProvider/OpenIdOfflineProvider.csproj @@ -24,7 +24,7 @@ <DefineConstants>DEBUG;TRACE</DefineConstants> <ErrorReport>prompt</ErrorReport> <WarningLevel>4</WarningLevel> - <CodeContractsEnableRuntimeChecking>True</CodeContractsEnableRuntimeChecking> + <CodeContractsEnableRuntimeChecking>False</CodeContractsEnableRuntimeChecking> <CodeContractsCustomRewriterAssembly> </CodeContractsCustomRewriterAssembly> <CodeContractsCustomRewriterClass> @@ -46,6 +46,7 @@ <CodeContractsUseBaseLine>False</CodeContractsUseBaseLine> <CodeContractsRunInBackground>True</CodeContractsRunInBackground> <CodeContractsShowSquigglies>True</CodeContractsShowSquigglies> + <CodeContractsRuntimeOnlyPublicSurface>False</CodeContractsRuntimeOnlyPublicSurface> </PropertyGroup> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> <DebugType>pdbonly</DebugType> diff --git a/samples/OpenIdProviderMvc/App_Data/Users.xml b/samples/OpenIdProviderMvc/App_Data/Users.xml index cffe009..e171ac9 100644 --- a/samples/OpenIdProviderMvc/App_Data/Users.xml +++ b/samples/OpenIdProviderMvc/App_Data/Users.xml @@ -3,21 +3,26 @@ <User> <UserName>bob</UserName> <Password>test</Password> + <Salt>CDI=</Salt> </User> <User> <UserName>bob1</UserName> <Password>test</Password> + <Salt>CAI=</Salt> </User> <User> <UserName>bob2</UserName> <Password>test</Password> + <Salt>hYMC=</Salt> </User> <User> <UserName>bob3</UserName> <Password>test</Password> + <Salt>hTKDAg==</Salt> </User> <User> <UserName>bob4</UserName> <Password>test</Password> + <Salt>hTkDAg==</Salt> </User> </Users> diff --git a/samples/OpenIdProviderMvc/Code/AnonymousIdentifierProvider.cs b/samples/OpenIdProviderMvc/Code/AnonymousIdentifierProvider.cs new file mode 100644 index 0000000..9ead7c1 --- /dev/null +++ b/samples/OpenIdProviderMvc/Code/AnonymousIdentifierProvider.cs @@ -0,0 +1,43 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Web; +using DotNetOpenAuth.OpenId.Provider; +using DotNetOpenAuth.OpenId; +using System.Web.Security; +using OpenIdProviderMvc.Models; + +namespace OpenIdProviderMvc.Code { + internal class AnonymousIdentifierProvider : AnonymousIdentifierProviderBase { + internal AnonymousIdentifierProvider() + : base(GetIdentifierBase("anon")) { + } + + protected override byte[] GetHashSaltForLocalIdentifier(Identifier localIdentifier) { + // This is just a sample with no database... a real web app MUST return + // a reasonable salt here and have that salt be persistent for each user. + var membership = (ReadOnlyXmlMembershipProvider)Membership.Provider; + string username = User.GetUserFromClaimedIdentifier(new Uri(localIdentifier)); + string salt = membership.GetSalt(username); + return Convert.FromBase64String(salt); + ////return AnonymousIdentifierProviderBase.GetNewSalt(5); + } + + private static Uri GetIdentifierBase(string subPath) { + if (HttpContext.Current == null) { + throw new InvalidOperationException(); + } + + if (String.IsNullOrEmpty(subPath)) { + throw new ArgumentNullException("subPath"); + } + + string appPath = HttpContext.Current.Request.ApplicationPath; + if (!appPath.EndsWith("/")) { + appPath += "/"; + } + + return new Uri(HttpContext.Current.Request.Url, appPath + subPath + "/"); + } + } +} diff --git a/samples/OpenIdProviderMvc/Code/ReadOnlyXmlMembershipProvider.cs b/samples/OpenIdProviderMvc/Code/ReadOnlyXmlMembershipProvider.cs index 3da0f8e..cc5a321 100644 --- a/samples/OpenIdProviderMvc/Code/ReadOnlyXmlMembershipProvider.cs +++ b/samples/OpenIdProviderMvc/Code/ReadOnlyXmlMembershipProvider.cs @@ -236,6 +236,11 @@ throw new NotSupportedException(); } + internal string GetSalt(string userName) { + this.ReadMembershipDataStore(); + return this.users[userName].Email; + } + // Helper method private void ReadMembershipDataStore() { lock (this) { @@ -246,11 +251,13 @@ XmlNodeList nodes = doc.GetElementsByTagName("User"); foreach (XmlNode node in nodes) { + // Yes, we're misusing some of these fields. A real app would + // have the right fields from a database to use. MembershipUser user = new MembershipUser( Name, // Provider name node["UserName"].InnerText, // Username null, // providerUserKey - null, // Email + node["Salt"].InnerText, // Email string.Empty, // passwordQuestion node["Password"].InnerText, // Comment true, // isApproved diff --git a/samples/OpenIdProviderMvc/Controllers/OpenIdController.cs b/samples/OpenIdProviderMvc/Controllers/OpenIdController.cs index fff0a62..a46c39a 100644 --- a/samples/OpenIdProviderMvc/Controllers/OpenIdController.cs +++ b/samples/OpenIdProviderMvc/Controllers/OpenIdController.cs @@ -7,6 +7,7 @@ namespace OpenIdProviderMvc.Controllers { using System.Web.Mvc.Ajax; using DotNetOpenAuth.Messaging; using DotNetOpenAuth.OpenId.Provider; + using OpenIdProviderMvc.Code; public class OpenIdController : Controller { internal static OpenIdProvider OpenIdProvider = new OpenIdProvider(); @@ -24,7 +25,7 @@ namespace OpenIdProviderMvc.Controllers { if (authRequest != null) { PendingAuthenticationRequest = authRequest; if (User.Identity.IsAuthenticated && (authRequest.IsDirectedIdentity || Models.User.GetClaimedIdentifierForUser(User.Identity.Name) == authRequest.LocalIdentifier)) { - return this.SendAssertion(); + return this.SendAssertion(true); } else { return RedirectToAction("LogOn", "Account", new { returnUrl = Url.Action("SendAssertion") }); } @@ -41,7 +42,7 @@ namespace OpenIdProviderMvc.Controllers { } [Authorize] - public ActionResult SendAssertion() { + public ActionResult SendAssertion(bool pseudonymous) { IAuthenticationRequest authReq = PendingAuthenticationRequest; PendingAuthenticationRequest = null; if (authReq == null) { @@ -53,6 +54,10 @@ namespace OpenIdProviderMvc.Controllers { authReq.ClaimedIdentifier = authReq.LocalIdentifier; authReq.IsAuthenticated = true; } else { + if (pseudonymous) { + throw new InvalidOperationException("Pseudonymous identifiers are only available when used with directed identity."); + } + if (authReq.LocalIdentifier == Models.User.GetClaimedIdentifierForUser(User.Identity.Name)) { authReq.IsAuthenticated = true; if (!authReq.IsDelegatedIdentifier) { @@ -62,6 +67,12 @@ namespace OpenIdProviderMvc.Controllers { authReq.IsAuthenticated = false; } } + + if (pseudonymous) { + var anonProvider = new AnonymousIdentifierProvider(); + authReq.ScrubPersonallyIdentifiableInformation(anonProvider, true); + } + return OpenIdProvider.PrepareResponse(authReq).AsActionResult(); } } diff --git a/samples/OpenIdProviderMvc/Controllers/UserController.cs b/samples/OpenIdProviderMvc/Controllers/UserController.cs index 70bea04..0a5b04e 100644 --- a/samples/OpenIdProviderMvc/Controllers/UserController.cs +++ b/samples/OpenIdProviderMvc/Controllers/UserController.cs @@ -8,16 +8,18 @@ namespace OpenIdProviderMvc.Controllers { public class UserController : Controller { public ActionResult Identity(string id) { - var redirect = this.RedirectIfNotNormalizedRequestUri(); - if (redirect != null) { - return redirect; + if (!string.IsNullOrEmpty(id)) { + var redirect = this.RedirectIfNotNormalizedRequestUri(); + if (redirect != null) { + return redirect; + } } if (Request.AcceptTypes.Contains("application/xrds+xml")) { return View("Xrds"); } - this.ViewData["username"] = id; + this.ViewData["username"] = string.IsNullOrEmpty(id) ? "anonymous" : id; return View(); } diff --git a/samples/OpenIdProviderMvc/Global.asax.cs b/samples/OpenIdProviderMvc/Global.asax.cs index b0d1b60..4cfc6fe 100644 --- a/samples/OpenIdProviderMvc/Global.asax.cs +++ b/samples/OpenIdProviderMvc/Global.asax.cs @@ -22,6 +22,10 @@ "user/{id}/{action}", new { controller = "User", action = "Identity", id = string.Empty }); routes.MapRoute( + "PPID identifiers", + "anon/{anonId}", + new { controller = "User", action = "Identity", id = string.Empty }); + routes.MapRoute( "Default", // Route name "{controller}/{action}/{id}", // URL with parameters new { controller = "Home", action = "Index", id = string.Empty }); // Parameter defaults diff --git a/samples/OpenIdProviderMvc/Models/User.cs b/samples/OpenIdProviderMvc/Models/User.cs index 577aa05..4c9093c 100644 --- a/samples/OpenIdProviderMvc/Models/User.cs +++ b/samples/OpenIdProviderMvc/Models/User.cs @@ -7,15 +7,23 @@ using System.Web.Routing; internal class User { + internal static Uri ClaimedIdentifierBaseUri { + get { + string appPath = HttpContext.Current.Request.ApplicationPath.ToLowerInvariant(); + if (!appPath.EndsWith("/")) { + appPath += "/"; + } + + return new Uri(HttpContext.Current.Request.Url, appPath + "user/"); + } + } + internal static Uri GetClaimedIdentifierForUser(string username) { - string appPath = HttpContext.Current.Request.ApplicationPath; - if (!appPath.EndsWith("/")) { - appPath += "/"; + if (String.IsNullOrEmpty(username)) { + throw new ArgumentNullException("username"); } - Uri claimedIdentifier = new Uri( - HttpContext.Current.Request.Url, - appPath + "user/" + username); - return new Uri(claimedIdentifier.AbsoluteUri.ToLowerInvariant()); + + return new Uri(ClaimedIdentifierBaseUri, username.ToLowerInvariant()); } internal static string GetUserFromClaimedIdentifier(Uri claimedIdentifier) { diff --git a/samples/OpenIdProviderMvc/OpenIdProviderMvc.csproj b/samples/OpenIdProviderMvc/OpenIdProviderMvc.csproj index 80e8e64..6b71e6a 100644 --- a/samples/OpenIdProviderMvc/OpenIdProviderMvc.csproj +++ b/samples/OpenIdProviderMvc/OpenIdProviderMvc.csproj @@ -65,6 +65,7 @@ </ItemGroup> <ItemGroup> <Compile Include="Code\AccountMembershipService.cs" /> + <Compile Include="Code\AnonymousIdentifierProvider.cs" /> <Compile Include="Code\FormsAuthenticationService.cs" /> <Compile Include="Code\IFormsAuthentication.cs" /> <Compile Include="Code\IMembershipService.cs" /> diff --git a/samples/OpenIdProviderMvc/Views/Shared/Site.Master b/samples/OpenIdProviderMvc/Views/Shared/Site.Master index 8df2d5f..073908e 100644 --- a/samples/OpenIdProviderMvc/Views/Shared/Site.Master +++ b/samples/OpenIdProviderMvc/Views/Shared/Site.Master @@ -13,7 +13,7 @@ <div class="page"> <div id="header"> <div id="title"> - <h1>My MVC Application</h1> + <h1>OpenID Provider MVC Application</h1> </div> <div id="logindisplay"> <% Html.RenderPartial("LogOnUserControl"); %> diff --git a/samples/OpenIdProviderMvc/Views/User/Identity.aspx b/samples/OpenIdProviderMvc/Views/User/Identity.aspx index 632df43..bb50899 100644 --- a/samples/OpenIdProviderMvc/Views/User/Identity.aspx +++ b/samples/OpenIdProviderMvc/Views/User/Identity.aspx @@ -3,7 +3,7 @@ <%@ Register Assembly="DotNetOpenAuth" Namespace="DotNetOpenAuth.OpenId.Provider" TagPrefix="op" %> <asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server"> - <%=ViewData["username"]%> + <%=Html.Encode(ViewData["username"])%> identity page </asp:Content> <asp:Content runat="server" ContentPlaceHolderID="HeadContent"> @@ -12,7 +12,7 @@ </asp:Content> <asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server"> <h2>This is - <%=ViewData["username"]%>'s OpenID identity page </h2> + <%=Html.Encode(ViewData["username"])%>'s OpenID identity page </h2> <% if (string.Equals(User.Identity.Name, ViewData["username"])) { %> <p>This is <b>your</b> identity page. </p> diff --git a/src/DotNetOpenAuth/DotNetOpenAuth.csproj b/src/DotNetOpenAuth/DotNetOpenAuth.csproj index 2c30aaf..dd78f98 100644 --- a/src/DotNetOpenAuth/DotNetOpenAuth.csproj +++ b/src/DotNetOpenAuth/DotNetOpenAuth.csproj @@ -25,7 +25,7 @@ <DocumentationFile>..\..\bin\Debug\DotNetOpenAuth.xml</DocumentationFile> <RunCodeAnalysis>false</RunCodeAnalysis> <CodeAnalysisRules>-Microsoft.Design#CA1054;-Microsoft.Design#CA1056;-Microsoft.Design#CA1055</CodeAnalysisRules> - <CodeContractsEnableRuntimeChecking>True</CodeContractsEnableRuntimeChecking> + <CodeContractsEnableRuntimeChecking>False</CodeContractsEnableRuntimeChecking> <CodeContractsCustomRewriterAssembly> </CodeContractsCustomRewriterAssembly> <CodeContractsCustomRewriterClass> @@ -47,6 +47,7 @@ <CodeContractsRunInBackground>True</CodeContractsRunInBackground> <CodeContractsShowSquigglies>True</CodeContractsShowSquigglies> <CodeContractsArithmeticObligations>False</CodeContractsArithmeticObligations> + <CodeContractsRuntimeOnlyPublicSurface>False</CodeContractsRuntimeOnlyPublicSurface> </PropertyGroup> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> <DebugType>pdbonly</DebugType> @@ -367,6 +368,7 @@ <Compile Include="OpenId\Messages\SignedResponseRequest.cs" /> <Compile Include="OpenId\NoDiscoveryIdentifier.cs" /> <Compile Include="OpenId\OpenIdUtilities.cs" /> + <Compile Include="OpenId\Provider\IAnonymousIdentifierProvider.cs" /> <Compile Include="OpenId\Provider\AuthenticationChallengeEventArgs.cs" /> <Compile Include="OpenId\Provider\AuthenticationRequest.cs" /> <Compile Include="OpenId\Provider\AutoResponsiveRequest.cs" /> @@ -379,6 +381,8 @@ <Compile Include="OpenId\Provider\ProviderEndpoint.cs" /> <Compile Include="OpenId\Provider\Request.cs" /> <Compile Include="OpenId\Provider\RequestContract.cs" /> + <Compile Include="OpenId\Provider\AnonymousIdentifierProviderBase.cs" /> + <Compile Include="OpenId\Provider\AnonymousIdentifierProviderBaseContract.cs" /> <Compile Include="OpenId\Provider\StandardProviderApplicationStore.cs" /> <Compile Include="OpenId\Realm.cs" /> <Compile Include="OpenId\RelyingPartyDescription.cs" /> diff --git a/src/DotNetOpenAuth/Messaging/MessagingUtilities.cs b/src/DotNetOpenAuth/Messaging/MessagingUtilities.cs index fe1e4e8..bd9aeac 100644 --- a/src/DotNetOpenAuth/Messaging/MessagingUtilities.cs +++ b/src/DotNetOpenAuth/Messaging/MessagingUtilities.cs @@ -30,6 +30,8 @@ namespace DotNetOpenAuth.Messaging { /// <remarks>The random number generator is thread-safe.</remarks> internal static readonly RandomNumberGenerator CryptoRandomDataGenerator = new RNGCryptoServiceProvider(); + internal static readonly Random NonCryptoRandomDataGenerator = new Random(); + /// <summary> /// A set of escaping mappings that help secure a string from javscript execution. /// </summary> @@ -158,6 +160,17 @@ namespace DotNetOpenAuth.Messaging { } /// <summary> + /// Gets a buffer of random data (not cryptographically strong). + /// </summary> + /// <param name="length">The length of the sequence to generate.</param> + /// <returns>The generated values, which may contain zeros.</returns> + internal static byte[] GetNonCryptoRandomData(int length) { + byte[] buffer = new byte[length]; + NonCryptoRandomDataGenerator.NextBytes(buffer); + return buffer; + } + + /// <summary> /// Adds a set of HTTP headers to an <see cref="HttpResponse"/> instance, /// taking care to set some headers to the appropriate properties of /// <see cref="HttpResponse" /> diff --git a/src/DotNetOpenAuth/OpenId/OpenIdStrings.Designer.cs b/src/DotNetOpenAuth/OpenId/OpenIdStrings.Designer.cs index 3c4116a..d9ee375 100644 --- a/src/DotNetOpenAuth/OpenId/OpenIdStrings.Designer.cs +++ b/src/DotNetOpenAuth/OpenId/OpenIdStrings.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. @@ -214,6 +214,15 @@ namespace DotNetOpenAuth.OpenId { } /// <summary> + /// Looks up a localized string similar to This operation is supported only under identifier select (directed identity) scenarios.. + /// </summary> + internal static string DirectedIdentityRequired { + get { + return ResourceManager.GetString("DirectedIdentityRequired", resourceCulture); + } + } + + /// <summary> /// Looks up a localized string similar to URI is not SSL yet requireSslDiscovery is set to true.. /// </summary> internal static string ExplicitHttpUriSuppliedWithSslRequirement { @@ -241,6 +250,15 @@ namespace DotNetOpenAuth.OpenId { } /// <summary> + /// Looks up a localized string similar to Response extensions with personally identifiable information was found and is not allowed in this scenario.. + /// </summary> + internal static string ExtensionsWithPiiFound { + get { + return ResourceManager.GetString("ExtensionsWithPiiFound", resourceCulture); + } + } + + /// <summary> /// Looks up a localized string similar to Fragment segments do not apply to XRI identifiers.. /// </summary> internal static string FragmentNotAllowedOnXRIs { diff --git a/src/DotNetOpenAuth/OpenId/OpenIdStrings.resx b/src/DotNetOpenAuth/OpenId/OpenIdStrings.resx index 7356c10..2fb4488 100644 --- a/src/DotNetOpenAuth/OpenId/OpenIdStrings.resx +++ b/src/DotNetOpenAuth/OpenId/OpenIdStrings.resx @@ -301,4 +301,10 @@ Discovered endpoint info: <data name="UnsupportedChannelConfiguration" xml:space="preserve"> <value>This feature is unavailable due to an unrecognized channel configuration.</value> </data> + <data name="DirectedIdentityRequired" xml:space="preserve"> + <value>This operation is supported only under identifier select (directed identity) scenarios.</value> + </data> + <data name="ExtensionsWithPiiFound" xml:space="preserve"> + <value>Response extensions with personally identifiable information was found and is not allowed in this scenario.</value> + </data> </root>
\ No newline at end of file diff --git a/src/DotNetOpenAuth/OpenId/Provider/AnonymousIdentifierProviderBase.cs b/src/DotNetOpenAuth/OpenId/Provider/AnonymousIdentifierProviderBase.cs new file mode 100644 index 0000000..68e7efc --- /dev/null +++ b/src/DotNetOpenAuth/OpenId/Provider/AnonymousIdentifierProviderBase.cs @@ -0,0 +1,84 @@ +//----------------------------------------------------------------------- +// <copyright file="AnonymousIdentifierProviderBase.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.CodeAnalysis; + using System.Diagnostics.Contracts; + using System.Linq; + using System.Security.Cryptography; + using System.Text; + using DotNetOpenAuth.Messaging; + + [ContractClass(typeof(AnonymousIdentifierProviderBaseContract))] + public abstract class AnonymousIdentifierProviderBase : IAnonymousIdentifierProvider { + /// <summary> + /// Initializes a new instance of the <see cref="StandardAnonymousIdentifierProvider"/> class. + /// </summary> + public AnonymousIdentifierProviderBase(Uri baseIdentifier) { + Contract.Requires(baseIdentifier != null); + Contract.Ensures(this.BaseIdentifier == baseIdentifier); + this.Hasher = HashAlgorithm.Create("SHA512"); + this.Encoder = Encoding.UTF8; + this.BaseIdentifier = baseIdentifier; + } + + public Uri BaseIdentifier { get; private set; } + + protected HashAlgorithm Hasher { get; private set; } + + protected Encoding Encoder { get; private set; } + + #region IAnonymousIdentifierProvider Members + + public Uri GetAnonymousIdentifier(Identifier localIdentifier, Realm relyingPartyRealm) { + byte[] salt = GetHashSaltForLocalIdentifier(localIdentifier); + string valueToHash = localIdentifier + "#" + (relyingPartyRealm ?? string.Empty); + byte[] valueAsBytes = this.Encoder.GetBytes(valueToHash); + byte[] bytesToHash = new byte[valueAsBytes.Length + salt.Length]; + valueAsBytes.CopyTo(bytesToHash, 0); + salt.CopyTo(bytesToHash, valueAsBytes.Length); + byte[] hash = this.Hasher.ComputeHash(bytesToHash); + string base64Hash = Convert.ToBase64String(hash).Replace('/', '_'); + string uriHash = Uri.EscapeUriString(base64Hash); + Uri anonymousIdentifier = new Uri(this.BaseIdentifier, uriHash); + return anonymousIdentifier; + } + + #endregion + + protected static byte[] GetNewSalt(int length) { + return MessagingUtilities.GetNonCryptoRandomData(length); + } + + /// <summary> + /// Gets the salt to use for generating an anonymous identifier for a given OP local identifier. + /// </summary> + /// <param name="localIdentifier">The OP local identifier.</param> + /// <returns>The salt to use in the hash.</returns> + /// <remarks> + /// It is important that this method always return the same value for a given + /// <paramref name="localIdentifier"/>. + /// New salts can be generated for local identifiers without previously assigned salt + /// values by calling <see cref="GetNewSalt"/> or by a custom method. + /// </remarks> + protected abstract byte[] GetHashSaltForLocalIdentifier(Identifier localIdentifier); + +#if CONTRACTS_FULL + /// <summary> + /// Verifies conditions that should be true for any valid state of this object. + /// </summary> + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called by code contracts.")] + [ContractInvariantMethod] + protected void ObjectInvariant() { + Contract.Invariant(this.Hasher != null); + Contract.Invariant(this.Encoder != null); + Contract.Invariant(this.BaseIdentifier != null); + } +#endif + } +} diff --git a/src/DotNetOpenAuth/OpenId/Provider/AnonymousIdentifierProviderBaseContract.cs b/src/DotNetOpenAuth/OpenId/Provider/AnonymousIdentifierProviderBaseContract.cs new file mode 100644 index 0000000..88b31b9 --- /dev/null +++ b/src/DotNetOpenAuth/OpenId/Provider/AnonymousIdentifierProviderBaseContract.cs @@ -0,0 +1,23 @@ +//----------------------------------------------------------------------- +// <copyright file="AnonymousIdentifierProviderBaseContract.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.Provider { + using System; + using System.Diagnostics.Contracts; + + [ContractClassFor(typeof(AnonymousIdentifierProviderBase))] + internal abstract class AnonymousIdentifierProviderBaseContract : AnonymousIdentifierProviderBase { + private AnonymousIdentifierProviderBaseContract() + : base(null) { + } + + protected override byte[] GetHashSaltForLocalIdentifier(Identifier localIdentifier) { + Contract.Requires(localIdentifier != null); + Contract.Ensures(Contract.Result<byte[]>() != null); + throw new NotImplementedException(); + } + } +} diff --git a/src/DotNetOpenAuth/OpenId/Provider/AuthenticationRequest.cs b/src/DotNetOpenAuth/OpenId/Provider/AuthenticationRequest.cs index 34ade49..1c0ac1f 100644 --- a/src/DotNetOpenAuth/OpenId/Provider/AuthenticationRequest.cs +++ b/src/DotNetOpenAuth/OpenId/Provider/AuthenticationRequest.cs @@ -12,6 +12,10 @@ namespace DotNetOpenAuth.OpenId.Provider { using System.Text; using DotNetOpenAuth.Messaging; using DotNetOpenAuth.OpenId.Messages; + using System.Diagnostics.Contracts; + using DotNetOpenAuth.OpenId.Extensions.SimpleRegistration; + using DotNetOpenAuth.OpenId.Extensions.AttributeExchange; + using System.Security.Cryptography; /// <summary> /// Implements the <see cref="IAuthenticationRequest"/> interface @@ -232,6 +236,28 @@ namespace DotNetOpenAuth.OpenId.Provider { } /// <summary> + /// Removes all personally identifiable information from the positive assertion. + /// </summary> + /// <remarks> + /// The openid.claimed_id and openid.identity values are hashed. + /// </remarks> + public void ScrubPersonallyIdentifiableInformation(IAnonymousIdentifierProvider anonymousIdentifierProvider, bool pairwiseUnique) { + ErrorUtilities.VerifyOperation(this.IsDirectedIdentity, OpenIdStrings.DirectedIdentityRequired); + ErrorUtilities.VerifyArgumentNotNull(anonymousIdentifierProvider, "anonymousIdentifierProvider"); + + ErrorUtilities.VerifyOperation( + !(this.GetResponseExtension<ClaimsResponse>().Any() || this.GetResponseExtension<FetchResponse>().Any()), + OpenIdStrings.ExtensionsWithPiiFound); + + // When generating the anonymous identifiers, the openid.identity and openid.claimed_id + // will always end up with matching values. + var anonymousIdentifier = anonymousIdentifierProvider.GetAnonymousIdentifier(this.LocalIdentifier, pairwiseUnique ? this.Realm : null); + Logger.OpenId.InfoFormat("Sending anonymous identifier assertion {0} for local identifier {1}.", anonymousIdentifier, this.LocalIdentifier); + this.positiveResponse.ClaimedIdentifier = anonymousIdentifier; + this.positiveResponse.LocalIdentifier = anonymousIdentifier; + } + + /// <summary> /// Gets a value indicating whether verification of the return URL claimed by the Relying Party /// succeeded. /// </summary> diff --git a/src/DotNetOpenAuth/OpenId/Provider/IAnonymousIdentifierProvider.cs b/src/DotNetOpenAuth/OpenId/Provider/IAnonymousIdentifierProvider.cs new file mode 100644 index 0000000..c865efa --- /dev/null +++ b/src/DotNetOpenAuth/OpenId/Provider/IAnonymousIdentifierProvider.cs @@ -0,0 +1,44 @@ +//----------------------------------------------------------------------- +// <copyright file="IAnonymousIdentifierProvider.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; +using System.Diagnostics.Contracts; + + /// <summary> + /// Services for generating and consuming anonymous OpenID identifiers. + /// </summary> + [ContractClass(typeof(IAnonymousIdentifierProviderContract))] + public interface IAnonymousIdentifierProvider { + /// <summary> + /// Gets the anonymous identifier for some user. + /// </summary> + /// <param name="localIdentifier">The OP local identifier for the authenticating user.</param> + /// <param name="relyingPartyRealm">The realm of the relying party requesting authentication. May be null if a pairwise-unique identifier based on the realm is not desired.</param> + /// <returns> + /// A discoverable OpenID Claimed Identifier that gives no hint regarding the real identity of the controlling user. + /// </returns> + Uri GetAnonymousIdentifier(Identifier localIdentifier, Realm relyingPartyRealm); + } + + [ContractClassFor(typeof(IAnonymousIdentifierProvider))] + internal abstract class IAnonymousIdentifierProviderContract : IAnonymousIdentifierProvider { + private IAnonymousIdentifierProviderContract() { + } + + #region IAnonymousIdentifierProvider Members + + Uri IAnonymousIdentifierProvider.GetAnonymousIdentifier(Identifier localIdentifier, Realm relyingPartyRealm) { + Contract.Requires(localIdentifier != null); + Contract.Ensures(Contract.Result<Uri>() != null); + throw new NotImplementedException(); + } + #endregion + } +} diff --git a/src/DotNetOpenAuth/OpenId/Provider/IAuthenticationRequest.cs b/src/DotNetOpenAuth/OpenId/Provider/IAuthenticationRequest.cs index b1ef269..ee32c8e 100644 --- a/src/DotNetOpenAuth/OpenId/Provider/IAuthenticationRequest.cs +++ b/src/DotNetOpenAuth/OpenId/Provider/IAuthenticationRequest.cs @@ -9,12 +9,15 @@ namespace DotNetOpenAuth.OpenId.Provider { using System.Collections.Generic; using System.Text; using DotNetOpenAuth.Messaging; + using System.Diagnostics.Contracts; + using DotNetOpenAuth.OpenId.Messages; /// <summary> /// Instances of this interface represent incoming authentication requests. /// This interface provides the details of the request and allows setting /// the response. /// </summary> + [ContractClass(typeof(IAuthenticationRequestContract))] public interface IAuthenticationRequest : IRequest { /// <summary> /// Gets the version of OpenID being used by the relying party that sent the request. @@ -111,6 +114,14 @@ namespace DotNetOpenAuth.OpenId.Provider { void SetClaimedIdentifierFragment(string fragment); /// <summary> + /// Removes all personally identifiable information from the positive assertion. + /// </summary> + /// <remarks> + /// The openid.claimed_id and openid.identity values are hashed. + /// </remarks> + void ScrubPersonallyIdentifiableInformation(IAnonymousIdentifierProvider anonymousIdentifierProvider, bool pairwiseUnique); + + /// <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> @@ -123,4 +134,92 @@ namespace DotNetOpenAuth.OpenId.Provider { /// </remarks> bool IsReturnUrlDiscoverable(IDirectWebRequestHandler requestHandler); } + + [ContractClassFor(typeof(IAuthenticationRequest))] + internal abstract class IAuthenticationRequestContract : IAuthenticationRequest { + #region IAuthenticationRequest Members + + ProtocolVersion IAuthenticationRequest.RelyingPartyVersion { + get { throw new NotImplementedException(); } + } + + bool IAuthenticationRequest.Immediate { + get { throw new NotImplementedException(); } + } + + Realm IAuthenticationRequest.Realm { + get { throw new NotImplementedException(); } + } + + bool IAuthenticationRequest.IsDirectedIdentity { + get { throw new NotImplementedException(); } + } + + bool IAuthenticationRequest.IsDelegatedIdentifier { + get { throw new NotImplementedException(); } + } + + Identifier IAuthenticationRequest.LocalIdentifier { + get { + throw new NotImplementedException(); + } + set { + throw new NotImplementedException(); + } + } + + Identifier IAuthenticationRequest.ClaimedIdentifier { + get { + throw new NotImplementedException(); + } + set { + throw new NotImplementedException(); + } + } + + bool? IAuthenticationRequest.IsAuthenticated { + get { + throw new NotImplementedException(); + } + set { + throw new NotImplementedException(); + } + } + + void IAuthenticationRequest.SetClaimedIdentifierFragment(string fragment) { + throw new NotImplementedException(); + } + + void IAuthenticationRequest.ScrubPersonallyIdentifiableInformation(IAnonymousIdentifierProvider anonymousIdentifierProvider, bool pairwiseUnique) { + Contract.Requires(((IAuthenticationRequest)this).IsDirectedIdentity); + Contract.Requires(anonymousIdentifierProvider != null); + throw new NotImplementedException(); + } + + bool IAuthenticationRequest.IsReturnUrlDiscoverable(IDirectWebRequestHandler requestHandler) { + throw new NotImplementedException(); + } + + #endregion + + #region IRequest Members + + bool IRequest.IsResponseReady { + get { throw new NotImplementedException(); } + } + + void IRequest.AddResponseExtension(IOpenIdMessageExtension extension) { + throw new NotImplementedException(); + } + + T IRequest.GetExtension<T>() { + throw new NotImplementedException(); + } + + IOpenIdMessageExtension IRequest.GetExtension(Type extensionType) { + throw new NotImplementedException(); + } + + #endregion + } } diff --git a/src/DotNetOpenAuth/OpenId/Provider/Request.cs b/src/DotNetOpenAuth/OpenId/Provider/Request.cs index 1c5ad02..9d50584 100644 --- a/src/DotNetOpenAuth/OpenId/Provider/Request.cs +++ b/src/DotNetOpenAuth/OpenId/Provider/Request.cs @@ -190,5 +190,16 @@ namespace DotNetOpenAuth.OpenId.Provider { } #endregion + + /// <summary> + /// Gets a response extension that may be pending for transmission in the response message. + /// </summary> + /// <typeparam name="T">The type of extension being sought.</typeparam> + /// <returns>The extension(s) that match the given type; or <c>null</c> if none of the given type was added.</returns> + protected IEnumerable<T> GetResponseExtension<T>() where T : IOpenIdMessageExtension { + // Technically an extension should never show up twice.... but T might be + // a base class or interface that matches more than one extension. + return this.responseExtensions.OfType<T>(); + } } } |