//-----------------------------------------------------------------------
//
// Copyright (c) Andrew Arnott. All rights reserved.
//
//-----------------------------------------------------------------------
namespace DotNetOpenAuth.OpenId.Extensions {
using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Globalization;
using System.Text;
using DotNetOpenAuth.Messaging;
///
/// Manages the processing and construction of OpenID extensions parts.
///
internal class ExtensionArgumentsManager {
///
/// This contains a set of aliases that we must be willing to implicitly
/// match to namespaces for backward compatibility with other OpenID libraries.
///
private static readonly Dictionary typeUriToAliasAffinity = new Dictionary {
{ Extensions.SimpleRegistration.Constants.sreg_ns, Extensions.SimpleRegistration.Constants.sreg_compatibility_alias },
{ Extensions.ProviderAuthenticationPolicy.Constants.TypeUri, Extensions.ProviderAuthenticationPolicy.Constants.CompatibilityAlias },
};
///
/// The version of OpenID that the message is using.
///
private Protocol protocol;
///
/// Whether extensions are being read or written.
///
private bool isReadMode;
///
/// The alias manager that will track Type URI to alias mappings.
///
private AliasManager aliasManager = new AliasManager();
///
/// A complex dictionary where the key is the Type URI of the extension,
/// and the value is another dictionary of the name/value args of the extension.
///
private Dictionary> extensions = new Dictionary>();
///
/// Prevents a default instance of the class from being created.
///
private ExtensionArgumentsManager() { }
///
/// Gets a value indicating whether the extensions are being read (as opposed to written).
///
internal bool ReadMode {
get { return this.isReadMode; }
}
///
/// Creates a instance to process incoming extensions.
///
/// The parameters in the OpenID message.
/// The newly created instance of .
public static ExtensionArgumentsManager CreateIncomingExtensions(IDictionary query) {
Contract.Requires(query != null);
var mgr = new ExtensionArgumentsManager();
mgr.protocol = Protocol.Detect(query);
mgr.isReadMode = true;
string aliasPrefix = mgr.protocol.openid.ns + ".";
// First pass looks for namespace aliases
foreach (var pair in query) {
if (pair.Key.StartsWith(aliasPrefix, StringComparison.Ordinal)) {
mgr.aliasManager.SetAlias(pair.Key.Substring(aliasPrefix.Length), pair.Value);
}
}
// For backwards compatibility, add certain aliases if they aren't defined.
if (mgr.protocol.Version.Major < 2) {
foreach (var pair in typeUriToAliasAffinity) {
if (!mgr.aliasManager.IsAliasAssignedTo(pair.Key) &&
!mgr.aliasManager.IsAliasUsed(pair.Value)) {
mgr.aliasManager.SetAlias(pair.Value, pair.Key);
}
}
}
// Second pass looks for extensions using those aliases
foreach (var pair in query) {
if (!pair.Key.StartsWith(mgr.protocol.openid.Prefix, StringComparison.Ordinal)) {
continue;
}
string possibleAlias = pair.Key.Substring(mgr.protocol.openid.Prefix.Length);
int periodIndex = possibleAlias.IndexOf(".", StringComparison.Ordinal);
if (periodIndex >= 0) {
possibleAlias = possibleAlias.Substring(0, periodIndex);
}
string typeUri;
if ((typeUri = mgr.aliasManager.TryResolveAlias(possibleAlias)) != null) {
if (!mgr.extensions.ContainsKey(typeUri)) {
mgr.extensions[typeUri] = new Dictionary();
}
string key = periodIndex >= 0 ? pair.Key.Substring(mgr.protocol.openid.Prefix.Length + possibleAlias.Length + 1) : string.Empty;
mgr.extensions[typeUri].Add(key, pair.Value);
}
}
return mgr;
}
///
/// Creates a instance to prepare outgoing extensions.
///
/// The protocol version used for the outgoing message.
///
/// The newly created instance of .
///
public static ExtensionArgumentsManager CreateOutgoingExtensions(Protocol protocol) {
var mgr = new ExtensionArgumentsManager();
mgr.protocol = protocol;
// Affinity for certain alias for backwards compatibility
foreach (var pair in typeUriToAliasAffinity) {
mgr.aliasManager.SetAlias(pair.Value, pair.Key);
}
return mgr;
}
///
/// Adds query parameters for OpenID extensions to the request directed
/// at the OpenID provider.
///
/// The extension type URI.
/// The arguments for this extension to add to the message.
public void AddExtensionArguments(string extensionTypeUri, IDictionary arguments) {
Contract.Requires(!this.ReadMode);
Contract.Requires(!String.IsNullOrEmpty(extensionTypeUri));
Contract.Requires(arguments != null);
if (arguments.Count == 0) {
return;
}
IDictionary extensionArgs;
if (!this.extensions.TryGetValue(extensionTypeUri, out extensionArgs)) {
this.extensions.Add(extensionTypeUri, extensionArgs = new Dictionary(arguments.Count));
}
ErrorUtilities.VerifyProtocol(extensionArgs.Count == 0, OpenIdStrings.ExtensionAlreadyAddedWithSameTypeURI, extensionTypeUri);
foreach (var pair in arguments) {
extensionArgs.Add(pair.Key, pair.Value);
}
}
///
/// Gets the actual arguments to add to a querystring or other response,
/// where type URI, alias, and actual key/values are all defined.
///
///
/// true if the generated parameter names should include the 'openid.' prefix.
/// This should be true for all but direct response messages.
///
/// A dictionary of key=value pairs to add to the message to carry the extension.
internal IDictionary GetArgumentsToSend(bool includeOpenIdPrefix) {
Contract.Requires(!this.ReadMode);
Dictionary args = new Dictionary();
foreach (var typeUriAndExtension in this.extensions) {
string typeUri = typeUriAndExtension.Key;
var extensionArgs = typeUriAndExtension.Value;
if (extensionArgs.Count == 0) {
continue;
}
string alias = this.aliasManager.GetAlias(typeUri);
// send out the alias declaration
string openidPrefix = includeOpenIdPrefix ? this.protocol.openid.Prefix : string.Empty;
args.Add(openidPrefix + this.protocol.openidnp.ns + "." + alias, typeUri);
string prefix = openidPrefix + alias;
foreach (var pair in extensionArgs) {
string key = prefix;
if (pair.Key.Length > 0) {
key += "." + pair.Key;
}
args.Add(key, pair.Value);
}
}
return args;
}
///
/// Gets the fields carried by a given OpenId extension.
///
/// The type URI of the extension whose fields are being queried for.
///
/// The fields included in the given extension, or null if the extension is not present.
///
internal IDictionary GetExtensionArguments(string extensionTypeUri) {
Contract.Requires(!String.IsNullOrEmpty(extensionTypeUri));
Contract.Requires(this.ReadMode);
IDictionary extensionArgs;
this.extensions.TryGetValue(extensionTypeUri, out extensionArgs);
return extensionArgs;
}
///
/// Gets whether any arguments for a given extension are present.
///
/// The extension Type URI in question.
/// true if this extension is present; false otherwise.
internal bool ContainsExtension(string extensionTypeUri) {
return this.extensions.ContainsKey(extensionTypeUri);
}
///
/// Gets the type URIs of all discovered extensions in the message.
///
/// A sequence of the type URIs.
internal IEnumerable GetExtensionTypeUris() {
return this.extensions.Keys;
}
}
}