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