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