summaryrefslogtreecommitdiffstats
path: root/src/DotNetOpenAuth.OpenId.Provider/OpenId/Provider
diff options
context:
space:
mode:
Diffstat (limited to 'src/DotNetOpenAuth.OpenId.Provider/OpenId/Provider')
-rw-r--r--src/DotNetOpenAuth.OpenId.Provider/OpenId/Provider/Behaviors/AXFetchAsSregTransform.cs1
-rw-r--r--src/DotNetOpenAuth.OpenId.Provider/OpenId/Provider/Extensions/ExtensionsInteropHelper.cs162
-rw-r--r--src/DotNetOpenAuth.OpenId.Provider/OpenId/Provider/Extensions/UI/UIRequestTools.cs67
-rw-r--r--src/DotNetOpenAuth.OpenId.Provider/OpenId/Provider/HmacShaAssociationProvider.cs71
-rw-r--r--src/DotNetOpenAuth.OpenId.Provider/OpenId/Provider/OpenIdProviderUtilities.cs70
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&lt;T&gt;()"/> 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>
+ /// &lt;Service xmlns="xri://$xrd*($v*2.0)"&gt;
+ /// &lt;Type&gt;http://specs.openid.net/extensions/ui/icon&lt;/Type&gt;
+ /// &lt;URI&gt;http://consumer.example.com/images/image.jpg&lt;/URI&gt;
+ /// &lt;/Service&gt;
+ /// </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;
+ }
+ }
+ }
+}