diff options
Diffstat (limited to 'src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty')
4 files changed, 394 insertions, 0 deletions
diff --git a/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/Behaviors/AXFetchAsSregTransform.cs b/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/Behaviors/AXFetchAsSregTransform.cs new file mode 100644 index 0000000..b2794ec --- /dev/null +++ b/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/Behaviors/AXFetchAsSregTransform.cs @@ -0,0 +1,76 @@ +//----------------------------------------------------------------------- +// <copyright file="AXFetchAsSregTransform.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.RelyingParty.Behaviors { + using System; + using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; + using System.Linq; + using System.Text; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OpenId.Behaviors; + using DotNetOpenAuth.OpenId.Extensions; + using DotNetOpenAuth.OpenId.Extensions.AttributeExchange; + using DotNetOpenAuth.OpenId.Extensions.SimpleRegistration; + using DotNetOpenAuth.OpenId.RelyingParty; + using DotNetOpenAuth.OpenId.RelyingParty.Extensions; + + /// <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> + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Sreg", Justification = "Abbreviation")] + public sealed class AXFetchAsSregTransform : AXFetchAsSregTransformBase, IRelyingPartyBehavior { + /// <summary> + /// Initializes a new instance of the <see cref="AXFetchAsSregTransform"/> class. + /// </summary> + public AXFetchAsSregTransform() { + } + + #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) { + // Don't create AX extensions for OpenID 1.x messages, since AX requires OpenID 2.0. + if (request.Provider.Version.Major >= 2) { + request.SpreadSregToAX(this.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 + } +} diff --git a/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/Behaviors/GsaIcamProfile.cs b/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/Behaviors/GsaIcamProfile.cs new file mode 100644 index 0000000..5a1ddaa --- /dev/null +++ b/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/Behaviors/GsaIcamProfile.cs @@ -0,0 +1,124 @@ +//----------------------------------------------------------------------- +// <copyright file="GsaIcamProfile.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.RelyingParty.Behaviors { + using System; + using System.Diagnostics.CodeAnalysis; + using System.Diagnostics.Contracts; + using System.Linq; + using DotNetOpenAuth.Configuration; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OpenId.Behaviors; + using DotNetOpenAuth.OpenId.Extensions.AttributeExchange; + using DotNetOpenAuth.OpenId.Extensions.ProviderAuthenticationPolicy; + using DotNetOpenAuth.OpenId.Extensions.SimpleRegistration; + using DotNetOpenAuth.OpenId.RelyingParty; + + /// <summary> + /// Implements the Identity, Credential, & Access Management (ICAM) OpenID 2.0 Profile + /// for the General Services Administration (GSA). + /// </summary> + /// <remarks> + /// <para>Relying parties that include this profile are always held to the terms required by the profile, + /// but Providers are only affected by the special behaviors of the profile when the RP specifically + /// indicates that they want to use this profile. </para> + /// </remarks> + [Serializable] + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Icam", Justification = "Acronym")] + public sealed class GsaIcamProfile : GsaIcamProfileBase, IRelyingPartyBehavior { + /// <summary> + /// Initializes a new instance of the <see cref="GsaIcamProfile"/> class. + /// </summary> + public GsaIcamProfile() { + if (DisableSslRequirement) { + Logger.OpenId.Warn("GSA level 1 behavior has its RequireSsl requirement disabled."); + } + } + + #region IRelyingPartyBehavior Members + + /// <summary> + /// Applies a well known set of security requirements. + /// </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) { + if (securitySettings.MaximumHashBitLength < 256) { + securitySettings.MaximumHashBitLength = 256; + } + + securitySettings.RequireSsl = !DisableSslRequirement; + securitySettings.RequireDirectedIdentity = true; + securitySettings.RequireAssociation = true; + securitySettings.RejectDelegatingIdentifiers = true; + securitySettings.IgnoreUnsignedExtensions = true; + securitySettings.MinimumRequiredOpenIdVersion = ProtocolVersion.V20; + } + + /// <summary> + /// Called when an authentication request is about to be sent. + /// </summary> + /// <param name="request">The request.</param> + void IRelyingPartyBehavior.OnOutgoingAuthenticationRequest(RelyingParty.IAuthenticationRequest request) { + RelyingParty.AuthenticationRequest requestInternal = (RelyingParty.AuthenticationRequest)request; + ErrorUtilities.VerifyProtocol(string.Equals(request.Realm.Scheme, Uri.UriSchemeHttps, StringComparison.Ordinal) || DisableSslRequirement, BehaviorStrings.RealmMustBeHttps); + + var pape = requestInternal.AppliedExtensions.OfType<PolicyRequest>().SingleOrDefault(); + if (pape == null) { + request.AddExtension(pape = new PolicyRequest()); + } + + if (!pape.PreferredPolicies.Contains(AuthenticationPolicies.PrivatePersonalIdentifier)) { + pape.PreferredPolicies.Add(AuthenticationPolicies.PrivatePersonalIdentifier); + } + + if (!pape.PreferredPolicies.Contains(AuthenticationPolicies.USGovernmentTrustLevel1)) { + pape.PreferredPolicies.Add(AuthenticationPolicies.USGovernmentTrustLevel1); + } + + if (!AllowPersonallyIdentifiableInformation && !pape.PreferredPolicies.Contains(AuthenticationPolicies.NoPersonallyIdentifiableInformation)) { + pape.PreferredPolicies.Add(AuthenticationPolicies.NoPersonallyIdentifiableInformation); + } + + if (pape.PreferredPolicies.Contains(AuthenticationPolicies.NoPersonallyIdentifiableInformation)) { + ErrorUtilities.VerifyProtocol( + (!requestInternal.AppliedExtensions.OfType<ClaimsRequest>().Any() && + !requestInternal.AppliedExtensions.OfType<FetchRequest>().Any()), + BehaviorStrings.PiiIncludedWithNoPiiPolicy); + } + + Reporting.RecordEventOccurrence(this, "RP"); + } + + /// <summary> + /// Called when an incoming positive assertion is received. + /// </summary> + /// <param name="assertion">The positive assertion.</param> + void IRelyingPartyBehavior.OnIncomingPositiveAssertion(IAuthenticationResponse assertion) { + PolicyResponse pape = assertion.GetExtension<PolicyResponse>(); + ErrorUtilities.VerifyProtocol( + pape != null && + pape.ActualPolicies.Contains(AuthenticationPolicies.USGovernmentTrustLevel1) && + pape.ActualPolicies.Contains(AuthenticationPolicies.PrivatePersonalIdentifier), + BehaviorStrings.PapeResponseOrRequiredPoliciesMissing); + + ErrorUtilities.VerifyProtocol(AllowPersonallyIdentifiableInformation || pape.ActualPolicies.Contains(AuthenticationPolicies.NoPersonallyIdentifiableInformation), BehaviorStrings.PapeResponseOrRequiredPoliciesMissing); + + if (pape.ActualPolicies.Contains(AuthenticationPolicies.NoPersonallyIdentifiableInformation)) { + ErrorUtilities.VerifyProtocol( + assertion.GetExtension<ClaimsResponse>() == null && + assertion.GetExtension<FetchResponse>() == null, + BehaviorStrings.PiiIncludedWithNoPiiPolicy); + } + } + + #endregion + } +} diff --git a/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/Extensions/ExtensionsInteropHelper.cs b/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/Extensions/ExtensionsInteropHelper.cs new file mode 100644 index 0000000..0f7778f --- /dev/null +++ b/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/Extensions/ExtensionsInteropHelper.cs @@ -0,0 +1,152 @@ +//----------------------------------------------------------------------- +// <copyright file="ExtensionsInteropHelper.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.RelyingParty.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> + public static class ExtensionsInteropHelper { + /// <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="attributeFormats"/> 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> + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Sreg", Justification = "Abbreviation")] + public static void SpreadSregToAX(this RelyingParty.IAuthenticationRequest request, AXAttributeFormats attributeFormats) { + Requires.NotNull(request, "request"); + + var req = (RelyingParty.AuthenticationRequest)request; + var sreg = req.AppliedExtensions.OfType<ClaimsRequest>().SingleOrDefault(); + if (sreg == null) { + Logger.OpenId.Debug("No Simple Registration (ClaimsRequest) extension present in the request to spread to AX."); + return; + } + + if (req.DiscoveryResult.IsExtensionSupported<ClaimsRequest>()) { + Logger.OpenId.Debug("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. + AXAttributeFormats detectedFormat; + if (TryDetectOPAttributeFormat(request, out detectedFormat)) { + Logger.OpenId.Debug("Detected OP support for AX but not for Sreg. Removing Sreg extension request and using AX instead."); + attributeFormats = detectedFormat; + req.Extensions.Remove(sreg); + } else { + Logger.OpenId.Debug("Could not determine whether OP supported Sreg or AX. Using both extensions."); + } + + foreach (AXAttributeFormats format in OpenIdExtensionsInteropHelper.ForEachFormat(attributeFormats)) { + OpenIdExtensionsInteropHelper.FetchAttribute(ax, format, WellKnownAttributes.BirthDate.WholeBirthDate, sreg.BirthDate); + OpenIdExtensionsInteropHelper.FetchAttribute(ax, format, WellKnownAttributes.Contact.HomeAddress.Country, sreg.Country); + OpenIdExtensionsInteropHelper.FetchAttribute(ax, format, WellKnownAttributes.Contact.Email, sreg.Email); + OpenIdExtensionsInteropHelper.FetchAttribute(ax, format, WellKnownAttributes.Name.FullName, sreg.FullName); + OpenIdExtensionsInteropHelper.FetchAttribute(ax, format, WellKnownAttributes.Person.Gender, sreg.Gender); + OpenIdExtensionsInteropHelper.FetchAttribute(ax, format, WellKnownAttributes.Preferences.Language, sreg.Language); + OpenIdExtensionsInteropHelper.FetchAttribute(ax, format, WellKnownAttributes.Name.Alias, sreg.Nickname); + OpenIdExtensionsInteropHelper.FetchAttribute(ax, format, WellKnownAttributes.Contact.HomeAddress.PostalCode, sreg.PostalCode); + OpenIdExtensionsInteropHelper.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> + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Sreg", Justification = "Abbreviation")] + public static ClaimsResponse UnifyExtensionsAsSreg(this RelyingParty.IAuthenticationResponse response, bool allowUnsigned) { + Requires.NotNull(response, "response"); + + var resp = (RelyingParty.IAuthenticationResponse)response; + var sreg = allowUnsigned ? resp.GetUntrustedExtension<ClaimsResponse>() : resp.GetExtension<ClaimsResponse>(); + if (sreg != null) { + return sreg; + } + + AXAttributeFormats formats = AXAttributeFormats.All; + 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, formats); + sreg.Country = fetchResponse.GetAttributeValue(WellKnownAttributes.Contact.HomeAddress.Country, formats); + sreg.PostalCode = fetchResponse.GetAttributeValue(WellKnownAttributes.Contact.HomeAddress.PostalCode, formats); + sreg.Email = fetchResponse.GetAttributeValue(WellKnownAttributes.Contact.Email, formats); + sreg.FullName = fetchResponse.GetAttributeValue(WellKnownAttributes.Name.FullName, formats); + sreg.Language = fetchResponse.GetAttributeValue(WellKnownAttributes.Preferences.Language, formats); + sreg.Nickname = fetchResponse.GetAttributeValue(WellKnownAttributes.Name.Alias, formats); + sreg.TimeZone = fetchResponse.GetAttributeValue(WellKnownAttributes.Preferences.TimeZone, formats); + string gender = fetchResponse.GetAttributeValue(WellKnownAttributes.Person.Gender, formats); + if (gender != null) { + sreg.Gender = (Gender)OpenIdExtensionsInteropHelper.GenderEncoder.Decode(gender); + } + } + + return sreg; + } + + /// <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 OpenIdExtensionsInteropHelper.ForEachFormat(formats).Select(format => fetchResponse.GetAttributeValue(OpenIdExtensionsInteropHelper.TransformAXFormat(typeUri, format))).FirstOrDefault(s => s != null); + } + + /// <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 bool TryDetectOPAttributeFormat(RelyingParty.IAuthenticationRequest request, out AXAttributeFormats attributeFormat) { + Requires.NotNull(request, "request"); + attributeFormat = OpenIdExtensionsInteropHelper.DetectAXFormat(request.DiscoveryResult.Capabilities); + return attributeFormat != AXAttributeFormats.None; + } + } +} diff --git a/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/Extensions/UIUtilities.cs b/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/Extensions/UIUtilities.cs new file mode 100644 index 0000000..4606bf7 --- /dev/null +++ b/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/Extensions/UIUtilities.cs @@ -0,0 +1,42 @@ +//----------------------------------------------------------------------- +// <copyright file="UIUtilities.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.RelyingParty.Extensions.UI { + using System; + using System.Diagnostics.Contracts; + using System.Globalization; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OpenId.RelyingParty; + + /// <summary> + /// Constants used in implementing support for the UI extension. + /// </summary> + internal static class UIUtilities { + /// <summary> + /// Gets the <c>window.open</c> javascript snippet to use to open a popup window + /// compliant with the UI extension. + /// </summary> + /// <param name="relyingParty">The relying party.</param> + /// <param name="request">The authentication request to place in the window.</param> + /// <param name="windowName">The name to assign to the popup window.</param> + /// <returns>A string starting with 'window.open' and forming just that one method call.</returns> + internal static string GetWindowPopupScript(OpenIdRelyingParty relyingParty, IAuthenticationRequest request, string windowName) { + Requires.NotNull(relyingParty, "relyingParty"); + Requires.NotNull(request, "request"); + Requires.NotNullOrEmpty(windowName, "windowName"); + + Uri popupUrl = request.RedirectingResponse.GetDirectUriRequest(relyingParty.Channel); + + return string.Format( + CultureInfo.InvariantCulture, + "(window.showModalDialog ? window.showModalDialog({0}, {1}, 'status:0;resizable:1;scroll:1;center:1;dialogWidth:{2}px; dialogHeight:{3}') : window.open({0}, {1}, 'status=0,toolbar=0,location=1,resizable=1,scrollbars=1,left=' + ((screen.width - {2}) / 2) + ',top=' + ((screen.height - {3}) / 2) + ',width={2},height={3}'));", + MessagingUtilities.GetSafeJavascriptValue(popupUrl.AbsoluteUri), + MessagingUtilities.GetSafeJavascriptValue(windowName), + OpenId.Extensions.UI.UIUtilities.PopupWidth, + OpenId.Extensions.UI.UIUtilities.PopupHeight); + } + } +} |