summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/DotNetOpenAuth.AspNet/Clients/OAuth/AuthenticationOnlyCookieOAuthTokenManager.cs5
-rw-r--r--src/DotNetOpenAuth.AspNet/Clients/OAuth/LinkedInClient.cs9
-rw-r--r--src/DotNetOpenAuth.AspNet/Clients/OAuth/OAuthClient.cs17
-rw-r--r--src/DotNetOpenAuth.AspNet/Clients/OAuth/TwitterClient.cs9
-rw-r--r--src/DotNetOpenAuth.AspNet/MachineKeyUtil.cs20
-rw-r--r--src/DotNetOpenAuth.AspNet/OpenAuthSecurityManager.cs53
-rw-r--r--src/DotNetOpenAuth.AspNet/UriHelper.cs3
7 files changed, 96 insertions, 20 deletions
diff --git a/src/DotNetOpenAuth.AspNet/Clients/OAuth/AuthenticationOnlyCookieOAuthTokenManager.cs b/src/DotNetOpenAuth.AspNet/Clients/OAuth/AuthenticationOnlyCookieOAuthTokenManager.cs
index a58549a..2ec988b 100644
--- a/src/DotNetOpenAuth.AspNet/Clients/OAuth/AuthenticationOnlyCookieOAuthTokenManager.cs
+++ b/src/DotNetOpenAuth.AspNet/Clients/OAuth/AuthenticationOnlyCookieOAuthTokenManager.cs
@@ -86,7 +86,10 @@ namespace DotNetOpenAuth.AspNet.Clients {
/// <param name="requestToken">The request token.</param>
/// <param name="requestTokenSecret">The request token secret.</param>
public void StoreRequestToken(string requestToken, string requestTokenSecret) {
- var cookie = new HttpCookie(TokenCookieKey);
+ var cookie = new HttpCookie(TokenCookieKey) {
+ HttpOnly = true
+ };
+
if (FormsAuthentication.RequireSSL) {
cookie.Secure = true;
}
diff --git a/src/DotNetOpenAuth.AspNet/Clients/OAuth/LinkedInClient.cs b/src/DotNetOpenAuth.AspNet/Clients/OAuth/LinkedInClient.cs
index ac8186d..26bc88d 100644
--- a/src/DotNetOpenAuth.AspNet/Clients/OAuth/LinkedInClient.cs
+++ b/src/DotNetOpenAuth.AspNet/Clients/OAuth/LinkedInClient.cs
@@ -89,7 +89,7 @@ namespace DotNetOpenAuth.AspNet.Clients {
Justification = "We don't care if the request fails.")]
protected override AuthenticationResult VerifyAuthenticationCore(AuthorizedTokenResponse response) {
// See here for Field Selectors API http://developer.linkedin.com/docs/DOC-1014
- const string ProfileRequestUrl = "http://api.linkedin.com/v1/people/~:(id,first-name,last-name,headline,industry,summary)";
+ const string ProfileRequestUrl = "https://api.linkedin.com/v1/people/~:(id,first-name,last-name,headline,industry,summary)";
string accessToken = response.AccessToken;
@@ -99,7 +99,7 @@ namespace DotNetOpenAuth.AspNet.Clients {
try {
using (WebResponse profileResponse = request.GetResponse()) {
using (Stream responseStream = profileResponse.GetResponseStream()) {
- XDocument document = XDocument.Load(responseStream);
+ XDocument document = LoadXDocumentFromStream(responseStream);
string userId = document.Root.Element("id").Value;
string firstName = document.Root.Element("first-name").Value;
@@ -117,11 +117,12 @@ namespace DotNetOpenAuth.AspNet.Clients {
isSuccessful: true, provider: this.ProviderName, providerUserId: userId, userName: userName, extraData: extraData);
}
}
- } catch (Exception exception) {
+ }
+ catch (Exception exception) {
return new AuthenticationResult(exception);
}
}
#endregion
}
-}
+} \ No newline at end of file
diff --git a/src/DotNetOpenAuth.AspNet/Clients/OAuth/OAuthClient.cs b/src/DotNetOpenAuth.AspNet/Clients/OAuth/OAuthClient.cs
index 3f9e85a..9a9f40d 100644
--- a/src/DotNetOpenAuth.AspNet/Clients/OAuth/OAuthClient.cs
+++ b/src/DotNetOpenAuth.AspNet/Clients/OAuth/OAuthClient.cs
@@ -8,7 +8,10 @@ namespace DotNetOpenAuth.AspNet.Clients {
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
+ using System.IO;
using System.Web;
+ using System.Xml;
+ using System.Xml.Linq;
using DotNetOpenAuth.Messaging;
using DotNetOpenAuth.OAuth;
using DotNetOpenAuth.OAuth.ChannelElements;
@@ -154,6 +157,20 @@ namespace DotNetOpenAuth.AspNet.Clients {
#region Methods
/// <summary>
+ /// Helper method to load an XDocument from an input stream.
+ /// </summary>
+ /// <param name="stream">The input stream from which to load the document.</param>
+ /// <returns>The XML document.</returns>
+ internal static XDocument LoadXDocumentFromStream(Stream stream) {
+ const int MaxChars = 0x10000; // 64k
+
+ XmlReaderSettings settings = new XmlReaderSettings() {
+ MaxCharactersInDocument = MaxChars
+ };
+ return XDocument.Load(XmlReader.Create(stream, settings));
+ }
+
+ /// <summary>
/// Check if authentication succeeded after user is redirected back from the service provider.
/// </summary>
/// <param name="response">
diff --git a/src/DotNetOpenAuth.AspNet/Clients/OAuth/TwitterClient.cs b/src/DotNetOpenAuth.AspNet/Clients/OAuth/TwitterClient.cs
index 96c1701..886917a 100644
--- a/src/DotNetOpenAuth.AspNet/Clients/OAuth/TwitterClient.cs
+++ b/src/DotNetOpenAuth.AspNet/Clients/OAuth/TwitterClient.cs
@@ -92,7 +92,7 @@ namespace DotNetOpenAuth.AspNet.Clients {
string userId = response.ExtraData["user_id"];
string userName = response.ExtraData["screen_name"];
- var profileRequestUrl = new Uri("http://api.twitter.com/1/users/show.xml?user_id="
+ var profileRequestUrl = new Uri("https://api.twitter.com/1/users/show.xml?user_id="
+ MessagingUtilities.EscapeUriDataStringRfc3986(userId));
var profileEndpoint = new MessageReceivingEndpoint(profileRequestUrl, HttpDeliveryMethods.GetRequest);
HttpWebRequest request = this.WebWorker.PrepareAuthorizedRequest(profileEndpoint, accessToken);
@@ -102,14 +102,15 @@ namespace DotNetOpenAuth.AspNet.Clients {
try {
using (WebResponse profileResponse = request.GetResponse()) {
using (Stream responseStream = profileResponse.GetResponseStream()) {
- XDocument document = XDocument.Load(responseStream);
+ XDocument document = LoadXDocumentFromStream(responseStream);
extraData.AddDataIfNotEmpty(document, "name");
extraData.AddDataIfNotEmpty(document, "location");
extraData.AddDataIfNotEmpty(document, "description");
extraData.AddDataIfNotEmpty(document, "url");
}
}
- } catch (Exception) {
+ }
+ catch (Exception) {
// At this point, the authentication is already successful.
// Here we are just trying to get additional data if we can.
// If it fails, no problem.
@@ -121,4 +122,4 @@ namespace DotNetOpenAuth.AspNet.Clients {
#endregion
}
-}
+} \ No newline at end of file
diff --git a/src/DotNetOpenAuth.AspNet/MachineKeyUtil.cs b/src/DotNetOpenAuth.AspNet/MachineKeyUtil.cs
index f5c8547..eb2020b 100644
--- a/src/DotNetOpenAuth.AspNet/MachineKeyUtil.cs
+++ b/src/DotNetOpenAuth.AspNet/MachineKeyUtil.cs
@@ -10,8 +10,10 @@ namespace DotNetOpenAuth.AspNet {
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Net;
+ using System.Reflection;
using System.Security.Cryptography;
using System.Text;
+ using System.Web;
using System.Web.Security;
/// <summary>
@@ -84,12 +86,20 @@ namespace DotNetOpenAuth.AspNet {
/// </summary>
/// <returns>The machine key implementation</returns>
private static IMachineKey GetMachineKeyImpl() {
- ProtectUnprotect protectThunk = (ProtectUnprotect)Delegate.CreateDelegate(typeof(ProtectUnprotect), typeof(MachineKey), "Protect", ignoreCase: false, throwOnBindFailure: false);
- ProtectUnprotect unprotectThunk = (ProtectUnprotect)Delegate.CreateDelegate(typeof(ProtectUnprotect), typeof(MachineKey), "Unprotect", ignoreCase: false, throwOnBindFailure: false);
+ // Late bind to the MachineKey.Protect / Unprotect methods only if <httpRuntime targetFramework="4.5" />.
+ // This helps ensure that round-tripping the payloads continues to work even if the application is
+ // deployed to a mixed 4.0 / 4.5 farm environment.
+ PropertyInfo targetFrameworkProperty = typeof(HttpRuntime).GetProperty("TargetFramework", typeof(Version));
+ Version targetFramework = (targetFrameworkProperty != null) ? targetFrameworkProperty.GetValue(null, null) as Version : null;
+ if (targetFramework != null && targetFramework >= new Version(4, 5)) {
+ ProtectUnprotect protectThunk = (ProtectUnprotect)Delegate.CreateDelegate(typeof(ProtectUnprotect), typeof(MachineKey), "Protect", ignoreCase: false, throwOnBindFailure: false);
+ ProtectUnprotect unprotectThunk = (ProtectUnprotect)Delegate.CreateDelegate(typeof(ProtectUnprotect), typeof(MachineKey), "Unprotect", ignoreCase: false, throwOnBindFailure: false);
+ if (protectThunk != null && unprotectThunk != null) {
+ return new MachineKey45(protectThunk, unprotectThunk); // ASP.NET 4.5
+ }
+ }
- return (protectThunk != null && unprotectThunk != null)
- ? (IMachineKey)new MachineKey45(protectThunk, unprotectThunk) // ASP.NET 4.5
- : (IMachineKey)new MachineKey40(); // ASP.NET 4.0
+ return new MachineKey40(); // ASP.NET 4.0
}
/// <summary>
diff --git a/src/DotNetOpenAuth.AspNet/OpenAuthSecurityManager.cs b/src/DotNetOpenAuth.AspNet/OpenAuthSecurityManager.cs
index d33913a..6a898a1 100644
--- a/src/DotNetOpenAuth.AspNet/OpenAuthSecurityManager.cs
+++ b/src/DotNetOpenAuth.AspNet/OpenAuthSecurityManager.cs
@@ -7,6 +7,7 @@
namespace DotNetOpenAuth.AspNet {
using System;
using System.Diagnostics.CodeAnalysis;
+ using System.Text;
using System.Web;
using System.Web.Security;
using DotNetOpenAuth.AspNet.Clients;
@@ -19,6 +20,11 @@ namespace DotNetOpenAuth.AspNet {
#region Constants and Fields
/// <summary>
+ /// Purposes string used for protecting the anti-XSRF token.
+ /// </summary>
+ private const string AntiXsrfPurposeString = "DotNetOpenAuth.AspNet.AntiXsrfToken.v1";
+
+ /// <summary>
/// The provider query string name.
/// </summary>
private const string ProviderQueryStringName = "__provider__";
@@ -166,7 +172,10 @@ namespace DotNetOpenAuth.AspNet {
string sessionId = Guid.NewGuid().ToString("N");
uri = uri.AttachQueryStringParameter(SessionIdQueryStringName, sessionId);
- var xsrfCookie = new HttpCookie(SessionIdCookieName, sessionId) {
+ // The cookie value will be the current username secured against the session id we just created.
+ byte[] encryptedCookieBytes = MachineKeyUtil.Protect(Encoding.UTF8.GetBytes(GetUsername(this.requestContext)), AntiXsrfPurposeString, "Token: " + sessionId);
+
+ var xsrfCookie = new HttpCookie(SessionIdCookieName, HttpServerUtility.UrlTokenEncode(encryptedCookieBytes)) {
HttpOnly = true
};
if (FormsAuthentication.RequireSSL) {
@@ -245,6 +254,19 @@ namespace DotNetOpenAuth.AspNet {
}
/// <summary>
+ /// Returns the username of the current logged-in user.
+ /// </summary>
+ /// <param name="context">The HTTP request context.</param>
+ /// <returns>The username, or String.Empty if anonymous.</returns>
+ private static string GetUsername(HttpContextBase context) {
+ string username = null;
+ if (context.User.Identity.IsAuthenticated) {
+ username = context.User.Identity.Name;
+ }
+ return username ?? string.Empty;
+ }
+
+ /// <summary>
/// Validates the request against XSRF attack.
/// </summary>
/// <param name="sessionId">The session id embedded in the query string.</param>
@@ -252,24 +274,45 @@ namespace DotNetOpenAuth.AspNet {
/// <c>true</c> if the request is safe. Otherwise, <c>false</c>.
/// </returns>
private bool ValidateRequestAgainstXsrfAttack(out string sessionId) {
+ sessionId = null;
+
// get the session id query string parameter
string queryStringSessionId = this.requestContext.Request.QueryString[SessionIdQueryStringName];
// verify that the query string value is a valid guid
Guid guid;
if (!Guid.TryParse(queryStringSessionId, out guid)) {
- sessionId = null;
return false;
}
- // get the cookie id query string parameter
+ // the cookie value should be the current username secured against this guid
var cookie = this.requestContext.Request.Cookies[SessionIdCookieName];
+ if (cookie == null || string.IsNullOrEmpty(cookie.Value)) {
+ return false;
+ }
+
+ // extract the username embedded within the cookie
+ // if there is any error at all (crypto, malformed, etc.), fail gracefully
+ string usernameInCookie = null;
+ try {
+ byte[] encryptedCookieBytes = HttpServerUtility.UrlTokenDecode(cookie.Value);
+ byte[] decryptedCookieBytes = MachineKeyUtil.Unprotect(encryptedCookieBytes, AntiXsrfPurposeString, "Token: " + queryStringSessionId);
+ usernameInCookie = Encoding.UTF8.GetString(decryptedCookieBytes);
+ }
+ catch {
+ return false;
+ }
- bool successful = cookie != null && queryStringSessionId == cookie.Value;
+ string currentUsername = GetUsername(this.requestContext);
+ bool successful = string.Equals(currentUsername, usernameInCookie, StringComparison.OrdinalIgnoreCase);
if (successful) {
// be a good citizen, clean up cookie when the authentication succeeds
- this.requestContext.Response.Cookies.Remove(SessionIdCookieName);
+ var xsrfCookie = new HttpCookie(SessionIdCookieName, string.Empty) {
+ HttpOnly = true,
+ Expires = DateTime.Now.AddYears(-1)
+ };
+ this.requestContext.Response.Cookies.Set(xsrfCookie);
}
sessionId = queryStringSessionId;
diff --git a/src/DotNetOpenAuth.AspNet/UriHelper.cs b/src/DotNetOpenAuth.AspNet/UriHelper.cs
index 602f00c..bdf2ed0 100644
--- a/src/DotNetOpenAuth.AspNet/UriHelper.cs
+++ b/src/DotNetOpenAuth.AspNet/UriHelper.cs
@@ -21,7 +21,8 @@ namespace DotNetOpenAuth.AspNet {
/// The url.
/// </param>
/// <param name="parameterName">
- /// The parameter name.
+ /// The parameter name. This value should not be provided by an end user; the caller should
+ /// ensure that this value comes only from a literal string.
/// </param>
/// <param name="parameterValue">
/// The parameter value.