diff options
Diffstat (limited to 'src')
37 files changed, 539 insertions, 80 deletions
diff --git a/src/DotNetOpenAuth.AspNet.Test/OAuth2ClientTest.cs b/src/DotNetOpenAuth.AspNet.Test/OAuth2ClientTest.cs index 89a483c..e60df01 100644 --- a/src/DotNetOpenAuth.AspNet.Test/OAuth2ClientTest.cs +++ b/src/DotNetOpenAuth.AspNet.Test/OAuth2ClientTest.cs @@ -47,7 +47,16 @@ namespace DotNetOpenAuth.AspNet.Test { var client = new MockOAuth2Client(); // Act && Assert - Assert.Throws<ArgumentNullException>(() => client.VerifyAuthentication(null)); + Assert.Throws<ArgumentNullException>(() => client.VerifyAuthentication(null, new Uri("http://me.com"))); + } + + [TestCase] + public void VerifyAuthenticationWithoutReturnUrlThrows() { + // Arrange + var client = new MockOAuth2Client(); + + // Act && Assert + Assert.Throws<InvalidOperationException>(() => client.VerifyAuthentication(new Mock<HttpContextBase>().Object)); } [TestCase] @@ -59,7 +68,7 @@ namespace DotNetOpenAuth.AspNet.Test { context.Setup(c => c.Request.QueryString).Returns(queryStrings); // Act - AuthenticationResult result = client.VerifyAuthentication(context.Object); + AuthenticationResult result = client.VerifyAuthentication(context.Object, new Uri("http://me.com")); // Assert Assert.IsFalse(result.IsSuccessful); @@ -75,7 +84,7 @@ namespace DotNetOpenAuth.AspNet.Test { context.Setup(c => c.Request.QueryString).Returns(queryStrings); // Act - AuthenticationResult result = client.VerifyAuthentication(context.Object); + AuthenticationResult result = client.VerifyAuthentication(context.Object, new Uri("http://me.com")); // Assert Assert.IsFalse(result.IsSuccessful); @@ -91,7 +100,7 @@ namespace DotNetOpenAuth.AspNet.Test { context.Setup(c => c.Request.QueryString).Returns(queryStrings); // Act - AuthenticationResult result = client.VerifyAuthentication(context.Object); + AuthenticationResult result = client.VerifyAuthentication(context.Object, new Uri("http://me.com")); // Assert Assert.True(result.IsSuccessful); diff --git a/src/DotNetOpenAuth.AspNet/Clients/OAuth/AuthenticationOnlyCookieOAuthTokenManager.cs b/src/DotNetOpenAuth.AspNet/Clients/OAuth/AuthenticationOnlyCookieOAuthTokenManager.cs index 2ec988b..efc382f 100644 --- a/src/DotNetOpenAuth.AspNet/Clients/OAuth/AuthenticationOnlyCookieOAuthTokenManager.cs +++ b/src/DotNetOpenAuth.AspNet/Clients/OAuth/AuthenticationOnlyCookieOAuthTokenManager.cs @@ -17,7 +17,7 @@ namespace DotNetOpenAuth.AspNet.Clients { /// <summary> /// Key used for token cookie /// </summary> - private const string TokenCookieKey = "OAuthTokenSecret"; + protected const string TokenCookieKey = "OAuthTokenSecret"; /// <summary> /// Primary request context. @@ -41,7 +41,7 @@ namespace DotNetOpenAuth.AspNet.Clients { /// <summary> /// Gets the effective HttpContext object to use. /// </summary> - private HttpContextBase Context { + protected HttpContextBase Context { get { return this.primaryContext ?? new HttpContextWrapper(HttpContext.Current); } @@ -54,15 +54,13 @@ namespace DotNetOpenAuth.AspNet.Clients { /// <returns> /// The token's secret /// </returns> - public string GetTokenSecret(string token) { + public virtual string GetTokenSecret(string token) { HttpCookie cookie = this.Context.Request.Cookies[TokenCookieKey]; if (cookie == null || string.IsNullOrEmpty(cookie.Values[token])) { return null; } - byte[] cookieBytes = HttpServerUtility.UrlTokenDecode(cookie.Values[token]); - byte[] clearBytes = MachineKeyUtil.Unprotect(cookieBytes, TokenCookieKey, "Token:" + token); - string secret = Encoding.UTF8.GetString(clearBytes); + string secret = DecodeAndUnprotectToken(token, cookie.Values[token]); return secret; } @@ -72,7 +70,7 @@ namespace DotNetOpenAuth.AspNet.Clients { /// <param name="requestToken">The request token.</param> /// <param name="accessToken">The access token.</param> /// <param name="accessTokenSecret">The access token secret.</param> - public void ReplaceRequestTokenWithAccessToken(string requestToken, string accessToken, string accessTokenSecret) { + public virtual void ReplaceRequestTokenWithAccessToken(string requestToken, string accessToken, string accessTokenSecret) { var cookie = new HttpCookie(TokenCookieKey) { Value = string.Empty, Expires = DateTime.UtcNow.AddDays(-5) @@ -85,7 +83,7 @@ namespace DotNetOpenAuth.AspNet.Clients { /// </summary> /// <param name="requestToken">The request token.</param> /// <param name="requestTokenSecret">The request token secret.</param> - public void StoreRequestToken(string requestToken, string requestTokenSecret) { + public virtual void StoreRequestToken(string requestToken, string requestTokenSecret) { var cookie = new HttpCookie(TokenCookieKey) { HttpOnly = true }; @@ -94,10 +92,36 @@ namespace DotNetOpenAuth.AspNet.Clients { cookie.Secure = true; } - byte[] cookieBytes = Encoding.UTF8.GetBytes(requestTokenSecret); - var secretBytes = MachineKeyUtil.Protect(cookieBytes, TokenCookieKey, "Token:" + requestToken); - cookie.Values[requestToken] = HttpServerUtility.UrlTokenEncode(secretBytes); + var encryptedToken = ProtectAndEncodeToken(requestToken, requestTokenSecret); + cookie.Values[requestToken] = encryptedToken; + this.Context.Response.Cookies.Set(cookie); } + + /// <summary> + /// Protect and url-encode the specified token secret. + /// </summary> + /// <param name="token">The token to be used as a key.</param> + /// <param name="tokenSecret">The token secret to be protected</param> + /// <returns>The encrypted and protected string.</returns> + protected static string ProtectAndEncodeToken(string token, string tokenSecret) + { + byte[] cookieBytes = Encoding.UTF8.GetBytes(tokenSecret); + var secretBytes = MachineKeyUtil.Protect(cookieBytes, TokenCookieKey, "Token:" + token); + return HttpServerUtility.UrlTokenEncode(secretBytes); + } + + /// <summary> + /// Url-decode and unprotect the specified encrypted token string. + /// </summary> + /// <param name="token">The token to be used as a key.</param> + /// <param name="encryptedToken">The encrypted token to be decrypted</param> + /// <returns>The original token secret</returns> + protected static string DecodeAndUnprotectToken(string token, string encryptedToken) + { + byte[] cookieBytes = HttpServerUtility.UrlTokenDecode(encryptedToken); + byte[] clearBytes = MachineKeyUtil.Unprotect(cookieBytes, TokenCookieKey, "Token:" + token); + return Encoding.UTF8.GetString(clearBytes); + } } }
\ No newline at end of file diff --git a/src/DotNetOpenAuth.AspNet/Clients/OAuth/CookieOAuthTokenManager.cs b/src/DotNetOpenAuth.AspNet/Clients/OAuth/CookieOAuthTokenManager.cs new file mode 100644 index 0000000..398ee85 --- /dev/null +++ b/src/DotNetOpenAuth.AspNet/Clients/OAuth/CookieOAuthTokenManager.cs @@ -0,0 +1,79 @@ +//----------------------------------------------------------------------- +// <copyright file="CookieOAuthTokenManager.cs" company="Microsoft"> +// Copyright (c) Microsoft. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.AspNet.Clients { + using System.Web; + using System.Web.Security; + + /// <summary> + /// Stores OAuth tokens in the current request's cookie. + /// </summary> + /// <remarks> + /// This class is different from the <see cref="AuthenticationOnlyCookieOAuthTokenManager"/> in that + /// it also stores the access token after the authentication has succeeded. + /// </remarks> + public class CookieOAuthTokenManager : AuthenticationOnlyCookieOAuthTokenManager { + /// <summary> + /// Initializes a new instance of the <see cref="CookieOAuthTokenManager"/> class. + /// </summary> + public CookieOAuthTokenManager() { + } + + /// <summary> + /// Initializes a new instance of the <see cref="CookieOAuthTokenManager"/> class. + /// </summary> + /// <param name="context">The current request context.</param> + public CookieOAuthTokenManager(HttpContextBase context) + : base(context) { + } + + /// <summary> + /// Gets the token secret from the specified token. + /// </summary> + /// <param name="token">The token.</param> + /// <returns> + /// The token's secret + /// </returns> + public override string GetTokenSecret(string token) { + string secret = base.GetTokenSecret(token); + if (secret != null) { + return secret; + } + + // The base class checks for cookies in the Request object. + // Here we check in the Response object as well because we + // may have set it earlier in the request life cycle. + HttpCookie cookie = this.Context.Response.Cookies[TokenCookieKey]; + if (cookie == null || string.IsNullOrEmpty(cookie.Values[token])) { + return null; + } + + secret = DecodeAndUnprotectToken(token, cookie.Values[token]); + return secret; + } + + /// <summary> + /// Replaces the request token with access token. + /// </summary> + /// <param name="requestToken">The request token.</param> + /// <param name="accessToken">The access token.</param> + /// <param name="accessTokenSecret">The access token secret.</param> + public override void ReplaceRequestTokenWithAccessToken(string requestToken, string accessToken, string accessTokenSecret) { + var cookie = new HttpCookie(TokenCookieKey) { + HttpOnly = true + }; + + if (FormsAuthentication.RequireSSL) { + cookie.Secure = true; + } + + var encryptedToken = ProtectAndEncodeToken(accessToken, accessTokenSecret); + cookie.Values[accessToken] = encryptedToken; + + this.Context.Response.Cookies.Set(cookie); + } + } +}
\ No newline at end of file diff --git a/src/DotNetOpenAuth.AspNet/Clients/OAuth/DotNetOpenAuthWebConsumer.cs b/src/DotNetOpenAuth.AspNet/Clients/OAuth/DotNetOpenAuthWebConsumer.cs index 7eda8e4..9af6804 100644 --- a/src/DotNetOpenAuth.AspNet/Clients/OAuth/DotNetOpenAuthWebConsumer.cs +++ b/src/DotNetOpenAuth.AspNet/Clients/OAuth/DotNetOpenAuthWebConsumer.cs @@ -77,7 +77,7 @@ namespace DotNetOpenAuth.AspNet.Clients { /// The callback. /// </param> public void RequestAuthentication(Uri callback) { - var redirectParameters = new Dictionary<string, string> { { "force_login", "false" } }; + var redirectParameters = new Dictionary<string, string>(); UserAuthorizationRequest request = this.webConsumer.PrepareRequestUserAuthorization( callback, null, redirectParameters); this.webConsumer.Channel.PrepareResponse(request).Send(); diff --git a/src/DotNetOpenAuth.AspNet/Clients/OAuth/LinkedInClient.cs b/src/DotNetOpenAuth.AspNet/Clients/OAuth/LinkedInClient.cs index 26bc88d..3c157f3 100644 --- a/src/DotNetOpenAuth.AspNet/Clients/OAuth/LinkedInClient.cs +++ b/src/DotNetOpenAuth.AspNet/Clients/OAuth/LinkedInClient.cs @@ -60,7 +60,7 @@ namespace DotNetOpenAuth.AspNet.Clients { [SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "We can't dispose the object because we still need it through the app lifetime.")] public LinkedInClient(string consumerKey, string consumerSecret) - : this(consumerKey, consumerSecret, new AuthenticationOnlyCookieOAuthTokenManager()) { } + : this(consumerKey, consumerSecret, new CookieOAuthTokenManager()) { } /// <summary> /// Initializes a new instance of the <see cref="LinkedInClient"/> class. diff --git a/src/DotNetOpenAuth.AspNet/Clients/OAuth2/FacebookClient.cs b/src/DotNetOpenAuth.AspNet/Clients/OAuth2/FacebookClient.cs index 8cb5cc5..a8c121d 100644 --- a/src/DotNetOpenAuth.AspNet/Clients/OAuth2/FacebookClient.cs +++ b/src/DotNetOpenAuth.AspNet/Clients/OAuth2/FacebookClient.cs @@ -78,7 +78,8 @@ namespace DotNetOpenAuth.AspNet.Clients { builder.AppendQueryArgs( new Dictionary<string, string> { { "client_id", this.appId }, - { "redirect_uri", returnUrl.AbsoluteUri } + { "redirect_uri", returnUrl.AbsoluteUri }, + { "scope", "email" }, }); return builder.Uri; } @@ -133,6 +134,7 @@ namespace DotNetOpenAuth.AspNet.Clients { { "redirect_uri", NormalizeHexEncoding(returnUrl.AbsoluteUri) }, { "client_secret", this.appSecret }, { "code", authorizationCode }, + { "scope", "email" }, }); using (WebClient client = new WebClient()) { @@ -170,4 +172,4 @@ namespace DotNetOpenAuth.AspNet.Clients { #endregion } -}
\ No newline at end of file +} diff --git a/src/DotNetOpenAuth.AspNet/Clients/OAuth2/MicrosoftClient.cs b/src/DotNetOpenAuth.AspNet/Clients/OAuth2/MicrosoftClient.cs index 5ac5591..faea868 100644 --- a/src/DotNetOpenAuth.AspNet/Clients/OAuth2/MicrosoftClient.cs +++ b/src/DotNetOpenAuth.AspNet/Clients/OAuth2/MicrosoftClient.cs @@ -20,12 +20,12 @@ namespace DotNetOpenAuth.AspNet.Clients { /// <summary> /// The authorization endpoint. /// </summary> - private const string AuthorizationEndpoint = "https://oauth.live.com/authorize"; + private const string AuthorizationEndpoint = "https://login.live.com/oauth20_authorize.srf"; /// <summary> /// The token endpoint. /// </summary> - private const string TokenEndpoint = "https://oauth.live.com/token"; + private const string TokenEndpoint = "https://login.live.com/oauth20_token.srf"; /// <summary> /// The _app id. diff --git a/src/DotNetOpenAuth.AspNet/DotNetOpenAuth.AspNet.csproj b/src/DotNetOpenAuth.AspNet/DotNetOpenAuth.AspNet.csproj index 2f03ec7..405ac3c 100644 --- a/src/DotNetOpenAuth.AspNet/DotNetOpenAuth.AspNet.csproj +++ b/src/DotNetOpenAuth.AspNet/DotNetOpenAuth.AspNet.csproj @@ -28,7 +28,7 @@ <Import Project="$(ProjectRoot)tools\DotNetOpenAuth.props" /> <Import Project="$(ProjectRoot)tools\DotNetOpenAuth.Product.props" /> <PropertyGroup> - <RootNamespace>DotNetOpenAuth.AspNet</RootNamespace> + <RootNamespace>DotNetOpenAuth.AspNet</RootNamespace> </PropertyGroup> <ItemGroup> <Reference Include="System" /> @@ -48,6 +48,7 @@ <Compile Include="Clients\OAuth\AuthenticationOnlyCookieOAuthTokenManager.cs"> <SubType>Code</SubType> </Compile> + <Compile Include="Clients\OAuth\CookieOAuthTokenManager.cs" /> <Compile Include="Clients\OAuth\IOAuthTokenManager.cs" /> <Compile Include="Clients\OAuth\SimpleConsumerTokenManager.cs" /> <Compile Include="IAuthenticationClient.cs" /> diff --git a/src/DotNetOpenAuth.Core/Loggers/Log4NetLogger.cs b/src/DotNetOpenAuth.Core/Loggers/Log4NetLogger.cs index 293a6b2..01da034 100644 --- a/src/DotNetOpenAuth.Core/Loggers/Log4NetLogger.cs +++ b/src/DotNetOpenAuth.Core/Loggers/Log4NetLogger.cs @@ -201,6 +201,8 @@ namespace DotNetOpenAuth.Loggers { return IsLog4NetPresent ? CreateLogger(name) : null; } catch (FileLoadException) { // wrong log4net.dll version return null; + } catch (TargetInvocationException) { // Thrown due to some security issues on .NET 4.5. + return null; } catch (TypeLoadException) { // Thrown by mono (http://stackoverflow.com/questions/10805773/error-when-pushing-dotnetopenauth-to-staging-or-production-environment) return null; } diff --git a/src/DotNetOpenAuth.Core/Loggers/TraceLogger.cs b/src/DotNetOpenAuth.Core/Loggers/TraceLogger.cs index 9b0bb0f..1b80c7d 100644 --- a/src/DotNetOpenAuth.Core/Loggers/TraceLogger.cs +++ b/src/DotNetOpenAuth.Core/Loggers/TraceLogger.cs @@ -77,210 +77,270 @@ namespace DotNetOpenAuth.Loggers { /// See <see cref="ILog"/>. /// </summary> public void Debug(object message) { - Trace.TraceInformation(message.ToString()); + if (this.IsDebugEnabled) { + Trace.TraceInformation(message.ToString()); + } } /// <summary> /// See <see cref="ILog"/>. /// </summary> public void Debug(object message, Exception exception) { - Trace.TraceInformation(message + ": " + exception.ToString()); + if (this.IsDebugEnabled) { + Trace.TraceInformation(message + ": " + exception.ToString()); + } } /// <summary> /// See <see cref="ILog"/>. /// </summary> public void DebugFormat(string format, params object[] args) { - Trace.TraceInformation(format, args); + if (this.IsDebugEnabled) { + Trace.TraceInformation(format, args); + } } /// <summary> /// See <see cref="ILog"/>. /// </summary> public void DebugFormat(string format, object arg0) { - Trace.TraceInformation(format, arg0); + if (this.IsDebugEnabled) { + Trace.TraceInformation(format, arg0); + } } /// <summary> /// See <see cref="ILog"/>. /// </summary> public void DebugFormat(string format, object arg0, object arg1) { - Trace.TraceInformation(format, arg0, arg1); + if (this.IsDebugEnabled) { + Trace.TraceInformation(format, arg0, arg1); + } } /// <summary> /// See <see cref="ILog"/>. /// </summary> public void DebugFormat(string format, object arg0, object arg1, object arg2) { - Trace.TraceInformation(format, arg0, arg1, arg2); + if (this.IsDebugEnabled) { + Trace.TraceInformation(format, arg0, arg1, arg2); + } } /// <summary> /// See <see cref="ILog"/>. /// </summary> public void Info(object message) { - Trace.TraceInformation(message.ToString()); + if (this.IsInfoEnabled) { + Trace.TraceInformation(message.ToString()); + } } /// <summary> /// See <see cref="ILog"/>. /// </summary> public void Info(object message, Exception exception) { - Trace.TraceInformation(message + ": " + exception.ToString()); + if (this.IsInfoEnabled) { + Trace.TraceInformation(message + ": " + exception.ToString()); + } } /// <summary> /// See <see cref="ILog"/>. /// </summary> public void InfoFormat(string format, params object[] args) { - Trace.TraceInformation(format, args); + if (this.IsInfoEnabled) { + Trace.TraceInformation(format, args); + } } /// <summary> /// See <see cref="ILog"/>. /// </summary> public void InfoFormat(string format, object arg0) { - Trace.TraceInformation(format, arg0); + if (this.IsInfoEnabled) { + Trace.TraceInformation(format, arg0); + } } /// <summary> /// See <see cref="ILog"/>. /// </summary> public void InfoFormat(string format, object arg0, object arg1) { - Trace.TraceInformation(format, arg0, arg1); + if (this.IsInfoEnabled) { + Trace.TraceInformation(format, arg0, arg1); + } } /// <summary> /// See <see cref="ILog"/>. /// </summary> public void InfoFormat(string format, object arg0, object arg1, object arg2) { - Trace.TraceInformation(format, arg0, arg1, arg2); + if (this.IsInfoEnabled) { + Trace.TraceInformation(format, arg0, arg1, arg2); + } } /// <summary> /// See <see cref="ILog"/>. /// </summary> public void Warn(object message) { - Trace.TraceWarning(message.ToString()); + if (this.IsWarnEnabled) { + Trace.TraceWarning(message.ToString()); + } } /// <summary> /// See <see cref="ILog"/>. /// </summary> public void Warn(object message, Exception exception) { - Trace.TraceWarning(message + ": " + exception.ToString()); + if (this.IsWarnEnabled) { + Trace.TraceWarning(message + ": " + exception.ToString()); + } } /// <summary> /// See <see cref="ILog"/>. /// </summary> public void WarnFormat(string format, params object[] args) { - Trace.TraceWarning(format, args); + if (this.IsWarnEnabled) { + Trace.TraceWarning(format, args); + } } /// <summary> /// See <see cref="ILog"/>. /// </summary> public void WarnFormat(string format, object arg0) { - Trace.TraceWarning(format, arg0); + if (this.IsWarnEnabled) { + Trace.TraceWarning(format, arg0); + } } /// <summary> /// See <see cref="ILog"/>. /// </summary> public void WarnFormat(string format, object arg0, object arg1) { - Trace.TraceWarning(format, arg0, arg1); + if (this.IsWarnEnabled) { + Trace.TraceWarning(format, arg0, arg1); + } } /// <summary> /// See <see cref="ILog"/>. /// </summary> public void WarnFormat(string format, object arg0, object arg1, object arg2) { - Trace.TraceWarning(format, arg0, arg1, arg2); + if (this.IsWarnEnabled) { + Trace.TraceWarning(format, arg0, arg1, arg2); + } } /// <summary> /// See <see cref="ILog"/>. /// </summary> public void Error(object message) { - Trace.TraceError(message.ToString()); + if (this.IsErrorEnabled) { + Trace.TraceError(message.ToString()); + } } /// <summary> /// See <see cref="ILog"/>. /// </summary> public void Error(object message, Exception exception) { - Trace.TraceError(message + ": " + exception.ToString()); + if (this.IsErrorEnabled) { + Trace.TraceError(message + ": " + exception.ToString()); + } } /// <summary> /// See <see cref="ILog"/>. /// </summary> public void ErrorFormat(string format, params object[] args) { - Trace.TraceError(format, args); + if (this.IsErrorEnabled) { + Trace.TraceError(format, args); + } } /// <summary> /// See <see cref="ILog"/>. /// </summary> public void ErrorFormat(string format, object arg0) { - Trace.TraceError(format, arg0); + if (this.IsErrorEnabled) { + Trace.TraceError(format, arg0); + } } /// <summary> /// See <see cref="ILog"/>. /// </summary> public void ErrorFormat(string format, object arg0, object arg1) { - Trace.TraceError(format, arg0, arg1); + if (this.IsErrorEnabled) { + Trace.TraceError(format, arg0, arg1); + } } /// <summary> /// See <see cref="ILog"/>. /// </summary> public void ErrorFormat(string format, object arg0, object arg1, object arg2) { - Trace.TraceError(format, arg0, arg1, arg2); + if (this.IsErrorEnabled) { + Trace.TraceError(format, arg0, arg1, arg2); + } } /// <summary> /// See <see cref="ILog"/>. /// </summary> public void Fatal(object message) { - Trace.TraceError(message.ToString()); + if (this.IsFatalEnabled) { + Trace.TraceError(message.ToString()); + } } /// <summary> /// See <see cref="ILog"/>. /// </summary> public void Fatal(object message, Exception exception) { - Trace.TraceError(message + ": " + exception.ToString()); + if (this.IsFatalEnabled) { + Trace.TraceError(message + ": " + exception.ToString()); + } } /// <summary> /// See <see cref="ILog"/>. /// </summary> public void FatalFormat(string format, params object[] args) { - Trace.TraceError(format, args); + if (this.IsFatalEnabled) { + Trace.TraceError(format, args); + } } /// <summary> /// See <see cref="ILog"/>. /// </summary> public void FatalFormat(string format, object arg0) { - Trace.TraceError(format, arg0); + if (this.IsFatalEnabled) { + Trace.TraceError(format, arg0); + } } /// <summary> /// See <see cref="ILog"/>. /// </summary> public void FatalFormat(string format, object arg0, object arg1) { - Trace.TraceError(format, arg0, arg1); + if (this.IsFatalEnabled) { + Trace.TraceError(format, arg0, arg1); + } } /// <summary> /// See <see cref="ILog"/>. /// </summary> public void FatalFormat(string format, object arg0, object arg1, object arg2) { - Trace.TraceError(format, arg0, arg1, arg2); + if (this.IsFatalEnabled) { + Trace.TraceError(format, arg0, arg1, arg2); + } } #endregion diff --git a/src/DotNetOpenAuth.Core/Messaging/DataBagFormatterBase.cs b/src/DotNetOpenAuth.Core/Messaging/DataBagFormatterBase.cs index 69ee8dc..2452502 100644 --- a/src/DotNetOpenAuth.Core/Messaging/DataBagFormatterBase.cs +++ b/src/DotNetOpenAuth.Core/Messaging/DataBagFormatterBase.cs @@ -217,8 +217,8 @@ namespace DotNetOpenAuth.Messaging { if (this.signed) { using (var dataStream = new MemoryStream(data)) { var dataReader = new BinaryReader(dataStream); - signature = dataReader.ReadBuffer(); - data = dataReader.ReadBuffer(); + signature = dataReader.ReadBuffer(1024); + data = dataReader.ReadBuffer(8 * 1024); } // Verify that the verification code was issued by message authorization server. diff --git a/src/DotNetOpenAuth.Core/Messaging/MessagePartAttribute.cs b/src/DotNetOpenAuth.Core/Messaging/MessagePartAttribute.cs index 6fd95ee..8ef9b7e 100644 --- a/src/DotNetOpenAuth.Core/Messaging/MessagePartAttribute.cs +++ b/src/DotNetOpenAuth.Core/Messaging/MessagePartAttribute.cs @@ -101,6 +101,15 @@ namespace DotNetOpenAuth.Messaging { } /// <summary> + /// Gets or sets a value indicating whether the value contained by this property contains + /// sensitive information that should generally not be logged. + /// </summary> + /// <value> + /// <c>true</c> if this instance is security sensitive; otherwise, <c>false</c>. + /// </value> + public bool IsSecuritySensitive { get; set; } + + /// <summary> /// Gets or sets the minimum version of the protocol this attribute applies to /// and overrides any attributes with lower values for this property. /// </summary> diff --git a/src/DotNetOpenAuth.Core/Messaging/MessagingStrings.Designer.cs b/src/DotNetOpenAuth.Core/Messaging/MessagingStrings.Designer.cs index 2fe273f..4f89589 100644 --- a/src/DotNetOpenAuth.Core/Messaging/MessagingStrings.Designer.cs +++ b/src/DotNetOpenAuth.Core/Messaging/MessagingStrings.Designer.cs @@ -1,7 +1,7 @@ //------------------------------------------------------------------------------ // <auto-generated> // This code was generated by a tool. -// Runtime Version:4.0.30319.239 +// Runtime Version:4.0.30319.18010 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. @@ -106,6 +106,15 @@ namespace DotNetOpenAuth.Messaging { } /// <summary> + /// Looks up a localized string similar to Decoding failed due to data corruption.. + /// </summary> + internal static string DataCorruptionDetected { + get { + return ResourceManager.GetString("DataCorruptionDetected", resourceCulture); + } + } + + /// <summary> /// Looks up a localized string similar to An instance of type {0} was expected, but received unexpected derived type {1}.. /// </summary> internal static string DerivedTypeNotExpected { diff --git a/src/DotNetOpenAuth.Core/Messaging/MessagingStrings.resx b/src/DotNetOpenAuth.Core/Messaging/MessagingStrings.resx index fbdb63d..15ca046 100644 --- a/src/DotNetOpenAuth.Core/Messaging/MessagingStrings.resx +++ b/src/DotNetOpenAuth.Core/Messaging/MessagingStrings.resx @@ -112,10 +112,10 @@ <value>2.0</value> </resheader> <resheader name="reader"> - <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> + <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> </resheader> <resheader name="writer"> - <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> + <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> </resheader> <data name="ArgumentPropertyMissing" xml:space="preserve"> <value>Argument's {0}.{1} property is required but is empty or null.</value> @@ -333,4 +333,7 @@ <data name="UnexpectedBufferLength" xml:space="preserve"> <value>Unexpected buffer length.</value> </data> + <data name="DataCorruptionDetected" xml:space="preserve"> + <value>Decoding failed due to data corruption.</value> + </data> </root>
\ No newline at end of file diff --git a/src/DotNetOpenAuth.Core/Messaging/MessagingUtilities.cs b/src/DotNetOpenAuth.Core/Messaging/MessagingUtilities.cs index 5390da5..7aa4469 100644 --- a/src/DotNetOpenAuth.Core/Messaging/MessagingUtilities.cs +++ b/src/DotNetOpenAuth.Core/Messaging/MessagingUtilities.cs @@ -15,6 +15,9 @@ namespace DotNetOpenAuth.Messaging { using System.IO.Compression; using System.Linq; using System.Net; +#if CLR4 + using System.Net.Http; +#endif using System.Net.Mime; using System.Runtime.Serialization.Json; using System.Security; @@ -161,6 +164,29 @@ namespace DotNetOpenAuth.Messaging { return new OutgoingWebResponseActionResult(response); } +#if CLR4 + /// <summary> + /// Transforms an OutgoingWebResponse to a Web API-friendly HttpResponseMessage. + /// </summary> + /// <param name="outgoingResponse">The response to send to the user agent.</param> + /// <returns>The <see cref="HttpResponseMessage"/> instance to be returned by the Web API method.</returns> + public static HttpResponseMessage AsHttpResponseMessage(this OutgoingWebResponse outgoingResponse) { + HttpResponseMessage response = new HttpResponseMessage(outgoingResponse.Status); + if (outgoingResponse.ResponseStream != null) { + response.Content = new StreamContent(outgoingResponse.ResponseStream); + } + + var responseHeaders = outgoingResponse.Headers; + foreach (var header in responseHeaders.AllKeys) { + if (!response.Headers.TryAddWithoutValidation(header, responseHeaders[header])) { + response.Content.Headers.TryAddWithoutValidation(header, responseHeaders[header]); + } + } + + return response; + } +#endif + /// <summary> /// Gets the original request URL, as seen from the browser before any URL rewrites on the server if any. /// Cookieless session directory (if applicable) is also included. @@ -797,7 +823,7 @@ namespace DotNetOpenAuth.Messaging { using (var encryptedStream = new MemoryStream(buffer)) { var encryptedStreamReader = new BinaryReader(encryptedStream); - byte[] encryptedPrequel = encryptedStreamReader.ReadBytes(encryptedStreamReader.ReadInt32()); + byte[] encryptedPrequel = encryptedStreamReader.ReadBuffer(4096); byte[] prequel = crypto.Decrypt(encryptedPrequel, false); using (var symmetricCrypto = new RijndaelManaged()) { @@ -989,7 +1015,7 @@ namespace DotNetOpenAuth.Messaging { missingPaddingCharacters = 0; break; default: - throw ErrorUtilities.ThrowInternal("No more than two padding characters should be present for base64."); + throw new ProtocolException(MessagingStrings.DataCorruptionDetected, new ArgumentException("No more than two padding characters should be present for base64.")); } var builder = new StringBuilder(base64WebSafe, base64WebSafe.Length + missingPaddingCharacters); builder.Replace('-', '+').Replace('_', '/'); @@ -1659,10 +1685,17 @@ namespace DotNetOpenAuth.Messaging { /// Reads a buffer that is prefixed with its own length. /// </summary> /// <param name="reader">The binary reader positioned at the buffer length.</param> + /// <param name="maxBufferSize"> + /// The maximum size of the buffer that should be permitted. + /// Although the stream will indicate the size of the buffer, this mitigates data corruption + /// or DoS attacks causing the web server to allocate too much memory for a small data packet. + /// </param> /// <returns>The read buffer.</returns> - internal static byte[] ReadBuffer(this BinaryReader reader) { + internal static byte[] ReadBuffer(this BinaryReader reader, int maxBufferSize) { Requires.NotNull(reader, "reader"); + Requires.InRange(maxBufferSize > 0 && maxBufferSize < 1024 * 1024, "maxBufferSize"); int length = reader.ReadInt32(); + ErrorUtilities.VerifyProtocol(length <= maxBufferSize, MessagingStrings.DataCorruptionDetected); byte[] buffer = new byte[length]; ErrorUtilities.VerifyProtocol(reader.Read(buffer, 0, length) == length, MessagingStrings.UnexpectedBufferLength); return buffer; @@ -1894,7 +1927,8 @@ namespace DotNetOpenAuth.Messaging { // the public URL: if (serverVariables["HTTP_HOST"] != null) { ErrorUtilities.VerifySupported(request.Url.Scheme == Uri.UriSchemeHttps || request.Url.Scheme == Uri.UriSchemeHttp, "Only HTTP and HTTPS are supported protocols."); - string scheme = serverVariables["HTTP_X_FORWARDED_PROTO"] ?? request.Url.Scheme; + string scheme = serverVariables["HTTP_X_FORWARDED_PROTO"] ?? + (string.Equals(serverVariables["HTTP_FRONT_END_HTTPS"], "on", StringComparison.OrdinalIgnoreCase) ? Uri.UriSchemeHttps : request.Url.Scheme); Uri hostAndPort = new Uri(scheme + Uri.SchemeDelimiter + serverVariables["HTTP_HOST"]); UriBuilder publicRequestUri = new UriBuilder(request.Url); publicRequestUri.Scheme = scheme; diff --git a/src/DotNetOpenAuth.Core/Messaging/Reflection/MessagePart.cs b/src/DotNetOpenAuth.Core/Messaging/Reflection/MessagePart.cs index b2c4664..0f140d6 100644 --- a/src/DotNetOpenAuth.Core/Messaging/Reflection/MessagePart.cs +++ b/src/DotNetOpenAuth.Core/Messaging/Reflection/MessagePart.cs @@ -115,6 +115,7 @@ namespace DotNetOpenAuth.Messaging.Reflection { this.RequiredProtection = attribute.RequiredProtection; this.IsRequired = attribute.IsRequired; this.AllowEmpty = attribute.AllowEmpty; + this.IsSecuritySensitive = attribute.IsSecuritySensitive; this.memberDeclaredType = (this.field != null) ? this.field.FieldType : this.property.PropertyType; this.defaultMemberValue = DeriveDefaultValue(this.memberDeclaredType); @@ -189,6 +190,15 @@ namespace DotNetOpenAuth.Messaging.Reflection { internal bool IsConstantValueAvailableStatically { get; set; } /// <summary> + /// Gets or sets a value indicating whether the value contained by this property contains + /// sensitive information that should generally not be logged. + /// </summary> + /// <value> + /// <c>true</c> if this instance is security sensitive; otherwise, <c>false</c>. + /// </value> + internal bool IsSecuritySensitive { get; set; } + + /// <summary> /// Gets the static constant value for this message part without a message instance. /// </summary> internal string StaticConstantValue { diff --git a/src/DotNetOpenAuth.Core/Reporting.cs b/src/DotNetOpenAuth.Core/Reporting.cs index 951bb7c..80a3374 100644 --- a/src/DotNetOpenAuth.Core/Reporting.cs +++ b/src/DotNetOpenAuth.Core/Reporting.cs @@ -519,8 +519,12 @@ namespace DotNetOpenAuth { SendStats(); } catch (Exception ex) { // Something bad and unexpected happened. Just deactivate to avoid more trouble. - Logger.Library.Error("Error while trying to submit statistical report.", ex); - broken = true; + try { + broken = true; + Logger.Library.Error("Error while trying to submit statistical report.", ex); + } catch (Exception) { + // swallow exceptions to prevent a crash. + } } }); } diff --git a/src/DotNetOpenAuth.Core/Util.cs b/src/DotNetOpenAuth.Core/Util.cs index e9d617a..26b7b45 100644 --- a/src/DotNetOpenAuth.Core/Util.cs +++ b/src/DotNetOpenAuth.Core/Util.cs @@ -16,6 +16,7 @@ namespace DotNetOpenAuth { using DotNetOpenAuth.Configuration; using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.Messaging.Reflection; /// <summary> /// A grab-bag utility class. @@ -105,9 +106,16 @@ namespace DotNetOpenAuth { ////Contract.Requires(pairs != null); // CC: anonymous method can't handle it ErrorUtilities.VerifyArgumentNotNull(pairs, "pairs"); var dictionary = pairs as IDictionary<K, V>; + var messageDictionary = pairs as MessageDictionary; StringBuilder sb = new StringBuilder(dictionary != null ? dictionary.Count * 40 : 200); foreach (var pair in pairs) { - sb.AppendFormat("\t{0}: {1}{2}", pair.Key, pair.Value, Environment.NewLine); + var key = pair.Key.ToString(); + string value = pair.Value.ToString(); + if (messageDictionary != null && messageDictionary.Description.Mapping.ContainsKey(key) && messageDictionary.Description.Mapping[key].IsSecuritySensitive) { + value = "********"; + } + + sb.AppendFormat("\t{0}: {1}{2}", key, value, Environment.NewLine); } return sb.ToString(); }); diff --git a/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/AuthServerStrings.Designer.cs b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/AuthServerStrings.Designer.cs index 4b4f830..8941a94 100644 --- a/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/AuthServerStrings.Designer.cs +++ b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/AuthServerStrings.Designer.cs @@ -1,7 +1,7 @@ //------------------------------------------------------------------------------ // <auto-generated> // This code was generated by a tool. -// Runtime Version:4.0.30319.17614 +// Runtime Version:4.0.30319.18010 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. @@ -70,6 +70,15 @@ namespace DotNetOpenAuth.OAuth2 { } /// <summary> + /// Looks up a localized string similar to The access token's private signing key must be set.. + /// </summary> + internal static string AccessTokenSigningKeyMissing { + get { + return ResourceManager.GetString("AccessTokenSigningKeyMissing", resourceCulture); + } + } + + /// <summary> /// Looks up a localized string similar to The callback URL ({0}) is not allowed for this client.. /// </summary> internal static string ClientCallbackDisallowed { diff --git a/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/AuthServerStrings.resx b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/AuthServerStrings.resx index 29d841a..8aaa567 100644 --- a/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/AuthServerStrings.resx +++ b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/AuthServerStrings.resx @@ -112,14 +112,17 @@ <value>2.0</value> </resheader> <resheader name="reader"> - <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> + <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> </resheader> <resheader name="writer"> - <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> + <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> </resheader> <data name="AccessScopeExceedsGrantScope" xml:space="preserve"> <value>The requested access scope exceeds the grant scope.</value> </data> + <data name="AccessTokenSigningKeyMissing" xml:space="preserve"> + <value>The access token's private signing key must be set.</value> + </data> <data name="ClientCallbackDisallowed" xml:space="preserve"> <value>The callback URL ({0}) is not allowed for this client.</value> </data> diff --git a/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/AuthorizationServer.cs b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/AuthorizationServer.cs index 6a96c2d..1e404e7 100644 --- a/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/AuthorizationServer.cs +++ b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/AuthorizationServer.cs @@ -10,6 +10,9 @@ namespace DotNetOpenAuth.OAuth2 { using System.Diagnostics.CodeAnalysis; using System.Diagnostics.Contracts; using System.Linq; +#if CLR4 + using System.Net.Http; +#endif using System.Security.Cryptography; using System.Text; using System.Web; @@ -129,6 +132,17 @@ namespace DotNetOpenAuth.OAuth2 { this.Channel.Respond(response); } +#if CLR4 + /// <summary> + /// Handles an incoming request to the authorization server's token endpoint. + /// </summary> + /// <param name="request">The HTTP request.</param> + /// <returns>The HTTP response to send to the client.</returns> + public OutgoingWebResponse HandleTokenRequest(HttpRequestMessage request) { + return this.HandleTokenRequest(new HttpRequestInfo(request)); + } +#endif + /// <summary> /// Handles an incoming request to the authorization server's token endpoint. /// </summary> diff --git a/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/AuthorizationServerAccessToken.cs b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/AuthorizationServerAccessToken.cs index c577a0a..a127166 100644 --- a/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/AuthorizationServerAccessToken.cs +++ b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/AuthorizationServerAccessToken.cs @@ -45,6 +45,7 @@ namespace DotNetOpenAuth.OAuth2 { /// </summary> /// <returns>A non-empty string.</returns> protected internal override string Serialize() { + ErrorUtilities.VerifyHost(this.AccessTokenSigningKey != null, AuthServerStrings.AccessTokenSigningKeyMissing); var formatter = CreateFormatter(this.AccessTokenSigningKey, this.ResourceServerEncryptionKey); return formatter.Serialize(this); } diff --git a/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/ClientCredentialHttpBasicReader.cs b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/ClientCredentialHttpBasicReader.cs index 655d38f..6f0bbc4 100644 --- a/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/ClientCredentialHttpBasicReader.cs +++ b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/ClientCredentialHttpBasicReader.cs @@ -7,6 +7,7 @@ namespace DotNetOpenAuth.OAuth2.ChannelElements { using System; using System.Collections.Generic; + using System.Globalization; using System.Linq; using System.Text; using System.Web; @@ -21,10 +22,15 @@ namespace DotNetOpenAuth.OAuth2.ChannelElements { /// Gets this module's contribution to an HTTP 401 WWW-Authenticate header so the client knows what kind of authentication this module supports. /// </summary> public override string AuthenticateHeader { - get { return "Basic"; } + get { return string.Format(CultureInfo.InvariantCulture, "Basic realm=\"{0}\"", this.Realm); } } /// <summary> + /// Gets or sets the realm that is included in an HTTP WWW-Authenticate header included in a 401 Unauthorized response. + /// </summary> + public string Realm { get; set; } + + /// <summary> /// Attempts to extract client identification/authentication information from a message. /// </summary> /// <param name="authorizationServerHost">The authorization server host.</param> diff --git a/src/DotNetOpenAuth.OAuth2.Client/OAuth2/ClientBase.cs b/src/DotNetOpenAuth.OAuth2.Client/OAuth2/ClientBase.cs index c983f8c..49d0732 100644 --- a/src/DotNetOpenAuth.OAuth2.Client/OAuth2/ClientBase.cs +++ b/src/DotNetOpenAuth.OAuth2.Client/OAuth2/ClientBase.cs @@ -100,7 +100,7 @@ namespace DotNetOpenAuth.OAuth2 { Requires.NotNull(request, "request"); Requires.NotNull(authorization, "authorization"); Requires.True(!string.IsNullOrEmpty(authorization.AccessToken), "authorization"); - ErrorUtilities.VerifyProtocol(!authorization.AccessTokenExpirationUtc.HasValue || authorization.AccessTokenExpirationUtc < DateTime.UtcNow || authorization.RefreshToken != null, ClientStrings.AuthorizationExpired); + ErrorUtilities.VerifyProtocol(!authorization.AccessTokenExpirationUtc.HasValue || authorization.AccessTokenExpirationUtc >= DateTime.UtcNow || authorization.RefreshToken != null, ClientStrings.AuthorizationExpired); if (authorization.AccessTokenExpirationUtc.HasValue && authorization.AccessTokenExpirationUtc.Value < DateTime.UtcNow) { ErrorUtilities.VerifyProtocol(authorization.RefreshToken != null, ClientStrings.AccessTokenRefreshFailed); diff --git a/src/DotNetOpenAuth.OAuth2.Client/OAuth2/ClientCredentialApplicator.cs b/src/DotNetOpenAuth.OAuth2.Client/OAuth2/ClientCredentialApplicator.cs index cc4e45f..0677f5a 100644 --- a/src/DotNetOpenAuth.OAuth2.Client/OAuth2/ClientCredentialApplicator.cs +++ b/src/DotNetOpenAuth.OAuth2.Client/OAuth2/ClientCredentialApplicator.cs @@ -135,7 +135,9 @@ namespace DotNetOpenAuth.OAuth2 { clientIdentifier); } - request.Credentials = this.credential ?? new NetworkCredential(clientIdentifier, this.clientSecret); + // HttpWebRequest ignores the Credentials property until the remote server returns a 401 Unauthorized. + // So we also set the HTTP Authorization request header directly. + OAuthUtilities.ApplyHttpBasicAuth(request.Headers, clientIdentifier, this.clientSecret); } } } diff --git a/src/DotNetOpenAuth.OAuth2.ClientAuthorization/OAuth2/Messages/AccessTokenResourceOwnerPasswordCredentialsRequest.cs b/src/DotNetOpenAuth.OAuth2.ClientAuthorization/OAuth2/Messages/AccessTokenResourceOwnerPasswordCredentialsRequest.cs index 52e65be..a5d958a 100644 --- a/src/DotNetOpenAuth.OAuth2.ClientAuthorization/OAuth2/Messages/AccessTokenResourceOwnerPasswordCredentialsRequest.cs +++ b/src/DotNetOpenAuth.OAuth2.ClientAuthorization/OAuth2/Messages/AccessTokenResourceOwnerPasswordCredentialsRequest.cs @@ -82,7 +82,7 @@ namespace DotNetOpenAuth.OAuth2.Messages { /// Gets or sets the user's password. /// </summary> /// <value>The password.</value> - [MessagePart(Protocol.password, IsRequired = true)] + [MessagePart(Protocol.password, IsRequired = true, IsSecuritySensitive = true)] internal string Password { get; set; } /// <summary> diff --git a/src/DotNetOpenAuth.OAuth2.ClientAuthorization/OAuth2/Messages/AuthenticatedClientRequestBase.cs b/src/DotNetOpenAuth.OAuth2.ClientAuthorization/OAuth2/Messages/AuthenticatedClientRequestBase.cs index 4631d83..96eecbb 100644 --- a/src/DotNetOpenAuth.OAuth2.ClientAuthorization/OAuth2/Messages/AuthenticatedClientRequestBase.cs +++ b/src/DotNetOpenAuth.OAuth2.ClientAuthorization/OAuth2/Messages/AuthenticatedClientRequestBase.cs @@ -44,7 +44,7 @@ namespace DotNetOpenAuth.OAuth2.Messages { /// <remarks> /// REQUIRED. The client secret as described in Section 2.1 (Client Credentials). OPTIONAL if no client secret was issued. /// </remarks> - [MessagePart(Protocol.client_secret, IsRequired = false)] + [MessagePart(Protocol.client_secret, IsRequired = false, IsSecuritySensitive = true)] public string ClientSecret { get; internal set; } /// <summary> diff --git a/src/DotNetOpenAuth.OAuth2.ResourceServer/OAuth2/StandardAccessTokenAnalyzer.cs b/src/DotNetOpenAuth.OAuth2.ResourceServer/OAuth2/StandardAccessTokenAnalyzer.cs index 54d86ff..33b99f9 100644 --- a/src/DotNetOpenAuth.OAuth2.ResourceServer/OAuth2/StandardAccessTokenAnalyzer.cs +++ b/src/DotNetOpenAuth.OAuth2.ResourceServer/OAuth2/StandardAccessTokenAnalyzer.cs @@ -8,6 +8,7 @@ namespace DotNetOpenAuth.OAuth2 { using System; using System.Collections.Generic; using System.Diagnostics.Contracts; + using System.IO; using System.Security.Cryptography; using DotNetOpenAuth.Messaging; using DotNetOpenAuth.OAuth2.ChannelElements; @@ -23,8 +24,7 @@ namespace DotNetOpenAuth.OAuth2 { /// <param name="resourceServerPrivateEncryptionKey">The crypto service provider with the resource server private encryption key.</param> public StandardAccessTokenAnalyzer(RSACryptoServiceProvider authorizationServerPublicSigningKey, RSACryptoServiceProvider resourceServerPrivateEncryptionKey) { Requires.NotNull(authorizationServerPublicSigningKey, "authorizationServerPublicSigningKey"); - Requires.NotNull(resourceServerPrivateEncryptionKey, "resourceServerPrivateEncryptionKey"); - Requires.True(!resourceServerPrivateEncryptionKey.PublicOnly, "resourceServerPrivateEncryptionKey"); + Requires.True(resourceServerPrivateEncryptionKey == null || !resourceServerPrivateEncryptionKey.PublicOnly, "resourceServerPrivateEncryptionKey"); this.AuthorizationServerPublicSigningKey = authorizationServerPublicSigningKey; this.ResourceServerPrivateEncryptionKey = resourceServerPrivateEncryptionKey; } @@ -49,9 +49,15 @@ namespace DotNetOpenAuth.OAuth2 { /// <returns>The deserialized, validated token.</returns> /// <exception cref="ProtocolException">Thrown if the access token is expired, invalid, or from an untrusted authorization server.</exception> public virtual AccessToken DeserializeAccessToken(IDirectedProtocolMessage message, string accessToken) { + ErrorUtilities.VerifyProtocol(!string.IsNullOrEmpty(accessToken), ResourceServerStrings.MissingAccessToken); var accessTokenFormatter = AccessToken.CreateFormatter(this.AuthorizationServerPublicSigningKey, this.ResourceServerPrivateEncryptionKey); var token = new AccessToken(); - accessTokenFormatter.Deserialize(token, message, accessToken, Protocol.access_token); + try { + accessTokenFormatter.Deserialize(token, message, accessToken, Protocol.access_token); + } catch (IOException ex) { + throw new ProtocolException(ResourceServerStrings.InvalidAccessToken, ex); + } + return token; } } diff --git a/src/DotNetOpenAuth.OpenId.Provider/OpenId/Provider/AssociationDataBag.cs b/src/DotNetOpenAuth.OpenId.Provider/OpenId/Provider/AssociationDataBag.cs index ee48670..f619b76 100644 --- a/src/DotNetOpenAuth.OpenId.Provider/OpenId/Provider/AssociationDataBag.cs +++ b/src/DotNetOpenAuth.OpenId.Provider/OpenId/Provider/AssociationDataBag.cs @@ -72,7 +72,7 @@ namespace DotNetOpenAuth.OpenId.Provider { public void Deserialize(Stream stream) { var reader = new BinaryReader(stream); this.IsPrivateAssociation = reader.ReadBoolean(); - this.Secret = reader.ReadBuffer(); + this.Secret = reader.ReadBuffer(256); this.ExpiresUtc = TimestampEncoder.Epoch + TimeSpan.FromSeconds(reader.ReadInt32()); } diff --git a/src/DotNetOpenAuth.OpenId.RelyingParty.UI/OpenId/RelyingParty/SelectorButton.cs b/src/DotNetOpenAuth.OpenId.RelyingParty.UI/OpenId/RelyingParty/SelectorButton.cs index b4d0aa0..670189c 100644 --- a/src/DotNetOpenAuth.OpenId.RelyingParty.UI/OpenId/RelyingParty/SelectorButton.cs +++ b/src/DotNetOpenAuth.OpenId.RelyingParty.UI/OpenId/RelyingParty/SelectorButton.cs @@ -13,6 +13,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// A button that would appear in the <see cref="OpenIdSelector"/> control via its <see cref="OpenIdSelector.Buttons"/> collection. /// </summary> [ContractClass(typeof(SelectorButtonContract))] + [Serializable] public abstract class SelectorButton { /// <summary> /// Initializes a new instance of the <see cref="SelectorButton"/> class. diff --git a/src/DotNetOpenAuth.OpenId/OpenId/ChannelElements/ReturnToSignatureBindingElement.cs b/src/DotNetOpenAuth.OpenId/OpenId/ChannelElements/ReturnToSignatureBindingElement.cs index fa7768b..912a322 100644 --- a/src/DotNetOpenAuth.OpenId/OpenId/ChannelElements/ReturnToSignatureBindingElement.cs +++ b/src/DotNetOpenAuth.OpenId/OpenId/ChannelElements/ReturnToSignatureBindingElement.cs @@ -195,6 +195,11 @@ namespace DotNetOpenAuth.OpenId.ChannelElements { try { if (cryptoKey == null) { cryptoKey = this.cryptoKeyStore.GetKey(SecretUri.AbsoluteUri, returnToParameters[ReturnToSignatureHandleParameterName]); + ErrorUtilities.VerifyProtocol( + cryptoKey != null, + MessagingStrings.MissingDecryptionKeyForHandle, + SecretUri.AbsoluteUri, + returnToParameters[ReturnToSignatureHandleParameterName]); } using (var signer = HmacAlgorithms.Create(HmacAlgorithms.HmacSha256, cryptoKey.Key)) { diff --git a/src/DotNetOpenAuth.OpenId/OpenId/UriIdentifier.cs b/src/DotNetOpenAuth.OpenId/OpenId/UriIdentifier.cs index 631eab6..41417de 100644 --- a/src/DotNetOpenAuth.OpenId/OpenId/UriIdentifier.cs +++ b/src/DotNetOpenAuth.OpenId/OpenId/UriIdentifier.cs @@ -69,6 +69,11 @@ namespace DotNetOpenAuth.OpenId { /// </remarks> [SuppressMessage("Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline", Justification = "Some things just can't be done in a field initializer.")] static UriIdentifier() { + if (Type.GetType("Mono.Runtime") != null) { + // Uri scheme registration doesn't work on mono. + return; + } + // Our first attempt to handle trailing periods in path segments is to leverage // full trust if it's available to rewrite the rules. // In fact this is the ONLY way in .NET 3.5 (and arguably in .NET 4.0) to send diff --git a/src/DotNetOpenAuth.Test/DotNetOpenAuth.Test.csproj b/src/DotNetOpenAuth.Test/DotNetOpenAuth.Test.csproj index b58aa17..189a569 100644 --- a/src/DotNetOpenAuth.Test/DotNetOpenAuth.Test.csproj +++ b/src/DotNetOpenAuth.Test/DotNetOpenAuth.Test.csproj @@ -249,6 +249,7 @@ <Compile Include="Mocks\TestMessageFactory.cs" /> <Compile Include="OAuth2\AuthorizationServerTests.cs" /> <Compile Include="OAuth2\MessageFactoryTests.cs" /> + <Compile Include="OAuth2\ResourceServerTests.cs" /> <Compile Include="OAuth2\UserAgentClientAuthorizeTests.cs" /> <Compile Include="OAuth2\OAuth2Coordinator.cs" /> <Compile Include="OAuth2\OAuth2TestBase.cs" /> diff --git a/src/DotNetOpenAuth.Test/Messaging/MessagingUtilitiesTests.cs b/src/DotNetOpenAuth.Test/Messaging/MessagingUtilitiesTests.cs index 5c3870c..8620b93 100644 --- a/src/DotNetOpenAuth.Test/Messaging/MessagingUtilitiesTests.cs +++ b/src/DotNetOpenAuth.Test/Messaging/MessagingUtilitiesTests.cs @@ -9,13 +9,17 @@ namespace DotNetOpenAuth.Test.Messaging { using System.Collections.Generic; using System.Collections.Specialized; using System.Diagnostics; + using System.Globalization; using System.IO; + using System.Linq; using System.Net; + using System.Net.Http; using System.Text; using System.Text.RegularExpressions; using System.Web; using DotNetOpenAuth.Messaging; using DotNetOpenAuth.Test.Mocks; + using Moq; using NUnit.Framework; [TestFixture] @@ -63,6 +67,41 @@ namespace DotNetOpenAuth.Test.Messaging { } [Test] + public void AsHttpResponseMessage() { + var responseContent = new byte[10]; + (new Random()).NextBytes(responseContent); + var responseStream = new MemoryStream(responseContent); + var outgoingResponse = new OutgoingWebResponse(); + outgoingResponse.Headers.Add("X-SOME-HEADER", "value"); + outgoingResponse.Headers.Add("Content-Length", responseContent.Length.ToString(CultureInfo.InvariantCulture)); + outgoingResponse.ResponseStream = responseStream; + + var httpResponseMessage = outgoingResponse.AsHttpResponseMessage(); + Assert.That(httpResponseMessage, Is.Not.Null); + Assert.That(httpResponseMessage.Headers.GetValues("X-SOME-HEADER").ToList(), Is.EqualTo(new[] { "value" })); + Assert.That( + httpResponseMessage.Content.Headers.GetValues("Content-Length").ToList(), + Is.EqualTo(new[] { responseContent.Length.ToString(CultureInfo.InvariantCulture) })); + var actualContent = new byte[responseContent.Length + 1]; // give the opportunity to provide a bit more data than we expect. + var bytesRead = httpResponseMessage.Content.ReadAsStreamAsync().Result.Read(actualContent, 0, actualContent.Length); + Assert.That(bytesRead, Is.EqualTo(responseContent.Length)); // verify that only the data we expected came back. + var trimmedActualContent = new byte[bytesRead]; + Array.Copy(actualContent, trimmedActualContent, bytesRead); + Assert.That(trimmedActualContent, Is.EqualTo(responseContent)); + } + + [Test] + public void AsHttpResponseMessageNoContent() { + var outgoingResponse = new OutgoingWebResponse(); + outgoingResponse.Headers.Add("X-SOME-HEADER", "value"); + + var httpResponseMessage = outgoingResponse.AsHttpResponseMessage(); + Assert.That(httpResponseMessage, Is.Not.Null); + Assert.That(httpResponseMessage.Headers.GetValues("X-SOME-HEADER").ToList(), Is.EqualTo(new[] { "value" })); + Assert.That(httpResponseMessage.Content, Is.Null); + } + + [Test] public void ToDictionary() { NameValueCollection nvc = new NameValueCollection(); nvc["a"] = "b"; @@ -151,7 +190,7 @@ namespace DotNetOpenAuth.Test.Messaging { var httpHandler = new TestWebRequestHandler(); bool callbackTriggered = false; httpHandler.Callback = req => { - Match m = Regex.Match(req.ContentType, "multipart/form-data; boundary=(.+)"); + var m = Regex.Match(req.ContentType, "multipart/form-data; boundary=(.+)"); Assert.IsTrue(m.Success, "Content-Type HTTP header not set correctly."); string boundary = m.Groups[1].Value; boundary = boundary.Substring(0, boundary.IndexOf(';')); // trim off charset diff --git a/src/DotNetOpenAuth.Test/OAuth2/OAuth2TestBase.cs b/src/DotNetOpenAuth.Test/OAuth2/OAuth2TestBase.cs index f43a349..b9e32fe 100644 --- a/src/DotNetOpenAuth.Test/OAuth2/OAuth2TestBase.cs +++ b/src/DotNetOpenAuth.Test/OAuth2/OAuth2TestBase.cs @@ -8,6 +8,7 @@ namespace DotNetOpenAuth.Test.OAuth2 { using System; using System.Collections.Generic; using System.Linq; + using System.Security.Cryptography; using System.Text; using DotNetOpenAuth.Messaging; using DotNetOpenAuth.Messaging.Bindings; @@ -29,6 +30,8 @@ namespace DotNetOpenAuth.Test.OAuth2 { protected static readonly Uri ClientCallback = new Uri("http://client/callback"); + protected static readonly RSACryptoServiceProvider AsymmetricKey = new RSACryptoServiceProvider(512); + protected static readonly AuthorizationServerDescription AuthorizationServerDescription = new AuthorizationServerDescription { AuthorizationEndpoint = new Uri("https://authserver/authorize"), TokenEndpoint = new Uri("https://authserver/token"), @@ -55,7 +58,7 @@ namespace DotNetOpenAuth.Test.OAuth2 { MessagingUtilities.AreEquivalent(d.Scope, TestScopes)))).Returns(true); string canonicalUserName = ResourceOwnerUsername; authHostMock.Setup(m => m.TryAuthorizeResourceOwnerCredentialGrant(ResourceOwnerUsername, ResourceOwnerPassword, It.IsAny<IAccessTokenRequest>(), out canonicalUserName)).Returns(true); - authHostMock.Setup(m => m.CreateAccessToken(It.IsAny<IAccessTokenRequest>())).Returns(new AccessTokenResult(new AuthorizationServerAccessToken())); + authHostMock.Setup(m => m.CreateAccessToken(It.IsAny<IAccessTokenRequest>())).Returns(new AccessTokenResult(new AuthorizationServerAccessToken() { AccessTokenSigningKey = AsymmetricKey })); return authHostMock; } } diff --git a/src/DotNetOpenAuth.Test/OAuth2/ResourceServerTests.cs b/src/DotNetOpenAuth.Test/OAuth2/ResourceServerTests.cs new file mode 100644 index 0000000..a4d09de --- /dev/null +++ b/src/DotNetOpenAuth.Test/OAuth2/ResourceServerTests.cs @@ -0,0 +1,110 @@ +//----------------------------------------------------------------------- +// <copyright file="ResourceServerTests.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Test.OAuth2 { + using System; + using System.Collections.Generic; + using System.Collections.Specialized; + using System.Linq; + using System.Security.Cryptography; + using System.Text; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OAuth2; + using DotNetOpenAuth.OAuth2.ChannelElements; + using DotNetOpenAuth.OAuth2.Messages; + using Moq; + using NUnit.Framework; + + [TestFixture] + public class ResourceServerTests : OAuth2TestBase { + [Test] + public void GetAccessTokenWithMissingAccessToken() { + var resourceServer = new ResourceServer(new StandardAccessTokenAnalyzer(AsymmetricKey, null)); + + var requestHeaders = new NameValueCollection { + { "Authorization", "Bearer " }, + }; + var request = new HttpRequestInfo("GET", new Uri("http://localhost/resource"), headers: requestHeaders); + Assert.That(() => resourceServer.GetAccessToken(request), Throws.InstanceOf<ProtocolException>()); + } + + [Test] + public void GetPrincipalWithMissingAccessToken() { + var resourceServer = new ResourceServer(new StandardAccessTokenAnalyzer(AsymmetricKey, null)); + + var requestHeaders = new NameValueCollection { + { "Authorization", "Bearer " }, + }; + var request = new HttpRequestInfo("GET", new Uri("http://localhost/resource"), headers: requestHeaders); + Assert.That(() => resourceServer.GetPrincipal(request), Throws.InstanceOf<ProtocolException>()); + } + + [Test] + public void GetAccessTokenWithTotallyFakeToken() { + var resourceServer = new ResourceServer(new StandardAccessTokenAnalyzer(AsymmetricKey, null)); + + var requestHeaders = new NameValueCollection { + { "Authorization", "Bearer foobar" }, + }; + var request = new HttpRequestInfo("GET", new Uri("http://localhost/resource"), headers: requestHeaders); + Assert.That(() => resourceServer.GetAccessToken(request), Throws.InstanceOf<ProtocolException>()); + } + + [Test] + public void GetAccessTokenWithCorruptedToken() { + var accessToken = this.ObtainValidAccessToken(); + + var resourceServer = new ResourceServer(new StandardAccessTokenAnalyzer(AsymmetricKey, null)); + + var requestHeaders = new NameValueCollection { + { "Authorization", "Bearer " + accessToken.Substring(0, accessToken.Length - 1) + "zzz" }, + }; + var request = new HttpRequestInfo("GET", new Uri("http://localhost/resource"), headers: requestHeaders); + Assert.That(() => resourceServer.GetAccessToken(request), Throws.InstanceOf<ProtocolException>()); + } + + [Test] + public void GetAccessTokenWithValidToken() { + var accessToken = this.ObtainValidAccessToken(); + + var resourceServer = new ResourceServer(new StandardAccessTokenAnalyzer(AsymmetricKey, null)); + + var requestHeaders = new NameValueCollection { + { "Authorization", "Bearer " + accessToken }, + }; + var request = new HttpRequestInfo("GET", new Uri("http://localhost/resource"), headers: requestHeaders); + var resourceServerDecodedToken = resourceServer.GetAccessToken(request); + Assert.That(resourceServerDecodedToken, Is.Not.Null); + } + + private string ObtainValidAccessToken() { + string accessToken = null; + var authServer = CreateAuthorizationServerMock(); + authServer.Setup( + a => a.IsAuthorizationValid(It.Is<IAuthorizationDescription>(d => d.User == null && d.ClientIdentifier == ClientId && MessagingUtilities.AreEquivalent(d.Scope, TestScopes)))) + .Returns(true); + authServer.Setup( + a => a.TryAuthorizeClientCredentialsGrant(It.Is<IAccessTokenRequest>(d => d.ClientIdentifier == ClientId && MessagingUtilities.AreEquivalent(d.Scope, TestScopes)))) + .Returns(true); + var coordinator = new OAuth2Coordinator<WebServerClient>( + AuthorizationServerDescription, + authServer.Object, + new WebServerClient(AuthorizationServerDescription), + client => { + var authState = client.GetClientAccessToken(TestScopes); + Assert.That(authState.AccessToken, Is.Not.Null.And.Not.Empty); + Assert.That(authState.RefreshToken, Is.Null); + accessToken = authState.AccessToken; + }, + server => { + server.HandleTokenRequest().Respond(); + }); + coordinator.Run(); + + return accessToken; + } + } +} diff --git a/src/version.txt b/src/version.txt index 17ee6ea..53b95c0 100644 --- a/src/version.txt +++ b/src/version.txt @@ -1,3 +1,3 @@ -4.1.2 +4.1.5 -0.25.0-draft3 +0.25.0-draft5 |