//----------------------------------------------------------------------- // // Copyright (c) Andrew Arnott. All rights reserved. // //----------------------------------------------------------------------- namespace DotNetOpenAuth.OpenId.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.AttributeExchange; using DotNetOpenAuth.OpenId.Extensions.SimpleRegistration; using DotNetOpenAuth.OpenId.Messages; /// /// A set of methods designed to assist in improving interop across different /// OpenID implementations and their extensions. /// public static class ExtensionsInteropRelyingPartyHelper { /// /// 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. /// /// The authentication request. /// The attribute formats to use in the AX request. /// /// If discovery on the user-supplied identifier yields hints regarding which /// extensions and attribute formats the Provider supports, this method MAY ignore the /// argument and accomodate the Provider to minimize /// the size of the request. /// If the request does not carry an sreg extension, the method logs a warning but /// otherwise quietly returns doing nothing. /// [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Sreg", Justification = "Abbreviation")] public static void SpreadSregToAX(this RelyingParty.IAuthenticationRequest request, AXAttributeFormats attributeFormats) { Contract.Requires(request != null); var req = (RelyingParty.AuthenticationRequest)request; var sreg = req.AppliedExtensions.OfType().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()) { Logger.OpenId.Debug("Skipping generation of AX request because the Identifier advertises the Provider supports the Sreg extension."); return; } var ax = req.AppliedExtensions.OfType().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 ExtensionsInteropHelper.ForEachFormat(attributeFormats)) { ExtensionsInteropHelper.FetchAttribute(ax, format, WellKnownAttributes.BirthDate.WholeBirthDate, sreg.BirthDate); ExtensionsInteropHelper.FetchAttribute(ax, format, WellKnownAttributes.Contact.HomeAddress.Country, sreg.Country); ExtensionsInteropHelper.FetchAttribute(ax, format, WellKnownAttributes.Contact.Email, sreg.Email); ExtensionsInteropHelper.FetchAttribute(ax, format, WellKnownAttributes.Name.FullName, sreg.FullName); ExtensionsInteropHelper.FetchAttribute(ax, format, WellKnownAttributes.Person.Gender, sreg.Gender); ExtensionsInteropHelper.FetchAttribute(ax, format, WellKnownAttributes.Preferences.Language, sreg.Language); ExtensionsInteropHelper.FetchAttribute(ax, format, WellKnownAttributes.Name.Alias, sreg.Nickname); ExtensionsInteropHelper.FetchAttribute(ax, format, WellKnownAttributes.Contact.HomeAddress.PostalCode, sreg.PostalCode); ExtensionsInteropHelper.FetchAttribute(ax, format, WellKnownAttributes.Preferences.TimeZone, sreg.TimeZone); } } /// /// Looks for Simple Registration and Attribute Exchange (all known formats) /// response extensions and returns them as a Simple Registration extension. /// /// The authentication response. /// if set to true unsigned extensions will be included in the search. /// /// The Simple Registration response if found, /// or a fabricated one based on the Attribute Exchange extension if found, /// or just an empty if there was no data. /// Never null. [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Sreg", Justification = "Abbreviation")] public static ClaimsResponse UnifyExtensionsAsSreg(this RelyingParty.IAuthenticationResponse response, bool allowUnsigned) { Contract.Requires(response != null); var resp = (RelyingParty.IAuthenticationResponse)response; var sreg = allowUnsigned ? resp.GetUntrustedExtension() : resp.GetExtension(); if (sreg != null) { return sreg; } AXAttributeFormats formats = AXAttributeFormats.All; sreg = new ClaimsResponse(); var fetchResponse = allowUnsigned ? resp.GetUntrustedExtension() : resp.GetExtension(); 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)ExtensionsInteropHelper.GenderEncoder.Decode(gender); } } return sreg; } /// /// Gets the attribute value if available. /// /// The AX fetch response extension to look for the attribute value. /// The type URI of the attribute, using the axschema.org format of . /// The AX type URI formats to search. /// /// The first value of the attribute, if available. /// internal static string GetAttributeValue(this FetchResponse fetchResponse, string typeUri, AXAttributeFormats formats) { return ExtensionsInteropHelper.ForEachFormat(formats).Select(format => fetchResponse.GetAttributeValue(ExtensionsInteropHelper.TransformAXFormat(typeUri, format))).FirstOrDefault(s => s != null); } /// /// Tries to find the exact format of AX attribute Type URI supported by the Provider. /// /// The authentication request. /// The attribute formats the RP will try if this discovery fails. /// The AX format(s) to use based on the Provider's advertised AX support. private static bool TryDetectOPAttributeFormat(RelyingParty.IAuthenticationRequest request, out AXAttributeFormats attributeFormat) { Contract.Requires(request != null); attributeFormat = ExtensionsInteropHelper.DetectAXFormat(request.DiscoveryResult.Capabilities); return attributeFormat != AXAttributeFormats.None; } } }