summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAndrew Arnott <andrewarnott@gmail.com>2009-03-20 15:49:32 -0700
committerAndrew Arnott <andrewarnott@gmail.com>2009-03-20 15:49:32 -0700
commit2c8250b619c91a7ca520dc88ca23ae8f327a2bc6 (patch)
tree61017318edd5186de7a31720ad291d331bb83b60
parent878598a9c7316985d640a3b4f970ee8cb7b952e0 (diff)
downloadDotNetOpenAuth-2c8250b619c91a7ca520dc88ca23ae8f327a2bc6.zip
DotNetOpenAuth-2c8250b619c91a7ca520dc88ca23ae8f327a2bc6.tar.gz
DotNetOpenAuth-2c8250b619c91a7ca520dc88ca23ae8f327a2bc6.tar.bz2
Added ASP.NET InfoCard Selector control.
-rw-r--r--samples/InfoCardRelyingParty/Default.aspx11
-rw-r--r--samples/InfoCardRelyingParty/Login.aspx27
-rw-r--r--samples/InfoCardRelyingParty/MembersOnly/Default.aspx12
-rw-r--r--samples/InfoCardRelyingParty/MembersOnly/Web.config18
-rw-r--r--samples/InfoCardRelyingParty/Site.Master31
-rw-r--r--samples/InfoCardRelyingParty/images/dotnetopenid_tiny.gifbin0 -> 3548 bytes
-rw-r--r--samples/InfoCardRelyingParty/styles.css10
-rw-r--r--samples/InfoCardRelyingParty/web.config167
-rw-r--r--src/DotNetOpenAuth.sln29
-rw-r--r--src/DotNetOpenAuth/ComponentModel/ClaimTypeConverter.cs171
-rw-r--r--src/DotNetOpenAuth/DotNetOpenAuth.csproj43
-rw-r--r--src/DotNetOpenAuth/InfoCard/ClaimType.cs49
-rw-r--r--src/DotNetOpenAuth/InfoCard/InfoCardImage.cs136
-rw-r--r--src/DotNetOpenAuth/InfoCard/InfoCardSelector.cs666
-rw-r--r--src/DotNetOpenAuth/InfoCard/InfoCardStrings.Designer.cs108
-rw-r--r--src/DotNetOpenAuth/InfoCard/InfoCardStrings.resx135
-rw-r--r--src/DotNetOpenAuth/InfoCard/ReceivedTokenEventArgs.cs41
-rw-r--r--src/DotNetOpenAuth/InfoCard/ReceivingTokenEventArgs.cs71
-rw-r--r--src/DotNetOpenAuth/InfoCard/SupportingScript.js92
-rw-r--r--src/DotNetOpenAuth/InfoCard/Token/InformationCardException.cs62
-rw-r--r--src/DotNetOpenAuth/InfoCard/Token/Token.cs177
-rw-r--r--src/DotNetOpenAuth/InfoCard/Token/TokenDecryptor.cs209
-rw-r--r--src/DotNetOpenAuth/InfoCard/Token/TokenUtility.cs290
-rw-r--r--src/DotNetOpenAuth/InfoCard/TokenProcessingErrorEventArgs.cs49
-rw-r--r--src/DotNetOpenAuth/InfoCard/WellKnownClaimTypes.cs177
-rw-r--r--src/DotNetOpenAuth/InfoCard/infocard_114x80.pngbin0 -> 3821 bytes
-rw-r--r--src/DotNetOpenAuth/InfoCard/infocard_14x10.pngbin0 -> 478 bytes
-rw-r--r--src/DotNetOpenAuth/InfoCard/infocard_214x150.pngbin0 -> 8346 bytes
-rw-r--r--src/DotNetOpenAuth/InfoCard/infocard_23x16.pngbin0 -> 810 bytes
-rw-r--r--src/DotNetOpenAuth/InfoCard/infocard_300x210.pngbin0 -> 13184 bytes
-rw-r--r--src/DotNetOpenAuth/InfoCard/infocard_34x24.pngbin0 -> 1129 bytes
-rw-r--r--src/DotNetOpenAuth/InfoCard/infocard_365x256.pngbin0 -> 17191 bytes
-rw-r--r--src/DotNetOpenAuth/InfoCard/infocard_41x29.pngbin0 -> 1297 bytes
-rw-r--r--src/DotNetOpenAuth/InfoCard/infocard_50x35.pngbin0 -> 1644 bytes
-rw-r--r--src/DotNetOpenAuth/InfoCard/infocard_60x42.pngbin0 -> 2071 bytes
-rw-r--r--src/DotNetOpenAuth/InfoCard/infocard_71x50.pngbin0 -> 2394 bytes
-rw-r--r--src/DotNetOpenAuth/InfoCard/infocard_81x57.pngbin0 -> 2850 bytes
-rw-r--r--src/DotNetOpenAuth/InfoCard/infocard_92x64.pngbin0 -> 3174 bytes
-rw-r--r--src/DotNetOpenAuth/Messaging/ErrorUtilities.cs20
-rw-r--r--src/DotNetOpenAuth/Properties/AssemblyInfo.cs1
40 files changed, 2802 insertions, 0 deletions
diff --git a/samples/InfoCardRelyingParty/Default.aspx b/samples/InfoCardRelyingParty/Default.aspx
new file mode 100644
index 0000000..80efa8f
--- /dev/null
+++ b/samples/InfoCardRelyingParty/Default.aspx
@@ -0,0 +1,11 @@
+<%@ Page Title="" Language="VB" MasterPageFile="~/Site.Master" %>
+
+<script runat="server">
+
+</script>
+
+<asp:Content ID="Content2" ContentPlaceHolderID="Main" runat="Server">
+ <h2>Relying Party </h2>
+ <p>Visit the <asp:HyperLink ID="HyperLink1" runat="server" NavigateUrl="~/MembersOnly/Default.aspx"
+ Text="Members Only" /> area. (This will trigger a login demo). </p>
+</asp:Content>
diff --git a/samples/InfoCardRelyingParty/Login.aspx b/samples/InfoCardRelyingParty/Login.aspx
new file mode 100644
index 0000000..cc2b410
--- /dev/null
+++ b/samples/InfoCardRelyingParty/Login.aspx
@@ -0,0 +1,27 @@
+<%@ Page Title="" Language="VB" MasterPageFile="~/Site.Master" AutoEventWireup="false"
+ ValidateRequest="false" %>
+<%@ Import Namespace="DotNetOpenAuth.InfoCard" %>
+
+<script runat="server">
+ Protected Sub InfoCardSelector1_ReceivedToken(ByVal sender As Object, ByVal e As ReceivedTokenEventArgs) Handles InfoCardSelector1.ReceivedToken
+ Session("SiteSpecificID") = e.Token.SiteSpecificId
+ FormsAuthentication.RedirectFromLoginPage(e.Token.UniqueId, False)
+ End Sub
+</script>
+
+<%@ Register Assembly="DotNetOpenAuth" Namespace="DotNetOpenAuth.InfoCard" TagPrefix="ic" %>
+<asp:Content ID="Content2" ContentPlaceHolderID="Main" runat="Server">
+ <p>This login page demonstrates logging in using the InfoCard selector. Click the InfoCard
+ image below to login. </p>
+ <ic:InfoCardSelector runat="server" ID="InfoCardSelector1">
+ <ClaimTypes>
+ <ic:ClaimType Name="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/rsa" />
+ </ClaimTypes>
+ <UnsupportedTemplate>
+ <p>You're using a browser that doesn't seem to have Information Card Selector support.
+ </p>
+ <p>In a real web application you might want to put an alternate login method here, or
+ a link to find out how to enable the user's browser to use InfoCards. </p>
+ </UnsupportedTemplate>
+ </ic:InfoCardSelector>
+</asp:Content>
diff --git a/samples/InfoCardRelyingParty/MembersOnly/Default.aspx b/samples/InfoCardRelyingParty/MembersOnly/Default.aspx
new file mode 100644
index 0000000..1c6cae9
--- /dev/null
+++ b/samples/InfoCardRelyingParty/MembersOnly/Default.aspx
@@ -0,0 +1,12 @@
+<%@ Page Language="VB" AutoEventWireup="true" MasterPageFile="~/Site.Master" %>
+
+<asp:Content ID="Content1" runat="server" ContentPlaceHolderID="Main">
+ <h2>
+ Members Only Area
+ </h2>
+ <p>
+ Congratulations, <b><%= Session("SiteSpecificID") %></b>.
+ You have completed the InfoCard login process.
+ </p>
+ <p>Your secure unique ID on this site is <asp:LoginName ID="LoginName1" runat="server" />.</p>
+</asp:Content>
diff --git a/samples/InfoCardRelyingParty/MembersOnly/Web.config b/samples/InfoCardRelyingParty/MembersOnly/Web.config
new file mode 100644
index 0000000..3cfad05
--- /dev/null
+++ b/samples/InfoCardRelyingParty/MembersOnly/Web.config
@@ -0,0 +1,18 @@
+<?xml version="1.0"?>
+<!--
+ Note: As an alternative to hand editing this file you can use the
+ web admin tool to configure settings for your application. Use
+ the Website->Asp.Net Configuration option in Visual Studio.
+ A full list of settings and comments can be found in
+ machine.config.comments usually located in
+ \Windows\Microsoft.Net\Framework\v2.x\Config
+-->
+<configuration>
+ <appSettings/>
+ <connectionStrings/>
+ <system.web>
+ <authorization>
+ <deny users="?"/>
+ </authorization>
+ </system.web>
+</configuration>
diff --git a/samples/InfoCardRelyingParty/Site.Master b/samples/InfoCardRelyingParty/Site.Master
new file mode 100644
index 0000000..7d3dae7
--- /dev/null
+++ b/samples/InfoCardRelyingParty/Site.Master
@@ -0,0 +1,31 @@
+<%@ Master Language="C#" AutoEventWireup="true" %>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+
+<script runat="server">
+ protected void Page_Load(object sender, EventArgs e) {
+ }
+</script>
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head runat="server">
+ <title>InfoCard Relying Party, by DotNetOpenAuth</title>
+ <link href="styles.css" rel="stylesheet" type="text/css" />
+ <asp:ContentPlaceHolder ID="head" runat="server" />
+</head>
+<body>
+ <form id="form1" runat="server">
+ <span style="float: right">
+ <asp:Label runat="server" ID="friendlyUsername" Text="" EnableViewState="false" />
+ <asp:LoginStatus ID="LoginStatus1" runat="server" />
+ </span>
+ <div>
+ <a href="http://dotnetopenid.googlecode.com">
+ <img runat="server" src="~/images/dotnetopenid_tiny.gif" title="Jump to the project web site."
+ alt="DotNetOpenId" border='0' /></a>
+ </div>
+ <div>
+ <asp:ContentPlaceHolder ID="Main" runat="server" />
+ </div>
+ </form>
+</body>
+</html>
diff --git a/samples/InfoCardRelyingParty/images/dotnetopenid_tiny.gif b/samples/InfoCardRelyingParty/images/dotnetopenid_tiny.gif
new file mode 100644
index 0000000..c4ed4f5
--- /dev/null
+++ b/samples/InfoCardRelyingParty/images/dotnetopenid_tiny.gif
Binary files differ
diff --git a/samples/InfoCardRelyingParty/styles.css b/samples/InfoCardRelyingParty/styles.css
new file mode 100644
index 0000000..2e4d3db
--- /dev/null
+++ b/samples/InfoCardRelyingParty/styles.css
@@ -0,0 +1,10 @@
+h2
+{
+ font-style: italic;
+}
+
+body
+{
+ font-family: Cambria, Arial, Times New Roman;
+ font-size: 12pt;
+} \ No newline at end of file
diff --git a/samples/InfoCardRelyingParty/web.config b/samples/InfoCardRelyingParty/web.config
new file mode 100644
index 0000000..b8383c5
--- /dev/null
+++ b/samples/InfoCardRelyingParty/web.config
@@ -0,0 +1,167 @@
+<?xml version="1.0"?>
+<!--
+ Note: As an alternative to hand editing this file you can use the
+ web admin tool to configure settings for your application. Use
+ the Website->Asp.Net Configuration option in Visual Studio.
+ A full list of settings and comments can be found in
+ machine.config.comments usually located in
+ \Windows\Microsoft.Net\Framework\v2.x\Config
+-->
+<configuration>
+
+
+ <configSections>
+ <sectionGroup name="system.web.extensions" type="System.Web.Configuration.SystemWebExtensionsSectionGroup, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35">
+ <sectionGroup name="scripting" type="System.Web.Configuration.ScriptingSectionGroup, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35">
+ <section name="scriptResourceHandler" type="System.Web.Configuration.ScriptingScriptResourceHandlerSection, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" allowDefinition="MachineToApplication"/>
+ <sectionGroup name="webServices" type="System.Web.Configuration.ScriptingWebServicesSectionGroup, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35">
+ <section name="jsonSerialization" type="System.Web.Configuration.ScriptingJsonSerializationSection, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" allowDefinition="Everywhere" />
+ <section name="profileService" type="System.Web.Configuration.ScriptingProfileServiceSection, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" allowDefinition="MachineToApplication" />
+ <section name="authenticationService" type="System.Web.Configuration.ScriptingAuthenticationServiceSection, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" allowDefinition="MachineToApplication" />
+ <section name="roleService" type="System.Web.Configuration.ScriptingRoleServiceSection, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" allowDefinition="MachineToApplication" />
+ </sectionGroup>
+ </sectionGroup>
+ </sectionGroup>
+ </configSections>
+
+
+ <appSettings/>
+ <connectionStrings/>
+ <system.web>
+ <!--
+ Set compilation debug="true" to insert debugging
+ symbols into the compiled page. Because this
+ affects performance, set this value to true only
+ during development.
+
+ Visual Basic options:
+ Set strict="true" to disallow all data type conversions
+ where data loss can occur.
+ Set explicit="true" to force declaration of all variables.
+ -->
+ <compilation debug="false" strict="false" explicit="true">
+
+ <assemblies>
+ <add assembly="System.Core, Version=3.5.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089"/>
+ <add assembly="System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
+ <add assembly="System.Data.DataSetExtensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089"/>
+ <add assembly="System.Xml.Linq, Version=3.5.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089"/>
+ </assemblies>
+
+ </compilation>
+ <pages>
+ <namespaces>
+ <clear />
+ <add namespace="System" />
+ <add namespace="System.Collections" />
+ <add namespace="System.Collections.Generic" />
+ <add namespace="System.Collections.Specialized" />
+ <add namespace="System.Configuration" />
+ <add namespace="System.Text" />
+ <add namespace="System.Text.RegularExpressions" />
+ <add namespace="System.Linq" />
+ <add namespace="System.Xml.Linq" />
+ <add namespace="System.Web" />
+ <add namespace="System.Web.Caching" />
+ <add namespace="System.Web.SessionState" />
+ <add namespace="System.Web.Security" />
+ <add namespace="System.Web.Profile" />
+ <add namespace="System.Web.UI" />
+ <add namespace="System.Web.UI.WebControls" />
+ <add namespace="System.Web.UI.WebControls.WebParts" />
+ <add namespace="System.Web.UI.HtmlControls" />
+ </namespaces>
+
+ <controls>
+ <add tagPrefix="asp" namespace="System.Web.UI" assembly="System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
+ <add tagPrefix="asp" namespace="System.Web.UI.WebControls" assembly="System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
+ </controls>
+
+ </pages>
+ <!--
+ The <authentication> section enables configuration
+ of the security authentication mode used by
+ ASP.NET to identify an incoming user.
+ -->
+ <authentication mode="Forms" />
+ <!--
+ The <customErrors> section enables configuration
+ of what to do if/when an unhandled error occurs
+ during the execution of a request. Specifically,
+ it enables developers to configure html error pages
+ to be displayed in place of a error stack trace.
+
+ <customErrors mode="RemoteOnly" defaultRedirect="GenericErrorPage.htm">
+ <error statusCode="403" redirect="NoAccess.htm" />
+ <error statusCode="404" redirect="FileNotFound.htm" />
+ </customErrors>
+ -->
+
+
+ <httpHandlers>
+ <remove verb="*" path="*.asmx"/>
+ <add verb="*" path="*.asmx" validate="false" type="System.Web.Script.Services.ScriptHandlerFactory, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
+ <add verb="*" path="*_AppService.axd" validate="false" type="System.Web.Script.Services.ScriptHandlerFactory, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
+ <add verb="GET,HEAD" path="ScriptResource.axd" type="System.Web.Handlers.ScriptResourceHandler, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" validate="false"/>
+ </httpHandlers>
+ <httpModules>
+ <add name="ScriptModule" type="System.Web.Handlers.ScriptModule, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
+ </httpModules>
+
+
+ </system.web>
+
+ <system.codedom>
+ <compilers>
+ <compiler language="c#;cs;csharp" extension=".cs" warningLevel="4"
+ type="Microsoft.CSharp.CSharpCodeProvider, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
+ <providerOption name="CompilerVersion" value="v3.5"/>
+ <providerOption name="WarnAsError" value="false"/>
+ </compiler>
+ <compiler language="vb;vbs;visualbasic;vbscript" extension=".vb" warningLevel="4"
+ type="Microsoft.VisualBasic.VBCodeProvider, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
+ <providerOption name="CompilerVersion" value="v3.5"/>
+ <providerOption name="OptionInfer" value="true"/>
+ <providerOption name="WarnAsError" value="false"/>
+ </compiler>
+ </compilers>
+ </system.codedom>
+
+ <!--
+ The system.webServer section is required for running ASP.NET AJAX under Internet
+ Information Services 7.0. It is not necessary for previous version of IIS.
+ -->
+ <system.webServer>
+ <validation validateIntegratedModeConfiguration="false"/>
+ <modules>
+ <remove name="ScriptModule" />
+ <add name="ScriptModule" preCondition="managedHandler" type="System.Web.Handlers.ScriptModule, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
+ </modules>
+ <handlers>
+ <remove name="WebServiceHandlerFactory-Integrated"/>
+ <remove name="ScriptHandlerFactory" />
+ <remove name="ScriptHandlerFactoryAppServices" />
+ <remove name="ScriptResource" />
+ <add name="ScriptHandlerFactory" verb="*" path="*.asmx" preCondition="integratedMode"
+ type="System.Web.Script.Services.ScriptHandlerFactory, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
+ <add name="ScriptHandlerFactoryAppServices" verb="*" path="*_AppService.axd" preCondition="integratedMode"
+ type="System.Web.Script.Services.ScriptHandlerFactory, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
+ <add name="ScriptResource" preCondition="integratedMode" verb="GET,HEAD" path="ScriptResource.axd" type="System.Web.Handlers.ScriptResourceHandler, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
+ </handlers>
+ </system.webServer>
+
+
+ <runtime>
+ <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
+ <dependentAssembly>
+ <assemblyIdentity name="System.Web.Extensions" publicKeyToken="31bf3856ad364e35"/>
+ <bindingRedirect oldVersion="1.0.0.0-1.1.0.0" newVersion="3.5.0.0"/>
+ </dependentAssembly>
+ <dependentAssembly>
+ <assemblyIdentity name="System.Web.Extensions.Design" publicKeyToken="31bf3856ad364e35"/>
+ <bindingRedirect oldVersion="1.0.0.0-1.1.0.0" newVersion="3.5.0.0"/>
+ </dependentAssembly>
+ </assemblyBinding>
+ </runtime>
+
+</configuration>
diff --git a/src/DotNetOpenAuth.sln b/src/DotNetOpenAuth.sln
index 51d220f..5626c4a 100644
--- a/src/DotNetOpenAuth.sln
+++ b/src/DotNetOpenAuth.sln
@@ -127,6 +127,28 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RelyingPartyMvc", "..\sampl
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenIdProviderMvc", "..\samples\OpenIdProviderMvc\OpenIdProviderMvc.csproj", "{AEA29D4D-396F-47F6-BC81-B58D4B855245}"
EndProject
+Project("{E24C65DC-7377-472B-9ABA-BC803B73C61A}") = "InfoCardRelyingParty", "..\samples\InfoCardRelyingParty", "{6EB90284-BD15-461C-BBF2-131CF55F7C8B}"
+ ProjectSection(WebsiteProperties) = preProject
+ TargetFramework = "3.5"
+ ProjectReferences = "{3191B653-F76D-4C1A-9A5A-347BC3AAAAB7}|DotNetOpenAuth.dll;"
+ Debug.AspNetCompiler.VirtualPath = "/InfoCardRelyingParty"
+ Debug.AspNetCompiler.PhysicalPath = "..\samples\InfoCardRelyingParty\"
+ Debug.AspNetCompiler.TargetPath = "PrecompiledWeb\InfoCardRelyingParty\"
+ Debug.AspNetCompiler.Updateable = "true"
+ Debug.AspNetCompiler.ForceOverwrite = "true"
+ Debug.AspNetCompiler.FixedNames = "false"
+ Debug.AspNetCompiler.Debug = "True"
+ Release.AspNetCompiler.VirtualPath = "/InfoCardRelyingParty"
+ Release.AspNetCompiler.PhysicalPath = "..\samples\InfoCardRelyingParty\"
+ Release.AspNetCompiler.TargetPath = "PrecompiledWeb\InfoCardRelyingParty\"
+ Release.AspNetCompiler.Updateable = "true"
+ Release.AspNetCompiler.ForceOverwrite = "true"
+ Release.AspNetCompiler.FixedNames = "false"
+ Release.AspNetCompiler.Debug = "False"
+ VWDPort = "25552"
+ DefaultWebSiteLanguage = "Visual Basic"
+ EndProjectSection
+EndProject
Global
GlobalSection(TestCaseManagementSettings) = postSolution
CategoryFile = DotNetOpenAuth.vsmdi
@@ -208,6 +230,12 @@ Global
{AEA29D4D-396F-47F6-BC81-B58D4B855245}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AEA29D4D-396F-47F6-BC81-B58D4B855245}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AEA29D4D-396F-47F6-BC81-B58D4B855245}.Release|Any CPU.Build.0 = Release|Any CPU
+ {6EB90284-BD15-461C-BBF2-131CF55F7C8B}.CodeAnalysis|Any CPU.ActiveCfg = Debug|Any CPU
+ {6EB90284-BD15-461C-BBF2-131CF55F7C8B}.CodeAnalysis|Any CPU.Build.0 = Debug|Any CPU
+ {6EB90284-BD15-461C-BBF2-131CF55F7C8B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {6EB90284-BD15-461C-BBF2-131CF55F7C8B}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {6EB90284-BD15-461C-BBF2-131CF55F7C8B}.Release|Any CPU.ActiveCfg = Debug|Any CPU
+ {6EB90284-BD15-461C-BBF2-131CF55F7C8B}.Release|Any CPU.Build.0 = Debug|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -223,5 +251,6 @@ Global
{2A59DE0A-B76A-4B42-9A33-04D34548353D} = {B4C6F647-C046-4B54-BE12-7701C4119EE7}
{07B193F1-68AD-4E9C-98AF-BEFB5E9403CB} = {B4C6F647-C046-4B54-BE12-7701C4119EE7}
{AEA29D4D-396F-47F6-BC81-B58D4B855245} = {B4C6F647-C046-4B54-BE12-7701C4119EE7}
+ {6EB90284-BD15-461C-BBF2-131CF55F7C8B} = {B4C6F647-C046-4B54-BE12-7701C4119EE7}
EndGlobalSection
EndGlobal
diff --git a/src/DotNetOpenAuth/ComponentModel/ClaimTypeConverter.cs b/src/DotNetOpenAuth/ComponentModel/ClaimTypeConverter.cs
new file mode 100644
index 0000000..b054cd6
--- /dev/null
+++ b/src/DotNetOpenAuth/ComponentModel/ClaimTypeConverter.cs
@@ -0,0 +1,171 @@
+//-----------------------------------------------------------------------
+// <copyright file="ClaimTypeConverter.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.ComponentModel {
+ using System;
+ using System.ComponentModel;
+ using System.ComponentModel.Design.Serialization;
+ using System.Diagnostics.Contracts;
+ using System.Globalization;
+ using System.Linq;
+ using System.Reflection;
+ using DotNetOpenAuth.InfoCard;
+
+ /// <summary>
+ /// A design-time helper to allow Intellisense to aid typing
+ /// ClaimType URIs.
+ /// </summary>
+ public class ClaimTypeConverter : TypeConverter {
+ /// <summary>
+ /// A cache of the standard claim types known to the application.
+ /// </summary>
+ private static Uri[] standardValues = ReflectStandardValues();
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="ClaimTypeConverter"/> class.
+ /// </summary>
+ [Obsolete("This class is meant for design-time use within an IDE, and not meant to be used directly by runtime code.")]
+ public ClaimTypeConverter() {
+ }
+
+ /// <summary>
+ /// Returns whether this object supports a standard set of values that can be picked from a list, using the specified context.
+ /// </summary>
+ /// <param name="context">An <see cref="T:System.ComponentModel.ITypeDescriptorContext"/> that provides a format context.</param>
+ /// <returns>
+ /// true if <see cref="M:System.ComponentModel.TypeConverter.GetStandardValues"/> should be called to find a common set of values the object supports; otherwise, false.
+ /// </returns>
+ public override bool GetStandardValuesSupported(ITypeDescriptorContext context) {
+ return true;
+ }
+
+ /// <summary>
+ /// Returns a collection of standard values for the data type this type converter is designed for when provided with a format context.
+ /// </summary>
+ /// <param name="context">An <see cref="T:System.ComponentModel.ITypeDescriptorContext"/> that provides a format context that can be used to extract additional information about the environment from which this converter is invoked. This parameter or properties of this parameter can be null.</param>
+ /// <returns>
+ /// A <see cref="T:System.ComponentModel.TypeConverter.StandardValuesCollection"/> that holds a standard set of valid values, or null if the data type does not support a standard set of values.
+ /// </returns>
+ public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext context) {
+ return new StandardValuesCollection(standardValues);
+ }
+
+ /// <summary>
+ /// Returns whether the collection of standard values returned from <see cref="M:System.ComponentModel.TypeConverter.GetStandardValues"/> is an exclusive list of possible values, using the specified context.
+ /// </summary>
+ /// <param name="context">An <see cref="T:System.ComponentModel.ITypeDescriptorContext"/> that provides a format context.</param>
+ /// <returns>
+ /// true if the <see cref="T:System.ComponentModel.TypeConverter.StandardValuesCollection"/> returned from <see cref="M:System.ComponentModel.TypeConverter.GetStandardValues"/> is an exhaustive list of possible values; false if other values are possible.
+ /// </returns>
+ public override bool GetStandardValuesExclusive(ITypeDescriptorContext context) {
+ return false;
+ }
+
+ /// <summary>
+ /// Returns whether this converter can convert an object of the given type to the type of this converter, using the specified context.
+ /// </summary>
+ /// <param name="context">An <see cref="T:System.ComponentModel.ITypeDescriptorContext"/> that provides a format context.</param>
+ /// <param name="sourceType">A <see cref="T:System.Type"/> that represents the type you want to convert from.</param>
+ /// <returns>
+ /// true if this converter can perform the conversion; otherwise, false.
+ /// </returns>
+ public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) {
+ return sourceType == typeof(string) || base.CanConvertFrom(context, sourceType);
+ }
+
+ /// <summary>
+ /// Returns whether this converter can convert the object to the specified type, using the specified context.
+ /// </summary>
+ /// <param name="context">An <see cref="T:System.ComponentModel.ITypeDescriptorContext"/> that provides a format context.</param>
+ /// <param name="destinationType">A <see cref="T:System.Type"/> that represents the type you want to convert to.</param>
+ /// <returns>
+ /// true if this converter can perform the conversion; otherwise, false.
+ /// </returns>
+ public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) {
+ return destinationType == typeof(string)
+ || destinationType == typeof(InstanceDescriptor)
+ || base.CanConvertTo(context, destinationType);
+ }
+
+ /// <summary>
+ /// Converts the given object to the type of this converter, using the specified context and culture information.
+ /// </summary>
+ /// <param name="context">An <see cref="T:System.ComponentModel.ITypeDescriptorContext"/> that provides a format context.</param>
+ /// <param name="culture">The <see cref="T:System.Globalization.CultureInfo"/> to use as the current culture.</param>
+ /// <param name="value">The <see cref="T:System.Object"/> to convert.</param>
+ /// <returns>
+ /// An <see cref="T:System.Object"/> that represents the converted value.
+ /// </returns>
+ /// <exception cref="T:System.NotSupportedException">
+ /// The conversion cannot be performed.
+ /// </exception>
+ public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) {
+ string stringValue = value as string;
+ if (stringValue != null) {
+ return new Uri(stringValue);
+ } else {
+ return base.ConvertFrom(context, culture, value);
+ }
+ }
+
+ /// <summary>
+ /// Converts the given value object to the specified type, using the specified context and culture information.
+ /// </summary>
+ /// <param name="context">An <see cref="T:System.ComponentModel.ITypeDescriptorContext"/> that provides a format context.</param>
+ /// <param name="culture">A <see cref="T:System.Globalization.CultureInfo"/>. If null is passed, the current culture is assumed.</param>
+ /// <param name="value">The <see cref="T:System.Object"/> to convert.</param>
+ /// <param name="destinationType">The <see cref="T:System.Type"/> to convert the <paramref name="value"/> parameter to.</param>
+ /// <returns>
+ /// An <see cref="T:System.Object"/> that represents the converted value.
+ /// </returns>
+ /// <exception cref="T:System.ArgumentNullException">
+ /// The <paramref name="destinationType"/> parameter is null.
+ /// </exception>
+ /// <exception cref="T:System.NotSupportedException">
+ /// The conversion cannot be performed.
+ /// </exception>
+ public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType) {
+ Uri uriValue = (Uri)value;
+ if (destinationType == typeof(string)) {
+ return uriValue.AbsoluteUri;
+ } else if (destinationType == typeof(InstanceDescriptor)) {
+ MemberInfo uriCtor = typeof(Uri).GetConstructor(new Type[] { typeof(string) });
+ return new InstanceDescriptor(uriCtor, new object[] { uriValue.AbsoluteUri });
+ } else {
+ return base.ConvertTo(context, culture, value, destinationType);
+ }
+ }
+
+ /// <summary>
+ /// Returns whether the given value object is valid for this type and for the specified context.
+ /// </summary>
+ /// <param name="context">An <see cref="T:System.ComponentModel.ITypeDescriptorContext"/> that provides a format context.</param>
+ /// <param name="value">The <see cref="T:System.Object"/> to test for validity.</param>
+ /// <returns>
+ /// true if the specified value is valid for this object; otherwise, false.
+ /// </returns>
+ public override bool IsValid(ITypeDescriptorContext context, object value) {
+ if (value is Uri) {
+ return true;
+ } else if (value is string) {
+ Uri result;
+ return Uri.TryCreate((string)value, UriKind.Absolute, out result);
+ } else {
+ return false;
+ }
+ }
+
+ /// <summary>
+ /// Gets the standard claim type URIs known to the library.
+ /// </summary>
+ /// <returns>An array of the standard claim types.</returns>
+ [Pure]
+ private static Uri[] ReflectStandardValues() {
+ return (from field in typeof(WellKnownClaimTypes).GetFields(BindingFlags.Static | BindingFlags.Public)
+ select new Uri((string)field.GetValue(null))).ToArray();
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth/DotNetOpenAuth.csproj b/src/DotNetOpenAuth/DotNetOpenAuth.csproj
index 70befc8..d05bc16 100644
--- a/src/DotNetOpenAuth/DotNetOpenAuth.csproj
+++ b/src/DotNetOpenAuth/DotNetOpenAuth.csproj
@@ -136,6 +136,12 @@
</Reference>
<Reference Include="System.Data" />
<Reference Include="System.Drawing" />
+ <Reference Include="System.IdentityModel">
+ <RequiredTargetFramework>3.0</RequiredTargetFramework>
+ </Reference>
+ <Reference Include="System.IdentityModel.Selectors">
+ <RequiredTargetFramework>3.0</RequiredTargetFramework>
+ </Reference>
<Reference Include="System.ServiceModel">
<RequiredTargetFramework>3.0</RequiredTargetFramework>
</Reference>
@@ -174,6 +180,23 @@
<Compile Include="Configuration\UntrustedWebRequestElement.cs" />
<Compile Include="Configuration\HostNameOrRegexCollection.cs" />
<Compile Include="Configuration\HostNameElement.cs" />
+ <Compile Include="InfoCard\ClaimType.cs" />
+ <Compile Include="ComponentModel\ClaimTypeConverter.cs" />
+ <Compile Include="InfoCard\ReceivingTokenEventArgs.cs" />
+ <Compile Include="InfoCard\TokenProcessingErrorEventArgs.cs" />
+ <Compile Include="InfoCard\WellKnownClaimTypes.cs" />
+ <Compile Include="InfoCard\InfoCardImage.cs" />
+ <Compile Include="InfoCard\InfoCardSelector.cs" />
+ <Compile Include="InfoCard\InfoCardStrings.Designer.cs">
+ <AutoGen>True</AutoGen>
+ <DesignTime>True</DesignTime>
+ <DependentUpon>InfoCardStrings.resx</DependentUpon>
+ </Compile>
+ <Compile Include="InfoCard\ReceivedTokenEventArgs.cs" />
+ <Compile Include="InfoCard\Token\InformationCardException.cs" />
+ <Compile Include="InfoCard\Token\Token.cs" />
+ <Compile Include="InfoCard\Token\TokenUtility.cs" />
+ <Compile Include="InfoCard\Token\TokenDecryptor.cs" />
<Compile Include="Messaging\CachedDirectWebResponse.cs" />
<Compile Include="Messaging\ChannelContract.cs" />
<Compile Include="Messaging\DirectWebRequestOptions.cs" />
@@ -477,6 +500,26 @@
<EmbeddedResource Include="OpenId\RelyingParty\OpenIdAjaxTextBox.js" />
<EmbeddedResource Include="OpenId\RelyingParty\spinner.gif" />
</ItemGroup>
+ <ItemGroup>
+ <EmbeddedResource Include="InfoCard\InfoCardStrings.resx">
+ <Generator>ResXFileCodeGenerator</Generator>
+ <LastGenOutput>InfoCardStrings.Designer.cs</LastGenOutput>
+ </EmbeddedResource>
+ <EmbeddedResource Include="InfoCard\infocard_114x80.png" />
+ <EmbeddedResource Include="InfoCard\infocard_14x10.png" />
+ <EmbeddedResource Include="InfoCard\infocard_214x150.png" />
+ <EmbeddedResource Include="InfoCard\infocard_23x16.png" />
+ <EmbeddedResource Include="InfoCard\infocard_300x210.png" />
+ <EmbeddedResource Include="InfoCard\infocard_34x24.png" />
+ <EmbeddedResource Include="InfoCard\infocard_365x256.png" />
+ <EmbeddedResource Include="InfoCard\infocard_41x29.png" />
+ <EmbeddedResource Include="InfoCard\infocard_50x35.png" />
+ <EmbeddedResource Include="InfoCard\infocard_60x42.png" />
+ <EmbeddedResource Include="InfoCard\infocard_71x50.png" />
+ <EmbeddedResource Include="InfoCard\infocard_81x57.png" />
+ <EmbeddedResource Include="InfoCard\infocard_92x64.png" />
+ <EmbeddedResource Include="InfoCard\SupportingScript.js" />
+ </ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="..\..\tools\DotNetOpenAuth.Versioning.targets" />
</Project> \ No newline at end of file
diff --git a/src/DotNetOpenAuth/InfoCard/ClaimType.cs b/src/DotNetOpenAuth/InfoCard/ClaimType.cs
new file mode 100644
index 0000000..84ae2d7
--- /dev/null
+++ b/src/DotNetOpenAuth/InfoCard/ClaimType.cs
@@ -0,0 +1,49 @@
+//-----------------------------------------------------------------------
+// <copyright file="ClaimType.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.InfoCard {
+ using System;
+ using System.ComponentModel;
+ using System.Web.UI;
+
+ /// <summary>
+ /// Description of a claim that is requested or required in a submitted Information Card.
+ /// </summary>
+ [PersistChildren(false)]
+ [Serializable]
+ public class ClaimType {
+ /// <summary>
+ /// Initializes a new instance of the <see cref="ClaimType"/> class.
+ /// </summary>
+ public ClaimType() {
+ }
+
+ /// <summary>
+ /// Gets or sets the URI of a requested claim.
+ /// </summary>
+ [TypeConverter(typeof(ComponentModel.ClaimTypeConverter))]
+ public Uri Name { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether this claim is optional.
+ /// </summary>
+ /// <value>
+ /// <c>true</c> if this instance is optional; otherwise, <c>false</c>.
+ /// </value>
+ [DefaultValue(false)]
+ public bool IsOptional { get; set; }
+
+ /// <summary>
+ /// Returns a <see cref="T:System.String"/> that represents the current <see cref="T:System.Object"/>.
+ /// </summary>
+ /// <returns>
+ /// A <see cref="T:System.String"/> that represents the current <see cref="T:System.Object"/>.
+ /// </returns>
+ public override string ToString() {
+ return this.Name != null ? this.Name.AbsoluteUri : null;
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth/InfoCard/InfoCardImage.cs b/src/DotNetOpenAuth/InfoCard/InfoCardImage.cs
new file mode 100644
index 0000000..2b7b25f
--- /dev/null
+++ b/src/DotNetOpenAuth/InfoCard/InfoCardImage.cs
@@ -0,0 +1,136 @@
+//-----------------------------------------------------------------------
+// <copyright file="InfoCardImage.cs" company="Dominick Baier, Andrew Arnott">
+// Copyright (c) Dominick Baier, Andrew Arnott. All rights reserved.
+// </copyright>
+// <license>New BSD License</license>
+//-----------------------------------------------------------------------
+
+// embedded images
+[assembly: System.Web.UI.WebResource(DotNetOpenAuth.Util.DefaultNamespace + ".InfoCard.infocard_114x80.png", "image/png")]
+[assembly: System.Web.UI.WebResource(DotNetOpenAuth.Util.DefaultNamespace + ".InfoCard.infocard_14x10.png", "image/png")]
+[assembly: System.Web.UI.WebResource(DotNetOpenAuth.Util.DefaultNamespace + ".InfoCard.infocard_214x150.png", "image/png")]
+[assembly: System.Web.UI.WebResource(DotNetOpenAuth.Util.DefaultNamespace + ".InfoCard.infocard_23x16.png", "image/png")]
+[assembly: System.Web.UI.WebResource(DotNetOpenAuth.Util.DefaultNamespace + ".InfoCard.infocard_300x210.png", "image/png")]
+[assembly: System.Web.UI.WebResource(DotNetOpenAuth.Util.DefaultNamespace + ".InfoCard.infocard_34x24.png", "image/png")]
+[assembly: System.Web.UI.WebResource(DotNetOpenAuth.Util.DefaultNamespace + ".InfoCard.infocard_365x256.png", "image/png")]
+[assembly: System.Web.UI.WebResource(DotNetOpenAuth.Util.DefaultNamespace + ".InfoCard.infocard_41x29.png", "image/png")]
+[assembly: System.Web.UI.WebResource(DotNetOpenAuth.Util.DefaultNamespace + ".InfoCard.infocard_50x35.png", "image/png")]
+[assembly: System.Web.UI.WebResource(DotNetOpenAuth.Util.DefaultNamespace + ".InfoCard.infocard_60x42.png", "image/png")]
+[assembly: System.Web.UI.WebResource(DotNetOpenAuth.Util.DefaultNamespace + ".InfoCard.infocard_71x50.png", "image/png")]
+[assembly: System.Web.UI.WebResource(DotNetOpenAuth.Util.DefaultNamespace + ".InfoCard.infocard_81x57.png", "image/png")]
+[assembly: System.Web.UI.WebResource(DotNetOpenAuth.Util.DefaultNamespace + ".InfoCard.infocard_92x64.png", "image/png")]
+
+namespace DotNetOpenAuth.InfoCard {
+ using System;
+ using System.Diagnostics.CodeAnalysis;
+ using System.Globalization;
+
+ /// <summary>
+ /// A set of sizes for which standard InfoCard icons are available.
+ /// </summary>
+ public enum InfoCardImageSize {
+ /// <summary>
+ /// A standard InfoCard icon with size 14x10
+ /// </summary>
+ [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "x", Justification = "By design")]
+ Size14x10,
+
+ /// <summary>
+ /// A standard InfoCard icon with size 23x16
+ /// </summary>
+ [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "x", Justification = "By design")]
+ Size23x16,
+
+ /// <summary>
+ /// A standard InfoCard icon with size 34x24
+ /// </summary>
+ [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "x", Justification = "By design")]
+ Size34x24,
+
+ /// <summary>
+ /// A standard InfoCard icon with size 41x29
+ /// </summary>
+ [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "x", Justification = "By design")]
+ Size41x29,
+
+ /// <summary>
+ /// A standard InfoCard icon with size 50x35
+ /// </summary>
+ [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "x", Justification = "By design")]
+ Size50x35,
+
+ /// <summary>
+ /// A standard InfoCard icon with size 60x42
+ /// </summary>
+ [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "x", Justification = "By design")]
+ Size60x42,
+
+ /// <summary>
+ /// A standard InfoCard icon with size 71x50
+ /// </summary>
+ [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "x", Justification = "By design")]
+ Size71x50,
+
+ /// <summary>
+ /// A standard InfoCard icon with size 92x64
+ /// </summary>
+ [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "x", Justification = "By design")]
+ Size92x64,
+
+ /// <summary>
+ /// A standard InfoCard icon with size 114x80
+ /// </summary>
+ [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "x", Justification = "By design")]
+ Size114x80,
+
+ /// <summary>
+ /// A standard InfoCard icon with size 164x108
+ /// </summary>
+ [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "x", Justification = "By design")]
+ Size164x108,
+
+ /// <summary>
+ /// A standard InfoCard icon with size 214x50
+ /// </summary>
+ [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "x", Justification = "By design")]
+ Size214x50,
+
+ /// <summary>
+ /// A standard InfoCard icon with size 300x210
+ /// </summary>
+ [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "x", Justification = "By design")]
+ Size300x210,
+
+ /// <summary>
+ /// A standard InfoCard icon with size 365x256
+ /// </summary>
+ [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "x", Justification = "By design")]
+ Size365x256,
+ }
+
+ /// <summary>
+ /// Assists in selecting the InfoCard image to display in the user agent.
+ /// </summary>
+ internal static class InfoCardImage {
+ /// <summary>
+ /// The default size of the InfoCard icon to use.
+ /// </summary>
+ internal const InfoCardImageSize DefaultImageSize = InfoCardImageSize.Size114x80;
+
+ /// <summary>
+ /// The format to use when generating the image manifest resource stream name.
+ /// </summary>
+ private const string UrlFormatString = Util.DefaultNamespace + ".InfoCard.infocard_{0}.png";
+
+ /// <summary>
+ /// Gets the name of the image manifest resource stream for an InfoCard image of the given size.
+ /// </summary>
+ /// <param name="size">The size of the desired InfoCard image.</param>
+ /// <returns>The manifest resource stream name.</returns>
+ internal static string GetImageManifestResourceStreamName(InfoCardImageSize size) {
+ string imageSize = size.ToString();
+ imageSize = imageSize.Substring(4);
+ return String.Format(CultureInfo.InvariantCulture, UrlFormatString, imageSize);
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth/InfoCard/InfoCardSelector.cs b/src/DotNetOpenAuth/InfoCard/InfoCardSelector.cs
new file mode 100644
index 0000000..0cfb39e
--- /dev/null
+++ b/src/DotNetOpenAuth/InfoCard/InfoCardSelector.cs
@@ -0,0 +1,666 @@
+//-----------------------------------------------------------------------
+// <copyright file="InfoCardSelector.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// Certain elements are Copyright (c) 2007 Dominick Baier.
+// </copyright>
+//-----------------------------------------------------------------------
+
+[assembly: System.Web.UI.WebResource("DotNetOpenAuth.InfoCard.SupportingScript.js", "text/javascript")]
+
+namespace DotNetOpenAuth.InfoCard {
+ using System;
+ using System.Collections.ObjectModel;
+ using System.ComponentModel;
+ using System.Diagnostics.Contracts;
+ using System.Globalization;
+ using System.IO;
+ using System.Linq;
+ using System.Web.UI;
+ using System.Web.UI.HtmlControls;
+ using System.Web.UI.WebControls;
+ using System.Xml.XPath;
+ using DotNetOpenAuth.Messaging;
+
+ /// <summary>
+ /// The type of Information Card that is being asked for.
+ /// </summary>
+ public enum IssuerType {
+ /// <summary>
+ /// An InfoCard that the user creates at his/her own machine.
+ /// </summary>
+ SelfIssued,
+
+ /// <summary>
+ /// An InfoCard that is supplied by a third party so assert claims as valid
+ /// according to that third party.
+ /// </summary>
+ Managed,
+ }
+
+ /// <summary>
+ /// The style to use for NOT displaying a hidden region.
+ /// </summary>
+ public enum RenderMode {
+ /// <summary>
+ /// A hidden region should be invisible while still occupying space in the page layout.
+ /// </summary>
+ Static,
+
+ /// <summary>
+ /// A hidden region should collapse so that it does not occupy space in the page layout.
+ /// </summary>
+ Dynamic
+ }
+
+ /// <summary>
+ /// An Information Card selector ASP.NET control.
+ /// </summary>
+ [ParseChildren(true, "ClaimTypes")]
+ [PersistChildren(false)]
+ [DefaultEvent("ReceivedToken")]
+ [ToolboxData("<{0}:InfoCardSelector runat=\"server\"><ClaimTypes><{0}:ClaimType Name=\"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/privatepersonalidentifier\" /></ClaimTypes><UnsupportedTemplate><p>Your browser does not support Information Cards.</p></UnsupportedTemplate></{0}:InfoCardSelector>")]
+ [ContractVerification(true)]
+ public class InfoCardSelector : CompositeControl, IPostBackEventHandler {
+ #region Property constants
+
+ /// <summary>
+ /// Default value for the <see cref="RenderMode"/> property.
+ /// </summary>
+ private const RenderMode RenderModeDefault = RenderMode.Dynamic;
+
+ /// <summary>
+ /// Default value for the <see cref="AutoPostBack"/> property.
+ /// </summary>
+ private const bool AutoPostBackDefault = true;
+
+ /// <summary>
+ /// Default value for the <see cref="AutoPopup"/> property.
+ /// </summary>
+ private const bool AutoPopupDefault = false;
+
+ /// <summary>
+ /// Default value for the <see cref="PrivacyUrl"/> property.
+ /// </summary>
+ private const string PrivacyUrlDefault = "";
+
+ /// <summary>
+ /// Default value for the <see cref="PrivacyVersion"/> property.
+ /// </summary>
+ private const string PrivacyVersionDefault = "";
+
+ /// <summary>
+ /// Default value for the <see cref="InfoCardImage"/> property.
+ /// </summary>
+ private const InfoCardImageSize InfoCardImageDefault = InfoCardImage.DefaultImageSize;
+
+ /// <summary>
+ /// Default value for the <see cref="IssuerPolicy"/> property.
+ /// </summary>
+ private const string IssuerPolicyDefault = "";
+
+ /// <summary>
+ /// Default value for the <see cref="Issuer"/> property.
+ /// </summary>
+ private const string IssuerDefault = "";
+
+ /// <summary>
+ /// The default value for the <see cref="IssuerType"/> property.
+ /// </summary>
+ private const IssuerType IssuerTypeDefault = IssuerType.SelfIssued;
+
+ /// <summary>
+ /// The default value for the <see cref="TokenType"/> property.
+ /// </summary>
+ private const string TokenTypeDefault = "urn:oasis:names:tc:SAML:1.0:assertion";
+
+ /// <summary>
+ /// The viewstate key for storing the <see cref="Issuer" /> property.
+ /// </summary>
+ private const string IssuerViewStateKey = "Issuer";
+
+ /// <summary>
+ /// The viewstate key for storing the <see cref="IssuerPolicy" /> property.
+ /// </summary>
+ private const string IssuerPolicyViewStateKey = "IssuerPolicy";
+
+ /// <summary>
+ /// The viewstate key for storing the <see cref="AutoPopup" /> property.
+ /// </summary>
+ private const string AutoPopupViewStateKey = "AutoPopup";
+
+ /// <summary>
+ /// The viewstate key for storing the <see cref="ClaimTypes" /> property.
+ /// </summary>
+ private const string ClaimTypesViewStateKey = "ClaimTypes";
+
+ /// <summary>
+ /// The viewstate key for storing the <see cref="TokenType" /> property.
+ /// </summary>
+ private const string TokenTypeViewStateKey = "TokenType";
+
+ /// <summary>
+ /// The viewstate key for storing the <see cref="PrivacyUrl" /> property.
+ /// </summary>
+ private const string PrivacyUrlViewStateKey = "PrivacyUrl";
+
+ /// <summary>
+ /// The viewstate key for storing the <see cref="PrivacyVersion" /> property.
+ /// </summary>
+ private const string PrivacyVersionViewStateKey = "PrivacyVersion";
+
+ /// <summary>
+ /// The viewstate key for storing the <see cref="AutoPostBack" /> property.
+ /// </summary>
+ private const string AutoPostBackViewStateKey = "AutoPostBack";
+
+ /// <summary>
+ /// The viewstate key for storing the <see cref="IssuerType" /> property.
+ /// </summary>
+ private const string IssuerTypeViewStateKey = "IssueType";
+
+ /// <summary>
+ /// The viewstate key for storing the <see cref="ImageSize" /> property.
+ /// </summary>
+ private const string ImageSizeViewStateKey = "ImageSize";
+
+ /// <summary>
+ /// The viewstate key for storing the <see cref="RenderMode" /> property.
+ /// </summary>
+ private const string RenderModeViewStateKey = "RenderMode";
+
+ #endregion
+
+ #region Categories
+
+ /// <summary>
+ /// The "Behavior" property category.
+ /// </summary>
+ private const string BehaviorCategory = "Behavior";
+
+ /// <summary>
+ /// The "Appearance" property category.
+ /// </summary>
+ private const string AppearanceCategory = "Appearance";
+
+ /// <summary>
+ /// The "InfoCard" property category.
+ /// </summary>
+ private const string InfoCardCategory = "InfoCard";
+
+ #endregion
+
+ /// <summary>
+ /// The Issuer URI to use for self-issued cards.
+ /// </summary>
+ private const string SelfIssuedUri = "http://schemas.xmlsoap.org/ws/2005/05/identity/issuer/self";
+
+ /// <summary>
+ /// The resource name for getting at the SupportingScript.js embedded manifest stream.
+ /// </summary>
+ private const string ScriptResourceName = "DotNetOpenAuth.InfoCard.SupportingScript.js";
+
+ /// <summary>
+ /// The panel containing the controls to display if InfoCard is supported in the user agent.
+ /// </summary>
+ private Panel infoCardSupportedPanel;
+
+ /// <summary>
+ /// The panel containing the controls to display if InfoCard is NOT supported in the user agent.
+ /// </summary>
+ private Panel infoCardNotSupportedPanel;
+
+ /// <summary>
+ /// Occurs when an InfoCard has been submitted but not decoded yet.
+ /// </summary>
+ [Category(InfoCardCategory)]
+ public event EventHandler<ReceivingTokenEventArgs> ReceivingToken;
+
+ /// <summary>
+ /// Occurs when an InfoCard has been submitted and decoded.
+ /// </summary>
+ [Category(InfoCardCategory)]
+ public event EventHandler<ReceivedTokenEventArgs> ReceivedToken;
+
+ /// <summary>
+ /// Occurs when an InfoCard token is submitted but an error occurs in processing.
+ /// </summary>
+ [Category(InfoCardCategory)]
+ public event EventHandler<TokenProcessingErrorEventArgs> TokenProcessingError;
+
+ #region Properties
+
+ /// <summary>
+ /// Gets the set of claims that are requested from the Information Card..
+ /// </summary>
+ [Description("Specifies the required and optional claims.")]
+ [PersistenceMode(PersistenceMode.InnerProperty), Category(InfoCardCategory)]
+ public Collection<ClaimType> ClaimTypes {
+ get {
+ Contract.Ensures(Contract.Result<Collection<ClaimType>>() != null);
+ if (this.ViewState[ClaimTypesViewStateKey] == null) {
+ var claims = new Collection<ClaimType>();
+ this.ViewState[ClaimTypesViewStateKey] = claims;
+ return claims;
+ } else {
+ return (Collection<ClaimType>)this.ViewState[ClaimTypesViewStateKey];
+ }
+ }
+ }
+
+ /// <summary>
+ /// Gets or sets the issuer URI, applicable only if the <see cref="IssuerType"/>
+ /// property is set to <see cref="InfoCard.IssuerType.Managed"/>.
+ /// </summary>
+ [Description("When receiving managed cards, this is the only Issuer whose cards will be accepted.")]
+ [Category(InfoCardCategory), DefaultValue(IssuerDefault)]
+ public string Issuer {
+ get { return (string)this.ViewState[IssuerViewStateKey] ?? IssuerDefault; }
+ set { this.ViewState[IssuerViewStateKey] = value; }
+ }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether a self-issued or a managed Card should be submitted.
+ /// </summary>
+ [Description("Specifies the issuer type. Select Managed to specify the issuer URI on your own. Select SelfIssued to use the well-known self issued URI.")]
+ [Category(InfoCardCategory), DefaultValue(IssuerTypeDefault)]
+ public IssuerType IssuerType {
+ get { return (IssuerType)(this.ViewState[IssuerTypeViewStateKey] ?? IssuerTypeDefault); }
+ set { this.ViewState[IssuerTypeViewStateKey] = value; }
+ }
+
+ /// <summary>
+ /// Gets or sets the issuer policy URI.
+ /// </summary>
+ [Description("Specifies the URI of the issuer MEX endpoint")]
+ [Category(InfoCardCategory), DefaultValue(IssuerPolicyDefault)]
+ public string IssuerPolicy {
+ get { return (string)this.ViewState[IssuerPolicyViewStateKey] ?? IssuerPolicyDefault; }
+ set { this.ViewState[IssuerPolicyViewStateKey] = value; }
+ }
+
+ /// <summary>
+ /// Gets or sets the URL to this site's privacy policy.
+ /// </summary>
+ [Description("The URL to this site's privacy policy.")]
+ [Category(InfoCardCategory), DefaultValue(PrivacyUrlDefault)]
+ public string PrivacyUrl {
+ get { return (string)this.ViewState[PrivacyUrlViewStateKey] ?? PrivacyUrlDefault; }
+ set { this.ViewState[PrivacyUrlViewStateKey] = value; }
+ }
+
+ /// <summary>
+ /// Gets or sets the version of the privacy policy file.
+ /// </summary>
+ [Description("Specifies the version of the privacy policy file")]
+ [Category(InfoCardCategory), DefaultValue(PrivacyVersionDefault)]
+ public string PrivacyVersion {
+ get { return (string)this.ViewState[PrivacyVersionViewStateKey] ?? PrivacyVersionDefault; }
+ set { this.ViewState[PrivacyVersionViewStateKey] = value; }
+ }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether a postback will automatically
+ /// be invoked when the user selects an Information Card.
+ /// </summary>
+ [Description("Specifies if the pages automatically posts back after the user has selected a card")]
+ [Category(BehaviorCategory), DefaultValue(AutoPostBackDefault)]
+ public bool AutoPostBack {
+ get { return (bool)(this.ViewState[AutoPostBackViewStateKey] ?? AutoPostBackDefault); }
+ set { this.ViewState[AutoPostBackViewStateKey] = value; }
+ }
+
+ /// <summary>
+ /// Gets or sets the size of the standard InfoCard image to display.
+ /// </summary>
+ /// <value>The default size is 114x80.</value>
+ [Description("The size of the InfoCard image to use. Defaults to 114x80.")]
+ [DefaultValue(InfoCardImageDefault), Category(AppearanceCategory)]
+ public InfoCardImageSize ImageSize {
+ get { return (InfoCardImageSize)(this.ViewState[ImageSizeViewStateKey] ?? InfoCardImageDefault); }
+ set { this.ViewState[ImageSizeViewStateKey] = value; }
+ }
+
+ /// <summary>
+ /// Gets or sets the template to display when the user agent lacks
+ /// an Information Card selector.
+ /// </summary>
+ [Browsable(false), DefaultValue("")]
+ [PersistenceMode(PersistenceMode.InnerProperty), TemplateContainer(typeof(InfoCardSelector))]
+ public virtual ITemplate UnsupportedTemplate { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether a hidden region (either
+ /// the unsupported or supported InfoCard HTML)
+ /// collapses or merely becomes invisible when it is not to be displayed.
+ /// </summary>
+ [Description("Whether the hidden region collapses or merely becomes invisible.")]
+ [Category(AppearanceCategory), DefaultValue(RenderModeDefault)]
+ public RenderMode RenderMode {
+ get { return (RenderMode)(this.ViewState[RenderModeViewStateKey] ?? RenderModeDefault); }
+ set { this.ViewState[RenderModeViewStateKey] = value; }
+ }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether the identity selector will be triggered at page load.
+ /// </summary>
+ [Description("Controls whether the InfoCard selector automatically appears when the page is loaded.")]
+ [Category(BehaviorCategory), DefaultValue(AutoPopupDefault)]
+ public bool AutoPopup {
+ get { return (bool)(this.ViewState[AutoPopupViewStateKey] ?? AutoPopupDefault); }
+ set { this.ViewState[AutoPopupViewStateKey] = value; }
+ }
+
+ #endregion
+
+ /// <summary>
+ /// Gets the name of the hidden field that is used to transport the token back to the server.
+ /// </summary>
+ private string HiddenFieldName {
+ get { return this.ClientID + "_tokenxml"; }
+ }
+
+ /// <summary>
+ /// Gets the XML token, which will be encrypted if it was received over SSL.
+ /// </summary>
+ private string TokenXml {
+ get { return this.Page.Request.Form[this.HiddenFieldName]; }
+ }
+
+ /// <summary>
+ /// Gets or sets the type of token the page is prepared to receive.
+ /// </summary>
+ [Description("Specifies the token type. Defaults to SAML 1.0")]
+ [DefaultValue(TokenTypeDefault), Category(InfoCardCategory)]
+ private string TokenType {
+ get { return (string)this.ViewState[TokenTypeViewStateKey] ?? TokenTypeDefault; }
+ set { this.ViewState[TokenTypeViewStateKey] = value; }
+ }
+
+ /// <summary>
+ /// When implemented by a class, enables a server control to process an event raised when a form is posted to the server.
+ /// </summary>
+ /// <param name="eventArgument">A <see cref="T:System.String"/> that represents an optional event argument to be passed to the event handler.</param>
+ public void RaisePostBackEvent(string eventArgument) {
+ if (!string.IsNullOrEmpty(this.TokenXml)) {
+ bool encrypted = Token.IsEncrypted(this.TokenXml);
+ TokenDecryptor decryptor = encrypted ? new TokenDecryptor() : null;
+ ReceivingTokenEventArgs receivingArgs = this.OnReceivingToken(this.TokenXml, decryptor);
+
+ if (!receivingArgs.Cancel) {
+ try {
+ Token token = new Token(this.TokenXml, this.Page.Request.Url, decryptor);
+ this.OnReceivedToken(token);
+ } catch (InformationCardException ex) {
+ this.OnTokenProcessingError(this.TokenXml, ex);
+ return;
+ }
+ }
+ }
+ }
+
+ /// <summary>
+ /// Fires the <see cref="ReceivingToken"/> event.
+ /// </summary>
+ /// <param name="tokenXml">The token XML, prior to any processing.</param>
+ /// <param name="decryptor">The decryptor to use, if the token is encrypted.</param>
+ /// <returns>The event arguments sent to the event handlers.</returns>
+ protected virtual ReceivingTokenEventArgs OnReceivingToken(string tokenXml, TokenDecryptor decryptor) {
+ Contract.Requires(tokenXml != null);
+ ErrorUtilities.VerifyArgumentNotNull(tokenXml, "tokenXml");
+
+ var args = new ReceivingTokenEventArgs(tokenXml, decryptor);
+ var receivingToken = this.ReceivingToken;
+ if (receivingToken != null) {
+ receivingToken(this, args);
+ }
+
+ return args;
+ }
+
+ /// <summary>
+ /// Fires the <see cref="ReceivedToken"/> event.
+ /// </summary>
+ /// <param name="token">The token, if it was decrypted.</param>
+ protected virtual void OnReceivedToken(Token token) {
+ Contract.Requires(token != null);
+ ErrorUtilities.VerifyArgumentNotNull(token, "token");
+
+ var receivedInfoCard = this.ReceivedToken;
+ if (receivedInfoCard != null) {
+ receivedInfoCard(this, new ReceivedTokenEventArgs(token));
+ }
+ }
+
+ /// <summary>
+ /// Fires the <see cref="TokenProcessingError"/> event.
+ /// </summary>
+ /// <param name="unprocessedToken">The unprocessed token.</param>
+ /// <param name="ex">The exception generated while processing the token.</param>
+ protected virtual void OnTokenProcessingError(string unprocessedToken, Exception ex) {
+ Contract.Requires(unprocessedToken != null);
+ Contract.Requires(ex != null);
+
+ var tokenProcessingError = this.TokenProcessingError;
+ if (tokenProcessingError != null) {
+ TokenProcessingErrorEventArgs args = new TokenProcessingErrorEventArgs(unprocessedToken, ex);
+ tokenProcessingError(this, args);
+ }
+ }
+
+ /// <summary>
+ /// Raises the <see cref="E:System.Web.UI.Control.Init"/> event.
+ /// </summary>
+ /// <param name="e">An <see cref="T:System.EventArgs"/> object that contains the event data.</param>
+ protected override void OnInit(EventArgs e) {
+ base.OnInit(e);
+ this.Page.LoadComplete += delegate { this.EnsureChildControls(); };
+ }
+
+ /// <summary>
+ /// Called by the ASP.NET page framework to notify server controls that use composition-based implementation to create any child controls they contain in preparation for posting back or rendering.
+ /// </summary>
+ protected override void CreateChildControls() {
+ base.CreateChildControls();
+
+ this.Page.ClientScript.RegisterHiddenField(this.HiddenFieldName, "");
+
+ this.Controls.Add(this.infoCardSupportedPanel = this.CreateInfoCardSupportedPanel());
+ this.Controls.Add(this.infoCardNotSupportedPanel = this.CreateInfoCardUnsupportedPanel());
+
+ this.RenderSupportingScript();
+ }
+
+ /// <summary>
+ /// Creates a control that renders to &lt;Param Name="{0}" Value="{1}" /&gt;
+ /// </summary>
+ /// <param name="name">The parameter name.</param>
+ /// <param name="value">The parameter value.</param>
+ /// <returns>The control that renders to the Param tag.</returns>
+ private static Control CreateParam(string name, string value) {
+ Contract.Ensures(Contract.Result<Control>() != null);
+ HtmlGenericControl control = new HtmlGenericControl(HtmlTextWriterTag.Param.ToString());
+ control.Attributes.Add(HtmlTextWriterAttribute.Name.ToString(), name);
+ control.Attributes.Add(HtmlTextWriterAttribute.Value.ToString(), value);
+ return control;
+ }
+
+ /// <summary>
+ /// Creates the panel whose contents are displayed to the user
+ /// on a user agent that has an Information Card selector.
+ /// </summary>
+ /// <returns>The Panel control</returns>
+ [Pure]
+ private Panel CreateInfoCardSupportedPanel() {
+ Contract.Ensures(Contract.Result<Panel>() != null);
+
+ Panel supportedPanel = new Panel();
+
+ if (!this.DesignMode) {
+ // At the user agent, assume InfoCard is not supported until
+ // the JavaScript discovers otherwise and reveals this panel.
+ supportedPanel.Style[HtmlTextWriterStyle.Display] = "none";
+ }
+
+ supportedPanel.Controls.Add(this.CreateInfoCardSelectorObject());
+
+ // add clickable image
+ Image image = new Image();
+ image.ImageUrl = this.Page.ClientScript.GetWebResourceUrl(typeof(InfoCardSelector), InfoCardImage.GetImageManifestResourceStreamName(this.ImageSize));
+ image.AlternateText = InfoCardStrings.SelectorClickPrompt;
+ image.ToolTip = InfoCardStrings.SelectorClickPrompt;
+ image.Style[HtmlTextWriterStyle.Cursor] = "hand";
+
+ // generate call do __doPostback
+ PostBackOptions options = new PostBackOptions(this);
+ string postback = this.Page.ClientScript.GetPostBackEventReference(options);
+
+ // generate the onclick script for the image
+ string invokeScript = string.Format(
+ CultureInfo.InvariantCulture,
+ @"document.getElementById('{0}').value = document.getElementById('{1}_cs').value; {2}",
+ this.HiddenFieldName,
+ this.ClientID,
+ this.AutoPostBack ? postback : "");
+
+ image.Attributes["onclick"] = invokeScript;
+ supportedPanel.Controls.Add(image);
+
+ // trigger the selector at page load?
+ if (this.AutoPopup && !this.Page.IsPostBack) {
+ string loadScript = string.Format(
+ CultureInfo.InvariantCulture,
+ @"document.getElementById('{0}').value = document.getElementById('{1}_cs').value; {2}",
+ this.HiddenFieldName,
+ this.ClientID,
+ postback);
+
+ this.Page.ClientScript.RegisterStartupScript(typeof(InfoCardSelector), "selector_load_trigger", loadScript, true);
+ }
+
+ return supportedPanel;
+ }
+
+ /// <summary>
+ /// Creates the panel whose contents are displayed to the user
+ /// on a user agent that does not have an Information Card selector.
+ /// </summary>
+ /// <returns>The Panel control.</returns>
+ [Pure]
+ private Panel CreateInfoCardUnsupportedPanel() {
+ Contract.Ensures(Contract.Result<Panel>() != null);
+
+ Panel unsupportedPanel = new Panel();
+ if (this.UnsupportedTemplate != null) {
+ this.UnsupportedTemplate.InstantiateIn(unsupportedPanel);
+ }
+ return unsupportedPanel;
+ }
+
+ /// <summary>
+ /// Creates the info card selector &lt;object&gt; HTML tag.
+ /// </summary>
+ /// <returns>A control that renders to the &lt;object&gt; tag.</returns>
+ [Pure]
+ private Control CreateInfoCardSelectorObject() {
+ HtmlGenericControl cardSpaceControl = new HtmlGenericControl(HtmlTextWriterTag.Object.ToString());
+ cardSpaceControl.Attributes.Add(HtmlTextWriterAttribute.Type.ToString(), "application/x-informationcard");
+ cardSpaceControl.Attributes.Add(HtmlTextWriterAttribute.Name.ToString(), this.ClientID + "_cs");
+ cardSpaceControl.Attributes.Add(HtmlTextWriterAttribute.Id.ToString(), this.ClientID + "_cs");
+
+ // issuer
+ if (this.IssuerType == IssuerType.SelfIssued) {
+ cardSpaceControl.Controls.Add(CreateParam("issuer", SelfIssuedUri));
+ } else if (IssuerType == IssuerType.Managed) {
+ if (!string.IsNullOrEmpty(this.Issuer)) {
+ cardSpaceControl.Controls.Add(CreateParam("issuer", this.Issuer));
+ }
+ }
+
+ // issuer policy
+ if (!string.IsNullOrEmpty(this.IssuerPolicy)) {
+ cardSpaceControl.Controls.Add(CreateParam("issuerPolicy", this.IssuerPolicy));
+ }
+
+ // token type
+ if (!string.IsNullOrEmpty(this.TokenType)) {
+ cardSpaceControl.Controls.Add(CreateParam("tokenType", this.TokenType));
+ }
+
+ // claims
+ string requiredClaims, optionalClaims;
+ this.GetRequestedClaims(out requiredClaims, out optionalClaims);
+ ErrorUtilities.VerifyArgument(!string.IsNullOrEmpty(requiredClaims) || !string.IsNullOrEmpty(optionalClaims), InfoCardStrings.EmptyClaimListNotAllowed);
+ if (!string.IsNullOrEmpty(requiredClaims)) {
+ cardSpaceControl.Controls.Add(CreateParam("requiredClaims", requiredClaims));
+ }
+ if (!string.IsNullOrEmpty(optionalClaims)) {
+ cardSpaceControl.Controls.Add(CreateParam("optionalClaims", optionalClaims));
+ }
+
+ // privacy URL
+ if (!string.IsNullOrEmpty(this.PrivacyUrl)) {
+ cardSpaceControl.Controls.Add(CreateParam("privacyUrl", this.PrivacyUrl));
+ }
+
+ // privacy version
+ if (!string.IsNullOrEmpty(this.PrivacyVersion)) {
+ cardSpaceControl.Controls.Add(CreateParam("privacyVersion", this.PrivacyVersion));
+ }
+
+ return cardSpaceControl;
+ }
+
+ /// <summary>
+ /// Compiles lists of requested/required claims that should accompany
+ /// any submitted Information Card.
+ /// </summary>
+ /// <param name="required">A space-delimited list of claim type URIs for claims that must be included in a submitted Information Card.</param>
+ /// <param name="optional">A space-delimited list of claim type URIs for claims that may optionally be included in a submitted Information Card.</param>
+ [Pure]
+ private void GetRequestedClaims(out string required, out string optional) {
+ Contract.Requires(this.ClaimTypes != null);
+ Contract.Ensures(Contract.ValueAtReturn<string>(out required) != null);
+ Contract.Ensures(Contract.ValueAtReturn<string>(out optional) != null);
+
+ var nonEmptyClaimTypes = this.ClaimTypes.Where(c => c.Name != null);
+
+ var optionalClaims = from claim in nonEmptyClaimTypes
+ where claim.IsOptional
+ select claim.Name.AbsoluteUri;
+ var requiredClaims = from claim in nonEmptyClaimTypes
+ where !claim.IsOptional
+ select claim.Name.AbsoluteUri;
+
+ string[] requiredClaimsArray = requiredClaims.ToArray();
+ string[] optionalClaimsArray = optionalClaims.ToArray();
+ Contract.Assume(requiredClaimsArray != null);
+ Contract.Assume(optionalClaimsArray != null);
+ required = string.Join(" ", requiredClaimsArray);
+ optional = string.Join(" ", optionalClaimsArray);
+ }
+
+ /// <summary>
+ /// Adds Javascript snippets to the page to help the Information Card selector do its work,
+ /// or to downgrade gracefully if the user agent lacks an Information Card selector.
+ /// </summary>
+ private void RenderSupportingScript() {
+ Contract.Requires(this.infoCardSupportedPanel != null);
+
+ this.Page.ClientScript.RegisterClientScriptResource(typeof(InfoCardSelector), ScriptResourceName);
+
+ if (this.RenderMode == RenderMode.Static) {
+ this.Page.ClientScript.RegisterStartupScript(
+ typeof(InfoCardSelector),
+ "SelectorSupportingScript_" + this.ClientID,
+ string.Format(CultureInfo.InvariantCulture, "CheckStatic('{0}', '{1}');", this.infoCardSupportedPanel.ClientID, this.infoCardNotSupportedPanel.ClientID),
+ true);
+ } else if (RenderMode == RenderMode.Dynamic) {
+ this.Page.ClientScript.RegisterStartupScript(
+ typeof(InfoCardSelector),
+ "SelectorSupportingScript_" + this.ClientID,
+ string.Format(CultureInfo.InvariantCulture, "CheckDynamic('{0}', '{1}');", this.infoCardSupportedPanel.ClientID, this.infoCardNotSupportedPanel.ClientID),
+ true);
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/src/DotNetOpenAuth/InfoCard/InfoCardStrings.Designer.cs b/src/DotNetOpenAuth/InfoCard/InfoCardStrings.Designer.cs
new file mode 100644
index 0000000..4b1dc60
--- /dev/null
+++ b/src/DotNetOpenAuth/InfoCard/InfoCardStrings.Designer.cs
@@ -0,0 +1,108 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+// This code was generated by a tool.
+// Runtime Version:2.0.50727.3521
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace DotNetOpenAuth.InfoCard {
+ using System;
+
+
+ /// <summary>
+ /// A strongly-typed resource class, for looking up localized strings, etc.
+ /// </summary>
+ // This class was auto-generated by the StronglyTypedResourceBuilder
+ // class via a tool like ResGen or Visual Studio.
+ // To add or remove a member, edit your .ResX file then rerun ResGen
+ // with the /str option, or rebuild your VS project.
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "2.0.0.0")]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ internal class InfoCardStrings {
+
+ private static global::System.Resources.ResourceManager resourceMan;
+
+ private static global::System.Globalization.CultureInfo resourceCulture;
+
+ [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ internal InfoCardStrings() {
+ }
+
+ /// <summary>
+ /// Returns the cached ResourceManager instance used by this class.
+ /// </summary>
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Resources.ResourceManager ResourceManager {
+ get {
+ if (object.ReferenceEquals(resourceMan, null)) {
+ global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("DotNetOpenAuth.InfoCard.InfoCardStrings", typeof(InfoCardStrings).Assembly);
+ resourceMan = temp;
+ }
+ return resourceMan;
+ }
+ }
+
+ /// <summary>
+ /// Overrides the current thread's CurrentUICulture property for all
+ /// resource lookups using this strongly typed resource class.
+ /// </summary>
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Globalization.CultureInfo Culture {
+ get {
+ return resourceCulture;
+ }
+ set {
+ resourceCulture = value;
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to The token is invalid: The audience restrictions does not match the Relying Party..
+ /// </summary>
+ internal static string AudienceMismatch {
+ get {
+ return ResourceManager.GetString("AudienceMismatch", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to The list of claims requested for inclusion in the InfoCard must be non-empty..
+ /// </summary>
+ internal static string EmptyClaimListNotAllowed {
+ get {
+ return ResourceManager.GetString("EmptyClaimListNotAllowed", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to Failed to find the encryptionAlgorithm..
+ /// </summary>
+ internal static string EncryptionAlgorithmNotFound {
+ get {
+ return ResourceManager.GetString("EncryptionAlgorithmNotFound", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to This operation requires the PPID claim to be included in the InfoCard token..
+ /// </summary>
+ internal static string PpidClaimRequired {
+ get {
+ return ResourceManager.GetString("PpidClaimRequired", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to Click here to select your Information Card..
+ /// </summary>
+ internal static string SelectorClickPrompt {
+ get {
+ return ResourceManager.GetString("SelectorClickPrompt", resourceCulture);
+ }
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth/InfoCard/InfoCardStrings.resx b/src/DotNetOpenAuth/InfoCard/InfoCardStrings.resx
new file mode 100644
index 0000000..e82e8cd
--- /dev/null
+++ b/src/DotNetOpenAuth/InfoCard/InfoCardStrings.resx
@@ -0,0 +1,135 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+ <!--
+ Microsoft ResX Schema
+
+ Version 2.0
+
+ The primary goals of this format is to allow a simple XML format
+ that is mostly human readable. The generation and parsing of the
+ various data types are done through the TypeConverter classes
+ associated with the data types.
+
+ Example:
+
+ ... ado.net/XML headers & schema ...
+ <resheader name="resmimetype">text/microsoft-resx</resheader>
+ <resheader name="version">2.0</resheader>
+ <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+ <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+ <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+ <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+ <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+ <value>[base64 mime encoded serialized .NET Framework object]</value>
+ </data>
+ <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+ <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+ <comment>This is a comment</comment>
+ </data>
+
+ There are any number of "resheader" rows that contain simple
+ name/value pairs.
+
+ Each data row contains a name, and value. The row also contains a
+ type or mimetype. Type corresponds to a .NET class that support
+ text/value conversion through the TypeConverter architecture.
+ Classes that don't support this are serialized and stored with the
+ mimetype set.
+
+ The mimetype is used for serialized objects, and tells the
+ ResXResourceReader how to depersist the object. This is currently not
+ extensible. For a given mimetype the value must be set accordingly:
+
+ Note - application/x-microsoft.net.object.binary.base64 is the format
+ that the ResXResourceWriter will generate, however the reader can
+ read any of the formats listed below.
+
+ mimetype: application/x-microsoft.net.object.binary.base64
+ value : The object must be serialized with
+ : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.soap.base64
+ value : The object must be serialized with
+ : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.bytearray.base64
+ value : The object must be serialized into a byte array
+ : using a System.ComponentModel.TypeConverter
+ : and then encoded with base64 encoding.
+ -->
+ <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+ <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+ <xsd:element name="root" msdata:IsDataSet="true">
+ <xsd:complexType>
+ <xsd:choice maxOccurs="unbounded">
+ <xsd:element name="metadata">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" />
+ </xsd:sequence>
+ <xsd:attribute name="name" use="required" type="xsd:string" />
+ <xsd:attribute name="type" type="xsd:string" />
+ <xsd:attribute name="mimetype" type="xsd:string" />
+ <xsd:attribute ref="xml:space" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="assembly">
+ <xsd:complexType>
+ <xsd:attribute name="alias" type="xsd:string" />
+ <xsd:attribute name="name" type="xsd:string" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="data">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+ <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+ <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+ <xsd:attribute ref="xml:space" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="resheader">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required" />
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:choice>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:schema>
+ <resheader name="resmimetype">
+ <value>text/microsoft-resx</value>
+ </resheader>
+ <resheader name="version">
+ <value>2.0</value>
+ </resheader>
+ <resheader name="reader">
+ <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+ <resheader name="writer">
+ <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+ <data name="AudienceMismatch" xml:space="preserve">
+ <value>The token is invalid: The audience restrictions does not match the Relying Party.</value>
+ </data>
+ <data name="EmptyClaimListNotAllowed" xml:space="preserve">
+ <value>The list of claims requested for inclusion in the InfoCard must be non-empty.</value>
+ </data>
+ <data name="EncryptionAlgorithmNotFound" xml:space="preserve">
+ <value>Failed to find the encryptionAlgorithm.</value>
+ </data>
+ <data name="PpidClaimRequired" xml:space="preserve">
+ <value>This operation requires the PPID claim to be included in the InfoCard token.</value>
+ </data>
+ <data name="SelectorClickPrompt" xml:space="preserve">
+ <value>Click here to select your Information Card.</value>
+ </data>
+</root> \ No newline at end of file
diff --git a/src/DotNetOpenAuth/InfoCard/ReceivedTokenEventArgs.cs b/src/DotNetOpenAuth/InfoCard/ReceivedTokenEventArgs.cs
new file mode 100644
index 0000000..1511e2d
--- /dev/null
+++ b/src/DotNetOpenAuth/InfoCard/ReceivedTokenEventArgs.cs
@@ -0,0 +1,41 @@
+//-----------------------------------------------------------------------
+// <copyright file="ReceivedTokenEventArgs.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.InfoCard {
+ using System;
+ using System.Diagnostics.CodeAnalysis;
+ using System.Diagnostics.Contracts;
+ using System.Xml.XPath;
+
+ /// <summary>
+ /// Arguments for the <see cref="InfoCardSelector.ReceivedToken"/> event.
+ /// </summary>
+ public class ReceivedTokenEventArgs : EventArgs {
+ /// <summary>
+ /// Initializes a new instance of the <see cref="ReceivedTokenEventArgs"/> class.
+ /// </summary>
+ /// <param name="token">The token.</param>
+ internal ReceivedTokenEventArgs(Token token) {
+ this.Token = token;
+ }
+
+ /// <summary>
+ /// Gets the processed token.
+ /// </summary>
+ public Token Token { get; private set; }
+
+#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.Token != null);
+ }
+#endif
+ }
+}
diff --git a/src/DotNetOpenAuth/InfoCard/ReceivingTokenEventArgs.cs b/src/DotNetOpenAuth/InfoCard/ReceivingTokenEventArgs.cs
new file mode 100644
index 0000000..8656c10
--- /dev/null
+++ b/src/DotNetOpenAuth/InfoCard/ReceivingTokenEventArgs.cs
@@ -0,0 +1,71 @@
+//-----------------------------------------------------------------------
+// <copyright file="ReceivingTokenEventArgs.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.InfoCard {
+ using System;
+ using System.Diagnostics.CodeAnalysis;
+ using System.Diagnostics.Contracts;
+
+ /// <summary>
+ /// Arguments for the <see cref="InfoCardSelector.ReceivingToken"/> event.
+ /// </summary>
+ public class ReceivingTokenEventArgs : EventArgs {
+ /// <summary>
+ /// Initializes a new instance of the <see cref="ReceivingTokenEventArgs"/> class.
+ /// </summary>
+ /// <param name="tokenXml">The raw token XML, prior to any decryption.</param>
+ /// <param name="decryptor">The decryptor to use if the token is encrypted.</param>
+ internal ReceivingTokenEventArgs(string tokenXml, TokenDecryptor decryptor) {
+ Contract.Requires(tokenXml != null);
+
+ this.TokenXml = tokenXml;
+ this.IsEncrypted = Token.IsEncrypted(this.TokenXml);
+ this.Decryptor = decryptor;
+ }
+
+ /// <summary>
+ /// Gets a value indicating whether the token is encrypted.
+ /// </summary>
+ /// <value>
+ /// <c>true</c> if the token is encrypted; otherwise, <c>false</c>.
+ /// </value>
+ public bool IsEncrypted { get; private set; }
+
+ /// <summary>
+ /// Gets the raw token XML, prior to any decryption.
+ /// </summary>
+ public string TokenXml { get; private set; }
+
+ /// <summary>
+ /// Gets the object that will perform token decryption, if necessary.
+ /// </summary>
+ /// <value>The decryptor to use; or <c>null</c> if the token is not encrypted.</value>
+ public TokenDecryptor Decryptor { get; private set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether processing
+ /// this token should be canceled.
+ /// </summary>
+ /// <value><c>true</c> if cancel; otherwise, <c>false</c>.</value>
+ /// <remarks>
+ /// If set the <c>true</c>, the <see cref="InfoCardSelector.ReceivedToken"/>
+ /// event will never be fired.
+ /// </remarks>
+ public bool Cancel { get; set; }
+
+#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.TokenXml != null);
+ Contract.Invariant((this.Decryptor != null) == this.IsEncrypted);
+ }
+#endif
+ }
+}
diff --git a/src/DotNetOpenAuth/InfoCard/SupportingScript.js b/src/DotNetOpenAuth/InfoCard/SupportingScript.js
new file mode 100644
index 0000000..0b8ae2e
--- /dev/null
+++ b/src/DotNetOpenAuth/InfoCard/SupportingScript.js
@@ -0,0 +1,92 @@
+function AreCardsSupported() {
+ /// <summary>
+ /// Determines if information cards are supported by the
+ /// browser.
+ /// </summary>
+ /// <returns>
+ /// true-if the browser supports information cards.
+ ///</returns>
+
+ var IEVer = -1;
+ if (navigator.appName == 'Microsoft Internet Explorer') {
+ if (new RegExp("MSIE ([0-9]{1,}[\.0-9]{0,})").exec(navigator.userAgent) != null) {
+ IEVer = parseFloat(RegExp.$1);
+ }
+ }
+
+ // Look for IE 7+.
+ if (IEVer >= 7) {
+ var embed = document.createElement("object");
+ embed.setAttribute("type", "application/x-informationcard");
+
+ return "" + embed.issuerPolicy != "undefined" && embed.isInstalled;
+ }
+
+ // not IE (any version)
+ if (IEVer < 0 && navigator.mimeTypes && navigator.mimeTypes.length) {
+ // check to see if there is a mimeType handler.
+ x = navigator.mimeTypes['application/x-informationcard'];
+ if (x && x.enabledPlugin) {
+ return true;
+ }
+
+ // check for the IdentitySelector event handler is there.
+ if (document.addEventListener) {
+ var event = document.createEvent("Events");
+ event.initEvent("IdentitySelectorAvailable", true, true);
+ top.dispatchEvent(event);
+
+ if (top.IdentitySelectorAvailable == true) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+function HideStatic(divName) {
+ document.getElementById(divName).style.visibility = 'hidden';
+}
+
+function ShowStatic(divName) {
+ document.getElementById(divName).style.visibility = 'visible';
+}
+
+function HideDynamic(divName) {
+ document.getElementById(divName).style.display = 'none'
+}
+
+function ShowDynamic(divName) {
+ document.getElementById(divName).style.display = '';
+}
+
+function CheckDynamic(controlDiv, unsupportedDiv) {
+ if (AreCardsSupported()) {
+ ShowDynamic(controlDiv);
+ if (unsupportedDiv != '') {
+ HideDynamic(unsupportedDiv);
+ }
+ }
+ else {
+ HideDynamic(controlDiv);
+ if (unsupportedDiv != '') {
+ ShowDynamic(unsupportedDiv);
+ }
+ }
+}
+
+function CheckStatic(controlDiv, unsupportedDiv) {
+ if (AreCardsSupported()) {
+ ShowStatic(controlDiv);
+ if (unsupportedDiv != '') {
+ HideStatic(unsupportedDiv);
+ }
+ }
+ else {
+ HideStatic(controlDiv);
+ if (unsupportedDiv != '') {
+ ShowDynamic(unsupportedDiv);
+ }
+ }
+} \ No newline at end of file
diff --git a/src/DotNetOpenAuth/InfoCard/Token/InformationCardException.cs b/src/DotNetOpenAuth/InfoCard/Token/InformationCardException.cs
new file mode 100644
index 0000000..ff08be8
--- /dev/null
+++ b/src/DotNetOpenAuth/InfoCard/Token/InformationCardException.cs
@@ -0,0 +1,62 @@
+//-----------------------------------------------------------------------
+// <copyright file="InformationCardException.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.InfoCard {
+ using System;
+ using System.Runtime.Serialization;
+ using DotNetOpenAuth.Messaging;
+
+ /// <summary>
+ /// An exception class for Information Cards.
+ /// </summary>
+ [Serializable]
+ public class InformationCardException : ProtocolException {
+ /// <summary>
+ /// Initializes a new instance of the <see cref="InformationCardException"/> class.
+ /// </summary>
+ public InformationCardException() {
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="InformationCardException"/> class with a specified
+ /// error message.
+ /// </summary>
+ /// <param name="message">The error message.</param>
+ public InformationCardException(string message)
+ : base(message) {
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="InformationCardException"/> class
+ /// with a specified error message and a reference to the inner exception that is
+ /// the cause of this exception.
+ /// </summary>
+ /// <param name="message">The error message that explains the reason for the exception.</param>
+ /// <param name="innerException">
+ /// The exception that is the cause of the current exception, or a null reference
+ /// (Nothing in Visual Basic) if no inner exception is specified.
+ /// </param>
+ public InformationCardException(string message, Exception innerException)
+ : base(message, innerException) {
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="InformationCardException"/> class
+ /// with serialized data.
+ /// </summary>
+ /// <param name="info">The <see cref="T:System.Runtime.Serialization.SerializationInfo"/> that holds the serialized object data about the exception being thrown.</param>
+ /// <param name="context">The <see cref="T:System.Runtime.Serialization.StreamingContext"/> that contains contextual information about the source or destination.</param>
+ /// <exception cref="T:System.ArgumentNullException">
+ /// The <paramref name="info"/> parameter is null.
+ /// </exception>
+ /// <exception cref="T:System.Runtime.Serialization.SerializationException">
+ /// The class name is null or <see cref="P:System.Exception.HResult"/> is zero (0).
+ /// </exception>
+ protected InformationCardException(SerializationInfo info, StreamingContext context)
+ : base(info, context) {
+ }
+ }
+} \ No newline at end of file
diff --git a/src/DotNetOpenAuth/InfoCard/Token/Token.cs b/src/DotNetOpenAuth/InfoCard/Token/Token.cs
new file mode 100644
index 0000000..09100d9
--- /dev/null
+++ b/src/DotNetOpenAuth/InfoCard/Token/Token.cs
@@ -0,0 +1,177 @@
+//-----------------------------------------------------------------------
+// <copyright file="Token.cs" company="Andrew Arnott, Microsoft Corporation">
+// Copyright (c) Andrew Arnott, Microsoft Corporation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.InfoCard {
+ using System;
+ using System.Collections.Generic;
+ using System.Diagnostics.CodeAnalysis;
+ using System.Diagnostics.Contracts;
+ using System.IdentityModel.Claims;
+ using System.IdentityModel.Policy;
+ using System.IO;
+ using System.Security.Cryptography.X509Certificates;
+ using System.Text;
+ using System.Xml;
+ using System.Xml.XPath;
+ using DotNetOpenAuth.Messaging;
+
+ /// <summary>
+ /// The decrypted token that was submitted as an Information Card.
+ /// </summary>
+ [ContractVerification(true)]
+ public class Token {
+ /// <summary>
+ /// Backing field for the <see cref="Claims"/> property.
+ /// </summary>
+ private IDictionary<string, string> claims;
+
+ /// <summary>
+ /// Backing field for the <see cref="UniqueId"/> property.
+ /// </summary>
+ private string uniqueId;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="Token"/> class.
+ /// </summary>
+ /// <param name="tokenXml">Xml token, which may be encrypted.</param>
+ /// <param name="audience">The audience.</param>
+ /// <param name="decryptor">The decryptor to use to decrypt the token, if necessary..</param>
+ /// <exception cref="InformationCardException">Thrown for any problem decoding or decrypting the token.</exception>
+ internal Token(string tokenXml, Uri audience, TokenDecryptor decryptor) {
+ Contract.Requires(tokenXml != null && tokenXml.Length > 0);
+ Contract.Requires(audience != null);
+ Contract.Requires(decryptor != null || !IsEncrypted(tokenXml));
+ ErrorUtilities.VerifyNonZeroLength(tokenXml, "tokenXml");
+ ErrorUtilities.VerifyArgumentNotNull(audience, "audience");
+
+ byte[] decryptedBytes;
+ string decryptedString;
+
+ using (XmlReader tokenReader = XmlReader.Create(new StringReader(tokenXml))) {
+ if (IsEncrypted(tokenReader)) {
+ ErrorUtilities.VerifyArgumentNotNull(decryptor, "decryptor");
+ decryptedBytes = decryptor.DecryptToken(tokenReader);
+ decryptedString = Encoding.UTF8.GetString(decryptedBytes);
+ } else {
+ decryptedBytes = Encoding.UTF8.GetBytes(tokenXml);
+ decryptedString = tokenXml;
+ }
+ }
+
+ this.Xml = new XPathDocument(new StringReader(decryptedString)).CreateNavigator();
+ this.AuthorizationContext = TokenUtility.AuthenticateToken(this.Xml.ReadSubtree(), audience);
+ }
+
+ /// <summary>
+ /// Gets the AuthorizationContext behind this token.
+ /// </summary>
+ public AuthorizationContext AuthorizationContext { get; private set; }
+
+ /// <summary>
+ /// Gets the the decrypted token XML.
+ /// </summary>
+ public XPathNavigator Xml { get; private set; }
+
+ /// <summary>
+ /// Gets the UniqueID of this token, usable as a stable username that the user
+ /// has already verified belongs to him/her.
+ /// </summary>
+ /// <remarks>
+ /// By default, this uses the PPID and the Issuer's Public Key and hashes them
+ /// together to generate a UniqueID.
+ /// </remarks>
+ public string UniqueId {
+ get {
+ if (string.IsNullOrEmpty(this.uniqueId)) {
+ this.uniqueId = TokenUtility.GetUniqueName(this.AuthorizationContext);
+ }
+
+ return this.uniqueId;
+ }
+ }
+
+ /// <summary>
+ /// Gets the hash of the card issuer's public key.
+ /// </summary>
+ public string IssuerPubKeyHash {
+ get { return TokenUtility.GetIssuerPubKeyHash(this.AuthorizationContext); }
+ }
+
+ /// <summary>
+ /// Gets the Site Specific ID that the user sees in the Identity Selector.
+ /// </summary>
+ public string SiteSpecificId {
+ get {
+ Contract.Requires(this.Claims.ContainsKey(WellKnownClaimTypes.Ppid));
+ ErrorUtilities.VerifyOperation(this.Claims.ContainsKey(WellKnownClaimTypes.Ppid), InfoCardStrings.PpidClaimRequired);
+ return TokenUtility.CalculateSiteSpecificID(this.Claims[WellKnownClaimTypes.Ppid]);
+ }
+ }
+
+ /// <summary>
+ /// Gets the claims in all the claimsets as a dictionary of strings.
+ /// </summary>
+ public IDictionary<string, string> Claims {
+ get {
+ if (this.claims == null) {
+ this.claims = this.GetFlattenedClaims();
+ }
+
+ return this.claims;
+ }
+ }
+
+ /// <summary>
+ /// Determines whether the specified token XML is encrypted.
+ /// </summary>
+ /// <param name="tokenXml">The token XML.</param>
+ /// <returns>
+ /// <c>true</c> if the specified token XML is encrypted; otherwise, <c>false</c>.
+ /// </returns>
+ [Pure]
+ internal static bool IsEncrypted(string tokenXml) {
+ Contract.Requires(tokenXml != null);
+ ErrorUtilities.VerifyArgumentNotNull(tokenXml, "tokenXml");
+
+ using (XmlReader tokenReader = XmlReader.Create(new StringReader(tokenXml))) {
+ return IsEncrypted(tokenReader);
+ }
+ }
+
+ /// <summary>
+ /// Determines whether the specified token XML is encrypted.
+ /// </summary>
+ /// <param name="tokenXmlReader">The token XML.</param>
+ /// <returns>
+ /// <c>true</c> if the specified token XML is encrypted; otherwise, <c>false</c>.
+ /// </returns>
+ private static bool IsEncrypted(XmlReader tokenXmlReader) {
+ Contract.Requires(tokenXmlReader != null);
+ ErrorUtilities.VerifyArgumentNotNull(tokenXmlReader, "tokenXmlReader");
+ return tokenXmlReader.IsStartElement(TokenDecryptor.XmlEncryptionStrings.EncryptedData, TokenDecryptor.XmlEncryptionStrings.Namespace);
+ }
+
+ /// <summary>
+ /// Flattens the claims into a dictionary
+ /// </summary>
+ /// <returns>A dictionary of claim type URIs and claim values.</returns>
+ [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "Expensive call.")]
+ [Pure]
+ private IDictionary<string, string> GetFlattenedClaims() {
+ var flattenedClaims = new Dictionary<string, string>();
+
+ foreach (ClaimSet set in this.AuthorizationContext.ClaimSets) {
+ foreach (Claim claim in set) {
+ if (claim.Right == Rights.PossessProperty) {
+ flattenedClaims.Add(claim.ClaimType, TokenUtility.GetResourceValue(claim));
+ }
+ }
+ }
+
+ return flattenedClaims;
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth/InfoCard/Token/TokenDecryptor.cs b/src/DotNetOpenAuth/InfoCard/Token/TokenDecryptor.cs
new file mode 100644
index 0000000..d0ff08b
--- /dev/null
+++ b/src/DotNetOpenAuth/InfoCard/Token/TokenDecryptor.cs
@@ -0,0 +1,209 @@
+//-----------------------------------------------------------------------
+// <copyright file="TokenDecryptor.cs" company="Microsoft Corporation">
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// </copyright>
+// <license>
+// Microsoft Public License (Ms-PL).
+// See http://www.microsoft.com/opensource/licenses.mspx#Ms-PL
+// </license>
+// <author>This file was subsequently modified by Andrew Arnott.</author>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.InfoCard {
+ using System;
+ using System.Collections.Generic;
+ using System.Diagnostics.CodeAnalysis;
+ using System.Diagnostics.Contracts;
+ using System.IdentityModel.Selectors;
+ using System.IdentityModel.Tokens;
+ using System.Linq;
+ using System.Security.Cryptography;
+ using System.Security.Cryptography.X509Certificates;
+ using System.ServiceModel.Security;
+ using System.Xml;
+ using DotNetOpenAuth.Messaging;
+
+ /// <summary>
+ /// A utility class for decrypting InfoCard tokens.
+ /// </summary>
+ public class TokenDecryptor {
+ /// <summary>
+ /// Backing field for the <see cref="Tokens"/> property.
+ /// </summary>
+ private List<SecurityToken> tokens;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="TokenDecryptor"/> class.
+ /// </summary>
+ internal TokenDecryptor() {
+ this.tokens = new List<SecurityToken>();
+ StoreName storeName = StoreName.My;
+ StoreLocation storeLocation = StoreLocation.LocalMachine;
+ this.AddDecryptionCertificates(storeName, storeLocation);
+ }
+
+ /// <summary>
+ /// Gets a list of possible decryption certificates, from the store/location set
+ /// </summary>
+ /// <remarks>
+ /// Defaults to localmachine:my (same place SSL certs are)
+ /// </remarks>
+ public IList<SecurityToken> Tokens {
+ get { return this.tokens; }
+ }
+
+ /// <summary>
+ /// Adds a certificate to the list of certificates to decrypt with.
+ /// </summary>
+ /// <param name="certificate">The x509 cert to use for decryption</param>
+ public void AddDecryptionCertificate(X509Certificate2 certificate) {
+ this.Tokens.Add(new X509SecurityToken(certificate));
+ }
+
+ /// <summary>
+ /// Adds a certificate to the list of certificates to decrypt with.
+ /// </summary>
+ /// <param name="storeName">store name of the certificate</param>
+ /// <param name="storeLocation">store location</param>
+ /// <param name="thumbprint">thumbprint of the cert to use</param>
+ public void AddDecryptionCertificate(StoreName storeName, StoreLocation storeLocation, string thumbprint) {
+ this.AddDecryptionCertificates(
+ storeName,
+ storeLocation,
+ store => store.Find(X509FindType.FindByThumbprint, thumbprint, true));
+ }
+
+ /// <summary>
+ /// Adds a store of certificates to the list of certificates to decrypt with.
+ /// </summary>
+ /// <param name="storeName">store name of the certificates</param>
+ /// <param name="storeLocation">store location</param>
+ public void AddDecryptionCertificates(StoreName storeName, StoreLocation storeLocation) {
+ this.AddDecryptionCertificates(storeName, storeLocation, store => store);
+ }
+
+ /// <summary>
+ /// Decrpyts a security token from an XML EncryptedData
+ /// </summary>
+ /// <param name="reader">The encrypted token XML reader.</param>
+ /// <returns>A byte array of the contents of the encrypted token</returns>
+ internal byte[] DecryptToken(XmlReader reader) {
+ Contract.Requires(reader != null);
+ Contract.Ensures(Contract.Result<byte[]>() != null);
+ ErrorUtilities.VerifyArgumentNotNull(reader, "reader");
+
+ byte[] securityTokenData;
+ string encryptionAlgorithm;
+ SecurityKeyIdentifier keyIdentifier;
+ bool isEmptyElement;
+
+ ErrorUtilities.VerifyInternal(reader.IsStartElement(XmlEncryptionStrings.EncryptedData, XmlEncryptionStrings.Namespace), "Expected encrypted token starting XML element was not found.");
+ reader.Read(); // get started
+
+ // if it's not an encryption method, something is dreadfully wrong.
+ ErrorUtilities.VerifyInfoCard(reader.IsStartElement(XmlEncryptionStrings.EncryptionMethod, XmlEncryptionStrings.Namespace), InfoCardStrings.EncryptionAlgorithmNotFound);
+
+ // Looks good, let's grab the alg.
+ isEmptyElement = reader.IsEmptyElement;
+ encryptionAlgorithm = reader.GetAttribute(XmlEncryptionStrings.Algorithm);
+ reader.Read();
+
+ if (!isEmptyElement) {
+ while (reader.IsStartElement()) {
+ reader.Skip();
+ }
+ reader.ReadEndElement();
+ }
+
+ // get the key identifier
+ keyIdentifier = WSSecurityTokenSerializer.DefaultInstance.ReadKeyIdentifier(reader);
+
+ // resolve the symmetric key
+ SymmetricSecurityKey decryptingKey = (SymmetricSecurityKey)SecurityTokenResolver.CreateDefaultSecurityTokenResolver(this.tokens.AsReadOnly(), false).ResolveSecurityKey(keyIdentifier[0]);
+ SymmetricAlgorithm algorithm = decryptingKey.GetSymmetricAlgorithm(encryptionAlgorithm);
+
+ // dig for the security token data itself.
+ reader.ReadStartElement(XmlEncryptionStrings.CipherData, XmlEncryptionStrings.Namespace);
+ reader.ReadStartElement(XmlEncryptionStrings.CipherValue, XmlEncryptionStrings.Namespace);
+ securityTokenData = Convert.FromBase64String(reader.ReadString());
+ reader.ReadEndElement(); // CipherValue
+ reader.ReadEndElement(); // CipherData
+ reader.ReadEndElement(); // EncryptedData
+
+ // decrypto-magic!
+ int blockSizeBytes = algorithm.BlockSize / 8;
+ byte[] iv = new byte[blockSizeBytes];
+ Buffer.BlockCopy(securityTokenData, 0, iv, 0, iv.Length);
+ algorithm.Padding = PaddingMode.ISO10126;
+ algorithm.Mode = CipherMode.CBC;
+ ICryptoTransform decrTransform = algorithm.CreateDecryptor(algorithm.Key, iv);
+ byte[] plainText = decrTransform.TransformFinalBlock(securityTokenData, iv.Length, securityTokenData.Length - iv.Length);
+ decrTransform.Dispose();
+
+ return plainText;
+ }
+
+#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.Tokens != null);
+ }
+#endif
+
+ /// <summary>
+ /// Adds a store of certificates to the list of certificates to decrypt with.
+ /// </summary>
+ /// <param name="storeName">store name of the certificates</param>
+ /// <param name="storeLocation">store location</param>
+ /// <param name="filter">A filter to on the certificates to add.</param>
+ private void AddDecryptionCertificates(StoreName storeName, StoreLocation storeLocation, Func<X509Certificate2Collection, X509Certificate2Collection> filter) {
+ X509Store store = new X509Store(storeName, storeLocation);
+ store.Open(OpenFlags.ReadOnly);
+
+ this.tokens.AddRange((from cert in filter(store.Certificates).Cast<X509Certificate2>()
+ where cert.HasPrivateKey
+ select new X509SecurityToken(cert)).Cast<SecurityToken>());
+
+ store.Close();
+ }
+
+ /// <summary>
+ /// A set of strings used in parsing the XML token.
+ /// </summary>
+ internal static class XmlEncryptionStrings {
+ /// <summary>
+ /// The "http://www.w3.org/2001/04/xmlenc#" value.
+ /// </summary>
+ internal const string Namespace = "http://www.w3.org/2001/04/xmlenc#";
+
+ /// <summary>
+ /// The "EncryptionMethod" value.
+ /// </summary>
+ internal const string EncryptionMethod = "EncryptionMethod";
+
+ /// <summary>
+ /// The "CipherValue" value.
+ /// </summary>
+ internal const string CipherValue = "CipherValue";
+
+ /// <summary>
+ /// The "Algorithm" value.
+ /// </summary>
+ internal const string Algorithm = "Algorithm";
+
+ /// <summary>
+ /// The "EncryptedData" value.
+ /// </summary>
+ internal const string EncryptedData = "EncryptedData";
+
+ /// <summary>
+ /// The "CipherData" value.
+ /// </summary>
+ internal const string CipherData = "CipherData";
+ }
+ }
+} \ No newline at end of file
diff --git a/src/DotNetOpenAuth/InfoCard/Token/TokenUtility.cs b/src/DotNetOpenAuth/InfoCard/Token/TokenUtility.cs
new file mode 100644
index 0000000..c6009f9
--- /dev/null
+++ b/src/DotNetOpenAuth/InfoCard/Token/TokenUtility.cs
@@ -0,0 +1,290 @@
+//-----------------------------------------------------------------------
+// <copyright file="TokenUtility.cs" company="Microsoft Corporation">
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// </copyright>
+// <license>
+// Microsoft Public License (Ms-PL).
+// See http://www.microsoft.com/opensource/licenses.mspx#Ms-PL
+// </license>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.InfoCard {
+ using System;
+ using System.Collections.Generic;
+ using System.Configuration;
+ using System.Diagnostics.Contracts;
+ using System.IdentityModel.Claims;
+ using System.IdentityModel.Policy;
+ using System.IdentityModel.Selectors;
+ using System.IdentityModel.Tokens;
+ using System.IO;
+ using System.Linq;
+ using System.Net.Mail;
+ using System.Security.Cryptography;
+ using System.Security.Principal;
+ using System.ServiceModel.Security;
+ using System.Text;
+ using System.Xml;
+ using DotNetOpenAuth.Messaging;
+
+ /// <summary>
+ /// Tools for reading InfoCard tokens.
+ /// </summary>
+ internal static class TokenUtility {
+ /// <summary>
+ /// Gets the maximum amount the token can be out of sync with time.
+ /// </summary>
+ internal static TimeSpan MaximumClockSkew {
+ get { return DotNetOpenAuth.Configuration.DotNetOpenAuthSection.Configuration.Messaging.MaximumClockSkew; }
+ }
+
+ /// <summary>
+ /// Token Authentication. Translates the decrypted data into a AuthContext.
+ /// </summary>
+ /// <param name="reader">The token XML reader.</param>
+ /// <param name="audience">The audience that the token must be scoped for.
+ /// Use <c>null</c> to indicate any audience is acceptable.</param>
+ /// <returns>
+ /// The authorization context carried by the token.
+ /// </returns>
+ internal static AuthorizationContext AuthenticateToken(XmlReader reader, Uri audience) {
+ // Extensibility Point:
+ // in order to accept different token types, you would need to add additional
+ // code to create an authenticationcontext from the security token.
+ // This code only supports SamlSecurityToken objects.
+ SamlSecurityToken token = WSSecurityTokenSerializer.DefaultInstance.ReadToken(reader, null) as SamlSecurityToken;
+
+ if (null == token) {
+ throw new InformationCardException("Unable to read security token");
+ }
+
+ ////if (null != token.SecurityKeys && token.SecurityKeys.Count > 0)
+ //// throw new InformationCardException("Token Security Keys Exist");
+
+ if (audience != null &&
+ token.Assertion.Conditions != null &&
+ token.Assertion.Conditions.Conditions != null) {
+ foreach (SamlCondition condition in token.Assertion.Conditions.Conditions) {
+ SamlAudienceRestrictionCondition audienceCondition = condition as SamlAudienceRestrictionCondition;
+
+ if (audienceCondition != null) {
+ bool match = audienceCondition.Audiences.Contains(audience);
+
+ // The token is invalid if any condition is not valid.
+ // An audience restriction condition is valid if any audience
+ // matches the Relying Party.
+ ErrorUtilities.VerifyInfoCard(match, InfoCardStrings.AudienceMismatch);
+ }
+ }
+ }
+ var samlAuthenticator = new SamlSecurityTokenAuthenticator(
+ new List<SecurityTokenAuthenticator>(
+ new SecurityTokenAuthenticator[] {
+ new RsaSecurityTokenAuthenticator(),
+ new X509SecurityTokenAuthenticator(),
+ }),
+ MaximumClockSkew);
+
+ return AuthorizationContext.CreateDefaultAuthorizationContext(samlAuthenticator.ValidateToken(token));
+ }
+
+ /// <summary>
+ /// Translates claims to strings
+ /// </summary>
+ /// <param name="claim">Claim to translate to a string</param>
+ /// <returns>The string representation of a claim's value.</returns>
+ internal static string GetResourceValue(Claim claim) {
+ string strClaim = claim.Resource as string;
+ if (!string.IsNullOrEmpty(strClaim)) {
+ return strClaim;
+ }
+
+ IdentityReference reference = claim.Resource as IdentityReference;
+ if (null != reference) {
+ return reference.Value;
+ }
+
+ ICspAsymmetricAlgorithm rsa = claim.Resource as ICspAsymmetricAlgorithm;
+ if (null != rsa) {
+ using (SHA256 sha = new SHA256Managed()) {
+ return Convert.ToBase64String(sha.ComputeHash(rsa.ExportCspBlob(false)));
+ }
+ }
+
+ MailAddress mail = claim.Resource as MailAddress;
+ if (null != mail) {
+ return mail.ToString();
+ }
+
+ byte[] bufferValue = claim.Resource as byte[];
+ if (null != bufferValue) {
+ return Convert.ToBase64String(bufferValue);
+ }
+
+ return claim.Resource.ToString();
+ }
+
+ /// <summary>
+ /// Generates a UniqueID based off the Issuer's key
+ /// </summary>
+ /// <param name="authzContext">the Authorization Context</param>
+ /// <returns>the hash of the internal key of the issuer</returns>
+ internal static string GetIssuerPubKeyHash(AuthorizationContext authzContext) {
+ foreach (ClaimSet cs in authzContext.ClaimSets) {
+ Claim currentIssuerClaim = GetUniqueRsaClaim(cs.Issuer);
+
+ if (currentIssuerClaim != null) {
+ RSA rsa = currentIssuerClaim.Resource as RSA;
+ if (null == rsa) {
+ return null;
+ }
+
+ return ComputeCombinedId(rsa, "");
+ }
+ }
+
+ return null;
+ }
+
+ /// <summary>
+ /// Generates a UniqueID based off the Issuer's key and the PPID.
+ /// </summary>
+ /// <param name="authzContext">The Authorization Context</param>
+ /// <returns>A unique ID for this user at this web site.</returns>
+ internal static string GetUniqueName(AuthorizationContext authzContext) {
+ Contract.Requires(authzContext != null);
+ ErrorUtilities.VerifyArgumentNotNull(authzContext, "authzContext");
+
+ Claim uniqueIssuerClaim = null;
+ Claim uniqueUserClaim = null;
+
+ foreach (ClaimSet cs in authzContext.ClaimSets) {
+ Claim currentIssuerClaim = GetUniqueRsaClaim(cs.Issuer);
+
+ foreach (Claim c in cs.FindClaims(WellKnownClaimTypes.Ppid, Rights.PossessProperty)) {
+ if (null == currentIssuerClaim) {
+ // Found a claim in a ClaimSet with no RSA issuer.
+ return null;
+ }
+
+ if (null == uniqueUserClaim) {
+ uniqueUserClaim = c;
+ uniqueIssuerClaim = currentIssuerClaim;
+ } else if (!uniqueIssuerClaim.Equals(currentIssuerClaim)) {
+ // Found two of the desired claims with different
+ // issuers. No unique name.
+ return null;
+ } else if (!uniqueUserClaim.Equals(c)) {
+ // Found two of the desired claims with different
+ // values. No unique name.
+ return null;
+ }
+ }
+ }
+
+ // No claim of the desired type was found
+ if (null == uniqueUserClaim) {
+ return null;
+ }
+
+ // Unexpected resource type
+ string claimValue = uniqueUserClaim.Resource as string;
+ if (null == claimValue) {
+ return null;
+ }
+
+ // Unexpected resource type for RSA
+ RSA rsa = uniqueIssuerClaim.Resource as RSA;
+ if (null == rsa) {
+ return null;
+ }
+
+ return ComputeCombinedId(rsa, claimValue);
+ }
+
+ /// <summary>
+ /// Generates the Site Specific ID to match the one in the Identity Selector.
+ /// </summary>
+ /// <value>The ID displayed by the Identity Selector.</value>
+ /// <param name="ppid">The personal private identifier.</param>
+ /// <returns>A string containing the XXX-XXXX-XXX cosmetic value.</returns>
+ internal static string CalculateSiteSpecificID(string ppid) {
+ Contract.Requires(ppid != null);
+ ErrorUtilities.VerifyArgumentNotNull(ppid, "ppid");
+ Contract.Ensures(Contract.Result<string>() != null && Contract.Result<string>().Length > 0);
+
+ int callSignChars = 10;
+ char[] charMap = "QL23456789ABCDEFGHJKMNPRSTUVWXYZ".ToCharArray();
+ int charMapLength = charMap.Length;
+
+ byte[] raw = Convert.FromBase64String(ppid);
+ raw = SHA1.Create().ComputeHash(raw);
+
+ StringBuilder callSign = new StringBuilder();
+
+ for (int i = 0; i < callSignChars; i++) {
+ // after char 3 and char 7, place a dash
+ if (i == 3 || i == 7) {
+ callSign.Append('-');
+ }
+ callSign.Append(charMap[raw[i] % charMapLength]);
+ }
+ return callSign.ToString();
+ }
+
+ /// <summary>
+ /// Gets the Unique RSA Claim from the SAML token.
+ /// </summary>
+ /// <param name="cs">the claimset which contains the claim</param>
+ /// <returns>a RSA claim</returns>
+ private static Claim GetUniqueRsaClaim(ClaimSet cs) {
+ Contract.Requires(cs != null);
+ ErrorUtilities.VerifyArgumentNotNull(cs, "cs");
+
+ Claim rsa = null;
+
+ foreach (Claim c in cs.FindClaims(WellKnownClaimTypes.Rsa, Rights.PossessProperty)) {
+ if (null == rsa) {
+ rsa = c;
+ } else if (!rsa.Equals(c)) {
+ // Found two non-equal RSA claims
+ return null;
+ }
+ }
+ return rsa;
+ }
+
+ /// <summary>
+ /// Does the actual calculation of a combined ID from a value and an RSA key.
+ /// </summary>
+ /// <param name="issuerKey">The key of the issuer of the token</param>
+ /// <param name="claimValue">the claim value to hash with.</param>
+ /// <returns>A base64 representation of the combined ID.</returns>
+ private static string ComputeCombinedId(RSA issuerKey, string claimValue) {
+ Contract.Requires(issuerKey != null);
+ Contract.Requires(claimValue != null);
+ Contract.Ensures(Contract.Result<string>() != null);
+ ErrorUtilities.VerifyArgumentNotNull(issuerKey, "issuerKey");
+ ErrorUtilities.VerifyArgumentNotNull(claimValue, "claimValue");
+
+ int nameLength = Encoding.UTF8.GetByteCount(claimValue);
+ RSAParameters rsaParams = issuerKey.ExportParameters(false);
+ byte[] shaInput;
+ byte[] shaOutput;
+
+ int i = 0;
+ shaInput = new byte[rsaParams.Modulus.Length + rsaParams.Exponent.Length + nameLength];
+ rsaParams.Modulus.CopyTo(shaInput, i);
+ i += rsaParams.Modulus.Length;
+ rsaParams.Exponent.CopyTo(shaInput, i);
+ i += rsaParams.Exponent.Length;
+ i += Encoding.UTF8.GetBytes(claimValue, 0, claimValue.Length, shaInput, i);
+
+ using (SHA256 sha = SHA256.Create()) {
+ shaOutput = sha.ComputeHash(shaInput);
+ }
+
+ return Convert.ToBase64String(shaOutput);
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth/InfoCard/TokenProcessingErrorEventArgs.cs b/src/DotNetOpenAuth/InfoCard/TokenProcessingErrorEventArgs.cs
new file mode 100644
index 0000000..1132ac0
--- /dev/null
+++ b/src/DotNetOpenAuth/InfoCard/TokenProcessingErrorEventArgs.cs
@@ -0,0 +1,49 @@
+//-----------------------------------------------------------------------
+// <copyright file="TokenProcessingErrorEventArgs.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+namespace DotNetOpenAuth.InfoCard {
+ using System;
+ using System.Diagnostics.CodeAnalysis;
+ using System.Diagnostics.Contracts;
+
+ /// <summary>
+ /// Arguments for the <see cref="InfoCardSelector.TokenProcessingError"/> event.
+ /// </summary>
+ public class TokenProcessingErrorEventArgs : EventArgs {
+ /// <summary>
+ /// Initializes a new instance of the <see cref="TokenProcessingErrorEventArgs"/> class.
+ /// </summary>
+ /// <param name="tokenXml">The token XML.</param>
+ /// <param name="exception">The exception.</param>
+ internal TokenProcessingErrorEventArgs(string tokenXml, Exception exception) {
+ Contract.Requires(tokenXml != null);
+ Contract.Requires(exception != null);
+ this.TokenXml = tokenXml;
+ this.Exception = exception;
+ }
+
+ /// <summary>
+ /// Gets the raw token XML.
+ /// </summary>
+ public string TokenXml { get; private set; }
+
+ /// <summary>
+ /// Gets the exception that was generated while processing the token.
+ /// </summary>
+ public Exception Exception { get; private set; }
+
+#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.TokenXml != null);
+ Contract.Invariant(this.Exception != null);
+ }
+#endif
+ }
+}
diff --git a/src/DotNetOpenAuth/InfoCard/WellKnownClaimTypes.cs b/src/DotNetOpenAuth/InfoCard/WellKnownClaimTypes.cs
new file mode 100644
index 0000000..bcfd2d9
--- /dev/null
+++ b/src/DotNetOpenAuth/InfoCard/WellKnownClaimTypes.cs
@@ -0,0 +1,177 @@
+// <auto-generated />
+//-----------------------------------------------------------------------
+// <copyright file="ClaimTypes.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+using System.Diagnostics.CodeAnalysis;
+namespace DotNetOpenAuth.InfoCard {
+ /// <summary>
+ /// Well known claims that may be included in an Information Card.
+ /// </summary>
+ /// <remarks>
+ /// Constants defined in this class must be kept in sync with the
+ /// values in the <see cref="ClaimName"/> enum.
+ /// </remarks>
+ public static class WellKnownClaimTypes {
+ /// <summary>
+ /// The "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/anonymous" claim.
+ /// </summary>
+ public const string Anonymous = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/anonymous";
+
+ /// <summary>
+ /// The "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/authentication" claim.
+ /// </summary>
+ public const string Authentication = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/authentication";
+
+ /// <summary>
+ /// The "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/authorizationdecision" claim.
+ /// </summary>
+ public const string AuthorizationDecision = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/authorizationdecision";
+
+ /// <summary>
+ /// The "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/country" claim.
+ /// </summary>
+ public const string Country = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/country";
+
+ /// <summary>
+ /// The "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/dateofbirth" claim.
+ /// </summary>
+ public const string DateOfBirth = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/dateofbirth";
+
+ /// <summary>
+ /// The "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/denyonlysid" claim.
+ /// </summary>
+ public const string DenyOnlySid = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/denyonlysid";
+
+ /// <summary>
+ /// The "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/dns" claim.
+ /// </summary>
+ public const string Dns = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/dns";
+
+ /// <summary>
+ /// The "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress" claim.
+ /// </summary>
+ public const string Email = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress";
+
+ /// <summary>
+ /// The "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/gender" claim.
+ /// </summary>
+ public const string Gender = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/gender";
+
+ /// <summary>
+ /// The "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname" claim.
+ /// </summary>
+ public const string GivenName = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname";
+
+ /// <summary>
+ /// The "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/hash" claim.
+ /// </summary>
+ public const string Hash = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/hash";
+
+ /// <summary>
+ /// The "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/homephone" claim.
+ /// </summary>
+ public const string HomePhone = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/homephone";
+
+ /// <summary>
+ /// The "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/locality" claim.
+ /// </summary>
+ public const string Locality = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/locality";
+
+ /// <summary>
+ /// The "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/mobilephone" claim.
+ /// </summary>
+ public const string MobilePhone = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/mobilephone";
+
+ /// <summary>
+ /// The "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name" claim.
+ /// </summary>
+ public const string Name = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name";
+
+ /// <summary>
+ /// The "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier" claim.
+ /// </summary>
+ public const string NameIdentifier = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier";
+
+ /// <summary>
+ /// The "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/otherphone" claim.
+ /// </summary>
+ public const string OtherPhone = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/otherphone";
+
+ /// <summary>
+ /// The "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/postalcode" claim.
+ /// </summary>
+ public const string PostalCode = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/postalcode";
+
+ /// <summary>
+ /// The "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/privatepersonalidentifier" claim.
+ /// </summary>
+ [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Ppid", Justification = "By design")]
+ public const string Ppid = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/privatepersonalidentifier";
+
+ /// <summary>
+ /// The "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/rsa" claim.
+ /// </summary>
+ [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Rsa", Justification = "By design")]
+ public const string Rsa = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/rsa";
+
+ /// <summary>
+ /// The "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/sid" claim.
+ /// </summary>
+ public const string Sid = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/sid";
+
+ /// <summary>
+ /// The "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/spn" claim.
+ /// </summary>
+ [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Spn", Justification = "By design")]
+ public const string Spn = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/spn";
+
+ /// <summary>
+ /// The "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/stateorprovince" claim.
+ /// </summary>
+ public const string StateOrProvince = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/stateorprovince";
+
+ /// <summary>
+ /// The "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/streetaddress" claim.
+ /// </summary>
+ public const string StreetAddress = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/streetaddress";
+
+ /// <summary>
+ /// The "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname" claim.
+ /// </summary>
+ public const string Surname = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname";
+
+ /// <summary>
+ /// The "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/system" claim.
+ /// </summary>
+ public const string System = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/system";
+
+ /// <summary>
+ /// The "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/thumbprint" claim.
+ /// </summary>
+ public const string Thumbprint = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/thumbprint";
+
+ /// <summary>
+ /// The "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn" claim.
+ /// </summary>
+ [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Upn", Justification = "By design")]
+ public const string Upn = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn";
+
+ /// <summary>
+ /// The "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/uri" claim.
+ /// </summary>
+ public const string Uri = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/uri";
+
+ /// <summary>
+ /// The "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/webpage" claim.
+ /// </summary>
+ public const string Webpage = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/webpage";
+
+ /// <summary>
+ /// The "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/x500distinguishedname" claim.
+ /// </summary>
+ public const string X500DistinguishedName = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/x500distinguishedname";
+ }
+} \ No newline at end of file
diff --git a/src/DotNetOpenAuth/InfoCard/infocard_114x80.png b/src/DotNetOpenAuth/InfoCard/infocard_114x80.png
new file mode 100644
index 0000000..6dba25f
--- /dev/null
+++ b/src/DotNetOpenAuth/InfoCard/infocard_114x80.png
Binary files differ
diff --git a/src/DotNetOpenAuth/InfoCard/infocard_14x10.png b/src/DotNetOpenAuth/InfoCard/infocard_14x10.png
new file mode 100644
index 0000000..d63575d
--- /dev/null
+++ b/src/DotNetOpenAuth/InfoCard/infocard_14x10.png
Binary files differ
diff --git a/src/DotNetOpenAuth/InfoCard/infocard_214x150.png b/src/DotNetOpenAuth/InfoCard/infocard_214x150.png
new file mode 100644
index 0000000..71ebc7e
--- /dev/null
+++ b/src/DotNetOpenAuth/InfoCard/infocard_214x150.png
Binary files differ
diff --git a/src/DotNetOpenAuth/InfoCard/infocard_23x16.png b/src/DotNetOpenAuth/InfoCard/infocard_23x16.png
new file mode 100644
index 0000000..9dbea9f
--- /dev/null
+++ b/src/DotNetOpenAuth/InfoCard/infocard_23x16.png
Binary files differ
diff --git a/src/DotNetOpenAuth/InfoCard/infocard_300x210.png b/src/DotNetOpenAuth/InfoCard/infocard_300x210.png
new file mode 100644
index 0000000..e805b9d
--- /dev/null
+++ b/src/DotNetOpenAuth/InfoCard/infocard_300x210.png
Binary files differ
diff --git a/src/DotNetOpenAuth/InfoCard/infocard_34x24.png b/src/DotNetOpenAuth/InfoCard/infocard_34x24.png
new file mode 100644
index 0000000..b863f64
--- /dev/null
+++ b/src/DotNetOpenAuth/InfoCard/infocard_34x24.png
Binary files differ
diff --git a/src/DotNetOpenAuth/InfoCard/infocard_365x256.png b/src/DotNetOpenAuth/InfoCard/infocard_365x256.png
new file mode 100644
index 0000000..30092c5
--- /dev/null
+++ b/src/DotNetOpenAuth/InfoCard/infocard_365x256.png
Binary files differ
diff --git a/src/DotNetOpenAuth/InfoCard/infocard_41x29.png b/src/DotNetOpenAuth/InfoCard/infocard_41x29.png
new file mode 100644
index 0000000..d3c71ae
--- /dev/null
+++ b/src/DotNetOpenAuth/InfoCard/infocard_41x29.png
Binary files differ
diff --git a/src/DotNetOpenAuth/InfoCard/infocard_50x35.png b/src/DotNetOpenAuth/InfoCard/infocard_50x35.png
new file mode 100644
index 0000000..62ff78b
--- /dev/null
+++ b/src/DotNetOpenAuth/InfoCard/infocard_50x35.png
Binary files differ
diff --git a/src/DotNetOpenAuth/InfoCard/infocard_60x42.png b/src/DotNetOpenAuth/InfoCard/infocard_60x42.png
new file mode 100644
index 0000000..8e920c5
--- /dev/null
+++ b/src/DotNetOpenAuth/InfoCard/infocard_60x42.png
Binary files differ
diff --git a/src/DotNetOpenAuth/InfoCard/infocard_71x50.png b/src/DotNetOpenAuth/InfoCard/infocard_71x50.png
new file mode 100644
index 0000000..9e8f7fb
--- /dev/null
+++ b/src/DotNetOpenAuth/InfoCard/infocard_71x50.png
Binary files differ
diff --git a/src/DotNetOpenAuth/InfoCard/infocard_81x57.png b/src/DotNetOpenAuth/InfoCard/infocard_81x57.png
new file mode 100644
index 0000000..48d62b2
--- /dev/null
+++ b/src/DotNetOpenAuth/InfoCard/infocard_81x57.png
Binary files differ
diff --git a/src/DotNetOpenAuth/InfoCard/infocard_92x64.png b/src/DotNetOpenAuth/InfoCard/infocard_92x64.png
new file mode 100644
index 0000000..388e497
--- /dev/null
+++ b/src/DotNetOpenAuth/InfoCard/infocard_92x64.png
Binary files differ
diff --git a/src/DotNetOpenAuth/Messaging/ErrorUtilities.cs b/src/DotNetOpenAuth/Messaging/ErrorUtilities.cs
index 16b12ee..1c27d6c 100644
--- a/src/DotNetOpenAuth/Messaging/ErrorUtilities.cs
+++ b/src/DotNetOpenAuth/Messaging/ErrorUtilities.cs
@@ -150,6 +150,26 @@ namespace DotNetOpenAuth.Messaging {
}
/// <summary>
+ /// Checks a condition and throws an <see cref="InfoCard.InformationCardException"/>
+ /// if it evaluates to false.
+ /// </summary>
+ /// <param name="condition">The condition to check.</param>
+ /// <param name="errorMessage">The message to include in the exception, if created.</param>
+ /// <param name="args">The formatting arguments.</param>
+ /// <exception cref="InfoCard.InformationCardException">Thrown if <paramref name="condition"/> evaluates to <c>false</c>.</exception>
+ [Pure]
+ internal static void VerifyInfoCard(bool condition, string errorMessage, params object[] args) {
+ Contract.Requires(args != null);
+ Contract.Ensures(condition);
+ Contract.EnsuresOnThrow<InfoCard.InformationCardException>(!condition);
+ Contract.Assume(errorMessage != null);
+ if (!condition) {
+ errorMessage = string.Format(CultureInfo.CurrentCulture, errorMessage, args);
+ throw new InfoCard.InformationCardException(errorMessage);
+ }
+ }
+
+ /// <summary>
/// Throws a <see cref="ProtocolException"/> if some <paramref name="condition"/> evaluates to false.
/// </summary>
/// <param name="condition">True to do nothing; false to throw the exception.</param>
diff --git a/src/DotNetOpenAuth/Properties/AssemblyInfo.cs b/src/DotNetOpenAuth/Properties/AssemblyInfo.cs
index bba0948..69d4dc4 100644
--- a/src/DotNetOpenAuth/Properties/AssemblyInfo.cs
+++ b/src/DotNetOpenAuth/Properties/AssemblyInfo.cs
@@ -30,6 +30,7 @@ using System.Security.Permissions;
using System.Web.UI;
[assembly: TagPrefix("DotNetOpenAuth", "dnoa")]
+[assembly: TagPrefix("DotNetOpenAuth.InfoCard", "ic")]
[assembly: TagPrefix("DotNetOpenAuth.OAuth", "oauth")]
[assembly: TagPrefix("DotNetOpenAuth.OpenId", "openid")]
[assembly: TagPrefix("DotNetOpenAuth.OpenId.Provider", "op")]