// // disable StyleCop on this file
//-----------------------------------------------------------------------
//
// Copyright (c) Outercurve Foundation. All rights reserved.
//
//-----------------------------------------------------------------------
namespace DotNetOpenAuth.OpenId {
using System;
using System.Collections.Generic;
using DotNetOpenAuth.Messaging;
using System.Globalization;
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics;
using Validation;
///
/// An enumeration of the OpenID protocol versions supported by this library.
///
public enum ProtocolVersion {
///
/// OpenID Authentication 1.0
///
V10,
///
/// OpenID Authentication 1.1
///
V11,
///
/// OpenID Authentication 2.0
///
V20,
}
///
/// Tracks the several versions of OpenID this library supports and the unique
/// constants to each version used in the protocol.
///
[DebuggerDisplay("OpenID {Version}")]
internal sealed class Protocol {
///
/// The value of the openid.ns parameter in the OpenID 2.0 specification.
///
internal const string OpenId2Namespace = "http://specs.openid.net/auth/2.0";
///
/// The parameter of the callback parameter we tack onto the return_to URL
/// to store the replay-detection nonce.
///
internal const string ReturnToNonceParameter = OpenIdUtilities.CustomParameterPrefix + "request_nonce";
///
/// Scans a list for matches with some element of the OpenID protocol,
/// searching from newest to oldest protocol for the first and best match.
///
/// The type of element retrieved from the instance.
/// Takes a instance and returns an element of it.
/// The list to scan for matches.
/// The protocol with the element that matches some item in the list.
internal static Protocol FindBestVersion(Func elementOf, IEnumerable list) {
foreach (var protocol in Protocol.AllVersions) {
foreach (var item in list) {
if (item != null && item.Equals(elementOf(protocol)))
return protocol;
}
}
return null;
}
Protocol(QueryParameters queryBits) {
openidnp = queryBits;
openid = new QueryParameters(queryBits);
}
// Well-known, supported versions of the OpenID spec.
public static readonly Protocol V10 = new Protocol(new QueryParameters()) {
Version = new Version(1, 0),
XmlNamespace = "http://openid.net/xmlns/1.0",
QueryDeclaredNamespaceVersion = null,
ClaimedIdentifierServiceTypeURI = "http://openid.net/signon/1.0",
OPIdentifierServiceTypeURI = null, // not supported
ClaimedIdentifierForOPIdentifier = null, // not supported
RPReturnToTypeURI = null, // not supported
HtmlDiscoveryProviderKey = "openid.server",
HtmlDiscoveryLocalIdKey = "openid.delegate",
};
public static readonly Protocol V11 = new Protocol(new QueryParameters()) {
Version = new Version(1, 1),
XmlNamespace = "http://openid.net/xmlns/1.0",
QueryDeclaredNamespaceVersion = null,
ClaimedIdentifierServiceTypeURI = "http://openid.net/signon/1.1",
OPIdentifierServiceTypeURI = null, // not supported
ClaimedIdentifierForOPIdentifier = null, // not supported
RPReturnToTypeURI = null, // not supported
HtmlDiscoveryProviderKey = "openid.server",
HtmlDiscoveryLocalIdKey = "openid.delegate",
};
public static readonly Protocol V20 = new Protocol(new QueryParameters() {
Realm = "realm",
op_endpoint = "op_endpoint",
response_nonce = "response_nonce",
error_code = "error_code",
user_setup_url = null,
}) {
Version = new Version(2, 0),
XmlNamespace = null, // no longer applicable
QueryDeclaredNamespaceVersion = Protocol.OpenId2Namespace,
ClaimedIdentifierServiceTypeURI = "http://specs.openid.net/auth/2.0/signon",
OPIdentifierServiceTypeURI = "http://specs.openid.net/auth/2.0/server",
ClaimedIdentifierForOPIdentifier = "http://specs.openid.net/auth/2.0/identifier_select",
RPReturnToTypeURI = "http://specs.openid.net/auth/2.0/return_to",
HtmlDiscoveryProviderKey = "openid2.provider",
HtmlDiscoveryLocalIdKey = "openid2.local_id",
Args = new QueryArguments() {
SessionType = new QueryArguments.SessionTypes() {
NoEncryption = "no-encryption",
DH_SHA256 = "DH-SHA256",
DH_SHA384 = "DH-SHA384",
DH_SHA512 = "DH-SHA512",
},
SignatureAlgorithm = new QueryArguments.SignatureAlgorithms() {
HMAC_SHA256 = "HMAC-SHA256",
HMAC_SHA384 = "HMAC-SHA384",
HMAC_SHA512 = "HMAC-SHA512",
},
Mode = new QueryArguments.Modes() {
setup_needed = "setup_needed",
},
},
};
///
/// A list of all supported OpenID versions, in order starting from newest version.
///
public readonly static List AllVersions = new List() { V20, V11, V10 };
///
/// A list of all supported OpenID versions, in order starting from newest version.
/// V1.1 and V1.0 are considered the same and only V1.1 is in the list.
///
public readonly static List AllPracticalVersions = new List() { V20, V11 };
///
/// The default (or most recent) supported version of the OpenID protocol.
///
public readonly static Protocol Default = AllVersions[0];
public static Protocol Lookup(Version version) {
foreach (Protocol protocol in AllVersions) {
if (protocol.Version == version) return protocol;
}
throw new ArgumentOutOfRangeException("version");
}
public static Protocol Lookup(ProtocolVersion version) {
switch (version) {
case ProtocolVersion.V10: return Protocol.V10;
case ProtocolVersion.V11: return Protocol.V11;
case ProtocolVersion.V20: return Protocol.V20;
default: throw new ArgumentOutOfRangeException("version");
}
}
///
/// Attempts to detect the right OpenID protocol version based on the contents
/// of an incoming OpenID indirect message or direct request.
///
internal static Protocol Detect(IDictionary query) {
Requires.NotNull(query, "query");
return query.ContainsKey(V20.openid.ns) ? V20 : V11;
}
///
/// Attempts to detect the right OpenID protocol version based on the contents
/// of an incoming OpenID direct response message.
///
internal static Protocol DetectFromDirectResponse(IDictionary query) {
Requires.NotNull(query, "query");
return query.ContainsKey(V20.openidnp.ns) ? V20 : V11;
}
///
/// Attemps to detect the highest OpenID protocol version supported given a set
/// of XRDS Service Type URIs included for some service.
///
internal static Protocol Detect(IEnumerable serviceTypeURIs) {
Requires.NotNull(serviceTypeURIs, "serviceTypeURIs");
return FindBestVersion(p => p.OPIdentifierServiceTypeURI, serviceTypeURIs) ??
FindBestVersion(p => p.ClaimedIdentifierServiceTypeURI, serviceTypeURIs) ??
FindBestVersion(p => p.RPReturnToTypeURI, serviceTypeURIs);
}
///
/// The OpenID version that this instance describes.
///
public Version Version;
///
/// Returns the enum value for the instance.
///
public ProtocolVersion ProtocolVersion {
get {
switch (Version.Major) {
case 1: return ProtocolVersion.V11;
case 2: return ProtocolVersion.V20;
default: throw new ArgumentException(null); // this should never happen
}
}
}
///
/// The namespace of OpenId 1.x elements in XRDS documents.
///
public string XmlNamespace;
///
/// The value of the openid.ns parameter that appears on the query string
/// whenever data is passed between relying party and provider for OpenID 2.0
/// and later.
///
public string QueryDeclaredNamespaceVersion;
///
/// The XRD/Service/Type value discovered in an XRDS document when
/// "discovering" on a Claimed Identifier (http://andrewarnott.yahoo.com)
///
public string ClaimedIdentifierServiceTypeURI;
///
/// The XRD/Service/Type value discovered in an XRDS document when
/// "discovering" on an OP Identifier rather than a Claimed Identifier.
/// (http://yahoo.com)
///
public string OPIdentifierServiceTypeURI;
///
/// The XRD/Service/Type value discovered in an XRDS document when
/// "discovering" on a Realm URL and looking for the endpoint URL
/// that can receive authentication assertions.
///
public string RPReturnToTypeURI;
///
/// Used as the Claimed Identifier and the OP Local Identifier when
/// the User Supplied Identifier is an OP Identifier.
///
public string ClaimedIdentifierForOPIdentifier;
///
/// The value of the 'rel' attribute in an HTML document's LINK tag
/// when the same LINK tag's HREF attribute value contains the URL to an
/// OP Endpoint URL.
///
public string HtmlDiscoveryProviderKey;
///
/// The value of the 'rel' attribute in an HTML document's LINK tag
/// when the same LINK tag's HREF attribute value contains the URL to use
/// as the OP Local Identifier.
///
public string HtmlDiscoveryLocalIdKey;
///
/// Parts of the protocol that define parameter names that appear in the
/// query string. Each parameter name is prefixed with 'openid.'.
///
public readonly QueryParameters openid;
///
/// Parts of the protocol that define parameter names that appear in the
/// query string. Each parameter name is NOT prefixed with 'openid.'.
///
public readonly QueryParameters openidnp;
///
/// The various 'constants' that appear as parameter arguments (values).
///
public QueryArguments Args = new QueryArguments();
internal sealed class QueryParameters {
///
/// The value "openid."
///
public readonly string Prefix = "openid.";
[SuppressMessage("Microsoft.Performance", "CA1805:DoNotInitializeUnnecessarily")]
public QueryParameters() { }
[SuppressMessage("Microsoft.Performance", "CA1805:DoNotInitializeUnnecessarily")]
public QueryParameters(QueryParameters addPrefixTo) {
ns = addPrefix(addPrefixTo.ns);
return_to = addPrefix(addPrefixTo.return_to);
Realm = addPrefix(addPrefixTo.Realm);
mode = addPrefix(addPrefixTo.mode);
error = addPrefix(addPrefixTo.error);
error_code = addPrefix(addPrefixTo.error_code);
identity = addPrefix(addPrefixTo.identity);
op_endpoint = addPrefix(addPrefixTo.op_endpoint);
response_nonce = addPrefix(addPrefixTo.response_nonce);
claimed_id = addPrefix(addPrefixTo.claimed_id);
expires_in = addPrefix(addPrefixTo.expires_in);
assoc_type = addPrefix(addPrefixTo.assoc_type);
assoc_handle = addPrefix(addPrefixTo.assoc_handle);
session_type = addPrefix(addPrefixTo.session_type);
is_valid = addPrefix(addPrefixTo.is_valid);
sig = addPrefix(addPrefixTo.sig);
signed = addPrefix(addPrefixTo.signed);
user_setup_url = addPrefix(addPrefixTo.user_setup_url);
invalidate_handle = addPrefix(addPrefixTo.invalidate_handle);
dh_modulus = addPrefix(addPrefixTo.dh_modulus);
dh_gen = addPrefix(addPrefixTo.dh_gen);
dh_consumer_public = addPrefix(addPrefixTo.dh_consumer_public);
dh_server_public = addPrefix(addPrefixTo.dh_server_public);
enc_mac_key = addPrefix(addPrefixTo.enc_mac_key);
mac_key = addPrefix(addPrefixTo.mac_key);
}
string addPrefix(string original) {
return (original != null) ? Prefix + original : null;
}
// These fields default to 1.x specifications, and are overridden
// as necessary by later versions in the Protocol class initializers.
// Null values in any version suggests that that feature is absent from that version.
public string ns = "ns";
public string return_to = "return_to";
public string Realm = "trust_root";
public string mode = "mode";
public string error = "error";
public string error_code = null;
public string identity = "identity";
public string op_endpoint = null;
public string response_nonce = null;
public string claimed_id = "claimed_id";
public string expires_in = "expires_in";
public string assoc_type = "assoc_type";
public string assoc_handle = "assoc_handle";
public string session_type = "session_type";
public string is_valid = "is_valid";
public string sig = "sig";
public string signed = "signed";
public string user_setup_url = "user_setup_url";
public string invalidate_handle = "invalidate_handle";
public string dh_modulus = "dh_modulus";
public string dh_gen = "dh_gen";
public string dh_consumer_public = "dh_consumer_public";
public string dh_server_public = "dh_server_public";
public string enc_mac_key = "enc_mac_key";
public string mac_key = "mac_key";
#if CONTRACTS_FULL
///
/// Verifies conditions that should be true for any valid state of this object.
///
[SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Called by code contracts.")]
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called by code contracts.")]
[ContractInvariantMethod]
private void ObjectInvariant() {
Contract.Invariant(!string.IsNullOrEmpty(this.Prefix));
}
#endif
}
internal sealed class QueryArguments {
public ErrorCodes ErrorCode = new ErrorCodes();
public SessionTypes SessionType = new SessionTypes();
public SignatureAlgorithms SignatureAlgorithm = new SignatureAlgorithms();
public Modes Mode = new Modes();
public IsValidValues IsValid = new IsValidValues();
internal sealed class ErrorCodes {
public string UnsupportedType = "unsupported-type";
}
internal sealed class SessionTypes {
///
/// A preference order list of all supported session types.
///
public string[] All { get { return new[] { DH_SHA512, DH_SHA384, DH_SHA256, DH_SHA1, NoEncryption }; } }
public string[] AllDiffieHellman { get { return new[] { DH_SHA512, DH_SHA384, DH_SHA256, DH_SHA1 }; } }
public string DH_SHA1 = "DH-SHA1";
public string DH_SHA256;
public string DH_SHA384;
public string DH_SHA512;
public string NoEncryption = string.Empty;
public string Best {
get {
foreach (string algorithmName in All) {
if (algorithmName != null) {
return algorithmName;
}
}
throw new ProtocolException(); // really bad... we have no signing algorithms at all
}
}
}
internal sealed class SignatureAlgorithms {
///
/// A preference order list of signature algorithms we support.
///
public string[] All { get { return new[] { HMAC_SHA512, HMAC_SHA384, HMAC_SHA256, HMAC_SHA1 }; } }
public string HMAC_SHA1 = "HMAC-SHA1";
public string HMAC_SHA256;
public string HMAC_SHA384;
public string HMAC_SHA512;
public string Best {
get {
foreach (string algorithmName in All) {
if (algorithmName != null) {
return algorithmName;
}
}
throw new ProtocolException(); // really bad... we have no signing algorithms at all
}
}
}
internal sealed class Modes {
public string cancel = "cancel";
public string error = "error";
public string id_res = "id_res";
public string checkid_immediate = "checkid_immediate";
public string checkid_setup = "checkid_setup";
public string check_authentication = "check_authentication";
public string associate = "associate";
public string setup_needed = "id_res"; // V2 overrides this
}
internal sealed class IsValidValues {
public string True = "true";
public string False = "false";
}
}
///
/// The maximum time a user can be allowed to take to complete authentication.
///
///
/// This is used to calculate the length of time that nonces are stored.
/// This is internal until we can decide whether to leave this static, or make
/// it an instance member, or put it inside the IConsumerApplicationStore interface.
///
internal static TimeSpan MaximumUserAgentAuthenticationTime = TimeSpan.FromMinutes(5);
///
/// The maximum permissible difference in clocks between relying party and
/// provider web servers, discounting time zone differences.
///
///
/// This is used when storing/validating nonces from the provider.
/// If it is conceivable that a server's clock could be up to five minutes
/// off from true UTC time, then the maximum time skew should be set to
/// ten minutes to allow one server to be five minutes ahead and the remote
/// server to be five minutes behind and still be able to communicate.
///
internal static TimeSpan MaximumAllowableTimeSkew = TimeSpan.FromMinutes(10);
///
/// Checks whether a given Protocol version practically equals this one
/// for purposes of verifying a match for assertion verification.
///
/// The other version to check against this one.
/// true if this and the given Protocol versions are essentially the same.
///
/// OpenID v1.0 never had a spec, and 1.0 and 1.1 are indistinguishable because of that.
/// Therefore for assertion verification, 1.0 and 1.1 are considered equivalent.
///
public bool EqualsPractically(Protocol other) {
if (other == null) {
return false;
}
// Exact version match is definitely equality.
if (this.Version == other.Version) {
return true;
}
// If both protocol versions are 1.x, it doesn't matter if one
// is 1.0 and the other is 1.1 for assertion verification purposes.
if (this.Version.Major == 1 && other.Version.Major == 1) {
return true;
}
// Different version.
return false;
}
public override bool Equals(object obj) {
Protocol other = obj as Protocol;
if (other == null) {
return false;
}
return this.Version == other.Version;
}
public override int GetHashCode() {
return Version.GetHashCode();
}
public override string ToString() {
return string.Format(CultureInfo.CurrentCulture, "OpenID Authentication {0}.{1}", Version.Major, Version.Minor);
}
}
}