diff options
Diffstat (limited to 'src/DotNetOpenAuth.OpenId.Provider/OpenId/Provider')
5 files changed, 371 insertions, 0 deletions
diff --git a/src/DotNetOpenAuth.OpenId.Provider/OpenId/Provider/Behaviors/AXFetchAsSregTransform.cs b/src/DotNetOpenAuth.OpenId.Provider/OpenId/Provider/Behaviors/AXFetchAsSregTransform.cs index 09bde00..3a72c5e 100644 --- a/src/DotNetOpenAuth.OpenId.Provider/OpenId/Provider/Behaviors/AXFetchAsSregTransform.cs +++ b/src/DotNetOpenAuth.OpenId.Provider/OpenId/Provider/Behaviors/AXFetchAsSregTransform.cs @@ -16,6 +16,7 @@ namespace DotNetOpenAuth.OpenId.Provider.Behaviors { using DotNetOpenAuth.OpenId.Extensions.AttributeExchange; using DotNetOpenAuth.OpenId.Extensions.SimpleRegistration; using DotNetOpenAuth.OpenId.Provider; + using DotNetOpenAuth.OpenId.Provider.Extensions; using DotNetOpenAuth.OpenId.RelyingParty; /// <summary> diff --git a/src/DotNetOpenAuth.OpenId.Provider/OpenId/Provider/Extensions/ExtensionsInteropHelper.cs b/src/DotNetOpenAuth.OpenId.Provider/OpenId/Provider/Extensions/ExtensionsInteropHelper.cs new file mode 100644 index 0000000..eda768b --- /dev/null +++ b/src/DotNetOpenAuth.OpenId.Provider/OpenId/Provider/Extensions/ExtensionsInteropHelper.cs @@ -0,0 +1,162 @@ +//----------------------------------------------------------------------- +// <copyright file="ExtensionsInteropHelper.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.Provider.Extensions { + using System; + using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; + using System.Diagnostics.Contracts; + using System.Linq; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OpenId.Extensions; + 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> + internal static class ExtensionsInteropHelper { + /// <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> + internal static string TransformAXFormatTestHook(string axSchemaOrgFormatTypeUri, AXAttributeFormats targetFormat) { + return OpenIdExtensionsInteropHelper.TransformAXFormat(axSchemaOrgFormatTypeUri, targetFormat); + } + + /// <summary> + /// Looks for Simple Registration and Attribute Exchange (all known formats) + /// request extensions and returns them as a Simple Registration extension, + /// and adds the new extension to the original request message if it was absent. + /// </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) { + Requires.NotNull(request, "request"); + + var req = (Provider.HostProcessedRequest)request; + var sreg = req.GetExtension<ClaimsRequest>(); + if (sreg != null) { + return sreg; + } + + var ax = req.GetExtension<FetchRequest>(); + if (ax != null) { + sreg = new ClaimsRequest(DotNetOpenAuth.OpenId.Extensions.SimpleRegistration.Constants.sreg_ns); + sreg.Synthesized = true; + ((IProtocolMessageWithExtensions)req.RequestMessage).Extensions.Add(sreg); + 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 = req.Response as IProtocolMessageWithExtensions; // negative responses don't support extensions. + var sregRequest = request.GetExtension<ClaimsRequest>(); + if (sregRequest != null && response != 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 = OpenIdExtensionsInteropHelper.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, OpenIdExtensionsInteropHelper.GenderEncoder.Encode(sregResponse.Gender)); + } + } + } + } + + /// <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 = OpenIdExtensionsInteropHelper.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) { + Requires.NotNull(ax, "ax"); + Requires.NotNullOrEmpty(typeUri, "typeUri"); + + foreach (AXAttributeFormats format in OpenIdExtensionsInteropHelper.ForEachFormat(AXAttributeFormats.All)) { + string typeUriInFormat = OpenIdExtensionsInteropHelper.TransformAXFormat(typeUri, format); + if (ax.Attributes.Contains(typeUriInFormat)) { + return ax.Attributes[typeUriInFormat].IsRequired ? DemandLevel.Require : DemandLevel.Request; + } + } + + return DemandLevel.NoRequest; + } + } +} diff --git a/src/DotNetOpenAuth.OpenId.Provider/OpenId/Provider/Extensions/UI/UIRequestTools.cs b/src/DotNetOpenAuth.OpenId.Provider/OpenId/Provider/Extensions/UI/UIRequestTools.cs new file mode 100644 index 0000000..519efd7 --- /dev/null +++ b/src/DotNetOpenAuth.OpenId.Provider/OpenId/Provider/Extensions/UI/UIRequestTools.cs @@ -0,0 +1,67 @@ +//----------------------------------------------------------------------- +// <copyright file="UIRequestTools.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.Provider.Extensions.UI { + using System; + using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; + using System.Diagnostics.Contracts; + using System.Globalization; + using System.Linq; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OpenId.Messages; + using DotNetOpenAuth.OpenId.Provider; + using DotNetOpenAuth.Xrds; + + /// <summary> + /// OpenID User Interface extension 1.0 request message. + /// </summary> + /// <remarks> + /// <para>Implements the extension described by: http://wiki.openid.net/f/openid_ui_extension_draft01.html </para> + /// <para>This extension only applies to checkid_setup requests, since checkid_immediate requests display + /// no UI to the user. </para> + /// <para>For rules about how the popup window should be displayed, please see the documentation of + /// <see cref="UIModes.Popup"/>. </para> + /// <para>An RP may determine whether an arbitrary OP supports this extension (and thereby determine + /// whether to use a standard full window redirect or a popup) via the + /// <see cref="IdentifierDiscoveryResult.IsExtensionSupported<T>()"/> method.</para> + /// </remarks> + public static class UIRequestTools { + /// <summary> + /// Gets the URL of the RP icon for the OP to display. + /// </summary> + /// <param name="realm">The realm of the RP where the authentication request originated.</param> + /// <param name="webRequestHandler">The web request handler to use for discovery. + /// Usually available via <see cref="Channel.WebRequestHandler">OpenIdProvider.Channel.WebRequestHandler</see>.</param> + /// <returns> + /// A sequence of the RP's icons it has available for the Provider to display, in decreasing preferred order. + /// </returns> + /// <value>The icon URL.</value> + /// <remarks> + /// This property is automatically set for the OP with the result of RP discovery. + /// RPs should set this value by including an entry such as this in their XRDS document. + /// <example> + /// <Service xmlns="xri://$xrd*($v*2.0)"> + /// <Type>http://specs.openid.net/extensions/ui/icon</Type> + /// <URI>http://consumer.example.com/images/image.jpg</URI> + /// </Service> + /// </example> + /// </remarks> + public static IEnumerable<Uri> GetRelyingPartyIconUrls(Realm realm, IDirectWebRequestHandler webRequestHandler) { + Contract.Requires(realm != null); + Contract.Requires(webRequestHandler != null); + ErrorUtilities.VerifyArgumentNotNull(realm, "realm"); + ErrorUtilities.VerifyArgumentNotNull(webRequestHandler, "webRequestHandler"); + + XrdsDocument xrds = realm.Discover(webRequestHandler, false); + if (xrds == null) { + return Enumerable.Empty<Uri>(); + } else { + return xrds.FindRelyingPartyIcons(); + } + } + } +} diff --git a/src/DotNetOpenAuth.OpenId.Provider/OpenId/Provider/HmacShaAssociationProvider.cs b/src/DotNetOpenAuth.OpenId.Provider/OpenId/Provider/HmacShaAssociationProvider.cs new file mode 100644 index 0000000..3cfc0b6 --- /dev/null +++ b/src/DotNetOpenAuth.OpenId.Provider/OpenId/Provider/HmacShaAssociationProvider.cs @@ -0,0 +1,71 @@ +//----------------------------------------------------------------------- +// <copyright file="HmacShaAssociationProvider.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.Provider { + using System; + using System.Collections.Generic; + using System.Diagnostics.Contracts; + using System.Linq; + using System.Text; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OpenId.Provider; + + /// <summary> + /// OpenID Provider utility methods for HMAC-SHA* associations. + /// </summary> + internal static class HmacShaAssociationProvider { + /// <summary> + /// The default lifetime of a shared association when no lifetime is given + /// for a specific association type. + /// </summary> + private static readonly TimeSpan DefaultMaximumLifetime = TimeSpan.FromDays(14); + + /// <summary> + /// Creates a new association of a given type at an OpenID Provider. + /// </summary> + /// <param name="protocol">The protocol.</param> + /// <param name="associationType">Type of the association (i.e. HMAC-SHA1 or HMAC-SHA256)</param> + /// <param name="associationUse">A value indicating whether the new association will be used privately by the Provider for "dumb mode" authentication + /// or shared with the Relying Party for "smart mode" authentication.</param> + /// <param name="associationStore">The Provider's association store.</param> + /// <param name="securitySettings">The security settings of the Provider.</param> + /// <returns> + /// The newly created association. + /// </returns> + /// <remarks> + /// The new association is NOT automatically put into an association store. This must be done by the caller. + /// </remarks> + internal static HmacShaAssociation Create(Protocol protocol, string associationType, AssociationRelyingPartyType associationUse, IProviderAssociationStore associationStore, ProviderSecuritySettings securitySettings) { + Requires.NotNull(protocol, "protocol"); + Requires.NotNullOrEmpty(associationType, "associationType"); + Requires.NotNull(associationStore, "associationStore"); + Requires.NotNull(securitySettings, "securitySettings"); + Contract.Ensures(Contract.Result<HmacShaAssociation>() != null); + + int secretLength = HmacShaAssociation.GetSecretLength(protocol, associationType); + + // Generate the secret that will be used for signing + byte[] secret = MessagingUtilities.GetCryptoRandomData(secretLength); + + TimeSpan lifetime; + if (associationUse == AssociationRelyingPartyType.Smart) { + if (!securitySettings.AssociationLifetimes.TryGetValue(associationType, out lifetime)) { + lifetime = DefaultMaximumLifetime; + } + } else { + lifetime = HmacShaAssociation.DumbSecretLifetime; + } + + string handle = associationStore.Serialize(secret, DateTime.UtcNow + lifetime, associationUse == AssociationRelyingPartyType.Dumb); + + Contract.Assert(protocol != null); // All the way up to the method call, the condition holds, yet we get a Requires failure next + Contract.Assert(secret != null); + Contract.Assert(!String.IsNullOrEmpty(associationType)); + var result = HmacShaAssociation.Create(protocol, associationType, handle, secret, lifetime); + return result; + } + } +} diff --git a/src/DotNetOpenAuth.OpenId.Provider/OpenId/Provider/OpenIdProviderUtilities.cs b/src/DotNetOpenAuth.OpenId.Provider/OpenId/Provider/OpenIdProviderUtilities.cs new file mode 100644 index 0000000..cf525f1 --- /dev/null +++ b/src/DotNetOpenAuth.OpenId.Provider/OpenId/Provider/OpenIdProviderUtilities.cs @@ -0,0 +1,70 @@ +//----------------------------------------------------------------------- +// <copyright file="OpenIdProviderUtilities.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.Provider { + using System; + using System.Collections.Generic; + using System.Diagnostics.Contracts; + using System.Linq; + using System.Text; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OpenId.Messages; + using DotNetOpenAuth.OpenId.Provider; + + /// <summary> + /// Utility methods for OpenID Providers. + /// </summary> + internal static class OpenIdProviderUtilities { + /// <summary> + /// Called to create the Association based on a request previously given by the Relying Party. + /// </summary> + /// <param name="request">The prior request for an association.</param> + /// <param name="response">The response.</param> + /// <param name="associationStore">The Provider's association store.</param> + /// <param name="securitySettings">The security settings for the Provider. Should be <c>null</c> for Relying Parties.</param> + /// <returns> + /// The created association. + /// </returns> + /// <remarks> + /// The response message is updated to include the details of the created association by this method. + /// This method is called by both the Provider and the Relying Party, but actually performs + /// quite different operations in either scenario. + /// </remarks> + internal static Association CreateAssociation(AssociateRequest request, IAssociateSuccessfulResponseProvider response, IProviderAssociationStore associationStore, ProviderSecuritySettings securitySettings) { + Requires.NotNull(request, "request"); + Requires.NotNull(response, "response"); + Requires.NotNull(securitySettings, "securitySettings"); + + // We need to initialize some common properties based on the created association. + var association = response.CreateAssociationAtProvider(request, associationStore, securitySettings); + response.ExpiresIn = association.SecondsTillExpiration; + response.AssociationHandle = association.Handle; + + return association; + } + + /// <summary> + /// Determines whether the association with the specified handle is (still) valid. + /// </summary> + /// <param name="associationStore">The association store.</param> + /// <param name="containingMessage">The OpenID message that referenced this association handle.</param> + /// <param name="isPrivateAssociation">A value indicating whether a private association is expected.</param> + /// <param name="handle">The association handle.</param> + /// <returns> + /// <c>true</c> if the specified containing message is valid; otherwise, <c>false</c>. + /// </returns> + internal static bool IsValid(this IProviderAssociationStore associationStore, IProtocolMessage containingMessage, bool isPrivateAssociation, string handle) { + Requires.NotNull(associationStore, "associationStore"); + Requires.NotNull(containingMessage, "containingMessage"); + Requires.NotNullOrEmpty(handle, "handle"); + try { + return associationStore.Deserialize(containingMessage, isPrivateAssociation, handle) != null; + } catch (ProtocolException) { + return false; + } + } + } +} |