summaryrefslogtreecommitdiffstats
path: root/src/DotNetOpenAuth.OpenId/OpenId/OpenIdUtilities.cs
diff options
context:
space:
mode:
Diffstat (limited to 'src/DotNetOpenAuth.OpenId/OpenId/OpenIdUtilities.cs')
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/OpenIdUtilities.cs195
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;
+ }
+ }
+ }
+}