//-----------------------------------------------------------------------
//
// 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;
}
}
}