1
//-----------------------------------------------------------------------
2 // <copyright file="OpenIdUtilities.cs" company="Andrew Arnott">
3 // Copyright (c) Andrew Arnott. All rights reserved.
5 //-----------------------------------------------------------------------
7 namespace DotNetOpenAuth
.OpenId
{
9 using System
.Collections
.Generic
;
10 using System
.Diagnostics
.CodeAnalysis
;
13 using System
.Text
.RegularExpressions
;
15 using DotNetOpenAuth
.Messaging
;
16 using DotNetOpenAuth
.OpenId
.ChannelElements
;
19 /// A set of utilities especially useful to OpenID.
21 internal static class OpenIdUtilities
{
23 /// Gets the OpenID protocol instance for the version in a message.
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
);
33 /// Changes the position of some element in a list.
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);
46 /// Initializes the private secret if it has not yet been set.
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.");
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.
67 /// <param name="value">The base64 encoded value. May be null.</param>
69 /// The value; corrected if corruption had occurred.
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.
84 internal static string FixDoublyUriDecodedBase64String(string value) {
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
98 /// Rounds the given <see cref="DateTime"/> downward to the whole second.
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
);
107 /// Gets the fully qualified Realm URL, given a Realm that may be relative to a particular page.
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
)));
134 fullyQualifiedRealm
.Host
= "*." + fullyQualifiedRealm
.Host
;
138 new Realm(fullyQualifiedRealm
); // throws if not valid
140 return fullyQualifiedRealm
;