diff options
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 Binary files differnew file mode 100644 index 0000000..c4ed4f5 --- /dev/null +++ b/samples/InfoCardRelyingParty/images/dotnetopenid_tiny.gif 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 <Param Name="{0}" Value="{1}" /> + /// </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 <object> HTML tag. + /// </summary> + /// <returns>A control that renders to the <object> 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 Binary files differnew file mode 100644 index 0000000..6dba25f --- /dev/null +++ b/src/DotNetOpenAuth/InfoCard/infocard_114x80.png diff --git a/src/DotNetOpenAuth/InfoCard/infocard_14x10.png b/src/DotNetOpenAuth/InfoCard/infocard_14x10.png Binary files differnew file mode 100644 index 0000000..d63575d --- /dev/null +++ b/src/DotNetOpenAuth/InfoCard/infocard_14x10.png diff --git a/src/DotNetOpenAuth/InfoCard/infocard_214x150.png b/src/DotNetOpenAuth/InfoCard/infocard_214x150.png Binary files differnew file mode 100644 index 0000000..71ebc7e --- /dev/null +++ b/src/DotNetOpenAuth/InfoCard/infocard_214x150.png diff --git a/src/DotNetOpenAuth/InfoCard/infocard_23x16.png b/src/DotNetOpenAuth/InfoCard/infocard_23x16.png Binary files differnew file mode 100644 index 0000000..9dbea9f --- /dev/null +++ b/src/DotNetOpenAuth/InfoCard/infocard_23x16.png diff --git a/src/DotNetOpenAuth/InfoCard/infocard_300x210.png b/src/DotNetOpenAuth/InfoCard/infocard_300x210.png Binary files differnew file mode 100644 index 0000000..e805b9d --- /dev/null +++ b/src/DotNetOpenAuth/InfoCard/infocard_300x210.png diff --git a/src/DotNetOpenAuth/InfoCard/infocard_34x24.png b/src/DotNetOpenAuth/InfoCard/infocard_34x24.png Binary files differnew file mode 100644 index 0000000..b863f64 --- /dev/null +++ b/src/DotNetOpenAuth/InfoCard/infocard_34x24.png diff --git a/src/DotNetOpenAuth/InfoCard/infocard_365x256.png b/src/DotNetOpenAuth/InfoCard/infocard_365x256.png Binary files differnew file mode 100644 index 0000000..30092c5 --- /dev/null +++ b/src/DotNetOpenAuth/InfoCard/infocard_365x256.png diff --git a/src/DotNetOpenAuth/InfoCard/infocard_41x29.png b/src/DotNetOpenAuth/InfoCard/infocard_41x29.png Binary files differnew file mode 100644 index 0000000..d3c71ae --- /dev/null +++ b/src/DotNetOpenAuth/InfoCard/infocard_41x29.png diff --git a/src/DotNetOpenAuth/InfoCard/infocard_50x35.png b/src/DotNetOpenAuth/InfoCard/infocard_50x35.png Binary files differnew file mode 100644 index 0000000..62ff78b --- /dev/null +++ b/src/DotNetOpenAuth/InfoCard/infocard_50x35.png diff --git a/src/DotNetOpenAuth/InfoCard/infocard_60x42.png b/src/DotNetOpenAuth/InfoCard/infocard_60x42.png Binary files differnew file mode 100644 index 0000000..8e920c5 --- /dev/null +++ b/src/DotNetOpenAuth/InfoCard/infocard_60x42.png diff --git a/src/DotNetOpenAuth/InfoCard/infocard_71x50.png b/src/DotNetOpenAuth/InfoCard/infocard_71x50.png Binary files differnew file mode 100644 index 0000000..9e8f7fb --- /dev/null +++ b/src/DotNetOpenAuth/InfoCard/infocard_71x50.png diff --git a/src/DotNetOpenAuth/InfoCard/infocard_81x57.png b/src/DotNetOpenAuth/InfoCard/infocard_81x57.png Binary files differnew file mode 100644 index 0000000..48d62b2 --- /dev/null +++ b/src/DotNetOpenAuth/InfoCard/infocard_81x57.png diff --git a/src/DotNetOpenAuth/InfoCard/infocard_92x64.png b/src/DotNetOpenAuth/InfoCard/infocard_92x64.png Binary files differnew file mode 100644 index 0000000..388e497 --- /dev/null +++ b/src/DotNetOpenAuth/InfoCard/infocard_92x64.png 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")] |