diff options
Diffstat (limited to 'src/DotNetOpenAuth.OpenId/OpenId/OpenIdUtilities.cs')
-rw-r--r-- | src/DotNetOpenAuth.OpenId/OpenId/OpenIdUtilities.cs | 195 |
1 files changed, 195 insertions, 0 deletions
diff --git a/src/DotNetOpenAuth.OpenId/OpenId/OpenIdUtilities.cs b/src/DotNetOpenAuth.OpenId/OpenId/OpenIdUtilities.cs new file mode 100644 index 0000000..68babd9 --- /dev/null +++ b/src/DotNetOpenAuth.OpenId/OpenId/OpenIdUtilities.cs @@ -0,0 +1,195 @@ +//----------------------------------------------------------------------- +// <copyright file="OpenIdUtilities.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +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; + using DotNetOpenAuth.OpenId.Provider; + using DotNetOpenAuth.OpenId.RelyingParty; + + /// <summary> + /// A set of utilities especially useful to OpenID. + /// </summary> + public static class OpenIdUtilities { + /// <summary> + /// The prefix to designate this library's proprietary parameters added to the protocol. + /// </summary> + internal const string CustomParameterPrefix = "dnoa."; + + /// <summary> + /// Creates a random association handle. + /// </summary> + /// <returns>The association handle.</returns> + public static string GenerateRandomAssociationHandle() { + Contract.Ensures(!String.IsNullOrEmpty(Contract.Result<string>())); + + // 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); + } + + /// <summary> + /// Gets the OpenID protocol instance for the version in a message. + /// </summary> + /// <param name="message">The message.</param> + /// <returns>The OpenID protocol instance.</returns> + internal static Protocol GetProtocol(this IProtocolMessage message) { + Contract.Requires<ArgumentNullException>(message != null); + return Protocol.Lookup(message.Version); + } + + /// <summary> + /// Changes the position of some element in a list. + /// </summary> + /// <typeparam name="T">The type of elements stored in the list.</typeparam> + /// <param name="list">The list to be modified.</param> + /// <param name="position">The new position for the given element.</param> + /// <param name="value">The element to move within the list.</param> + /// <exception cref="InternalErrorException">Thrown if the element does not already exist in the list.</exception> + internal static void MoveTo<T>(this IList<T> list, int position, T value) { + ErrorUtilities.VerifyInternal(list.Remove(value), "Unable to find element in list."); + list.Insert(position, value); + } + + /// <summary> + /// Corrects any URI decoding the Provider may have inappropriately done + /// to our return_to URL, resulting in an otherwise corrupted base64 encoded value. + /// </summary> + /// <param name="value">The base64 encoded value. May be null.</param> + /// <returns> + /// The value; corrected if corruption had occurred. + /// </returns> + /// <remarks> + /// 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. + /// </remarks> + 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; + } + + /// <summary> + /// Rounds the given <see cref="DateTime"/> downward to the whole second. + /// </summary> + /// <param name="dateTime">The DateTime object to adjust.</param> + /// <returns>The new <see cref="DateTime"/> value.</returns> + internal static DateTime CutToSecond(DateTime dateTime) { + return new DateTime(dateTime.Ticks - (dateTime.Ticks % TimeSpan.TicksPerSecond), dateTime.Kind); + } + + /// <summary> + /// Gets the fully qualified Realm URL, given a Realm that may be relative to a particular page. + /// </summary> + /// <param name="page">The hosting page that has the realm value to resolve.</param> + /// <param name="realm">The realm, which may begin with "*." or "~/".</param> + /// <param name="requestContext">The request context.</param> + /// <returns>The fully-qualified realm.</returns> + [SuppressMessage("Microsoft.Usage", "CA1806:DoNotIgnoreMethodResults", MessageId = "DotNetOpenAuth.OpenId.Realm", Justification = "Using ctor for validation.")] + internal static UriBuilder GetResolvedRealm(Page page, string realm, HttpRequestInfo requestContext) { + Contract.Requires<ArgumentNullException>(page != null); + Contract.Requires<ArgumentNullException>(requestContext != null); + + // 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; + } + + /// <summary> + /// Gets the extension factories from the extension aggregator on an OpenID channel. + /// </summary> + /// <param name="channel">The channel.</param> + /// <returns>The list of factories that will be used to generate extension instances.</returns> + /// <remarks> + /// This is an extension method on <see cref="Channel"/> rather than an instance + /// method on <see cref="OpenIdChannel"/> because the <see cref="OpenIdRelyingParty"/> + /// and <see cref="OpenIdProvider"/> classes don't strong-type to <see cref="OpenIdChannel"/> + /// to allow flexibility in the specific type of channel the user (or tests) + /// can plug in. + /// </remarks> + internal static IList<IOpenIdExtensionFactory> GetExtensionFactories(this Channel channel) { + Contract.Requires<ArgumentNullException>(channel != null); + + var extensionsBindingElement = channel.BindingElements.OfType<ExtensionsBindingElement>().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; + } + + /// <summary> + /// Determines whether the association with the specified handle is (still) valid. + /// </summary> + /// <param name="associationStore">The association store.</param> + /// <param name="containingMessage">The OpenID message that referenced this association handle.</param> + /// <param name="isPrivateAssociation">A value indicating whether a private association is expected.</param> + /// <param name="handle">The association handle.</param> + /// <returns> + /// <c>true</c> if the specified containing message is valid; otherwise, <c>false</c>. + /// </returns> + internal static bool IsValid(this IProviderAssociationStore associationStore, IProtocolMessage containingMessage, bool isPrivateAssociation, string handle) { + Contract.Requires<ArgumentNullException>(associationStore != null); + Contract.Requires<ArgumentNullException>(containingMessage != null); + Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(handle)); + try { + return associationStore.Deserialize(containingMessage, isPrivateAssociation, handle) != null; + } catch (ProtocolException) { + return false; + } + } + } +} |