Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/dotnet/aspnetcore.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMartin Costello <martin@martincostello.com>2021-09-13 19:01:36 +0300
committerGitHub <noreply@github.com>2021-09-13 19:01:36 +0300
commit6f5d12202214cd2010653b6bfc77f6a77d09e6d5 (patch)
treef4789f751147243aa294e4f0a22e8ed85d01ecca
parente6a5af6633f08890afbd99a6ca7687c623454768 (diff)
Use one-shot static hash methods (#36368)
* Use one-shot static hash methods Use one-shot static hash methods for SHA1 and HMACSHA*. Also a readability change to use Convert.ToHexString(). * Assert on appsecret_proof Assert that the correct secret proof is added to the request URL. * Update URL Update URL that redirects. * Fix invalid documentation link Update link that resulted in a 404. * Expand TwitterHandler coverage Add test coverage for CreateTicketAsync(), ObtainAccessTokenAsync() and RetrieveUserDetailsAsync().
-rw-r--r--src/Mvc/shared/Mvc.Views.TestCommon/TestRazorCompiledItem.cs19
-rw-r--r--src/Security/Authentication/Facebook/src/FacebookHandler.cs17
-rw-r--r--src/Security/Authentication/Facebook/src/FacebookOptions.cs2
-rw-r--r--src/Security/Authentication/Twitter/src/TwitterHandler.cs26
-rw-r--r--src/Security/Authentication/test/FacebookTests.cs1
-rw-r--r--src/Security/Authentication/test/TwitterTests.cs184
-rw-r--r--src/Servers/IIS/IIS/test/testassets/shared/WebSockets/HandshakeHelpers.cs11
7 files changed, 198 insertions, 62 deletions
diff --git a/src/Mvc/shared/Mvc.Views.TestCommon/TestRazorCompiledItem.cs b/src/Mvc/shared/Mvc.Views.TestCommon/TestRazorCompiledItem.cs
index 4a077fb189..df74c31709 100644
--- a/src/Mvc/shared/Mvc.Views.TestCommon/TestRazorCompiledItem.cs
+++ b/src/Mvc/shared/Mvc.Views.TestCommon/TestRazorCompiledItem.cs
@@ -1,9 +1,6 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-using System;
-using System.Collections.Generic;
-using System.Globalization;
using System.Security.Cryptography;
using System.Text;
@@ -49,20 +46,8 @@ namespace Microsoft.AspNetCore.Razor.Hosting
public static string GetChecksum(string content)
{
- byte[] bytes;
- using (var sha = SHA1.Create())
- {
- bytes = sha.ComputeHash(Encoding.UTF8.GetBytes(content));
- }
-
- var result = new StringBuilder(bytes.Length);
- for (var i = 0; i < bytes.Length; i++)
- {
- // The x2 format means lowercase hex, where each byte is a 2-character string.
- result.Append(bytes[i].ToString("x2", CultureInfo.InvariantCulture));
- }
-
- return result.ToString();
+ var bytes = SHA1.HashData(Encoding.UTF8.GetBytes(content));
+ return Convert.ToHexString(bytes).ToLowerInvariant();
}
}
} \ No newline at end of file
diff --git a/src/Security/Authentication/Facebook/src/FacebookHandler.cs b/src/Security/Authentication/Facebook/src/FacebookHandler.cs
index 1c7491329f..b544bb92db 100644
--- a/src/Security/Authentication/Facebook/src/FacebookHandler.cs
+++ b/src/Security/Authentication/Facebook/src/FacebookHandler.cs
@@ -1,7 +1,6 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-using System.Collections.Generic;
using System.Globalization;
using System.Net.Http;
using System.Security.Claims;
@@ -9,7 +8,6 @@ using System.Security.Cryptography;
using System.Text;
using System.Text.Encodings.Web;
using System.Text.Json;
-using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication.OAuth;
using Microsoft.AspNetCore.WebUtilities;
using Microsoft.Extensions.Logging;
@@ -60,16 +58,15 @@ namespace Microsoft.AspNetCore.Authentication.Facebook
private string GenerateAppSecretProof(string accessToken)
{
- using (var algorithm = new HMACSHA256(Encoding.ASCII.GetBytes(Options.AppSecret)))
+ var key = Encoding.ASCII.GetBytes(Options.AppSecret);
+ var tokenBytes = Encoding.ASCII.GetBytes(accessToken);
+ var hash = HMACSHA256.HashData(key, tokenBytes);
+ var builder = new StringBuilder();
+ for (int i = 0; i < hash.Length; i++)
{
- var hash = algorithm.ComputeHash(Encoding.ASCII.GetBytes(accessToken));
- var builder = new StringBuilder();
- for (int i = 0; i < hash.Length; i++)
- {
- builder.Append(hash[i].ToString("x2", CultureInfo.InvariantCulture));
- }
- return builder.ToString();
+ builder.Append(hash[i].ToString("x2", CultureInfo.InvariantCulture));
}
+ return builder.ToString();
}
/// <inheritdoc />
diff --git a/src/Security/Authentication/Facebook/src/FacebookOptions.cs b/src/Security/Authentication/Facebook/src/FacebookOptions.cs
index 21abd7777e..8d853399b2 100644
--- a/src/Security/Authentication/Facebook/src/FacebookOptions.cs
+++ b/src/Security/Authentication/Facebook/src/FacebookOptions.cs
@@ -88,7 +88,7 @@ namespace Microsoft.AspNetCore.Authentication.Facebook
/// <summary>
/// Gets or sets if the <c>appsecret_proof</c> should be generated and sent with Facebook API calls.
/// </summary>
- /// <remarks>See https://developers.facebook.com/docs/graph-api/securing-requests/#appsecret_proof for more details.</remarks>
+ /// <remarks>See https://developers.facebook.com/docs/graph-api/security#appsecret_proof for more details.</remarks>
/// <value>Defaults to <see langword="true"/>.</value>
public bool SendAppSecretProof { get; set; }
diff --git a/src/Security/Authentication/Twitter/src/TwitterHandler.cs b/src/Security/Authentication/Twitter/src/TwitterHandler.cs
index 9d18aef74d..cade21e28a 100644
--- a/src/Security/Authentication/Twitter/src/TwitterHandler.cs
+++ b/src/Security/Authentication/Twitter/src/TwitterHandler.cs
@@ -1,20 +1,13 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-using System;
-using System.Collections.Generic;
-using System.Diagnostics;
using System.Globalization;
-using System.Linq;
using System.Net.Http;
-using System.Net.Http.Headers;
using System.Security.Claims;
using System.Security.Cryptography;
using System.Text;
using System.Text.Encodings.Web;
using System.Text.Json;
-using System.Threading.Tasks;
-using System.Xml;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.WebUtilities;
using Microsoft.Extensions.Logging;
@@ -290,7 +283,7 @@ namespace Microsoft.AspNetCore.Authentication.Twitter
private async Task<AccessToken> ObtainAccessTokenAsync(RequestToken token, string verifier)
{
- // https://dev.twitter.com/docs/api/1/post/oauth/access_token
+ // https://developer.twitter.com/en/docs/authentication/api-reference/access_token
Logger.ObtainAccessToken();
@@ -342,16 +335,13 @@ namespace Microsoft.AspNetCore.Authentication.Twitter
private static string ComputeSignature(string consumerSecret, string? tokenSecret, string signatureData)
{
- using (var algorithm = new HMACSHA1())
- {
- algorithm.Key = Encoding.ASCII.GetBytes(
- string.Format(CultureInfo.InvariantCulture,
- "{0}&{1}",
- Uri.EscapeDataString(consumerSecret),
- string.IsNullOrEmpty(tokenSecret) ? string.Empty : Uri.EscapeDataString(tokenSecret)));
- var hash = algorithm.ComputeHash(Encoding.ASCII.GetBytes(signatureData));
- return Convert.ToBase64String(hash);
- }
+ var key = Encoding.ASCII.GetBytes(
+ string.Format(CultureInfo.InvariantCulture,
+ "{0}&{1}",
+ Uri.EscapeDataString(consumerSecret),
+ string.IsNullOrEmpty(tokenSecret) ? string.Empty : Uri.EscapeDataString(tokenSecret)));
+ var hash = HMACSHA1.HashData(key, Encoding.ASCII.GetBytes(signatureData));
+ return Convert.ToBase64String(hash);
}
// https://developer.twitter.com/en/docs/apps/callback-urls
diff --git a/src/Security/Authentication/test/FacebookTests.cs b/src/Security/Authentication/test/FacebookTests.cs
index 638e6debed..b0b08b2424 100644
--- a/src/Security/Authentication/test/FacebookTests.cs
+++ b/src/Security/Authentication/test/FacebookTests.cs
@@ -369,6 +369,7 @@ namespace Microsoft.AspNetCore.Authentication.Facebook
Assert.Equal(1, finalUserInfoEndpoint.Count(c => c == '?'));
Assert.Contains("fields=email,timezone,picture", finalUserInfoEndpoint);
Assert.Contains("&access_token=", finalUserInfoEndpoint);
+ Assert.Contains("&appsecret_proof=b7fb6d5a4510926b4af6fe080497827d791dc45fe6541d88ba77bdf6e8e208c6&", finalUserInfoEndpoint);
}
private static async Task<IHost> CreateHost(Action<IApplicationBuilder> configure, Action<IServiceCollection> configureServices, Func<HttpContext, Task<bool>> handler)
diff --git a/src/Security/Authentication/test/TwitterTests.cs b/src/Security/Authentication/test/TwitterTests.cs
index 175f8e72a5..198cca6641 100644
--- a/src/Security/Authentication/test/TwitterTests.cs
+++ b/src/Security/Authentication/test/TwitterTests.cs
@@ -1,20 +1,24 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-using Microsoft.AspNetCore.Builder;
-using Microsoft.AspNetCore.Hosting;
-using Microsoft.AspNetCore.Http;
-using Microsoft.AspNetCore.TestHost;
-using Microsoft.Extensions.DependencyInjection;
-using Microsoft.Extensions.Hosting;
-using Microsoft.Net.Http.Headers;
using System;
+using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Security.Claims;
using System.Text;
+using System.Text.Encodings.Web;
using System.Threading.Tasks;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.DataProtection;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.TestHost;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.Logging.Abstractions;
+using Microsoft.Net.Http.Headers;
using Xunit;
namespace Microsoft.AspNetCore.Authentication.Twitter
@@ -381,6 +385,158 @@ namespace Microsoft.AspNetCore.Authentication.Twitter
Assert.Equal(HttpStatusCode.NotAcceptable, response.StatusCode);
}
+ [Fact]
+ public async Task CanSignIn()
+ {
+ var stateFormat = new SecureDataFormat<RequestToken>(new RequestTokenSerializer(), new EphemeralDataProtectionProvider(NullLoggerFactory.Instance).CreateProtector("TwitterTest"));
+ using var host = await CreateHost((options) =>
+ {
+ options.ConsumerKey = "Test App Id";
+ options.ConsumerSecret = "PLACEHOLDER";
+ options.SaveTokens = true;
+ options.StateDataFormat = stateFormat;
+ options.BackchannelHttpHandler = new TestHttpMessageHandler
+ {
+ Sender = req =>
+ {
+ if (req.RequestUri.GetComponents(UriComponents.SchemeAndServer | UriComponents.Path, UriFormat.UriEscaped) == "https://api.twitter.com/oauth/access_token")
+ {
+ var res = new HttpResponseMessage(HttpStatusCode.OK);
+ var content = new Dictionary<string, string>()
+ {
+ ["oauth_token"] = "Test Access Token",
+ ["oauth_token_secret"] = "PLACEHOLDER",
+ ["user_id"] = "123456",
+ ["screen_name"] = "@dotnet"
+ };
+ res.Content = new FormUrlEncodedContent(content);
+ return res;
+ }
+ return null;
+ }
+ };
+ });
+
+ var token = new RequestToken()
+ {
+ Token = "TestToken",
+ TokenSecret = "PLACEHOLDER",
+ Properties = new()
+ };
+
+ var correlationKey = ".xsrf";
+ var correlationValue = "TestCorrelationId";
+ token.Properties.Items.Add(correlationKey, correlationValue);
+ token.Properties.RedirectUri = "/me";
+ var state = stateFormat.Protect(token);
+ using var server = host.GetTestServer();
+ var transaction = await server.SendAsync(
+ "https://example.com/signin-twitter?oauth_token=TestToken&oauth_verifier=TestVerifier",
+ $".AspNetCore.Correlation.{correlationValue}=N;__TwitterState={UrlEncoder.Default.Encode(state)}");
+ Assert.Equal(HttpStatusCode.Redirect, transaction.Response.StatusCode);
+ Assert.Equal("/me", transaction.Response.Headers.GetValues("Location").First());
+
+ var authCookie = transaction.AuthenticationCookieValue;
+ transaction = await server.SendAsync("https://example.com/me", authCookie);
+ Assert.Equal(HttpStatusCode.OK, transaction.Response.StatusCode);
+ var expectedIssuer = TwitterDefaults.AuthenticationScheme;
+ Assert.Equal("@dotnet", transaction.FindClaimValue(ClaimTypes.Name, expectedIssuer));
+ Assert.Equal("123456", transaction.FindClaimValue(ClaimTypes.NameIdentifier, expectedIssuer));
+ Assert.Equal("123456", transaction.FindClaimValue("urn:twitter:userid", expectedIssuer));
+ Assert.Equal("@dotnet", transaction.FindClaimValue("urn:twitter:screenname", expectedIssuer));
+
+ transaction = await server.SendAsync("https://example.com/tokens", authCookie);
+ Assert.Equal(HttpStatusCode.OK, transaction.Response.StatusCode);
+ Assert.Equal("Test Access Token", transaction.FindTokenValue("access_token"));
+ Assert.Equal("PLACEHOLDER", transaction.FindTokenValue("access_token_secret"));
+ }
+
+ [Fact]
+ public async Task CanFetchUserDetails()
+ {
+ var verifyCredentialsEndpoint = "https://api.twitter.com/1.1/account/verify_credentials.json";
+ var finalVerifyCredentialsEndpoint = string.Empty;
+ var finalAuthorizationParameter = string.Empty;
+ var stateFormat = new SecureDataFormat<RequestToken>(new RequestTokenSerializer(), new EphemeralDataProtectionProvider(NullLoggerFactory.Instance).CreateProtector("TwitterTest"));
+ using var host = await CreateHost((options) =>
+ {
+ options.ConsumerKey = "Test App Id";
+ options.ConsumerSecret = "PLACEHOLDER";
+ options.RetrieveUserDetails = true;
+ options.StateDataFormat = stateFormat;
+ options.BackchannelHttpHandler = new TestHttpMessageHandler
+ {
+ Sender = req =>
+ {
+ if (req.RequestUri.GetComponents(UriComponents.SchemeAndServer | UriComponents.Path, UriFormat.UriEscaped) == "https://api.twitter.com/oauth/access_token")
+ {
+ var res = new HttpResponseMessage(HttpStatusCode.OK);
+ var content = new Dictionary<string, string>()
+ {
+ ["oauth_token"] = "Test Access Token",
+ ["oauth_token_secret"] = "PLACEHOLDER",
+ ["user_id"] = "123456",
+ ["screen_name"] = "@dotnet"
+ };
+ res.Content = new FormUrlEncodedContent(content);
+ return res;
+ }
+ if (req.RequestUri.GetComponents(UriComponents.SchemeAndServer | UriComponents.Path, UriFormat.UriEscaped) ==
+ new Uri(verifyCredentialsEndpoint).GetComponents(UriComponents.SchemeAndServer | UriComponents.Path, UriFormat.UriEscaped))
+ {
+ finalVerifyCredentialsEndpoint = req.RequestUri.ToString();
+ finalAuthorizationParameter = req.Headers.Authorization.Parameter;
+ var res = new HttpResponseMessage(HttpStatusCode.OK);
+ var graphResponse = "{ \"email\": \"Test email\" }";
+ res.Content = new StringContent(graphResponse, Encoding.UTF8);
+ return res;
+ }
+ return null;
+ }
+ };
+ });
+
+ var token = new RequestToken()
+ {
+ Token = "TestToken",
+ TokenSecret = "PLACEHOLDER",
+ Properties = new()
+ };
+
+ var correlationKey = ".xsrf";
+ var correlationValue = "TestCorrelationId";
+ token.Properties.Items.Add(correlationKey, correlationValue);
+ token.Properties.RedirectUri = "/me";
+ var state = stateFormat.Protect(token);
+ using var server = host.GetTestServer();
+ var transaction = await server.SendAsync(
+ "https://example.com/signin-twitter?oauth_token=TestToken&oauth_verifier=TestVerifier",
+ $".AspNetCore.Correlation.{correlationValue}=N;__TwitterState={UrlEncoder.Default.Encode(state)}");
+ Assert.Equal(HttpStatusCode.Redirect, transaction.Response.StatusCode);
+ Assert.Equal("/me", transaction.Response.Headers.GetValues("Location").First());
+
+ Assert.Equal(1, finalVerifyCredentialsEndpoint.Count(c => c == '?'));
+ Assert.Contains("include_email=true", finalVerifyCredentialsEndpoint);
+
+ Assert.Contains("oauth_consumer_key=", finalAuthorizationParameter);
+ Assert.Contains("oauth_nonce=", finalAuthorizationParameter);
+ Assert.Contains("oauth_signature=", finalAuthorizationParameter);
+ Assert.Contains("oauth_signature_method=", finalAuthorizationParameter);
+ Assert.Contains("oauth_timestamp=", finalAuthorizationParameter);
+ Assert.Contains("oauth_token=", finalAuthorizationParameter);
+ Assert.Contains("oauth_version=", finalAuthorizationParameter);
+
+ var authCookie = transaction.AuthenticationCookieValue;
+ transaction = await server.SendAsync("https://example.com/me", authCookie);
+ Assert.Equal(HttpStatusCode.OK, transaction.Response.StatusCode);
+ var expectedIssuer = TwitterDefaults.AuthenticationScheme;
+ Assert.Equal("@dotnet", transaction.FindClaimValue(ClaimTypes.Name, expectedIssuer));
+ Assert.Equal("123456", transaction.FindClaimValue(ClaimTypes.NameIdentifier, expectedIssuer));
+ Assert.Equal("123456", transaction.FindClaimValue("urn:twitter:userid", expectedIssuer));
+ Assert.Equal("@dotnet", transaction.FindClaimValue("urn:twitter:screenname", expectedIssuer));
+ Assert.Equal("Test email", transaction.FindClaimValue(ClaimTypes.Email, expectedIssuer));
+ }
+
private static async Task<IHost> CreateHost(Action<TwitterOptions> options, Func<HttpContext, Task<bool>> handler = null)
{
var host = new HostBuilder()
@@ -405,6 +561,16 @@ namespace Microsoft.AspNetCore.Authentication.Twitter
{
await Assert.ThrowsAsync<InvalidOperationException>(() => context.ForbidAsync("Twitter"));
}
+ else if (req.Path == new PathString("/me"))
+ {
+ await res.DescribeAsync(context.User);
+ }
+ else if (req.Path == new PathString("/tokens"))
+ {
+ var result = await context.AuthenticateAsync(TestExtensions.CookieAuthenticationScheme);
+ var tokens = result.Properties.GetTokens();
+ await res.DescribeAsync(tokens);
+ }
else if (handler == null || !await handler(context))
{
await next(context);
@@ -418,8 +584,8 @@ namespace Microsoft.AspNetCore.Authentication.Twitter
o.SignInScheme = "External";
options(o);
};
- services.AddAuthentication()
- .AddCookie("External", _ => { })
+ services.AddAuthentication(TestExtensions.CookieAuthenticationScheme)
+ .AddCookie(TestExtensions.CookieAuthenticationScheme, o => o.ForwardChallenge = TwitterDefaults.AuthenticationScheme)
.AddTwitter(wrapOptions);
}))
.Build();
diff --git a/src/Servers/IIS/IIS/test/testassets/shared/WebSockets/HandshakeHelpers.cs b/src/Servers/IIS/IIS/test/testassets/shared/WebSockets/HandshakeHelpers.cs
index 8bba71271c..c86b4493e1 100644
--- a/src/Servers/IIS/IIS/test/testassets/shared/WebSockets/HandshakeHelpers.cs
+++ b/src/Servers/IIS/IIS/test/testassets/shared/WebSockets/HandshakeHelpers.cs
@@ -29,13 +29,10 @@ namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests
throw new ArgumentNullException(nameof(requestKey));
}
- using (var algorithm = SHA1.Create())
- {
- string merged = requestKey + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
- byte[] mergedBytes = Encoding.UTF8.GetBytes(merged);
- byte[] hashedBytes = algorithm.ComputeHash(mergedBytes);
- return Convert.ToBase64String(hashedBytes);
- }
+ string merged = requestKey + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
+ byte[] mergedBytes = Encoding.UTF8.GetBytes(merged);
+ byte[] hashedBytes = SHA1.HashData(mergedBytes);
+ return Convert.ToBase64String(hashedBytes);
}
}
}