FxCop fixes.
[dotnetoauth.git] / src / DotNetOpenAuth / OpenId / OpenIdUtilities.cs
blobe1e99f778e397a4abaa051a921777612d9865300
1 //-----------------------------------------------------------------------
2 // <copyright file="OpenIdUtilities.cs" company="Andrew Arnott">
3 // Copyright (c) Andrew Arnott. All rights reserved.
4 // </copyright>
5 //-----------------------------------------------------------------------
7 namespace DotNetOpenAuth.OpenId {
8 using System;
9 using System.Collections.Generic;
10 using System.Diagnostics.CodeAnalysis;
11 using System.Linq;
12 using System.Text;
13 using System.Text.RegularExpressions;
14 using System.Web.UI;
15 using DotNetOpenAuth.Messaging;
16 using DotNetOpenAuth.OpenId.ChannelElements;
18 /// <summary>
19 /// A set of utilities especially useful to OpenID.
20 /// </summary>
21 internal static class OpenIdUtilities {
22 /// <summary>
23 /// Gets the OpenID protocol instance for the version in a message.
24 /// </summary>
25 /// <param name="message">The message.</param>
26 /// <returns>The OpenID protocol instance.</returns>
27 internal static Protocol GetProtocol(this IProtocolMessage message) {
28 ErrorUtilities.VerifyArgumentNotNull(message, "message");
29 return Protocol.Lookup(message.Version);
32 /// <summary>
33 /// Changes the position of some element in a list.
34 /// </summary>
35 /// <typeparam name="T">The type of elements stored in the list.</typeparam>
36 /// <param name="list">The list to be modified.</param>
37 /// <param name="position">The new position for the given element.</param>
38 /// <param name="value">The element to move within the list.</param>
39 /// <exception cref="InternalErrorException">Thrown if the element does not already exist in the list.</exception>
40 internal static void MoveTo<T>(this IList<T> list, int position, T value) {
41 ErrorUtilities.VerifyInternal(list.Remove(value), "Unable to find element in list.");
42 list.Insert(position, value);
45 /// <summary>
46 /// Initializes the private secret if it has not yet been set.
47 /// </summary>
48 /// <param name="secretStore">The secret store.</param>
49 internal static void InitializeSecretIfUnset(this IPrivateSecretStore secretStore) {
50 ErrorUtilities.VerifyArgumentNotNull(secretStore, "secretStore");
52 if (secretStore.PrivateSecret == null) {
53 secretStore.PrivateSecret = MessagingUtilities.GetCryptoRandomData(ReturnToSignatureBindingElement.OptimalPrivateSecretLength);
55 // Log that we created a new private secret.
56 // If this happens frequently, it's a sign that the store for this secret is not
57 // properly saving the value, and the result will be slower performance for
58 // Relying Parties (at best) and failed authentications (at worst).
59 Logger.Info("Generated and saved private secret. This should generally happen only at web application initialization time.");
63 /// <summary>
64 /// Corrects any URI decoding the Provider may have inappropriately done
65 /// to our return_to URL, resulting in an otherwise corrupted base64 encoded value.
66 /// </summary>
67 /// <param name="value">The base64 encoded value. May be null.</param>
68 /// <returns>
69 /// The value; corrected if corruption had occurred.
70 /// </returns>
71 /// <remarks>
72 /// AOL may have incorrectly URI-decoded the token for us in the return_to,
73 /// resulting in a token URI-decoded twice by the time we see it, and no
74 /// longer being a valid base64 string.
75 /// It turns out that the only symbols from base64 that is also encoded
76 /// in URI encoding rules are the + and / characters.
77 /// AOL decodes the %2b sequence to the + character
78 /// and the %2f sequence to the / character (it shouldn't decode at all).
79 /// When we do our own URI decoding, the + character becomes a space (corrupting base64)
80 /// but the / character remains a /, so no further corruption happens to this character.
81 /// So to correct this we just need to change any spaces we find in the token
82 /// back to + characters.
83 /// </remarks>
84 internal static string FixDoublyUriDecodedBase64String(string value) {
85 if (value == null) {
86 return null;
89 if (value.Contains(" ")) {
90 Logger.Error("Deserializing a corrupted token. The OpenID Provider may have inappropriately decoded the return_to URL before sending it back to us.");
91 value = value.Replace(' ', '+'); // Undo any extra decoding the Provider did
94 return value;
97 /// <summary>
98 /// Rounds the given <see cref="DateTime"/> downward to the whole second.
99 /// </summary>
100 /// <param name="dateTime">The DateTime object to adjust.</param>
101 /// <returns>The new <see cref="DateTime"/> value.</returns>
102 internal static DateTime CutToSecond(DateTime dateTime) {
103 return new DateTime(dateTime.Ticks - (dateTime.Ticks % TimeSpan.TicksPerSecond), dateTime.Kind);
106 /// <summary>
107 /// Gets the fully qualified Realm URL, given a Realm that may be relative to a particular page.
108 /// </summary>
109 /// <param name="page">The hosting page that has the realm value to resolve.</param>
110 /// <param name="realm">The realm, which may begin with "*." or "~/".</param>
111 /// <returns>The fully-qualified realm.</returns>
112 [SuppressMessage("Microsoft.Usage", "CA1806:DoNotIgnoreMethodResults", MessageId = "DotNetOpenAuth.OpenId.Realm", Justification = "Using ctor for validation.")]
113 internal static UriBuilder GetResolvedRealm(Page page, string realm) {
114 ErrorUtilities.VerifyArgumentNotNull(page, "page");
116 // Allow for *. realm notation, as well as ASP.NET ~/ shortcuts.
118 // We have to temporarily remove the *. notation if it's there so that
119 // the rest of our URL manipulation will succeed.
120 bool foundWildcard = false;
122 // Note: we don't just use string.Replace because poorly written URLs
123 // could potentially have multiple :// sequences in them.
124 MatchEvaluator matchDelegate = delegate(Match m) {
125 foundWildcard = true;
126 return m.Groups[1].Value;
128 string realmNoWildcard = Regex.Replace(realm, @"^(\w+://)\*\.", matchDelegate);
130 UriBuilder fullyQualifiedRealm = new UriBuilder(
131 new Uri(MessagingUtilities.GetRequestUrlFromContext(), page.ResolveUrl(realmNoWildcard)));
133 if (foundWildcard) {
134 fullyQualifiedRealm.Host = "*." + fullyQualifiedRealm.Host;
137 // Is it valid?
138 new Realm(fullyQualifiedRealm); // throws if not valid
140 return fullyQualifiedRealm;