//-----------------------------------------------------------------------
//
// Copyright (c) Outercurve Foundation. All rights reserved.
//
//-----------------------------------------------------------------------
namespace DotNetOpenAuth.OpenId {
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Contracts;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Web.UI;
using DotNetOpenAuth.Messaging;
using DotNetOpenAuth.OpenId.ChannelElements;
using DotNetOpenAuth.OpenId.Extensions;
using DotNetOpenAuth.OpenId.Messages;
///
/// A set of utilities especially useful to OpenID.
///
public static class OpenIdUtilities {
///
/// The prefix to designate this library's proprietary parameters added to the protocol.
///
internal const string CustomParameterPrefix = "dnoa.";
///
/// Creates a random association handle.
///
/// The association handle.
public static string GenerateRandomAssociationHandle() {
Contract.Ensures(!String.IsNullOrEmpty(Contract.Result()));
// Generate the handle. It must be unique, and preferably unpredictable,
// so we use a time element and a random data element to generate it.
string uniq = MessagingUtilities.GetCryptoRandomDataAsBase64(4);
return string.Format(CultureInfo.InvariantCulture, "{{{0}}}{{{1}}}", DateTime.UtcNow.Ticks, uniq);
}
///
/// Gets the OpenID protocol instance for the version in a message.
///
/// The message.
/// The OpenID protocol instance.
internal static Protocol GetProtocol(this IProtocolMessage message) {
Requires.NotNull(message, "message");
return Protocol.Lookup(message.Version);
}
///
/// Changes the position of some element in a list.
///
/// The type of elements stored in the list.
/// The list to be modified.
/// The new position for the given element.
/// The element to move within the list.
/// Thrown if the element does not already exist in the list.
internal static void MoveTo(this IList list, int position, T value) {
ErrorUtilities.VerifyInternal(list.Remove(value), "Unable to find element in list.");
list.Insert(position, value);
}
///
/// Corrects any URI decoding the Provider may have inappropriately done
/// to our return_to URL, resulting in an otherwise corrupted base64 encoded value.
///
/// The base64 encoded value. May be null.
///
/// The value; corrected if corruption had occurred.
///
///
/// AOL may have incorrectly URI-decoded the token for us in the return_to,
/// resulting in a token URI-decoded twice by the time we see it, and no
/// longer being a valid base64 string.
/// It turns out that the only symbols from base64 that is also encoded
/// in URI encoding rules are the + and / characters.
/// AOL decodes the %2b sequence to the + character
/// and the %2f sequence to the / character (it shouldn't decode at all).
/// When we do our own URI decoding, the + character becomes a space (corrupting base64)
/// but the / character remains a /, so no further corruption happens to this character.
/// So to correct this we just need to change any spaces we find in the token
/// back to + characters.
///
internal static string FixDoublyUriDecodedBase64String(string value) {
if (value == null) {
return null;
}
if (value.Contains(" ")) {
Logger.OpenId.Error("Deserializing a corrupted token. The OpenID Provider may have inappropriately decoded the return_to URL before sending it back to us.");
value = value.Replace(' ', '+'); // Undo any extra decoding the Provider did
}
return value;
}
///
/// Rounds the given downward to the whole second.
///
/// The DateTime object to adjust.
/// The new value.
internal static DateTime CutToSecond(DateTime dateTime) {
return new DateTime(dateTime.Ticks - (dateTime.Ticks % TimeSpan.TicksPerSecond), dateTime.Kind);
}
///
/// Gets the fully qualified Realm URL, given a Realm that may be relative to a particular page.
///
/// The hosting page that has the realm value to resolve.
/// The realm, which may begin with "*." or "~/".
/// The request context.
/// The fully-qualified realm.
[SuppressMessage("Microsoft.Usage", "CA1806:DoNotIgnoreMethodResults", MessageId = "DotNetOpenAuth.OpenId.Realm", Justification = "Using ctor for validation.")]
internal static UriBuilder GetResolvedRealm(Page page, string realm, HttpRequestInfo requestContext) {
Requires.NotNull(page, "page");
Requires.NotNull(requestContext, "requestContext");
// Allow for *. realm notation, as well as ASP.NET ~/ shortcuts.
// We have to temporarily remove the *. notation if it's there so that
// the rest of our URL manipulation will succeed.
bool foundWildcard = false;
// Note: we don't just use string.Replace because poorly written URLs
// could potentially have multiple :// sequences in them.
MatchEvaluator matchDelegate = delegate(Match m) {
foundWildcard = true;
return m.Groups[1].Value;
};
string realmNoWildcard = Regex.Replace(realm, @"^(\w+://)\*\.", matchDelegate);
UriBuilder fullyQualifiedRealm = new UriBuilder(
new Uri(requestContext.UrlBeforeRewriting, page.ResolveUrl(realmNoWildcard)));
if (foundWildcard) {
fullyQualifiedRealm.Host = "*." + fullyQualifiedRealm.Host;
}
// Is it valid?
new Realm(fullyQualifiedRealm); // throws if not valid
return fullyQualifiedRealm;
}
///
/// Gets the extension factories from the extension aggregator on an OpenID channel.
///
/// The channel.
/// The list of factories that will be used to generate extension instances.
///
/// This is an extension method on rather than an instance
/// method on because the OpenIdRelyingParty
/// and OpenIdProvider classes don't strong-type to
/// to allow flexibility in the specific type of channel the user (or tests)
/// can plug in.
///
internal static IList GetExtensionFactories(this Channel channel) {
Requires.NotNull(channel, "channel");
var extensionsBindingElement = channel.BindingElements.OfType().SingleOrDefault();
ErrorUtilities.VerifyOperation(extensionsBindingElement != null, OpenIdStrings.UnsupportedChannelConfiguration);
IOpenIdExtensionFactory factory = extensionsBindingElement.ExtensionFactory;
var aggregator = factory as OpenIdExtensionFactoryAggregator;
ErrorUtilities.VerifyOperation(aggregator != null, OpenIdStrings.UnsupportedChannelConfiguration);
return aggregator.Factories;
}
}
}