summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAndrew Arnott <andrewarnott@gmail.com>2009-04-22 23:14:41 -0700
committerAndrew Arnott <andrewarnott@gmail.com>2009-04-22 23:14:41 -0700
commit7323ea7a7b17e6fc0c6636f4c5784d5bfe2179e9 (patch)
tree58bd5d97392da0f279fa1338ba628bd2da4790d6
parent8f173adba793c6ef4efccb4ee21c17e24a442783 (diff)
downloadDotNetOpenAuth-7323ea7a7b17e6fc0c6636f4c5784d5bfe2179e9.zip
DotNetOpenAuth-7323ea7a7b17e6fc0c6636f4c5784d5bfe2179e9.tar.gz
DotNetOpenAuth-7323ea7a7b17e6fc0c6636f4c5784d5bfe2179e9.tar.bz2
Initial stab at PPID identifiers to protect privacy.
-rw-r--r--samples/OpenIdOfflineProvider/OpenIdOfflineProvider.csproj3
-rw-r--r--samples/OpenIdProviderMvc/App_Data/Users.xml5
-rw-r--r--samples/OpenIdProviderMvc/Code/AnonymousIdentifierProvider.cs43
-rw-r--r--samples/OpenIdProviderMvc/Code/ReadOnlyXmlMembershipProvider.cs9
-rw-r--r--samples/OpenIdProviderMvc/Controllers/OpenIdController.cs15
-rw-r--r--samples/OpenIdProviderMvc/Controllers/UserController.cs10
-rw-r--r--samples/OpenIdProviderMvc/Global.asax.cs4
-rw-r--r--samples/OpenIdProviderMvc/Models/User.cs22
-rw-r--r--samples/OpenIdProviderMvc/OpenIdProviderMvc.csproj1
-rw-r--r--samples/OpenIdProviderMvc/Views/Shared/Site.Master2
-rw-r--r--samples/OpenIdProviderMvc/Views/User/Identity.aspx4
-rw-r--r--src/DotNetOpenAuth/DotNetOpenAuth.csproj6
-rw-r--r--src/DotNetOpenAuth/Messaging/MessagingUtilities.cs13
-rw-r--r--src/DotNetOpenAuth/OpenId/OpenIdStrings.Designer.cs20
-rw-r--r--src/DotNetOpenAuth/OpenId/OpenIdStrings.resx6
-rw-r--r--src/DotNetOpenAuth/OpenId/Provider/AnonymousIdentifierProviderBase.cs84
-rw-r--r--src/DotNetOpenAuth/OpenId/Provider/AnonymousIdentifierProviderBaseContract.cs23
-rw-r--r--src/DotNetOpenAuth/OpenId/Provider/AuthenticationRequest.cs26
-rw-r--r--src/DotNetOpenAuth/OpenId/Provider/IAnonymousIdentifierProvider.cs44
-rw-r--r--src/DotNetOpenAuth/OpenId/Provider/IAuthenticationRequest.cs99
-rw-r--r--src/DotNetOpenAuth/OpenId/Provider/Request.cs11
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>();
+ }
}
}