diff options
Diffstat (limited to 'src')
12 files changed, 699 insertions, 229 deletions
diff --git a/src/DotNetOpenAuth.Test/DotNetOpenAuth.Test.csproj b/src/DotNetOpenAuth.Test/DotNetOpenAuth.Test.csproj index fa768a8..8a834dd 100644 --- a/src/DotNetOpenAuth.Test/DotNetOpenAuth.Test.csproj +++ b/src/DotNetOpenAuth.Test/DotNetOpenAuth.Test.csproj @@ -98,6 +98,10 @@ <HintPath>..\..\lib\Microsoft.Contracts.dll</HintPath> </Reference> <Reference Include="Microsoft.VisualStudio.QualityTools.UnitTestFramework, Version=9.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL" /> + <Reference Include="Moq, Version=3.1.416.3, Culture=neutral, PublicKeyToken=69f491c39445e920, processorArchitecture=MSIL"> + <SpecificVersion>False</SpecificVersion> + <HintPath>..\..\lib\Moq.dll</HintPath> + </Reference> <Reference Include="System" /> <Reference Include="System.configuration" /> <Reference Include="System.Core"> @@ -196,7 +200,7 @@ <Compile Include="OpenId\Extensions\SimpleRegistration\ClaimsRequestTests.cs" /> <Compile Include="OpenId\Extensions\UI\UIRequestTests.cs" /> <Compile Include="OpenId\IdentifierTests.cs" /> - <Compile Include="OpenId\InteropHelperTests.cs" /> + <Compile Include="OpenId\Extensions\ExtensionsInteropHelperTests.cs" /> <Compile Include="OpenId\Messages\AssociateDiffieHellmanRequestTests.cs" /> <Compile Include="OpenId\Messages\AssociateRequestTests.cs" /> <Compile Include="OpenId\Messages\AssociateUnsuccessfulResponseTests.cs" /> diff --git a/src/DotNetOpenAuth.Test/OpenId/Extensions/ExtensionsInteropHelperTests.cs b/src/DotNetOpenAuth.Test/OpenId/Extensions/ExtensionsInteropHelperTests.cs new file mode 100644 index 0000000..ee6aded --- /dev/null +++ b/src/DotNetOpenAuth.Test/OpenId/Extensions/ExtensionsInteropHelperTests.cs @@ -0,0 +1,132 @@ +//----------------------------------------------------------------------- +// <copyright file="InteropHelperTests.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Test.OpenId { + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using DotNetOpenAuth.OpenId; + using DotNetOpenAuth.OpenId.Extensions.AttributeExchange; + using DotNetOpenAuth.OpenId.Extensions.SimpleRegistration; + using DotNetOpenAuth.OpenId.RelyingParty; + using Microsoft.VisualStudio.TestTools.UnitTesting; + using Moq; + using DotNetOpenAuth.OpenId.Extensions; + + [TestClass] + public class ExtensionsInteropHelperTests : OpenIdTestBase { + private AuthenticationRequest authReq; + private ClaimsRequest sreg; + + [TestInitialize] + public override void SetUp() { + base.SetUp(); + + var rp = CreateRelyingParty(true); + Identifier identifier = this.GetMockIdentifier(ProtocolVersion.V20); + this.authReq = (AuthenticationRequest)rp.CreateRequest(identifier, RPRealmUri, RPUri); + this.sreg = new ClaimsRequest { + Nickname = DemandLevel.Request, + FullName = DemandLevel.Request, + BirthDate = DemandLevel.Request, + Email = DemandLevel.Require, + Country = DemandLevel.Request, + PostalCode = DemandLevel.Request, + Gender = DemandLevel.Request, + Language = DemandLevel.Request, + TimeZone = DemandLevel.Request, + }; + } + + /// <summary> + /// Verifies that without an Sreg extension to copy from, no AX extension request is added. + /// </summary> + [TestMethod] + public void SpreadSregToAXNoExtensions() { + ExtensionsInteropHelper.SpreadSregToAX(this.authReq, AXAttributeFormats.AXSchemaOrg); + Assert.AreEqual(0, this.authReq.AppliedExtensions.Count()); + } + + /// <summary> + /// Verifies that Sreg requests are correctly copied to axschema.org AX requests. + /// </summary> + [TestMethod] + public void SpreadSregToAXBasic() { + this.authReq.AddExtension(this.sreg); + ExtensionsInteropHelper.SpreadSregToAX(this.authReq, AXAttributeFormats.AXSchemaOrg); + var ax = this.authReq.AppliedExtensions.OfType<FetchRequest>().Single(); + Assert.IsFalse(ax.Attributes[WellKnownAttributes.Name.Alias].IsRequired); + Assert.IsFalse(ax.Attributes[WellKnownAttributes.Name.FullName].IsRequired); + Assert.IsFalse(ax.Attributes[WellKnownAttributes.BirthDate.WholeBirthDate].IsRequired); + Assert.IsTrue(ax.Attributes[WellKnownAttributes.Contact.Email].IsRequired); + Assert.IsFalse(ax.Attributes[WellKnownAttributes.Contact.HomeAddress.Country].IsRequired); + Assert.IsFalse(ax.Attributes[WellKnownAttributes.Contact.HomeAddress.PostalCode].IsRequired); + Assert.IsFalse(ax.Attributes[WellKnownAttributes.Person.Gender].IsRequired); + Assert.IsFalse(ax.Attributes[WellKnownAttributes.Preferences.Language].IsRequired); + Assert.IsFalse(ax.Attributes[WellKnownAttributes.Preferences.TimeZone].IsRequired); + } + + /// <summary> + /// Verifies that sreg can spread to multiple AX schemas. + /// </summary> + [TestMethod] + public void SpreadSregToAxMultipleSchemas() { + this.authReq.AddExtension(this.sreg); + ExtensionsInteropHelper.SpreadSregToAX(this.authReq, AXAttributeFormats.AXSchemaOrg | AXAttributeFormats.SchemaOpenIdNet); + var ax = this.authReq.AppliedExtensions.OfType<FetchRequest>().Single(); + Assert.IsTrue(ax.Attributes.Contains(WellKnownAttributes.Name.Alias)); + Assert.IsTrue(ax.Attributes.Contains(ExtensionsInteropHelper_Accessor.TransformAXFormat(WellKnownAttributes.Name.Alias, AXAttributeFormats.SchemaOpenIdNet))); + Assert.IsFalse(ax.Attributes.Contains(ExtensionsInteropHelper_Accessor.TransformAXFormat(WellKnownAttributes.Name.Alias, AXAttributeFormats.OpenIdNetSchema))); + } + + /// <summary> + /// Verifies no spread if the OP advertises sreg support. + /// </summary> + [TestMethod] + public void SpreadSregToAxNoOpIfOPSupportsSreg() { + this.authReq.AddExtension(this.sreg); + this.InjectAdvertisedTypeUri(DotNetOpenAuth.OpenId.Extensions.SimpleRegistration.Constants.sreg_ns); + ExtensionsInteropHelper.SpreadSregToAX(this.authReq, AXAttributeFormats.All); + Assert.IsFalse(this.authReq.AppliedExtensions.OfType<FetchRequest>().Any()); + } + + /// <summary> + /// Verifies a targeted AX request if the OP advertises a recognized type URI format. + /// </summary> + [TestMethod] + public void SpreadSregToAxTargetedAtOPFormat() { + this.authReq.AddExtension(this.sreg); + this.InjectAdvertisedTypeUri(WellKnownAttributes.Name.FullName); + ExtensionsInteropHelper.SpreadSregToAX(this.authReq, AXAttributeFormats.OpenIdNetSchema); + var ax = this.authReq.AppliedExtensions.OfType<FetchRequest>().Single(); + Assert.IsFalse(ax.Attributes.Contains(ExtensionsInteropHelper_Accessor.TransformAXFormat(WellKnownAttributes.Contact.Email, AXAttributeFormats.OpenIdNetSchema))); + Assert.IsTrue(ax.Attributes.Contains(WellKnownAttributes.Contact.Email)); + } + + /// <summary> + /// Verifies that TransformAXFormat correctly translates AX schema Type URIs. + /// </summary> + [TestMethod] + public void TransformAXFormatTest() { + Assert.AreEqual(WellKnownAttributes.Name.Alias, ExtensionsInteropHelper_Accessor.TransformAXFormat(WellKnownAttributes.Name.Alias, AXAttributeFormats.AXSchemaOrg)); + Assert.AreEqual("http://schema.openid.net/namePerson/friendly", ExtensionsInteropHelper_Accessor.TransformAXFormat(WellKnownAttributes.Name.Alias, AXAttributeFormats.SchemaOpenIdNet)); + Assert.AreEqual("http://openid.net/schema/namePerson/friendly", ExtensionsInteropHelper_Accessor.TransformAXFormat(WellKnownAttributes.Name.Alias, AXAttributeFormats.OpenIdNetSchema)); + } + + /// <summary> + /// Injects the advertised type URI into the list of advertised services for the authentication request. + /// </summary> + /// <param name="typeUri">The type URI.</param> + private void InjectAdvertisedTypeUri(string typeUri) { + var serviceEndpoint = ServiceEndpoint_Accessor.AttachShadow(((ServiceEndpoint)this.authReq.Provider)); + serviceEndpoint.ProviderDescription = ProviderEndpointDescription_Accessor.AttachShadow( + new ProviderEndpointDescription( + serviceEndpoint.ProviderDescription.Endpoint, + serviceEndpoint.ProviderDescription.Capabilities.Concat(new[] { typeUri }))); + } + } +} diff --git a/src/DotNetOpenAuth.Test/OpenId/InteropHelperTests.cs b/src/DotNetOpenAuth.Test/OpenId/InteropHelperTests.cs deleted file mode 100644 index cf34c72..0000000 --- a/src/DotNetOpenAuth.Test/OpenId/InteropHelperTests.cs +++ /dev/null @@ -1,24 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="InteropHelperTests.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.Test.OpenId { - using System; - using System.Collections.Generic; - using System.Linq; - using System.Text; - using Microsoft.VisualStudio.TestTools.UnitTesting; - - [TestClass] - public class InteropHelperTests { - /// <summary> - /// Verifies that Sreg requests are correctly copied to axschema.org AX requests. - /// </summary> - [TestMethod] - public void SpreadSregToAX() { - Assert.Inconclusive("Not yet implemented."); - } - } -} diff --git a/src/DotNetOpenAuth.Test/OpenId/OpenIdTestBase.cs b/src/DotNetOpenAuth.Test/OpenId/OpenIdTestBase.cs index 8fa5580..59c818c 100644 --- a/src/DotNetOpenAuth.Test/OpenId/OpenIdTestBase.cs +++ b/src/DotNetOpenAuth.Test/OpenId/OpenIdTestBase.cs @@ -187,7 +187,16 @@ namespace DotNetOpenAuth.Test.OpenId { /// </summary> /// <returns>The new instance.</returns> protected OpenIdRelyingParty CreateRelyingParty() { - var rp = new OpenIdRelyingParty(new StandardRelyingPartyApplicationStore()); + return this.CreateRelyingParty(false); + } + + /// <summary> + /// Creates a standard <see cref="OpenIdRelyingParty"/> instance for general testing. + /// </summary> + /// <param name="stateless">if set to <c>true</c> a stateless RP is created.</param> + /// <returns>The new instance.</returns> + protected OpenIdRelyingParty CreateRelyingParty(bool stateless) { + var rp = new OpenIdRelyingParty(stateless ? null : new StandardRelyingPartyApplicationStore()); rp.Channel.WebRequestHandler = this.MockResponder.MockWebRequestHandler; return rp; } diff --git a/src/DotNetOpenAuth/DotNetOpenAuth.csproj b/src/DotNetOpenAuth/DotNetOpenAuth.csproj index e29aa6d..d3eb5e2 100644 --- a/src/DotNetOpenAuth/DotNetOpenAuth.csproj +++ b/src/DotNetOpenAuth/DotNetOpenAuth.csproj @@ -330,6 +330,7 @@ <Compile Include="OpenId\Association.cs" /> <Compile Include="OpenId\AssociationMemoryStore.cs" /> <Compile Include="OpenId\Associations.cs" /> + <Compile Include="OpenId\Behaviors\AXFetchAsSregTransform.cs" /> <Compile Include="OpenId\Behaviors\BehaviorStrings.Designer.cs"> <AutoGen>True</AutoGen> <DesignTime>True</DesignTime> @@ -352,6 +353,7 @@ <Compile Include="OpenId\Extensions\AliasManager.cs" /> <Compile Include="OpenId\Extensions\AttributeExchange\AttributeRequest.cs" /> <Compile Include="OpenId\Extensions\AttributeExchange\AttributeValues.cs" /> + <Compile Include="OpenId\Extensions\AttributeExchange\AXAttributeFormats.cs" /> <Compile Include="OpenId\Extensions\AttributeExchange\AXUtilities.cs" /> <Compile Include="OpenId\Extensions\AttributeExchange\Constants.cs" /> <Compile Include="OpenId\Extensions\AttributeExchange\FetchRequest.cs" /> @@ -386,7 +388,7 @@ <Compile Include="OpenId\Extensions\UI\UIRequest.cs" /> <Compile Include="OpenId\Identifier.cs" /> <Compile Include="OpenId\IdentifierContract.cs" /> - <Compile Include="OpenId\InteropHelper.cs" /> + <Compile Include="OpenId\Extensions\ExtensionsInteropHelper.cs" /> <Compile Include="OpenId\Interop\AuthenticationResponseShim.cs" /> <Compile Include="OpenId\Interop\ClaimsResponseShim.cs" /> <Compile Include="OpenId\Interop\OpenIdRelyingPartyShim.cs" /> diff --git a/src/DotNetOpenAuth/OpenId/Behaviors/AXFetchAsSregTransform.cs b/src/DotNetOpenAuth/OpenId/Behaviors/AXFetchAsSregTransform.cs new file mode 100644 index 0000000..f895a27 --- /dev/null +++ b/src/DotNetOpenAuth/OpenId/Behaviors/AXFetchAsSregTransform.cs @@ -0,0 +1,126 @@ +//----------------------------------------------------------------------- +// <copyright file="AXFetchAsSregTransform.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.Behaviors { + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OpenId.Extensions; + using DotNetOpenAuth.OpenId.Extensions.AttributeExchange; + using DotNetOpenAuth.OpenId.Extensions.SimpleRegistration; + using DotNetOpenAuth.OpenId.Provider; + using DotNetOpenAuth.OpenId.RelyingParty; + + /// <summary> + /// An Attribute Exchange and Simple Registration filter to make all incoming attribute + /// requests look like Simple Registration requests, and to convert the response + /// to the originally requested extension and format. + /// </summary> + public class AXFetchAsSregTransform : IRelyingPartyBehavior, IProviderBehavior { + /// <summary> + /// Initializes static members of the <see cref="AXFetchAsSregTransform"/> class. + /// </summary> + static AXFetchAsSregTransform() { + AXFormats = AXAttributeFormats.Common; + } + + /// <summary> + /// Initializes a new instance of the <see cref="AXFetchAsSregTransform"/> class. + /// </summary> + public AXFetchAsSregTransform() { + } + + /// <summary> + /// Gets or sets the AX attribute type URI formats this transform is willing to work with. + /// </summary> + public static AXAttributeFormats AXFormats { get; set; } + + #region IRelyingPartyBehavior Members + + /// <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 IRelyingPartyBehavior.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 IRelyingPartyBehavior.OnOutgoingAuthenticationRequest(RelyingParty.IAuthenticationRequest request) { + request.SpreadSregToAX(AXFormats); + } + + /// <summary> + /// Called when an incoming positive assertion is received. + /// </summary> + /// <param name="assertion">The positive assertion.</param> + void IRelyingPartyBehavior.OnIncomingPositiveAssertion(IAuthenticationResponse assertion) { + if (assertion.GetExtension<ClaimsResponse>() == null) { + ClaimsResponse sreg = assertion.UnifyExtensionsAsSreg(true); + ((PositiveAnonymousResponse)assertion).Response.Extensions.Add(sreg); + } + } + + #endregion + + #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) { + var extensionRequest = request as Provider.HostProcessedRequest; + if (extensionRequest != null) { + if (extensionRequest.GetExtension<ClaimsRequest>() == null) { + ClaimsRequest sreg = extensionRequest.UnifyExtensionsAsSreg(); + if (sreg != null) { + ((IProtocolMessageWithExtensions)extensionRequest.RequestMessage).Extensions.Add(sreg); + } + } + } + + 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(Provider.IAuthenticationRequest request) { + request.ConvertSregToMatchRequest(); + return false; + } + + #endregion + } +} diff --git a/src/DotNetOpenAuth/OpenId/Extensions/AttributeExchange/AXAttributeFormats.cs b/src/DotNetOpenAuth/OpenId/Extensions/AttributeExchange/AXAttributeFormats.cs new file mode 100644 index 0000000..7e4ed73 --- /dev/null +++ b/src/DotNetOpenAuth/OpenId/Extensions/AttributeExchange/AXAttributeFormats.cs @@ -0,0 +1,45 @@ +//----------------------------------------------------------------------- +// <copyright file="AXAttributeFormats.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.Extensions.AttributeExchange { + using System; + + /// <summary> + /// The various Type URI formats an AX attribute may use by various remote parties. + /// </summary> + [Flags] + public enum AXAttributeFormats { + /// <summary> + /// No attribute format. + /// </summary> + None = 0x0, + + /// <summary> + /// AX attributes should use the Type URI format starting with <c>http://axschema.org/</c>. + /// </summary> + AXSchemaOrg = 0x1, + + /// <summary> + /// AX attributes should use the Type URI format starting with <c>http://schema.openid.net/</c>. + /// </summary> + SchemaOpenIdNet = 0x2, + + /// <summary> + /// AX attributes should use the Type URI format starting with <c>http://openid.net/schema/</c>. + /// </summary> + OpenIdNetSchema = 0x4, + + /// <summary> + /// All known schemas. + /// </summary> + All = 0xff, + + /// <summary> + /// The most common schemas. + /// </summary> + Common = AXSchemaOrg | SchemaOpenIdNet, + } +} diff --git a/src/DotNetOpenAuth/OpenId/Extensions/ExtensionsInteropHelper.cs b/src/DotNetOpenAuth/OpenId/Extensions/ExtensionsInteropHelper.cs new file mode 100644 index 0000000..eee75e2 --- /dev/null +++ b/src/DotNetOpenAuth/OpenId/Extensions/ExtensionsInteropHelper.cs @@ -0,0 +1,364 @@ +//----------------------------------------------------------------------- +// <copyright file="InteropHelper.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.Extensions { + using System; + using System.Collections.Generic; + using System.Diagnostics.Contracts; + using System.Linq; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OpenId.Extensions.AttributeExchange; + using DotNetOpenAuth.OpenId.Extensions.SimpleRegistration; + using DotNetOpenAuth.OpenId.Messages; + + /// <summary> + /// A set of methods designed to assist in improving interop across different + /// OpenID implementations and their extensions. + /// </summary> + public static class ExtensionsInteropHelper { + /// <summary> + /// The gender decoder to translate AX genders to Sreg. + /// </summary> + private static GenderEncoder genderEncoder = new GenderEncoder(); + + /// <summary> + /// Adds an Attribute Exchange (AX) extension to the authentication request + /// that asks for the same attributes as the Simple Registration (sreg) extension + /// that is already applied. + /// </summary> + /// <param name="request">The authentication request.</param> + /// <param name="attributeFormats">The attribute formats to use in the AX request.</param> + /// <remarks> + /// <para>If discovery on the user-supplied identifier yields hints regarding which + /// extensions and attribute formats the Provider supports, this method MAY ignore the + /// <paramref name="attributeFormat"/> argument and accomodate the Provider to minimize + /// the size of the request.</para> + /// <para>If the request does not carry an sreg extension, the method logs a warning but + /// otherwise quietly returns doing nothing.</para> + /// </remarks> + public static void SpreadSregToAX(this RelyingParty.IAuthenticationRequest request, AXAttributeFormats attributeFormats) { + Contract.Requires(request != null); + ErrorUtilities.VerifyArgumentNotNull(request, "request"); + + var req = (RelyingParty.AuthenticationRequest)request; + var sreg = req.AppliedExtensions.OfType<ClaimsRequest>().SingleOrDefault(); + if (sreg == null) { + Logger.OpenId.Warn("No Simple Registration (ClaimsRequest) extension present in the request to spread to AX."); + return; + } + + if (req.Provider.IsExtensionSupported<ClaimsRequest>()) { + Logger.OpenId.Info("Skipping generation of AX request because the Identifier advertises the Provider supports the Sreg extension."); + return; + } + + var ax = req.AppliedExtensions.OfType<FetchRequest>().SingleOrDefault(); + if (ax == null) { + ax = new FetchRequest(); + req.AddExtension(ax); + } + + // Try to use just one AX Type URI format if we can figure out which type the OP accepts. + attributeFormats = FocusAttributeFormat(request, attributeFormats); + + foreach (AXAttributeFormats format in ForEachFormat(attributeFormats)) { + FetchAttribute(ax, format, WellKnownAttributes.BirthDate.WholeBirthDate, sreg.BirthDate); + FetchAttribute(ax, format, WellKnownAttributes.Contact.HomeAddress.Country, sreg.Country); + FetchAttribute(ax, format, WellKnownAttributes.Contact.Email, sreg.Email); + FetchAttribute(ax, format, WellKnownAttributes.Name.FullName, sreg.FullName); + FetchAttribute(ax, format, WellKnownAttributes.Person.Gender, sreg.Gender); + FetchAttribute(ax, format, WellKnownAttributes.Preferences.Language, sreg.Language); + FetchAttribute(ax, format, WellKnownAttributes.Name.Alias, sreg.Nickname); + FetchAttribute(ax, format, WellKnownAttributes.Contact.HomeAddress.PostalCode, sreg.PostalCode); + FetchAttribute(ax, format, WellKnownAttributes.Preferences.TimeZone, sreg.TimeZone); + } + } + + /// <summary> + /// Looks for Simple Registration and Attribute Exchange (all known formats) + /// response extensions and returns them as a Simple Registration extension. + /// </summary> + /// <param name="response">The authentication response.</param> + /// <param name="allowUnsigned">if set to <c>true</c> unsigned extensions will be included in the search.</param> + /// <returns> + /// The Simple Registration response if found, + /// or a fabricated one based on the Attribute Exchange extension if found, + /// or just an empty <see cref="ClaimsResponse"/> if there was no data. + /// Never <c>null</c>.</returns> + public static ClaimsResponse UnifyExtensionsAsSreg(this RelyingParty.IAuthenticationResponse response, bool allowUnsigned) { + Contract.Requires(response != null); + ErrorUtilities.VerifyArgumentNotNull(response, "response"); + + var resp = (RelyingParty.IAuthenticationResponse)response; + var sreg = allowUnsigned ? resp.GetUntrustedExtension<ClaimsResponse>() : resp.GetExtension<ClaimsResponse>(); + if (sreg != null) { + return sreg; + } + + sreg = new ClaimsResponse(); + var fetchResponse = allowUnsigned ? resp.GetUntrustedExtension<FetchResponse>() : resp.GetExtension<FetchResponse>(); + if (fetchResponse != null) { + ((IOpenIdMessageExtension)sreg).IsSignedByRemoteParty = fetchResponse.IsSignedByProvider; + sreg.BirthDateRaw = fetchResponse.GetAttributeValue(WellKnownAttributes.BirthDate.WholeBirthDate, AXAttributeFormats.All); + sreg.Country = fetchResponse.GetAttributeValue(WellKnownAttributes.Contact.HomeAddress.Country); + sreg.PostalCode = fetchResponse.GetAttributeValue(WellKnownAttributes.Contact.HomeAddress.PostalCode); + sreg.Email = fetchResponse.GetAttributeValue(WellKnownAttributes.Contact.Email); + sreg.FullName = fetchResponse.GetAttributeValue(WellKnownAttributes.Name.FullName); + sreg.Language = fetchResponse.GetAttributeValue(WellKnownAttributes.Preferences.Language); + sreg.Nickname = fetchResponse.GetAttributeValue(WellKnownAttributes.Name.Alias); + sreg.TimeZone = fetchResponse.GetAttributeValue(WellKnownAttributes.Preferences.TimeZone); + string gender = fetchResponse.GetAttributeValue(WellKnownAttributes.Person.Gender); + if (gender != null) { + sreg.Gender = (Gender)genderEncoder.Decode(gender); + } + } + + return sreg; + } + + /// <summary> + /// Looks for Simple Registration and Attribute Exchange (all known formats) + /// request extensions and returns them as a Simple Registration extension. + /// </summary> + /// <param name="request">The authentication request.</param> + /// <returns> + /// The Simple Registration request if found, + /// or a fabricated one based on the Attribute Exchange extension if found, + /// or <c>null</c> if no attribute extension request is found.</returns> + internal static ClaimsRequest UnifyExtensionsAsSreg(this Provider.IHostProcessedRequest request) { + Contract.Requires(request != null); + ErrorUtilities.VerifyArgumentNotNull(request, "request"); + + var req = (Provider.AuthenticationRequest)request; + var sreg = req.GetExtension<ClaimsRequest>(); + if (sreg != null) { + return sreg; + } + + var ax = req.GetExtension<FetchRequest>(); + if (ax != null) { + sreg = new ClaimsRequest(); + sreg.Synthesized = true; + sreg.BirthDate = GetDemandLevelFor(ax, WellKnownAttributes.BirthDate.WholeBirthDate); + sreg.Country = GetDemandLevelFor(ax, WellKnownAttributes.Contact.HomeAddress.Country); + sreg.Email = GetDemandLevelFor(ax, WellKnownAttributes.Contact.Email); + sreg.FullName = GetDemandLevelFor(ax, WellKnownAttributes.Name.FullName); + sreg.Gender = GetDemandLevelFor(ax, WellKnownAttributes.Person.Gender); + sreg.Language = GetDemandLevelFor(ax, WellKnownAttributes.Preferences.Language); + sreg.Nickname = GetDemandLevelFor(ax, WellKnownAttributes.Name.Alias); + sreg.PostalCode = GetDemandLevelFor(ax, WellKnownAttributes.Contact.HomeAddress.PostalCode); + sreg.TimeZone = GetDemandLevelFor(ax, WellKnownAttributes.Preferences.TimeZone); + } + + return sreg; + } + + /// <summary> + /// Converts the Simple Registration extension response to whatever format the original + /// attribute request extension came in. + /// </summary> + /// <param name="request">The authentication request with the response extensions already added.</param> + /// <remarks> + /// If the original attribute request came in as AX, the Simple Registration extension is converted + /// to an AX response and then the Simple Registration extension is removed from the response. + /// </remarks> + internal static void ConvertSregToMatchRequest(this Provider.IHostProcessedRequest request) { + var req = (Provider.HostProcessedRequest)request; + var response = (IProtocolMessageWithExtensions)req.Response; + var sregRequest = request.GetExtension<ClaimsRequest>(); + if (sregRequest != null) { + if (sregRequest.Synthesized) { + var axRequest = request.GetExtension<FetchRequest>(); + ErrorUtilities.VerifyInternal(axRequest != null, "How do we have a synthesized Sreg request without an AX request?"); + + var sregResponse = response.Extensions.OfType<ClaimsResponse>().SingleOrDefault(); + if (sregResponse == null) { + // No Sreg response to copy from. + return; + } + + // Remove the sreg response since the RP didn't ask for it. + response.Extensions.Remove(sregResponse); + + AXAttributeFormats format = DetectAXFormat(axRequest.Attributes.Select(att => att.TypeUri)); + if (format == AXAttributeFormats.None) { + // No recognized AX attributes were requested. + return; + } + + var axResponse = response.Extensions.OfType<FetchResponse>().SingleOrDefault(); + if (axResponse == null) { + axResponse = new FetchResponse(); + response.Extensions.Add(axResponse); + } + + AddAXAttributeValue(axResponse, WellKnownAttributes.BirthDate.WholeBirthDate, format, sregResponse.BirthDateRaw); + AddAXAttributeValue(axResponse, WellKnownAttributes.Contact.HomeAddress.Country, format, sregResponse.Country); + AddAXAttributeValue(axResponse, WellKnownAttributes.Contact.HomeAddress.PostalCode, format, sregResponse.PostalCode); + AddAXAttributeValue(axResponse, WellKnownAttributes.Contact.Email, format, sregResponse.Email); + AddAXAttributeValue(axResponse, WellKnownAttributes.Name.FullName, format, sregResponse.FullName); + AddAXAttributeValue(axResponse, WellKnownAttributes.Name.Alias, format, sregResponse.Nickname); + AddAXAttributeValue(axResponse, WellKnownAttributes.Preferences.TimeZone, format, sregResponse.TimeZone); + AddAXAttributeValue(axResponse, WellKnownAttributes.Preferences.Language, format, sregResponse.Language); + if (sregResponse.Gender.HasValue) { + AddAXAttributeValue(axResponse, WellKnownAttributes.Person.Gender, format, genderEncoder.Encode(sregResponse.Gender)); + } + } + } + } + + /// <summary> + /// Gets the attribute value if available. + /// </summary> + /// <param name="fetchResponse">The AX fetch response extension to look for the attribute value.</param> + /// <param name="typeUri">The type URI of the attribute, using the axschema.org format of <see cref="WellKnownAttributes"/>.</param> + /// <param name="formats">The AX type URI formats to search.</param> + /// <returns> + /// The first value of the attribute, if available. + /// </returns> + internal static string GetAttributeValue(this FetchResponse fetchResponse, string typeUri, AXAttributeFormats formats) { + return ForEachFormat(formats).Select(format => fetchResponse.GetAttributeValue(TransformAXFormat(typeUri, format))).FirstOrDefault(s => s != null); + } + + /// <summary> + /// Adds the AX attribute value to the response if it is non-empty. + /// </summary> + /// <param name="ax">The AX Fetch response to add the attribute value to.</param> + /// <param name="typeUri">The attribute type URI in axschema.org format.</param> + /// <param name="format">The target format of the actual attribute to write out.</param> + /// <param name="value">The value of the attribute.</param> + private static void AddAXAttributeValue(FetchResponse ax, string typeUri, AXAttributeFormats format, string value) { + if (!string.IsNullOrEmpty(value)) { + string targetTypeUri = TransformAXFormat(typeUri, format); + if (ax.Attributes.Contains(targetTypeUri)) { + ax.Attributes.Add(targetTypeUri, value); + } + } + } + + /// <summary> + /// Gets the demand level for an AX attribute. + /// </summary> + /// <param name="ax">The AX fetch request to search for the attribute.</param> + /// <param name="typeUri">The type URI of the attribute in axschema.org format.</param> + /// <returns>The demand level for the attribute.</returns> + private static DemandLevel GetDemandLevelFor(FetchRequest ax, string typeUri) { + Contract.Requires(ax != null); + Contract.Requires(!String.IsNullOrEmpty(typeUri)); + + foreach (AXAttributeFormats format in ForEachFormat(AXAttributeFormats.All)) { + string typeUriInFormat = TransformAXFormat(typeUri, format); + if (ax.Attributes.Contains(typeUriInFormat)) { + return ax.Attributes[typeUriInFormat].IsRequired ? DemandLevel.Require : DemandLevel.Request; + } + } + + return DemandLevel.NoRequest; + } + + /// <summary> + /// Tries to find the exact format of AX attribute Type URI supported by the Provider. + /// </summary> + /// <param name="request">The authentication request.</param> + /// <param name="attributeFormat">The attribute formats the RP will try if this discovery fails.</param> + /// <returns>The AX format(s) to use based on the Provider's advertised AX support.</returns> + private static AXAttributeFormats FocusAttributeFormat(RelyingParty.IAuthenticationRequest request, AXAttributeFormats attributeFormat) { + Contract.Requires(request != null); + var provider = (RelyingParty.ServiceEndpoint)request.Provider; + AXAttributeFormats detected = DetectAXFormat(provider.ProviderDescription.Capabilities); + return detected != AXAttributeFormats.None ? detected : attributeFormat; + } + + /// <summary> + /// Detects the AX attribute type URI format from a given sample. + /// </summary> + /// <param name="typeURIs">The type URIs to scan for recognized formats.</param> + /// <returns>The first AX type URI format recognized in the list.</returns> + private static AXAttributeFormats DetectAXFormat(IEnumerable<string> typeURIs) { + Contract.Requires(typeURIs != null); + + if (typeURIs.Any(uri => uri.StartsWith("http://axschema.org/", StringComparison.Ordinal))) { + return AXAttributeFormats.AXSchemaOrg; + } + + if (typeURIs.Any(uri => uri.StartsWith("http://schema.openid.net/", StringComparison.Ordinal))) { + return AXAttributeFormats.SchemaOpenIdNet; + } + + if (typeURIs.Any(uri => uri.StartsWith("http://openid.net/schema/", StringComparison.Ordinal))) { + return AXAttributeFormats.OpenIdNetSchema; + } + + return AXAttributeFormats.None; + } + + /// <summary> + /// Transforms an AX attribute type URI from the axschema.org format into a given format. + /// </summary> + /// <param name="axSchemaOrgFormatTypeUri">The ax schema org format type URI.</param> + /// <param name="targetFormat">The target format. Only one flag should be set.</param> + /// <returns>The AX attribute type URI in the target format.</returns> + private static string TransformAXFormat(string axSchemaOrgFormatTypeUri, AXAttributeFormats targetFormat) { + Contract.Requires(!String.IsNullOrEmpty(axSchemaOrgFormatTypeUri)); + + switch (targetFormat) { + case AXAttributeFormats.AXSchemaOrg: + return axSchemaOrgFormatTypeUri; + case AXAttributeFormats.SchemaOpenIdNet: + return axSchemaOrgFormatTypeUri.Replace("axschema.org", "schema.openid.net"); + case AXAttributeFormats.OpenIdNetSchema: + return axSchemaOrgFormatTypeUri.Replace("axschema.org", "openid.net/schema"); + default: + throw new ArgumentOutOfRangeException("targetFormat"); + } + } + + /// <summary> + /// Splits the AX attribute format flags into individual values for processing. + /// </summary> + /// <param name="formats">The formats to split up into individual flags.</param> + /// <returns>A sequence of individual flags.</returns> + private static IEnumerable<AXAttributeFormats> ForEachFormat(AXAttributeFormats formats) { + if ((formats & AXAttributeFormats.AXSchemaOrg) != 0) { + yield return AXAttributeFormats.AXSchemaOrg; + } + + if ((formats & AXAttributeFormats.OpenIdNetSchema) != 0) { + yield return AXAttributeFormats.OpenIdNetSchema; + } + + if ((formats & AXAttributeFormats.SchemaOpenIdNet) != 0) { + yield return AXAttributeFormats.SchemaOpenIdNet; + } + } + + /// <summary> + /// Adds an attribute fetch request if it is not already present in the AX request. + /// </summary> + /// <param name="ax">The AX request to add the attribute request to.</param> + /// <param name="format">The format of the attribute's Type URI to use.</param> + /// <param name="axSchemaOrgFormatAttribute">The attribute in axschema.org format.</param> + /// <param name="demandLevel">The demand level.</param> + private static void FetchAttribute(FetchRequest ax, AXAttributeFormats format, string axSchemaOrgFormatAttribute, DemandLevel demandLevel) { + Contract.Requires(ax != null); + Contract.Requires(!String.IsNullOrEmpty(axSchemaOrgFormatAttribute)); + + string typeUri = TransformAXFormat(axSchemaOrgFormatAttribute, format); + if (!ax.Attributes.Contains(typeUri)) { + switch (demandLevel) { + case DemandLevel.Request: + ax.Attributes.AddOptional(typeUri); + break; + case DemandLevel.Require: + ax.Attributes.AddRequired(typeUri); + break; + default: + break; + } + } + } + } +} diff --git a/src/DotNetOpenAuth/OpenId/Extensions/SimpleRegistration/ClaimsRequest.cs b/src/DotNetOpenAuth/OpenId/Extensions/SimpleRegistration/ClaimsRequest.cs index 800a2ff..df88ed0 100644 --- a/src/DotNetOpenAuth/OpenId/Extensions/SimpleRegistration/ClaimsRequest.cs +++ b/src/DotNetOpenAuth/OpenId/Extensions/SimpleRegistration/ClaimsRequest.cs @@ -116,6 +116,12 @@ namespace DotNetOpenAuth.OpenId.Extensions.SimpleRegistration { public DemandLevel TimeZone { get; set; } /// <summary> + /// Gets or sets a value indicating whether this <see cref="ClaimsRequest"/> instance + /// is synthesized from an AX request at the Provider. + /// </summary> + internal bool Synthesized { get; set; } + + /// <summary> /// Gets or sets the value of the sreg.required parameter. /// </summary> /// <value>A comma-delimited list of sreg fields.</value> diff --git a/src/DotNetOpenAuth/OpenId/InteropHelper.cs b/src/DotNetOpenAuth/OpenId/InteropHelper.cs deleted file mode 100644 index e92bc5e..0000000 --- a/src/DotNetOpenAuth/OpenId/InteropHelper.cs +++ /dev/null @@ -1,201 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="InteropHelper.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OpenId { - using System; - using System.Collections.Generic; - using System.Diagnostics.Contracts; - using System.Linq; - using System.Text; - using DotNetOpenAuth.Messaging; - using DotNetOpenAuth.OpenId; - using DotNetOpenAuth.OpenId.Extensions.AttributeExchange; - using DotNetOpenAuth.OpenId.Extensions.SimpleRegistration; - - public static class InteropHelper { - /// <summary> - /// The various Type URI formats an AX attribute may use by various remote parties. - /// </summary> - [Flags] - public enum AXAttributeFormats { - /// <summary> - /// AX attributes should use the Type URI format starting with <c>http://axschema.org/</c>. - /// </summary> - AXSchemaOrg, - - /// <summary> - /// AX attributes should use the Type URI format starting with <c>http://schema.openid.net/</c>. - /// </summary> - SchemaOpenIdNet, - - /// <summary> - /// AX attributes should use the Type URI format starting with <c>http://openid.net/schema/</c>. - /// </summary> - OpenIdNetSchema, - } - - /// <summary> - /// Adds an Attribute Exchange (AX) extension to the authentication request - /// that asks for the same attributes as the Simple Registration (sreg) extension - /// that is already applied. - /// </summary> - /// <param name="request">The authentication request.</param> - /// <param name="attributeFormat">The attribute formats to include in the request.</param> - /// <remarks> - /// <para>If discovery on the user-supplied identifier yields hints regarding which - /// extensions and attribute formats the Provider supports, this method MAY ignore the - /// <paramref name="attributeFormat"/> argument and accomodate the Provider to minimize - /// the size of the request.</para> - /// <para>If the request does not carry an sreg extension, the method logs a warning but - /// otherwise quietly returns doing nothing.</para> - /// </remarks> - public static void SpreadSregToAX(this RelyingParty.IAuthenticationRequest request, AXAttributeFormats attributeFormats) { - Contract.Requires(request != null); - ErrorUtilities.VerifyArgumentNotNull(request, "request"); - - var req = (RelyingParty.AuthenticationRequest)request; - var sreg = req.AppliedExtensions.OfType<ClaimsRequest>().SingleOrDefault(); - if (sreg == null) { - Logger.OpenId.Warn("No Simple Registration (ClaimsRequest) extension present in the request to spread to AX."); - return; - } - - var ax = req.AppliedExtensions.OfType<FetchRequest>().SingleOrDefault(); - if (ax == null) { - ax = new FetchRequest(); - req.AddExtension(ax); - } - - if (req.Provider.IsExtensionSupported<ClaimsRequest>()) { - Logger.OpenId.Info("Skipping generation of AX request because the Identifier advertises the Provider supports the Sreg extension."); - return; - } - - // Try to use just one AX Type URI format if we can figure out which type the OP accepts. - attributeFormats = FocusAttributeFormat(request, attributeFormats); - - foreach (AXAttributeFormats format in ForEachFormat(attributeFormats)) { - FetchAttribute(ax, format, WellKnownAttributes.BirthDate.WholeBirthDate, sreg.BirthDate); - FetchAttribute(ax, format, WellKnownAttributes.Contact.HomeAddress.Country, sreg.Country); - FetchAttribute(ax, format, WellKnownAttributes.Contact.Email, sreg.Email); - FetchAttribute(ax, format, WellKnownAttributes.Name.FullName, sreg.FullName); - FetchAttribute(ax, format, WellKnownAttributes.Person.Gender, sreg.Gender); - FetchAttribute(ax, format, WellKnownAttributes.Preferences.Language, sreg.Language); - FetchAttribute(ax, format, WellKnownAttributes.Name.Alias, sreg.Nickname); - FetchAttribute(ax, format, WellKnownAttributes.Contact.HomeAddress.PostalCode, sreg.PostalCode); - FetchAttribute(ax, format, WellKnownAttributes.Preferences.TimeZone, sreg.TimeZone); - } - } - - public static void UnifyExtensions(this RelyingParty.IAuthenticationResponse response) { - Contract.Requires(response != null); - ErrorUtilities.VerifyArgumentNotNull(response, "response"); - - var resp = (RelyingParty.IAuthenticationResponse)response; - throw new NotImplementedException(); - } - - public static void UnifyExtensions(this Provider.IAuthenticationRequest request) { - Contract.Requires(request != null); - ErrorUtilities.VerifyArgumentNotNull(request, "request"); - - var req = (Provider.AuthenticationRequest)request; - throw new NotImplementedException(); - } - - /// <summary> - /// Tries to find the exact format of AX attribute Type URI supported by the Provider. - /// </summary> - /// <param name="request">The authentication request.</param> - /// <param name="attributeFormat">The attribute formats the RP will try if this discovery fails.</param> - /// <returns>The AX format(s) to use based on the Provider's advertised AX support.</returns> - private static AXAttributeFormats FocusAttributeFormat(RelyingParty.IAuthenticationRequest request, AXAttributeFormats attributeFormat) { - Contract.Requires(request != null); - - var provider = (RelyingParty.ServiceEndpoint)request.Provider; - - if (provider.ProviderDescription.Capabilities.Any(uri => uri.StartsWith("http://axschema.org/", StringComparison.Ordinal))) { - return AXAttributeFormats.AXSchemaOrg; - } - - if (provider.ProviderDescription.Capabilities.Any(uri => uri.StartsWith("http://schema.openid.net/", StringComparison.Ordinal))) { - return AXAttributeFormats.SchemaOpenIdNet; - } - - if (provider.ProviderDescription.Capabilities.Any(uri => uri.StartsWith("http://openid.net/schema/", StringComparison.Ordinal))) { - return AXAttributeFormats.OpenIdNetSchema; - } - - return attributeFormat; - } - - /// <summary> - /// Transforms an AX attribute type URI from the axschema.org format into a given format. - /// </summary> - /// <param name="axSchemaOrgFormatTypeUri">The ax schema org format type URI.</param> - /// <param name="targetFormat">The target format. Only one flag should be set.</param> - /// <returns>The AX attribute type URI in the target format.</returns> - private static string TransformAXFormat(string axSchemaOrgFormatTypeUri, AXAttributeFormats targetFormat) { - Contract.Requires(!String.IsNullOrEmpty(axSchemaOrgFormatTypeUri)); - - switch (targetFormat) { - case AXAttributeFormats.AXSchemaOrg: - return axSchemaOrgFormatTypeUri; - case AXAttributeFormats.SchemaOpenIdNet: - return axSchemaOrgFormatTypeUri.Replace("axschema.org", "schema.openid.net"); - case AXAttributeFormats.OpenIdNetSchema: - return axSchemaOrgFormatTypeUri.Replace("axschema.org", "openid.net/schema"); - default: - throw new ArgumentOutOfRangeException("targetFormat"); - } - } - - /// <summary> - /// Splits the AX attribute format flags into individual values for processing. - /// </summary> - /// <param name="formats">The formats to split up into individual flags.</param> - /// <returns>A sequence of individual flags.</returns> - private static IEnumerable<AXAttributeFormats> ForEachFormat(AXAttributeFormats formats) { - if ((formats & AXAttributeFormats.AXSchemaOrg) != 0) { - yield return AXAttributeFormats.AXSchemaOrg; - } - - if ((formats & AXAttributeFormats.OpenIdNetSchema) != 0) { - yield return AXAttributeFormats.OpenIdNetSchema; - } - - if ((formats & AXAttributeFormats.SchemaOpenIdNet) != 0) { - yield return AXAttributeFormats.SchemaOpenIdNet; - } - } - - /// <summary> - /// Adds an attribute fetch request if it is not already present in the AX request. - /// </summary> - /// <param name="ax">The ax.</param> - /// <param name="format">The format.</param> - /// <param name="axSchemaOrgFormatAttribute">The ax schema org format attribute.</param> - /// <param name="demandLevel">The demand level.</param> - private static void FetchAttribute(FetchRequest ax, AXAttributeFormats format, string axSchemaOrgFormatAttribute, DemandLevel demandLevel) { - Contract.Requires(ax != null); - Contract.Requires(!String.IsNullOrEmpty(axSchemaOrgFormatAttribute)); - - string typeUri = TransformAXFormat(axSchemaOrgFormatAttribute, format); - if (!ax.Attributes.Contains(typeUri)) { - switch (demandLevel) { - case DemandLevel.Request: - ax.Attributes.AddOptional(typeUri); - break; - case DemandLevel.Require: - ax.Attributes.AddRequired(typeUri); - break; - default: - break; - } - } - } - } -} diff --git a/src/DotNetOpenAuth/OpenId/Provider/Request.cs b/src/DotNetOpenAuth/OpenId/Provider/Request.cs index 4c2ee98..43697b2 100644 --- a/src/DotNetOpenAuth/OpenId/Provider/Request.cs +++ b/src/DotNetOpenAuth/OpenId/Provider/Request.cs @@ -132,7 +132,7 @@ namespace DotNetOpenAuth.OpenId.Provider { /// Gets the original request message. /// </summary> /// <value>This may be null in the case of an unrecognizable message.</value> - protected IDirectedProtocolMessage RequestMessage { + protected internal IDirectedProtocolMessage RequestMessage { get { return this.request; } } diff --git a/src/DotNetOpenAuth/Settings.StyleCop b/src/DotNetOpenAuth/Settings.StyleCop index 017d610..a4295eb 100644 --- a/src/DotNetOpenAuth/Settings.StyleCop +++ b/src/DotNetOpenAuth/Settings.StyleCop @@ -30,5 +30,12 @@ </Rules> <AnalyzerSettings /> </Analyzer> + <Analyzer AnalyzerId="Microsoft.StyleCop.CSharp.NamingRules"> + <AnalyzerSettings> + <CollectionProperty Name="Hungarian"> + <Value>ax</Value> + </CollectionProperty> + </AnalyzerSettings> + </Analyzer> </Analyzers> </StyleCopSettings>
\ No newline at end of file |