diff options
Diffstat (limited to 'src')
25 files changed, 675 insertions, 25 deletions
diff --git a/src/DotNetOpenAuth/Configuration/OpenIdProviderElement.cs b/src/DotNetOpenAuth/Configuration/OpenIdProviderElement.cs index d84766b..b51ccfb 100644 --- a/src/DotNetOpenAuth/Configuration/OpenIdProviderElement.cs +++ b/src/DotNetOpenAuth/Configuration/OpenIdProviderElement.cs @@ -20,6 +20,11 @@ namespace DotNetOpenAuth.Configuration { private const string SecuritySettingsConfigName = "security"; /// <summary> + /// Gets the name of the <behaviors> sub-element. + /// </summary> + private const string BehaviorsElementName = "behaviors"; + + /// <summary> /// The name of the custom store sub-element. /// </summary> private const string StoreConfigName = "store"; @@ -40,6 +45,16 @@ namespace DotNetOpenAuth.Configuration { } /// <summary> + /// Gets or sets the special behaviors to apply. + /// </summary> + [ConfigurationProperty(BehaviorsElementName, IsDefaultCollection = false)] + [ConfigurationCollection(typeof(TypeConfigurationCollection<IProviderBehavior>))] + public TypeConfigurationCollection<IProviderBehavior> Behaviors { + get { return (TypeConfigurationCollection<IProviderBehavior>)this[BehaviorsElementName] ?? new TypeConfigurationCollection<IProviderBehavior>(); } + set { this[BehaviorsElementName] = value; } + } + + /// <summary> /// Gets or sets the type to use for storing application state. /// </summary> [ConfigurationProperty(StoreConfigName)] diff --git a/src/DotNetOpenAuth/Configuration/OpenIdProviderSecuritySettingsElement.cs b/src/DotNetOpenAuth/Configuration/OpenIdProviderSecuritySettingsElement.cs index 72a6481..457955c 100644 --- a/src/DotNetOpenAuth/Configuration/OpenIdProviderSecuritySettingsElement.cs +++ b/src/DotNetOpenAuth/Configuration/OpenIdProviderSecuritySettingsElement.cs @@ -113,6 +113,7 @@ namespace DotNetOpenAuth.Configuration { Contract.Assume(element != null); settings.AssociationLifetimes.Add(element.AssociationType, element.MaximumLifetime); } + return settings; } } diff --git a/src/DotNetOpenAuth/Configuration/OpenIdRelyingPartyElement.cs b/src/DotNetOpenAuth/Configuration/OpenIdRelyingPartyElement.cs index e311969..cdf4fd3 100644 --- a/src/DotNetOpenAuth/Configuration/OpenIdRelyingPartyElement.cs +++ b/src/DotNetOpenAuth/Configuration/OpenIdRelyingPartyElement.cs @@ -25,6 +25,11 @@ namespace DotNetOpenAuth.Configuration { private const string SecuritySettingsConfigName = "security"; /// <summary> + /// Gets the name of the <behaviors> sub-element. + /// </summary> + private const string BehaviorsElementName = "behaviors"; + + /// <summary> /// Initializes a new instance of the <see cref="OpenIdRelyingPartyElement"/> class. /// </summary> public OpenIdRelyingPartyElement() { @@ -40,6 +45,16 @@ namespace DotNetOpenAuth.Configuration { } /// <summary> + /// Gets or sets the special behaviors to apply. + /// </summary> + [ConfigurationProperty(BehaviorsElementName, IsDefaultCollection = false)] + [ConfigurationCollection(typeof(TypeConfigurationCollection<IRelyingPartyBehavior>))] + public TypeConfigurationCollection<IRelyingPartyBehavior> Behaviors { + get { return (TypeConfigurationCollection<IRelyingPartyBehavior>)this[BehaviorsElementName] ?? new TypeConfigurationCollection<IRelyingPartyBehavior>(); } + set { this[BehaviorsElementName] = value; } + } + + /// <summary> /// Gets or sets the type to use for storing application state. /// </summary> [ConfigurationProperty(StoreConfigName)] diff --git a/src/DotNetOpenAuth/Configuration/OpenIdRelyingPartySecuritySettingsElement.cs b/src/DotNetOpenAuth/Configuration/OpenIdRelyingPartySecuritySettingsElement.cs index 437a0da..d10d9bd 100644 --- a/src/DotNetOpenAuth/Configuration/OpenIdRelyingPartySecuritySettingsElement.cs +++ b/src/DotNetOpenAuth/Configuration/OpenIdRelyingPartySecuritySettingsElement.cs @@ -200,6 +200,7 @@ namespace DotNetOpenAuth.Configuration { settings.RejectUnsolicitedAssertions = this.RejectUnsolicitedAssertions; settings.RejectDelegatingIdentifiers = this.RejectDelegatingIdentifiers; settings.IgnoreUnsignedExtensions = this.IgnoreUnsignedExtensions; + return settings; } } diff --git a/src/DotNetOpenAuth/DotNetOpenAuth.csproj b/src/DotNetOpenAuth/DotNetOpenAuth.csproj index 80e1a72..7da3a47 100644 --- a/src/DotNetOpenAuth/DotNetOpenAuth.csproj +++ b/src/DotNetOpenAuth/DotNetOpenAuth.csproj @@ -169,6 +169,9 @@ <Reference Include="System.Xml.Linq"> <RequiredTargetFramework>3.5</RequiredTargetFramework> </Reference> + <Reference Include="WindowsBase"> + <RequiredTargetFramework>3.0</RequiredTargetFramework> + </Reference> </ItemGroup> <ItemGroup> <Compile Include="ComponentModel\ClaimTypeSuggestions.cs" /> @@ -314,6 +317,12 @@ <Compile Include="OpenId\Association.cs" /> <Compile Include="OpenId\AssociationMemoryStore.cs" /> <Compile Include="OpenId\Associations.cs" /> + <Compile Include="OpenId\Behaviors\BehaviorStrings.Designer.cs"> + <AutoGen>True</AutoGen> + <DesignTime>True</DesignTime> + <DependentUpon>BehaviorStrings.resx</DependentUpon> + </Compile> + <Compile Include="OpenId\Behaviors\PpidGeneration.cs" /> <Compile Include="OpenId\ChannelElements\BackwardCompatibilityBindingElement.cs" /> <Compile Include="OpenId\ChannelElements\ExtensionsBindingElement.cs" /> <Compile Include="OpenId\ChannelElements\IOpenIdExtensionFactory.cs" /> @@ -394,6 +403,7 @@ <Compile Include="OpenId\Provider\IdentityEndpointNormalizationEventArgs.cs" /> <Compile Include="OpenId\Provider\IErrorReporting.cs" /> <Compile Include="OpenId\Provider\IProviderApplicationStore.cs" /> + <Compile Include="OpenId\Provider\IProviderBehavior.cs" /> <Compile Include="OpenId\Provider\IRequest.cs" /> <Compile Include="OpenId\Provider\ProviderEndpoint.cs" /> <Compile Include="OpenId\Provider\RelyingPartyDiscoveryResult.cs" /> @@ -431,6 +441,7 @@ <Compile Include="OpenId\RelyingParty\AssociationPreference.cs" /> <Compile Include="OpenId\RelyingParty\AuthenticationRequest.cs" /> <Compile Include="OpenId\RelyingParty\AuthenticationRequestMode.cs" /> + <Compile Include="OpenId\RelyingParty\IRelyingPartyBehavior.cs" /> <Compile Include="OpenId\RelyingParty\OpenIdRelyingPartyAjaxControlBase.cs" /> <Compile Include="OpenId\RelyingParty\NegativeAuthenticationResponse.cs" /> <Compile Include="OpenId\RelyingParty\OpenIdAjaxTextBox.cs" /> @@ -566,6 +577,10 @@ <EmbeddedResource Include="OpenId\RelyingParty\OpenIdRelyingPartyControlBase.js" /> </ItemGroup> <ItemGroup> + <EmbeddedResource Include="OpenId\Behaviors\BehaviorStrings.resx"> + <Generator>ResXFileCodeGenerator</Generator> + <LastGenOutput>BehaviorStrings.Designer.cs</LastGenOutput> + </EmbeddedResource> <EmbeddedResource Include="OpenId\RelyingParty\OpenIdRelyingPartyAjaxControlBase.js" /> </ItemGroup> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> diff --git a/src/DotNetOpenAuth/OpenId/Behaviors/BehaviorStrings.Designer.cs b/src/DotNetOpenAuth/OpenId/Behaviors/BehaviorStrings.Designer.cs new file mode 100644 index 0000000..4166f19 --- /dev/null +++ b/src/DotNetOpenAuth/OpenId/Behaviors/BehaviorStrings.Designer.cs @@ -0,0 +1,72 @@ +//------------------------------------------------------------------------------ +// <auto-generated> +// This code was generated by a tool. +// Runtime Version:2.0.50727.4918 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// </auto-generated> +//------------------------------------------------------------------------------ + +namespace DotNetOpenAuth.OpenId.Behaviors { + 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 BehaviorStrings { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal BehaviorStrings() { + } + + /// <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.OpenId.Behaviors.BehaviorStrings", typeof(BehaviorStrings).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 No PPID provider has been configured.. + /// </summary> + internal static string PpidProviderNotGiven { + get { + return ResourceManager.GetString("PpidProviderNotGiven", resourceCulture); + } + } + } +} diff --git a/src/DotNetOpenAuth/OpenId/Behaviors/BehaviorStrings.resx b/src/DotNetOpenAuth/OpenId/Behaviors/BehaviorStrings.resx new file mode 100644 index 0000000..23e3e73 --- /dev/null +++ b/src/DotNetOpenAuth/OpenId/Behaviors/BehaviorStrings.resx @@ -0,0 +1,123 @@ +<?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="PpidProviderNotGiven" xml:space="preserve"> + <value>No PPID provider has been configured.</value> + </data> +</root>
\ No newline at end of file diff --git a/src/DotNetOpenAuth/OpenId/Behaviors/PpidGeneration.cs b/src/DotNetOpenAuth/OpenId/Behaviors/PpidGeneration.cs new file mode 100644 index 0000000..3d2836e --- /dev/null +++ b/src/DotNetOpenAuth/OpenId/Behaviors/PpidGeneration.cs @@ -0,0 +1,99 @@ +//----------------------------------------------------------------------- +// <copyright file="PpidGeneration.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.Behaviors { + using System; + using System.Linq; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OpenId.Extensions.ProviderAuthenticationPolicy; + using DotNetOpenAuth.OpenId.Provider; + + /// <summary> + /// Offers OpenID Providers automatic PPID Claimed Identifier generation when requested + /// by a PAPE request. + /// </summary> + /// <remarks> + /// <para>PPIDs are set on positive authentication responses when the PAPE request includes + /// the <see cref="AuthenticationPolicies.PrivatePersonalIdentifier"/> authentication policy.</para> + /// <para>The static member <see cref="PpidGeneration.PpidIdentifierProvider"/> MUST + /// be set prior to any PPID requests come in. Typically this should be set in the + /// <see cref="HttpApplication.Start"/> event handler in the global.asax.cs file.</para> + /// </remarks> + [Serializable] + public sealed class PpidGeneration : IProviderBehavior { + /// <summary> + /// Gets or sets the provider for generating PPID identifiers. + /// </summary> + public static IDirectedIdentityIdentifierProvider PpidIdentifierProvider { get; set; } + + #region IProviderBehavior Members + + /// <summary> + /// Called when a request is received by the Provider. + /// </summary> + /// <param name="request">The incoming request.</param> + /// <returns> + /// <c>true</c> if this behavior owns this request and wants to stop other behaviors + /// from handling it; <c>false</c> to allow other behaviors to process this request. + /// </returns> + /// <remarks> + /// Implementations may set a new value to <see cref="IRequest.SecuritySettings"/> but + /// should not change the properties on the instance of <see cref="ProviderSecuritySettings"/> + /// itself as that instance may be shared across many requests. + /// </remarks> + bool IProviderBehavior.OnIncomingRequest(IRequest request) { + return false; + } + + /// <summary> + /// Called when the Provider is preparing to send a response to an authentication request. + /// </summary> + /// <param name="request">The request that is configured to generate the outgoing response.</param> + /// <returns> + /// <c>true</c> if this behavior owns this request and wants to stop other behaviors + /// from handling it; <c>false</c> to allow other behaviors to process this request. + /// </returns> + bool IProviderBehavior.OnOutgoingResponse(IAuthenticationRequest request) { + ErrorUtilities.VerifyArgumentNotNull(request, "request"); + + bool result = false; + + // Nothing to do for negative assertions. + if (!request.IsAuthenticated.Value) { + return result; + } + + var requestInternal = (Provider.AuthenticationRequest)request; + var responseMessage = (IProtocolMessageWithExtensions)requestInternal.Response; + + // Only apply our special policies if the RP requested it. + var papeRequest = request.GetExtension<PolicyRequest>(); + if (papeRequest != null) { + var papeResponse = responseMessage.Extensions.OfType<PolicyResponse>().SingleOrDefault(); + if (papeResponse == null) { + request.AddResponseExtension(papeResponse = new PolicyResponse()); + } + + if (papeRequest.PreferredPolicies.Contains(AuthenticationPolicies.PrivatePersonalIdentifier)) { + ErrorUtilities.VerifyProtocol(request.ClaimedIdentifier == request.LocalIdentifier, OpenIdStrings.DelegatingIdentifiersNotAllowed); + + // Mask the user's identity with a PPID. + ErrorUtilities.VerifyHost(PpidIdentifierProvider != null, BehaviorStrings.PpidProviderNotGiven); + Identifier ppidIdentifier = PpidIdentifierProvider.GetIdentifier(request.LocalIdentifier, request.Realm); + requestInternal.ResetClaimedAndLocalIdentifiers(ppidIdentifier); + + // Indicate that the RP is receiving a PPID claimed_id + if (!papeResponse.ActualPolicies.Contains(AuthenticationPolicies.PrivatePersonalIdentifier)) { + papeResponse.ActualPolicies.Add(AuthenticationPolicies.PrivatePersonalIdentifier); + } + } + } + return result; + } + + #endregion + } +} diff --git a/src/DotNetOpenAuth/OpenId/Extensions/ProviderAuthenticationPolicy/AuthenticationPolicies.cs b/src/DotNetOpenAuth/OpenId/Extensions/ProviderAuthenticationPolicy/AuthenticationPolicies.cs index d39692c..4392cd5 100644 --- a/src/DotNetOpenAuth/OpenId/Extensions/ProviderAuthenticationPolicy/AuthenticationPolicies.cs +++ b/src/DotNetOpenAuth/OpenId/Extensions/ProviderAuthenticationPolicy/AuthenticationPolicies.cs @@ -37,6 +37,12 @@ namespace DotNetOpenAuth.OpenId.Extensions.ProviderAuthenticationPolicy { public const string PhysicalMultiFactor = "http://schemas.openid.net/pape/policies/2007/06/multi-factor-physical"; /// <summary> + /// Indicates that the Provider MUST use a pair-wise pseudonym for the user that is persistent + /// and unique across the requesting realm as the openid.claimed_id and openid.identity (see Section 4.2). + /// </summary> + public const string PrivatePersonalIdentifier = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/privatepersonalidentifier"; + + /// <summary> /// Used in a PAPE response to indicate that no PAPE authentication policies could be satisfied. /// </summary> /// <remarks> diff --git a/src/DotNetOpenAuth/OpenId/Provider/AuthenticationRequest.cs b/src/DotNetOpenAuth/OpenId/Provider/AuthenticationRequest.cs index 7a547dd..56e73da 100644 --- a/src/DotNetOpenAuth/OpenId/Provider/AuthenticationRequest.cs +++ b/src/DotNetOpenAuth/OpenId/Provider/AuthenticationRequest.cs @@ -200,6 +200,18 @@ namespace DotNetOpenAuth.OpenId.Provider { this.positiveResponse.ClaimedIdentifier = builder.Uri; } + /// <summary> + /// Sets the Claimed and Local identifiers even after they have been initially set. + /// </summary> + /// <param name="identifier">The value to set to the <see cref="ClaimedIdentifier"/> and <see cref="LocalIdentifier"/> properties.</param> + internal void ResetClaimedAndLocalIdentifiers(Identifier identifier) { + Contract.Requires(identifier != null); + ErrorUtilities.VerifyArgumentNotNull(identifier, "identifier"); + + this.positiveResponse.ClaimedIdentifier = identifier; + this.positiveResponse.LocalIdentifier = identifier; + } + #endregion } } diff --git a/src/DotNetOpenAuth/OpenId/Provider/AutoResponsiveRequest.cs b/src/DotNetOpenAuth/OpenId/Provider/AutoResponsiveRequest.cs index 344c72f..d1d310e 100644 --- a/src/DotNetOpenAuth/OpenId/Provider/AutoResponsiveRequest.cs +++ b/src/DotNetOpenAuth/OpenId/Provider/AutoResponsiveRequest.cs @@ -28,8 +28,9 @@ namespace DotNetOpenAuth.OpenId.Provider { /// </summary> /// <param name="request">The request message.</param> /// <param name="response">The response that is ready for transmittal.</param> - internal AutoResponsiveRequest(IDirectedProtocolMessage request, IProtocolMessage response) - : base(request) { + /// <param name="securitySettings">The security settings.</param> + internal AutoResponsiveRequest(IDirectedProtocolMessage request, IProtocolMessage response, ProviderSecuritySettings securitySettings) + : base(request, securitySettings) { ErrorUtilities.VerifyArgumentNotNull(response, "response"); this.response = response; @@ -40,8 +41,9 @@ namespace DotNetOpenAuth.OpenId.Provider { /// for a response to an unrecognizable request. /// </summary> /// <param name="response">The response that is ready for transmittal.</param> - internal AutoResponsiveRequest(IProtocolMessage response) - : base(IndirectResponseBase.GetVersion(response)) { + /// <param name="securitySettings">The security settings.</param> + internal AutoResponsiveRequest(IProtocolMessage response, ProviderSecuritySettings securitySettings) + : base(IndirectResponseBase.GetVersion(response), securitySettings) { ErrorUtilities.VerifyArgumentNotNull(response, "response"); this.response = response; diff --git a/src/DotNetOpenAuth/OpenId/Provider/HostProcessedRequest.cs b/src/DotNetOpenAuth/OpenId/Provider/HostProcessedRequest.cs index b368022..4bb7d28 100644 --- a/src/DotNetOpenAuth/OpenId/Provider/HostProcessedRequest.cs +++ b/src/DotNetOpenAuth/OpenId/Provider/HostProcessedRequest.cs @@ -25,12 +25,17 @@ namespace DotNetOpenAuth.OpenId.Provider { private readonly NegativeAssertionResponse negativeResponse; /// <summary> + /// A cache of the result from discovery of the Realm URL. + /// </summary> + private RelyingPartyDiscoveryResult? realmDiscoveryResult; + + /// <summary> /// Initializes a new instance of the <see cref="HostProcessedRequest"/> class. /// </summary> /// <param name="provider">The provider that received the request.</param> /// <param name="request">The incoming request message.</param> protected HostProcessedRequest(OpenIdProvider provider, SignedResponseRequest request) - : base(request) { + : base(request, provider.SecuritySettings) { Contract.Requires(provider != null); this.negativeResponse = new NegativeAssertionResponse(request, provider.Channel); @@ -64,6 +69,13 @@ namespace DotNetOpenAuth.OpenId.Provider { #endregion /// <summary> + /// Gets a value indicating whether realm discovery been performed. + /// </summary> + internal bool HasRealmDiscoveryBeenPerformed { + get { return this.realmDiscoveryResult.HasValue; } + } + + /// <summary> /// Gets the negative response. /// </summary> protected NegativeAssertionResponse NegativeResponse { @@ -96,9 +108,25 @@ namespace DotNetOpenAuth.OpenId.Provider { Contract.Requires(provider != null); ErrorUtilities.VerifyArgumentNotNull(provider, "provider"); + if (!this.realmDiscoveryResult.HasValue) { + this.realmDiscoveryResult = this.IsReturnUrlDiscoverableCore(provider); + } + + return this.realmDiscoveryResult.Value; + } + + /// <summary> + /// Gets a value indicating whether verification of the return URL claimed by the Relying Party + /// succeeded. + /// </summary> + /// <param name="provider">The OpenIdProvider that is performing the RP discovery.</param> + /// <returns>Result of realm discovery.</returns> + private RelyingPartyDiscoveryResult IsReturnUrlDiscoverableCore(OpenIdProvider provider) { + Contract.Requires(provider != null); + ErrorUtilities.VerifyInternal(this.Realm != null, "Realm should have been read or derived by now."); try { - if (provider.SecuritySettings.RequireSsl && this.Realm.Scheme != Uri.UriSchemeHttps) { + if (this.SecuritySettings.RequireSsl && this.Realm.Scheme != Uri.UriSchemeHttps) { Logger.OpenId.WarnFormat("RP discovery failed because RequireSsl is true and RP discovery would begin at insecure URL {0}.", this.Realm); return RelyingPartyDiscoveryResult.NoServiceDocument; } diff --git a/src/DotNetOpenAuth/OpenId/Provider/IHostProcessedRequest.cs b/src/DotNetOpenAuth/OpenId/Provider/IHostProcessedRequest.cs index eee99f1..5256fdd 100644 --- a/src/DotNetOpenAuth/OpenId/Provider/IHostProcessedRequest.cs +++ b/src/DotNetOpenAuth/OpenId/Provider/IHostProcessedRequest.cs @@ -5,6 +5,7 @@ //----------------------------------------------------------------------- namespace DotNetOpenAuth.OpenId.Provider { + using System; using System.Diagnostics.Contracts; using DotNetOpenAuth.Messaging; @@ -80,6 +81,17 @@ namespace DotNetOpenAuth.OpenId.Provider { #region IRequest Members /// <summary> + /// Gets or sets the security settings that apply to this request. + /// </summary> + /// <value> + /// Defaults to the <see cref="OpenIdProvider.SecuritySettings"/> on the <see cref="OpenIdProvider"/>. + /// </value> + ProviderSecuritySettings IRequest.SecuritySettings { + get { throw new NotImplementedException(); } + set { throw new NotImplementedException(); } + } + + /// <summary> /// Gets a value indicating whether the response is ready to be sent to the user agent. /// </summary> /// <remarks> diff --git a/src/DotNetOpenAuth/OpenId/Provider/IProviderBehavior.cs b/src/DotNetOpenAuth/OpenId/Provider/IProviderBehavior.cs new file mode 100644 index 0000000..7159c02 --- /dev/null +++ b/src/DotNetOpenAuth/OpenId/Provider/IProviderBehavior.cs @@ -0,0 +1,43 @@ +//----------------------------------------------------------------------- +// <copyright file="IProviderBehavior.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.Provider { + using DotNetOpenAuth.OpenId.ChannelElements; + + /// <summary> + /// Applies a custom security policy to certain OpenID security settings and behaviors. + /// </summary> + /// <remarks> + /// BEFORE MARKING THIS INTERFACE PUBLIC: it's very important that we shift the methods to be channel-level + /// rather than facade class level and for the OpenIdChannel to be the one to invoke these methods. + /// </remarks> + internal interface IProviderBehavior { + /// <summary> + /// Called when a request is received by the Provider. + /// </summary> + /// <param name="request">The incoming request.</param> + /// <returns> + /// <c>true</c> if this behavior owns this request and wants to stop other behaviors + /// from handling it; <c>false</c> to allow other behaviors to process this request. + /// </returns> + /// <remarks> + /// Implementations may set a new value to <see cref="IRequest.SecuritySettings"/> but + /// should not change the properties on the instance of <see cref="ProviderSecuritySettings"/> + /// itself as that instance may be shared across many requests. + /// </remarks> + bool OnIncomingRequest(IRequest request); + + /// <summary> + /// Called when the Provider is preparing to send a response to an authentication request. + /// </summary> + /// <param name="request">The request that is configured to generate the outgoing response.</param> + /// <returns> + /// <c>true</c> if this behavior owns this request and wants to stop other behaviors + /// from handling it; <c>false</c> to allow other behaviors to process this request. + /// </returns> + bool OnOutgoingResponse(IAuthenticationRequest request); + } +} diff --git a/src/DotNetOpenAuth/OpenId/Provider/IRequest.cs b/src/DotNetOpenAuth/OpenId/Provider/IRequest.cs index 9f0f633..2ca96ca 100644 --- a/src/DotNetOpenAuth/OpenId/Provider/IRequest.cs +++ b/src/DotNetOpenAuth/OpenId/Provider/IRequest.cs @@ -33,6 +33,12 @@ namespace DotNetOpenAuth.OpenId.Provider { bool IsResponseReady { get; } /// <summary> + /// Gets or sets the security settings that apply to this request. + /// </summary> + /// <value>Defaults to the <see cref="OpenIdProvider.SecuritySettings"/> on the <see cref="OpenIdProvider"/>.</value> + ProviderSecuritySettings SecuritySettings { get; set; } + + /// <summary> /// Adds an extension to the response to send to the relying party. /// </summary> /// <param name="extension">The extension to add to the response message.</param> @@ -68,6 +74,17 @@ namespace DotNetOpenAuth.OpenId.Provider { #region IRequest Members /// <summary> + /// Gets or sets the security settings that apply to this request. + /// </summary> + /// <value> + /// Defaults to the <see cref="OpenIdProvider.SecuritySettings"/> on the <see cref="OpenIdProvider"/>. + /// </value> + ProviderSecuritySettings IRequest.SecuritySettings { + get { throw new NotImplementedException(); } + set { throw new NotImplementedException(); } + } + + /// <summary> /// Gets a value indicating whether the response is ready to be sent to the user agent. /// </summary> /// <value></value> diff --git a/src/DotNetOpenAuth/OpenId/Provider/OpenIdProvider.cs b/src/DotNetOpenAuth/OpenId/Provider/OpenIdProvider.cs index 0ec97ff..58b6887 100644 --- a/src/DotNetOpenAuth/OpenId/Provider/OpenIdProvider.cs +++ b/src/DotNetOpenAuth/OpenId/Provider/OpenIdProvider.cs @@ -7,6 +7,7 @@ namespace DotNetOpenAuth.OpenId.Provider { using System; using System.Collections.Generic; + using System.Collections.ObjectModel; using System.ComponentModel; using System.Diagnostics.CodeAnalysis; using System.Diagnostics.Contracts; @@ -31,6 +32,11 @@ namespace DotNetOpenAuth.OpenId.Provider { private const string ApplicationStoreKey = "DotNetOpenAuth.OpenId.Provider.OpenIdProvider.ApplicationStore"; /// <summary> + /// Backing store for the <see cref="Behaviors"/> property. + /// </summary> + private readonly Collection<IProviderBehavior> behaviors = new Collection<IProviderBehavior>(); + + /// <summary> /// Backing field for the <see cref="SecuritySettings"/> property. /// </summary> private ProviderSecuritySettings securitySettings; @@ -73,6 +79,10 @@ namespace DotNetOpenAuth.OpenId.Provider { this.AssociationStore = associationStore; this.SecuritySettings = DotNetOpenAuthSection.Configuration.OpenId.Provider.SecuritySettings.CreateSecuritySettings(); + foreach (var behavior in DotNetOpenAuthSection.Configuration.OpenId.Provider.Behaviors.CreateInstances(false)) { + this.behaviors.Add(behavior); + } + this.Channel = new OpenIdChannel(this.AssociationStore, nonceStore, this.SecuritySettings); } @@ -138,6 +148,13 @@ namespace DotNetOpenAuth.OpenId.Provider { public IErrorReporting ErrorReporting { get; set; } /// <summary> + /// Gets a list of custom behaviors to apply to OpenID actions. + /// </summary> + internal ICollection<IProviderBehavior> Behaviors { + get { return this.behaviors; } + } + + /// <summary> /// Gets the association store. /// </summary> internal IAssociationStore<AssociationRelyingPartyType> AssociationStore { get; private set; } @@ -199,24 +216,43 @@ namespace DotNetOpenAuth.OpenId.Provider { ErrorUtilities.ThrowProtocol(MessagingStrings.UnexpectedMessageReceivedOfMany); } + IRequest result = null; + var checkIdMessage = incomingMessage as CheckIdRequest; if (checkIdMessage != null) { - return new AuthenticationRequest(this, checkIdMessage); + result = new AuthenticationRequest(this, checkIdMessage); + } + + if (result == null) { + var extensionOnlyRequest = incomingMessage as SignedResponseRequest; + if (extensionOnlyRequest != null) { + result = new AnonymousRequest(this, extensionOnlyRequest); + } } - var extensionOnlyRequest = incomingMessage as SignedResponseRequest; - if (extensionOnlyRequest != null) { - return new AnonymousRequest(this, extensionOnlyRequest); + if (result == null) { + var checkAuthMessage = incomingMessage as CheckAuthenticationRequest; + if (checkAuthMessage != null) { + result = new AutoResponsiveRequest(incomingMessage, new CheckAuthenticationResponse(checkAuthMessage, this), this.SecuritySettings); + } } - var checkAuthMessage = incomingMessage as CheckAuthenticationRequest; - if (checkAuthMessage != null) { - return new AutoResponsiveRequest(incomingMessage, new CheckAuthenticationResponse(checkAuthMessage, this)); + if (result == null) { + var associateMessage = incomingMessage as AssociateRequest; + if (associateMessage != null) { + result = new AutoResponsiveRequest(incomingMessage, associateMessage.CreateResponse(this.AssociationStore, this.SecuritySettings), this.SecuritySettings); + } } - var associateMessage = incomingMessage as AssociateRequest; - if (associateMessage != null) { - return new AutoResponsiveRequest(incomingMessage, associateMessage.CreateResponse(this.AssociationStore, this.SecuritySettings)); + if (result != null) { + foreach (var behavior in this.Behaviors) { + if (behavior.OnIncomingRequest(result)) { + // This behavior matched this request. + break; + } + } + + return result; } throw ErrorUtilities.ThrowProtocol(MessagingStrings.UnexpectedMessageReceivedOfMany); @@ -248,6 +284,7 @@ namespace DotNetOpenAuth.OpenId.Provider { Contract.Requires(((Request)request).IsResponseReady); ErrorUtilities.VerifyArgumentNotNull(request, "request"); + this.ApplyBehaviorsToResponse(request); Request requestInternal = (Request)request; this.Channel.Send(requestInternal.Response); } @@ -264,6 +301,7 @@ namespace DotNetOpenAuth.OpenId.Provider { Contract.Requires(((Request)request).IsResponseReady); ErrorUtilities.VerifyArgumentNotNull(request, "request"); + this.ApplyBehaviorsToResponse(request); Request requestInternal = (Request)request; return this.Channel.PrepareResponse(requestInternal.Response); } @@ -391,6 +429,22 @@ namespace DotNetOpenAuth.OpenId.Provider { #endregion /// <summary> + /// Applies all behaviors to the response message. + /// </summary> + /// <param name="request">The request.</param> + private void ApplyBehaviorsToResponse(IRequest request) { + var authRequest = request as IAuthenticationRequest; + if (authRequest != null) { + foreach (var behavior in this.Behaviors) { + if (behavior.OnOutgoingResponse(authRequest)) { + // This behavior matched this request. + break; + } + } + } + } + + /// <summary> /// Prepares the return value for the GetRequest method in the event of an exception. /// </summary> /// <param name="ex">The exception that forms the basis of the error response. Must not be null.</param> @@ -445,9 +499,9 @@ namespace DotNetOpenAuth.OpenId.Provider { } if (incomingMessage != null) { - return new AutoResponsiveRequest(incomingMessage, errorMessage); + return new AutoResponsiveRequest(incomingMessage, errorMessage, this.SecuritySettings); } else { - return new AutoResponsiveRequest(errorMessage); + return new AutoResponsiveRequest(errorMessage, this.SecuritySettings); } } } diff --git a/src/DotNetOpenAuth/OpenId/Provider/PrivatePersonalIdentifierProviderBase.cs b/src/DotNetOpenAuth/OpenId/Provider/PrivatePersonalIdentifierProviderBase.cs index 8372b8f..43b258c 100644 --- a/src/DotNetOpenAuth/OpenId/Provider/PrivatePersonalIdentifierProviderBase.cs +++ b/src/DotNetOpenAuth/OpenId/Provider/PrivatePersonalIdentifierProviderBase.cs @@ -123,6 +123,11 @@ namespace DotNetOpenAuth.OpenId.Provider { ErrorUtilities.VerifyArgumentNotNull(localIdentifier, "localIdentifier"); ErrorUtilities.VerifyArgumentNotNull(relyingPartyRealm, "relyingPartyRealm"); + if (localIdentifier.ToString().StartsWith(this.BaseIdentifier.AbsoluteUri, StringComparison.Ordinal)) { + Logger.OpenId.Warn("Trying to generate a PPID from a PPID. Returning original PPID."); + return new Uri(localIdentifier); + } + byte[] salt = this.GetHashSaltForLocalIdentifier(localIdentifier); string valueToHash = localIdentifier + "#"; switch (this.PairwiseUnique) { diff --git a/src/DotNetOpenAuth/OpenId/Provider/ProviderSecuritySettings.cs b/src/DotNetOpenAuth/OpenId/Provider/ProviderSecuritySettings.cs index 4568dc2..876e412 100644 --- a/src/DotNetOpenAuth/OpenId/Provider/ProviderSecuritySettings.cs +++ b/src/DotNetOpenAuth/OpenId/Provider/ProviderSecuritySettings.cs @@ -7,11 +7,15 @@ namespace DotNetOpenAuth.OpenId.Provider { using System; using System.Collections.Generic; + using System.Collections.ObjectModel; + using System.Collections.Specialized; + using System.Linq; using DotNetOpenAuth.Messaging; /// <summary> /// Security settings that are applicable to providers. /// </summary> + [Serializable] public sealed class ProviderSecuritySettings : SecuritySettings { /// <summary> /// The default value for the <see cref="ProtectDownlevelReplayAttacks"/> property. @@ -81,5 +85,24 @@ namespace DotNetOpenAuth.OpenId.Provider { /// needed for testing the RP's rejection of unsigned extensions. /// </remarks> internal bool SignOutgoingExtensions { get; set; } + + /// <summary> + /// Creates a deep clone of this instance. + /// </summary> + /// <returns>A new instance that is a deep clone of this instance.</returns> + internal ProviderSecuritySettings Clone() { + var securitySettings = new ProviderSecuritySettings(); + foreach (var pair in this.AssociationLifetimes) { + securitySettings.AssociationLifetimes.Add(pair); + } + + securitySettings.MaximumHashBitLength = this.MaximumHashBitLength; + securitySettings.MinimumHashBitLength = this.MinimumHashBitLength; + securitySettings.ProtectDownlevelReplayAttacks = this.ProtectDownlevelReplayAttacks; + securitySettings.RequireSsl = this.RequireSsl; + securitySettings.SignOutgoingExtensions = this.SignOutgoingExtensions; + + return securitySettings; + } } } diff --git a/src/DotNetOpenAuth/OpenId/Provider/Request.cs b/src/DotNetOpenAuth/OpenId/Provider/Request.cs index 1c5ad02..4c2ee98 100644 --- a/src/DotNetOpenAuth/OpenId/Provider/Request.cs +++ b/src/DotNetOpenAuth/OpenId/Provider/Request.cs @@ -52,11 +52,15 @@ namespace DotNetOpenAuth.OpenId.Provider { /// Initializes a new instance of the <see cref="Request"/> class. /// </summary> /// <param name="request">The incoming request message.</param> - protected Request(IDirectedProtocolMessage request) { + /// <param name="securitySettings">The security settings from the channel.</param> + protected Request(IDirectedProtocolMessage request, ProviderSecuritySettings securitySettings) { Contract.Requires(request != null); + Contract.Requires(securitySettings != null); ErrorUtilities.VerifyArgumentNotNull(request, "request"); + ErrorUtilities.VerifyArgumentNotNull(securitySettings, "securitySettings"); this.request = request; + this.SecuritySettings = securitySettings; this.protocolVersion = this.request.Version; this.extensibleMessage = request as IProtocolMessageWithExtensions; } @@ -65,11 +69,15 @@ namespace DotNetOpenAuth.OpenId.Provider { /// Initializes a new instance of the <see cref="Request"/> class. /// </summary> /// <param name="version">The version.</param> - protected Request(Version version) { + /// <param name="securitySettings">The security settings.</param> + protected Request(Version version, ProviderSecuritySettings securitySettings) { Contract.Requires(version != null); + Contract.Requires(securitySettings != null); ErrorUtilities.VerifyArgumentNotNull(version, "version"); + ErrorUtilities.VerifyArgumentNotNull(securitySettings, "securitySettings"); this.protocolVersion = version; + this.SecuritySettings = securitySettings; } #region IRequest Members @@ -77,7 +85,6 @@ namespace DotNetOpenAuth.OpenId.Provider { /// <summary> /// Gets a value indicating whether the response is ready to be sent to the user agent. /// </summary> - /// <value></value> /// <remarks> /// This property returns false if there are properties that must be set on this /// request instance before the response can be sent. @@ -85,6 +92,12 @@ namespace DotNetOpenAuth.OpenId.Provider { public abstract bool IsResponseReady { get; } /// <summary> + /// Gets or sets the security settings that apply to this request. + /// </summary> + /// <value>Defaults to the <see cref="OpenIdProvider.SecuritySettings"/> on the <see cref="OpenIdProvider"/>.</value> + public ProviderSecuritySettings SecuritySettings { get; set; } + + /// <summary> /// Gets the response to send to the user agent. /// </summary> /// <exception cref="InvalidOperationException">Thrown if <see cref="IsResponseReady"/> is <c>false</c>.</exception> diff --git a/src/DotNetOpenAuth/OpenId/Provider/RequestContract.cs b/src/DotNetOpenAuth/OpenId/Provider/RequestContract.cs index ab84289..b94b37d 100644 --- a/src/DotNetOpenAuth/OpenId/Provider/RequestContract.cs +++ b/src/DotNetOpenAuth/OpenId/Provider/RequestContract.cs @@ -20,7 +20,7 @@ namespace DotNetOpenAuth.OpenId.Provider { /// <summary> /// Prevents a default instance of the <see cref="RequestContract"/> class from being created. /// </summary> - private RequestContract() : base((Version)null) { + private RequestContract() : base((Version)null, null) { } /// <summary> diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/AuthenticationRequest.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/AuthenticationRequest.cs index 25c4a15..19db0fa 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/AuthenticationRequest.cs +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/AuthenticationRequest.cs @@ -89,7 +89,13 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// </summary> /// <value></value> public OutgoingWebResponse RedirectingResponse { - get { return this.RelyingParty.Channel.PrepareResponse(this.CreateRequestMessage()); } + get { + foreach (var behavior in this.RelyingParty.Behaviors) { + behavior.OnOutgoingAuthenticationRequest(this); + } + + return this.RelyingParty.Channel.PrepareResponse(this.CreateRequestMessage()); + } } /// <summary> @@ -162,6 +168,13 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { set { this.associationPreference = value; } } + /// <summary> + /// Gets the extensions that have been added to th request. + /// </summary> + internal IEnumerable<IOpenIdMessageExtension> AppliedExtensions { + get { return this.extensions; } + } + #region IAuthenticationRequest methods /// <summary> diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/IRelyingPartyBehavior.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/IRelyingPartyBehavior.cs new file mode 100644 index 0000000..e7c38db --- /dev/null +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/IRelyingPartyBehavior.cs @@ -0,0 +1,43 @@ +//----------------------------------------------------------------------- +// <copyright file="IRelyingPartyBehavior.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.RelyingParty { + /// <summary> + /// Applies a custom security policy to certain OpenID security settings and behaviors. + /// </summary> + /// <remarks> + /// BEFORE MARKING THIS INTERFACE PUBLIC: it's very important that we shift the methods to be channel-level + /// rather than facade class level and for the OpenIdChannel to be the one to invoke these methods. + /// </remarks> + internal interface IRelyingPartyBehavior { + /// <summary> + /// Applies a well known set of security requirements to a default set of security settings. + /// </summary> + /// <param name="securitySettings">The security settings to enhance with the requirements of this profile.</param> + /// <remarks> + /// Care should be taken to never decrease security when applying a profile. + /// Profiles should only enhance security requirements to avoid being + /// incompatible with each other. + /// </remarks> + void ApplySecuritySettings(RelyingPartySecuritySettings securitySettings); + + /// <summary> + /// Called when an authentication request is about to be sent. + /// </summary> + /// <param name="request">The request.</param> + /// <remarks> + /// Implementations should be prepared to be called multiple times on the same outgoing message + /// without malfunctioning. + /// </remarks> + void OnOutgoingAuthenticationRequest(IAuthenticationRequest request); + + /// <summary> + /// Called when an incoming positive assertion is received. + /// </summary> + /// <param name="assertion">The positive assertion.</param> + void OnIncomingPositiveAssertion(IAuthenticationResponse assertion); + } +} diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingParty.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingParty.cs index 7533000..532f033 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingParty.cs +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingParty.cs @@ -7,6 +7,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { using System; using System.Collections.Generic; + using System.Collections.ObjectModel; using System.Collections.Specialized; using System.ComponentModel; using System.Diagnostics.CodeAnalysis; @@ -43,6 +44,11 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { private const string ApplicationStoreKey = "DotNetOpenAuth.OpenId.RelyingParty.OpenIdRelyingParty.ApplicationStore"; /// <summary> + /// Backing store for the <see cref="Behaviors"/> property. + /// </summary> + private readonly ObservableCollection<IRelyingPartyBehavior> behaviors = new ObservableCollection<IRelyingPartyBehavior>(); + + /// <summary> /// Backing field for the <see cref="SecuritySettings"/> property. /// </summary> private RelyingPartySecuritySettings securitySettings; @@ -85,6 +91,10 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { ErrorUtilities.VerifyArgument(associationStore == null || nonceStore != null, OpenIdStrings.AssociationStoreRequiresNonceStore); this.securitySettings = DotNetOpenAuthSection.Configuration.OpenId.RelyingParty.SecuritySettings.CreateSecuritySettings(); + this.behaviors.CollectionChanged += this.OnBehaviorsChanged; + foreach (var behavior in DotNetOpenAuthSection.Configuration.OpenId.RelyingParty.Behaviors.CreateInstances(false)) { + this.behaviors.Add(behavior); + } // Without a nonce store, we must rely on the Provider to protect against // replay attacks. But only 2.0+ Providers can be expected to provide @@ -210,6 +220,13 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { } /// <summary> + /// Gets a list of custom behaviors to apply to OpenID actions. + /// </summary> + internal ICollection<IRelyingPartyBehavior> Behaviors { + get { return this.behaviors; } + } + + /// <summary> /// Gets a value indicating whether this Relying Party can sign its return_to /// parameter in outgoing authentication requests. /// </summary> @@ -474,7 +491,12 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { NegativeAssertionResponse negativeAssertion; IndirectSignedResponse positiveExtensionOnly; if ((positiveAssertion = message as PositiveAssertionResponse) != null) { - return new PositiveAuthenticationResponse(positiveAssertion, this); + var response = new PositiveAuthenticationResponse(positiveAssertion, this); + foreach (var behavior in this.Behaviors) { + behavior.OnIncomingPositiveAssertion(response); + } + + return response; } else if ((positiveExtensionOnly = message as IndirectSignedResponse) != null) { return new PositiveAnonymousResponse(positiveExtensionOnly); } else if ((negativeAssertion = message as NegativeAssertionResponse) != null) { @@ -560,5 +582,16 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { } } } + + /// <summary> + /// Called by derived classes when behaviors are added or removed. + /// </summary> + /// <param name="sender">The collection being modified.</param> + /// <param name="e">The <see cref="System.Collections.Specialized.NotifyCollectionChangedEventArgs"/> instance containing the event data.</param> + private void OnBehaviorsChanged(object sender, NotifyCollectionChangedEventArgs e) { + foreach (IRelyingPartyBehavior profile in e.NewItems) { + profile.ApplySecuritySettings(this.SecuritySettings); + } + } } } diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/RelyingPartySecuritySettings.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/RelyingPartySecuritySettings.cs index 7d910d2..ff29498 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/RelyingPartySecuritySettings.cs +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/RelyingPartySecuritySettings.cs @@ -7,6 +7,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { using System; using System.Collections.Generic; + using System.Collections.ObjectModel; using System.Linq; using DotNetOpenAuth.Messaging; diff --git a/src/DotNetOpenAuth/OpenId/SecuritySettings.cs b/src/DotNetOpenAuth/OpenId/SecuritySettings.cs index a44b5fe..d4df697 100644 --- a/src/DotNetOpenAuth/OpenId/SecuritySettings.cs +++ b/src/DotNetOpenAuth/OpenId/SecuritySettings.cs @@ -5,12 +5,16 @@ //----------------------------------------------------------------------- namespace DotNetOpenAuth.OpenId { + using System; + using System.Collections.Generic; + using System.Collections.Specialized; using DotNetOpenAuth.Messaging; /// <summary> /// Security settings that may be applicable to both relying parties and providers. /// </summary> - public class SecuritySettings { + [Serializable] + public abstract class SecuritySettings { /// <summary> /// Gets the default minimum hash bit length. /// </summary> |