From 06ff5684548c9ea5a15687dfa8894fc173d30e60 Mon Sep 17 00:00:00 2001 From: Chris R Date: Fri, 25 Jan 2019 16:05:01 -0800 Subject: [PATCH 1/9] Replace JObject with JsonDocument in Authentication #4260 --- src/Security/Authentication/.gitignore | 2 + .../Properties/launchSettings.json | 27 ++++++ .../Properties/launchSettings.json | 27 ++++++ .../Core/src/JsonDocumentAuthExtensions.cs | 14 +++ .../Facebook/src/FacebookHandler.cs | 18 ++-- .../Google/src/GoogleHandler.cs | 15 +-- .../Properties/launchSettings.json | 27 ++++++ .../samples/JwtBearerSample/Startup.cs | 37 ++++++-- .../src/MicrosoftAccountHandler.cs | 16 ++-- .../src/MicrosoftAccountOptions.cs | 6 +- .../Authentication/OAuth/src/ClaimAction.cs | 6 +- .../src/ClaimActionCollectionMapExtensions.cs | 8 +- .../OAuth/src/CustomJsonClaimAction.cs | 10 +- .../OAuth/src/DeleteClaimAction.cs | 6 +- .../src/Events/OAuthCreatingTicketContext.cs | 14 +-- .../OAuth/src/JsonKeyClaimAction.cs | 25 +++-- .../OAuth/src/JsonSubKeyClaimAction.cs | 17 ++-- .../OAuth/src/MapAllClaimsAction.cs | 14 +-- ...oft.AspNetCore.Authentication.OAuth.csproj | 3 +- .../Authentication/OAuth/src/OAuthHandler.cs | 91 ++++++++++--------- .../OAuth/src/OAuthTokenResponse.cs | 25 +++-- .../Properties/launchSettings.json | 27 ++++++ .../Properties/launchSettings.json | 27 ++++++ .../samples/OpenIdConnectSample/Startup.cs | 10 +- .../Events/UserInformationReceivedContext.cs | 4 +- .../OpenIdConnect/src/OpenIdConnectHandler.cs | 48 +++++----- .../src/UniqueJsonKeyClaimAction.cs | 8 +- .../src/TwitterCreatingTicketContext.cs | 11 +-- .../Twitter/src/TwitterHandler.cs | 14 +-- .../Properties/launchSettings.json | 27 ++++++ .../Properties/launchSettings.json | 27 ++++++ .../samples/SocialSample/Startup.cs | 22 ++--- .../samples/SocialSample/web.config | 4 +- .../Authentication/test/ClaimActionTests.cs | 35 ++----- .../Authentication/test/FacebookTests.cs | 12 +-- .../Authentication/test/GoogleTests.cs | 3 +- .../test/MicrosoftAccountTests.cs | 3 +- 37 files changed, 451 insertions(+), 239 deletions(-) create mode 100644 src/Security/Authentication/.gitignore create mode 100644 src/Security/Authentication/Cookies/samples/CookieSample/Properties/launchSettings.json create mode 100644 src/Security/Authentication/Cookies/samples/CookieSessionSample/Properties/launchSettings.json create mode 100644 src/Security/Authentication/Core/src/JsonDocumentAuthExtensions.cs create mode 100644 src/Security/Authentication/JwtBearer/samples/JwtBearerSample/Properties/launchSettings.json create mode 100644 src/Security/Authentication/OpenIdConnect/samples/OpenIdConnect.AzureAdSample/Properties/launchSettings.json create mode 100644 src/Security/Authentication/OpenIdConnect/samples/OpenIdConnectSample/Properties/launchSettings.json create mode 100644 src/Security/Authentication/WsFederation/samples/WsFedSample/Properties/launchSettings.json create mode 100644 src/Security/Authentication/samples/SocialSample/Properties/launchSettings.json diff --git a/src/Security/Authentication/.gitignore b/src/Security/Authentication/.gitignore new file mode 100644 index 000000000000..c6dd745c38ef --- /dev/null +++ b/src/Security/Authentication/.gitignore @@ -0,0 +1,2 @@ +#launchSettings.json files are required for the auth samples. The ports must not change. +!launchSettings.json \ No newline at end of file diff --git a/src/Security/Authentication/Cookies/samples/CookieSample/Properties/launchSettings.json b/src/Security/Authentication/Cookies/samples/CookieSample/Properties/launchSettings.json new file mode 100644 index 000000000000..0c2b575f19bd --- /dev/null +++ b/src/Security/Authentication/Cookies/samples/CookieSample/Properties/launchSettings.json @@ -0,0 +1,27 @@ +{ + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:1780/", + "sslPort": 0 + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "CookieSample": { + "commandName": "Project", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "applicationUrl": "http://localhost:1782/" + } + } +} \ No newline at end of file diff --git a/src/Security/Authentication/Cookies/samples/CookieSessionSample/Properties/launchSettings.json b/src/Security/Authentication/Cookies/samples/CookieSessionSample/Properties/launchSettings.json new file mode 100644 index 000000000000..290ebb2efe7a --- /dev/null +++ b/src/Security/Authentication/Cookies/samples/CookieSessionSample/Properties/launchSettings.json @@ -0,0 +1,27 @@ +{ + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:1771/", + "sslPort": 0 + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "CookieSessionSample": { + "commandName": "Project", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "applicationUrl": "http://localhost:1776/" + } + } +} \ No newline at end of file diff --git a/src/Security/Authentication/Core/src/JsonDocumentAuthExtensions.cs b/src/Security/Authentication/Core/src/JsonDocumentAuthExtensions.cs new file mode 100644 index 000000000000..2ecaf0d81571 --- /dev/null +++ b/src/Security/Authentication/Core/src/JsonDocumentAuthExtensions.cs @@ -0,0 +1,14 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Text.Json; + +namespace Microsoft.AspNetCore.Authentication +{ + public static class JsonDocumentAuthExtensions + { + public static string GetString(this JsonDocument document, string key) => + document.RootElement.TryGetProperty(key, out var property) + ? property.ToString() : null; + } +} diff --git a/src/Security/Authentication/Facebook/src/FacebookHandler.cs b/src/Security/Authentication/Facebook/src/FacebookHandler.cs index eb42511431d6..c825d33560fa 100644 --- a/src/Security/Authentication/Facebook/src/FacebookHandler.cs +++ b/src/Security/Authentication/Facebook/src/FacebookHandler.cs @@ -8,12 +8,12 @@ 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; using Microsoft.Extensions.Options; -using Newtonsoft.Json.Linq; namespace Microsoft.AspNetCore.Authentication.Facebook { @@ -41,15 +41,13 @@ protected override async Task CreateTicketAsync(ClaimsIden throw new HttpRequestException($"An error occurred when retrieving Facebook user information ({response.StatusCode}). Please check if the authentication information is correct and the corresponding Facebook Graph API is enabled."); } - var payload = JObject.Parse(await response.Content.ReadAsStringAsync()); - - var context = new OAuthCreatingTicketContext(new ClaimsPrincipal(identity), properties, Context, Scheme, Options, Backchannel, tokens, payload); - context.RunClaimActions(); - - await Events.CreatingTicket(context); - - return new AuthenticationTicket(context.Principal, context.Properties, Scheme.Name); - + using (var payload = JsonDocument.Parse(await response.Content.ReadAsStringAsync())) + { + var context = new OAuthCreatingTicketContext(new ClaimsPrincipal(identity), properties, Context, Scheme, Options, Backchannel, tokens, payload); + context.RunClaimActions(); + await Events.CreatingTicket(context); + return new AuthenticationTicket(context.Principal, context.Properties, Scheme.Name); + } } private string GenerateAppSecretProof(string accessToken) diff --git a/src/Security/Authentication/Google/src/GoogleHandler.cs b/src/Security/Authentication/Google/src/GoogleHandler.cs index 88d48d446765..2f9fcf106c9a 100644 --- a/src/Security/Authentication/Google/src/GoogleHandler.cs +++ b/src/Security/Authentication/Google/src/GoogleHandler.cs @@ -7,12 +7,12 @@ using System.Net.Http.Headers; using System.Security.Claims; 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; using Microsoft.Extensions.Options; -using Newtonsoft.Json.Linq; namespace Microsoft.AspNetCore.Authentication.Google { @@ -37,13 +37,14 @@ protected override async Task CreateTicketAsync( throw new HttpRequestException($"An error occurred when retrieving Google user information ({response.StatusCode}). Please check if the authentication information is correct and the corresponding Google+ API is enabled."); } - var payload = JObject.Parse(await response.Content.ReadAsStringAsync()); - - var context = new OAuthCreatingTicketContext(new ClaimsPrincipal(identity), properties, Context, Scheme, Options, Backchannel, tokens, payload); - context.RunClaimActions(); + using (var payload = JsonDocument.Parse(await response.Content.ReadAsStringAsync())) + { - await Events.CreatingTicket(context); - return new AuthenticationTicket(context.Principal, context.Properties, Scheme.Name); + var context = new OAuthCreatingTicketContext(new ClaimsPrincipal(identity), properties, Context, Scheme, Options, Backchannel, tokens, payload); + context.RunClaimActions(); + await Events.CreatingTicket(context); + return new AuthenticationTicket(context.Principal, context.Properties, Scheme.Name); + } } // TODO: Abstract this properties override pattern into the base class? diff --git a/src/Security/Authentication/JwtBearer/samples/JwtBearerSample/Properties/launchSettings.json b/src/Security/Authentication/JwtBearer/samples/JwtBearerSample/Properties/launchSettings.json new file mode 100644 index 000000000000..889821c21dc7 --- /dev/null +++ b/src/Security/Authentication/JwtBearer/samples/JwtBearerSample/Properties/launchSettings.json @@ -0,0 +1,27 @@ +{ + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "https://localhost:44318/", + "sslPort": 44318 + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "SocialSample": { + "commandName": "Project", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "applicationUrl": "https://localhost:44318/" + } + } +} diff --git a/src/Security/Authentication/JwtBearer/samples/JwtBearerSample/Startup.cs b/src/Security/Authentication/JwtBearer/samples/JwtBearerSample/Startup.cs index 8c4a63cad67b..bd1d3710980e 100644 --- a/src/Security/Authentication/JwtBearer/samples/JwtBearerSample/Startup.cs +++ b/src/Security/Authentication/JwtBearer/samples/JwtBearerSample/Startup.cs @@ -1,7 +1,11 @@ using System; +using System.Buffers; using System.Collections.Generic; using System.IO; +using System.IO.Pipelines; using System.Runtime.ExceptionServices; +using System.Text.Json; +using System.Threading.Tasks; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Builder; @@ -10,7 +14,6 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Net.Http.Headers; -using Newtonsoft.Json.Linq; namespace JwtBearerSample { @@ -93,19 +96,41 @@ public void Configure(IApplicationBuilder app) { var reader = new StreamReader(context.Request.Body); var body = await reader.ReadToEndAsync(); - var obj = JObject.Parse(body); - var todo = new Todo() { Description = obj["Description"].Value(), Owner = context.User.Identity.Name }; + var obj = JsonDocument.Parse(body).RootElement; + var todo = new Todo() { Description = obj.GetProperty("Description").ToString(), Owner = context.User.Identity.Name }; Todos.Add(todo); } else { response.ContentType = "application/json"; response.Headers[HeaderNames.CacheControl] = "no-cache"; - var json = JToken.FromObject(Todos); - await response.WriteAsync(json.ToString()); + await SerializeAsync(Todos, response.Body); } }); }); } + + private async Task SerializeAsync(IList todos, Stream stream) + { + var pipe = new StreamPipeWriter(stream); + Serialize(todos, pipe); + await pipe.FlushAsync(); + pipe.Complete(); + } + + private void Serialize(IList todos, IBufferWriter output) + { + var writer = new Utf8JsonWriter(output); + writer.WriteStartArray(); + foreach (var todo in todos) + { + writer.WriteStartObject(); + writer.WriteString("Description", todo.Description); + writer.WriteString("Owner", todo.Owner); + writer.WriteEndObject(); + } + writer.WriteEndArray(); + writer.Flush(); + } } -} \ No newline at end of file +} diff --git a/src/Security/Authentication/MicrosoftAccount/src/MicrosoftAccountHandler.cs b/src/Security/Authentication/MicrosoftAccount/src/MicrosoftAccountHandler.cs index bba547277406..38194bbe83b1 100644 --- a/src/Security/Authentication/MicrosoftAccount/src/MicrosoftAccountHandler.cs +++ b/src/Security/Authentication/MicrosoftAccount/src/MicrosoftAccountHandler.cs @@ -5,11 +5,11 @@ using System.Net.Http.Headers; using System.Security.Claims; using System.Text.Encodings.Web; +using System.Text.Json; using System.Threading.Tasks; using Microsoft.AspNetCore.Authentication.OAuth; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; -using Newtonsoft.Json.Linq; namespace Microsoft.AspNetCore.Authentication.MicrosoftAccount { @@ -30,13 +30,13 @@ protected override async Task CreateTicketAsync(ClaimsIden throw new HttpRequestException($"An error occurred when retrieving Microsoft user information ({response.StatusCode}). Please check if the authentication information is correct and the corresponding Microsoft Account API is enabled."); } - var payload = JObject.Parse(await response.Content.ReadAsStringAsync()); - - var context = new OAuthCreatingTicketContext(new ClaimsPrincipal(identity), properties, Context, Scheme, Options, Backchannel, tokens, payload); - context.RunClaimActions(); - - await Events.CreatingTicket(context); - return new AuthenticationTicket(context.Principal, context.Properties, Scheme.Name); + using (var payload = JsonDocument.Parse(await response.Content.ReadAsStringAsync())) + { + var context = new OAuthCreatingTicketContext(new ClaimsPrincipal(identity), properties, Context, Scheme, Options, Backchannel, tokens, payload); + context.RunClaimActions(); + await Events.CreatingTicket(context); + return new AuthenticationTicket(context.Principal, context.Properties, Scheme.Name); + } } } } diff --git a/src/Security/Authentication/MicrosoftAccount/src/MicrosoftAccountOptions.cs b/src/Security/Authentication/MicrosoftAccount/src/MicrosoftAccountOptions.cs index dbca3507e914..8462913a3d93 100644 --- a/src/Security/Authentication/MicrosoftAccount/src/MicrosoftAccountOptions.cs +++ b/src/Security/Authentication/MicrosoftAccount/src/MicrosoftAccountOptions.cs @@ -2,10 +2,8 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Security.Claims; -using Microsoft.AspNetCore.Authentication; -using Microsoft.AspNetCore.Authentication.MicrosoftAccount; -using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Authentication.OAuth; +using Microsoft.AspNetCore.Http; namespace Microsoft.AspNetCore.Authentication.MicrosoftAccount { @@ -29,7 +27,7 @@ public MicrosoftAccountOptions() ClaimActions.MapJsonKey(ClaimTypes.Name, "displayName"); ClaimActions.MapJsonKey(ClaimTypes.GivenName, "givenName"); ClaimActions.MapJsonKey(ClaimTypes.Surname, "surname"); - ClaimActions.MapCustomJson(ClaimTypes.Email, user => user.Value("mail") ?? user.Value("userPrincipalName")); + ClaimActions.MapCustomJson(ClaimTypes.Email, user => user.GetString("mail") ?? user.GetString("userPrincipalName")); } } } diff --git a/src/Security/Authentication/OAuth/src/ClaimAction.cs b/src/Security/Authentication/OAuth/src/ClaimAction.cs index 78b63bb38e29..cab03e679dbb 100644 --- a/src/Security/Authentication/OAuth/src/ClaimAction.cs +++ b/src/Security/Authentication/OAuth/src/ClaimAction.cs @@ -1,8 +1,8 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Security.Claims; -using Newtonsoft.Json.Linq; +using System.Text.Json; namespace Microsoft.AspNetCore.Authentication.OAuth.Claims { @@ -37,6 +37,6 @@ public ClaimAction(string claimType, string valueType) /// The source data to examine. This value may be null. /// The identity to add Claims to. /// The value to use for Claim.Issuer when creating a Claim. - public abstract void Run(JObject userData, ClaimsIdentity identity, string issuer); + public abstract void Run(JsonDocument userData, ClaimsIdentity identity, string issuer); } } diff --git a/src/Security/Authentication/OAuth/src/ClaimActionCollectionMapExtensions.cs b/src/Security/Authentication/OAuth/src/ClaimActionCollectionMapExtensions.cs index 5a178957a09a..6fa625075f6b 100644 --- a/src/Security/Authentication/OAuth/src/ClaimActionCollectionMapExtensions.cs +++ b/src/Security/Authentication/OAuth/src/ClaimActionCollectionMapExtensions.cs @@ -1,10 +1,10 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Security.Claims; +using System.Text.Json; using Microsoft.AspNetCore.Authentication.OAuth.Claims; -using Newtonsoft.Json.Linq; namespace Microsoft.AspNetCore.Authentication { @@ -69,7 +69,7 @@ public static void MapJsonSubKey(this ClaimActionCollection collection, string c /// /// The value to use for Claim.Type when creating a Claim. /// The Func that will be called to select value from the given json user data. - public static void MapCustomJson(this ClaimActionCollection collection, string claimType, Func resolver) + public static void MapCustomJson(this ClaimActionCollection collection, string claimType, Func resolver) { collection.MapCustomJson(claimType, ClaimValueTypes.String, resolver); } @@ -82,7 +82,7 @@ public static void MapCustomJson(this ClaimActionCollection collection, string c /// The value to use for Claim.Type when creating a Claim. /// The value to use for Claim.ValueType when creating a Claim. /// The Func that will be called to select value from the given json user data. - public static void MapCustomJson(this ClaimActionCollection collection, string claimType, string valueType, Func resolver) + public static void MapCustomJson(this ClaimActionCollection collection, string claimType, string valueType, Func resolver) { collection.Add(new CustomJsonClaimAction(claimType, valueType, resolver)); } diff --git a/src/Security/Authentication/OAuth/src/CustomJsonClaimAction.cs b/src/Security/Authentication/OAuth/src/CustomJsonClaimAction.cs index 21a4f70e12ec..6d344a200698 100644 --- a/src/Security/Authentication/OAuth/src/CustomJsonClaimAction.cs +++ b/src/Security/Authentication/OAuth/src/CustomJsonClaimAction.cs @@ -1,9 +1,9 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Security.Claims; -using Newtonsoft.Json.Linq; +using System.Text.Json; namespace Microsoft.AspNetCore.Authentication.OAuth.Claims { @@ -18,7 +18,7 @@ public class CustomJsonClaimAction : ClaimAction /// The value to use for Claim.Type when creating a Claim. /// The value to use for Claim.ValueType when creating a Claim. /// The Func that will be called to select value from the given json user data. - public CustomJsonClaimAction(string claimType, string valueType, Func resolver) + public CustomJsonClaimAction(string claimType, string valueType, Func resolver) : base(claimType, valueType) { Resolver = resolver; @@ -27,10 +27,10 @@ public CustomJsonClaimAction(string claimType, string valueType, Func /// The Func that will be called to select value from the given json user data. /// - public Func Resolver { get; } + public Func Resolver { get; } /// - public override void Run(JObject userData, ClaimsIdentity identity, string issuer) + public override void Run(JsonDocument userData, ClaimsIdentity identity, string issuer) { if (userData == null) { diff --git a/src/Security/Authentication/OAuth/src/DeleteClaimAction.cs b/src/Security/Authentication/OAuth/src/DeleteClaimAction.cs index 75167cabcb7f..3fefa8bf8c47 100644 --- a/src/Security/Authentication/OAuth/src/DeleteClaimAction.cs +++ b/src/Security/Authentication/OAuth/src/DeleteClaimAction.cs @@ -1,9 +1,9 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Linq; using System.Security.Claims; -using Newtonsoft.Json.Linq; +using System.Text.Json; namespace Microsoft.AspNetCore.Authentication.OAuth.Claims { @@ -22,7 +22,7 @@ public DeleteClaimAction(string claimType) } /// - public override void Run(JObject userData, ClaimsIdentity identity, string issuer) + public override void Run(JsonDocument userData, ClaimsIdentity identity, string issuer) { foreach (var claim in identity.FindAll(ClaimType).ToList()) { diff --git a/src/Security/Authentication/OAuth/src/Events/OAuthCreatingTicketContext.cs b/src/Security/Authentication/OAuth/src/Events/OAuthCreatingTicketContext.cs index f660dd22476d..6c3a43c071c4 100644 --- a/src/Security/Authentication/OAuth/src/Events/OAuthCreatingTicketContext.cs +++ b/src/Security/Authentication/OAuth/src/Events/OAuthCreatingTicketContext.cs @@ -5,8 +5,8 @@ using System.Globalization; using System.Net.Http; using System.Security.Claims; +using System.Text.Json; using Microsoft.AspNetCore.Http; -using Newtonsoft.Json.Linq; namespace Microsoft.AspNetCore.Authentication.OAuth { @@ -33,7 +33,7 @@ public OAuthCreatingTicketContext( OAuthOptions options, HttpClient backchannel, OAuthTokenResponse tokens) - : this(principal, properties, context, scheme, options, backchannel, tokens, user: new JObject()) + : this(principal, properties, context, scheme, options, backchannel, tokens, user: JsonDocument.Parse(string.Empty)) { } /// @@ -55,7 +55,7 @@ public OAuthCreatingTicketContext( OAuthOptions options, HttpClient backchannel, OAuthTokenResponse tokens, - JObject user) + JsonDocument user) : base(context, scheme, options) { if (backchannel == null) @@ -82,9 +82,9 @@ public OAuthCreatingTicketContext( /// /// Gets the JSON-serialized user or an empty - /// if it is not available. + /// if it is not available. /// - public JObject User { get; } + public JsonDocument User { get; } /// /// Gets the token response returned by the authentication service. @@ -136,7 +136,7 @@ public TimeSpan? ExpiresIn public void RunClaimActions() => RunClaimActions(User); - public void RunClaimActions(JObject userData) + public void RunClaimActions(JsonDocument userData) { if (userData == null) { @@ -149,4 +149,4 @@ public void RunClaimActions(JObject userData) } } } -} \ No newline at end of file +} diff --git a/src/Security/Authentication/OAuth/src/JsonKeyClaimAction.cs b/src/Security/Authentication/OAuth/src/JsonKeyClaimAction.cs index ccd1a965dc87..340c2cd64238 100644 --- a/src/Security/Authentication/OAuth/src/JsonKeyClaimAction.cs +++ b/src/Security/Authentication/OAuth/src/JsonKeyClaimAction.cs @@ -1,8 +1,8 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Security.Claims; -using Newtonsoft.Json.Linq; +using System.Text.Json; namespace Microsoft.AspNetCore.Authentication.OAuth.Claims { @@ -30,20 +30,27 @@ public JsonKeyClaimAction(string claimType, string valueType, string jsonKey) public string JsonKey { get; } /// - public override void Run(JObject userData, ClaimsIdentity identity, string issuer) + public override void Run(JsonDocument userData, ClaimsIdentity identity, string issuer) { - var value = userData?[JsonKey]; - if (value is JValue) + if (userData == null || !userData.RootElement.TryGetProperty(JsonKey, out var value)) { - AddClaim(value?.ToString(), identity, issuer); + return; } - else if (value is JArray) + if (value.Type == JsonValueType.Array) { - foreach (var v in value) + foreach (var v in value.EnumerateArray()) { - AddClaim(v?.ToString(), identity, issuer); + AddClaim(v.ToString(), identity, issuer); } } + else if (value.Type == JsonValueType.Object || value.Type == JsonValueType.Undefined) + { + // Skip, because they were previously skipped + } + else + { + AddClaim(value.ToString(), identity, issuer); + } } private void AddClaim(string value, ClaimsIdentity identity, string issuer) diff --git a/src/Security/Authentication/OAuth/src/JsonSubKeyClaimAction.cs b/src/Security/Authentication/OAuth/src/JsonSubKeyClaimAction.cs index bc29672d0fad..b4bfb1d39fc0 100644 --- a/src/Security/Authentication/OAuth/src/JsonSubKeyClaimAction.cs +++ b/src/Security/Authentication/OAuth/src/JsonSubKeyClaimAction.cs @@ -1,8 +1,8 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using Newtonsoft.Json.Linq; using System.Security.Claims; +using System.Text.Json; namespace Microsoft.AspNetCore.Authentication.OAuth.Claims { @@ -32,7 +32,7 @@ public JsonSubKeyClaimAction(string claimType, string valueType, string jsonKey, public string SubKey { get; } /// - public override void Run(JObject userData, ClaimsIdentity identity, string issuer) + public override void Run(JsonDocument userData, ClaimsIdentity identity, string issuer) { var value = GetValue(userData, JsonKey, SubKey); if (!string.IsNullOrEmpty(value)) @@ -42,15 +42,12 @@ public override void Run(JObject userData, ClaimsIdentity identity, string issue } // Get the given subProperty from a property. - private static string GetValue(JObject userData, string propertyName, string subProperty) + private static string GetValue(JsonDocument userData, string propertyName, string subProperty) { - if (userData != null && userData.TryGetValue(propertyName, out var value)) + if (userData != null && userData.RootElement.TryGetProperty(propertyName, out var value) + && value.Type == JsonValueType.Object && value.TryGetProperty(subProperty, out value)) { - var subObject = JObject.Parse(value.ToString()); - if (subObject != null && subObject.TryGetValue(subProperty, out value)) - { - return value.ToString(); - } + return value.ToString(); } return null; } diff --git a/src/Security/Authentication/OAuth/src/MapAllClaimsAction.cs b/src/Security/Authentication/OAuth/src/MapAllClaimsAction.cs index b3bf5d99f15c..b8aad5a49309 100644 --- a/src/Security/Authentication/OAuth/src/MapAllClaimsAction.cs +++ b/src/Security/Authentication/OAuth/src/MapAllClaimsAction.cs @@ -1,9 +1,9 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Security.Claims; -using Newtonsoft.Json.Linq; +using System.Text.Json; namespace Microsoft.AspNetCore.Authentication.OAuth.Claims { @@ -17,24 +17,24 @@ public MapAllClaimsAction() : base("All", ClaimValueTypes.String) { } - public override void Run(JObject userData, ClaimsIdentity identity, string issuer) + public override void Run(JsonDocument userData, ClaimsIdentity identity, string issuer) { if (userData == null) { return; } - foreach (var pair in userData) + foreach (var pair in userData.RootElement.EnumerateObject()) { - var claimValue = userData.TryGetValue(pair.Key, out var value) ? value.ToString() : null; + var claimValue = userData.GetString(pair.Name); // Avoid adding a claim if there's a duplicate name and value. This often happens in OIDC when claims are // retrieved both from the id_token and from the user-info endpoint. - var duplicate = identity.FindFirst(c => string.Equals(c.Type, pair.Key, StringComparison.OrdinalIgnoreCase) + var duplicate = identity.FindFirst(c => string.Equals(c.Type, pair.Name, StringComparison.OrdinalIgnoreCase) && string.Equals(c.Value, claimValue, StringComparison.Ordinal)) != null; if (!duplicate) { - identity.AddClaim(new Claim(pair.Key, claimValue, ClaimValueTypes.String, issuer)); + identity.AddClaim(new Claim(pair.Name, claimValue, ClaimValueTypes.String, issuer)); } } } diff --git a/src/Security/Authentication/OAuth/src/Microsoft.AspNetCore.Authentication.OAuth.csproj b/src/Security/Authentication/OAuth/src/Microsoft.AspNetCore.Authentication.OAuth.csproj index 575f78c15cbb..85623aebce29 100644 --- a/src/Security/Authentication/OAuth/src/Microsoft.AspNetCore.Authentication.OAuth.csproj +++ b/src/Security/Authentication/OAuth/src/Microsoft.AspNetCore.Authentication.OAuth.csproj @@ -1,4 +1,4 @@ - + ASP.NET Core middleware that enables an application to support any standard OAuth 2.0 authentication workflow. @@ -10,7 +10,6 @@ - diff --git a/src/Security/Authentication/OAuth/src/OAuthHandler.cs b/src/Security/Authentication/OAuth/src/OAuthHandler.cs index a84f8a485ad6..3b4f4437cb37 100644 --- a/src/Security/Authentication/OAuth/src/OAuthHandler.cs +++ b/src/Security/Authentication/OAuth/src/OAuthHandler.cs @@ -9,13 +9,13 @@ using System.Security.Claims; using System.Text; using System.Text.Encodings.Web; +using System.Text.Json; using System.Threading.Tasks; using Microsoft.AspNetCore.WebUtilities; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Microsoft.Extensions.Primitives; using Microsoft.Net.Http.Headers; -using Newtonsoft.Json.Linq; namespace Microsoft.AspNetCore.Authentication.OAuth { @@ -99,62 +99,63 @@ protected override async Task HandleRemoteAuthenticateAsync return HandleRequestResult.Fail("Code was not found.", properties); } - var tokens = await ExchangeCodeAsync(code, BuildRedirectUri(Options.CallbackPath)); - - if (tokens.Error != null) - { - return HandleRequestResult.Fail(tokens.Error, properties); - } - - if (string.IsNullOrEmpty(tokens.AccessToken)) - { - return HandleRequestResult.Fail("Failed to retrieve access token.", properties); - } - - var identity = new ClaimsIdentity(ClaimsIssuer); - - if (Options.SaveTokens) + using (var tokens = await ExchangeCodeAsync(code, BuildRedirectUri(Options.CallbackPath))) { - var authTokens = new List(); - - authTokens.Add(new AuthenticationToken { Name = "access_token", Value = tokens.AccessToken }); - if (!string.IsNullOrEmpty(tokens.RefreshToken)) + if (tokens.Error != null) { - authTokens.Add(new AuthenticationToken { Name = "refresh_token", Value = tokens.RefreshToken }); + return HandleRequestResult.Fail(tokens.Error, properties); } - if (!string.IsNullOrEmpty(tokens.TokenType)) + if (string.IsNullOrEmpty(tokens.AccessToken)) { - authTokens.Add(new AuthenticationToken { Name = "token_type", Value = tokens.TokenType }); + return HandleRequestResult.Fail("Failed to retrieve access token.", properties); } - if (!string.IsNullOrEmpty(tokens.ExpiresIn)) + var identity = new ClaimsIdentity(ClaimsIssuer); + + if (Options.SaveTokens) { - int value; - if (int.TryParse(tokens.ExpiresIn, NumberStyles.Integer, CultureInfo.InvariantCulture, out value)) + var authTokens = new List(); + + authTokens.Add(new AuthenticationToken { Name = "access_token", Value = tokens.AccessToken }); + if (!string.IsNullOrEmpty(tokens.RefreshToken)) + { + authTokens.Add(new AuthenticationToken { Name = "refresh_token", Value = tokens.RefreshToken }); + } + + if (!string.IsNullOrEmpty(tokens.TokenType)) { - // https://www.w3.org/TR/xmlschema-2/#dateTime - // https://msdn.microsoft.com/en-us/library/az4se3k1(v=vs.110).aspx - var expiresAt = Clock.UtcNow + TimeSpan.FromSeconds(value); - authTokens.Add(new AuthenticationToken + authTokens.Add(new AuthenticationToken { Name = "token_type", Value = tokens.TokenType }); + } + + if (!string.IsNullOrEmpty(tokens.ExpiresIn)) + { + int value; + if (int.TryParse(tokens.ExpiresIn, NumberStyles.Integer, CultureInfo.InvariantCulture, out value)) { - Name = "expires_at", - Value = expiresAt.ToString("o", CultureInfo.InvariantCulture) - }); + // https://www.w3.org/TR/xmlschema-2/#dateTime + // https://msdn.microsoft.com/en-us/library/az4se3k1(v=vs.110).aspx + var expiresAt = Clock.UtcNow + TimeSpan.FromSeconds(value); + authTokens.Add(new AuthenticationToken + { + Name = "expires_at", + Value = expiresAt.ToString("o", CultureInfo.InvariantCulture) + }); + } } - } - properties.StoreTokens(authTokens); - } + properties.StoreTokens(authTokens); + } - var ticket = await CreateTicketAsync(identity, properties, tokens); - if (ticket != null) - { - return HandleRequestResult.Success(ticket); - } - else - { - return HandleRequestResult.Fail("Failed to retrieve user information from remote server.", properties); + var ticket = await CreateTicketAsync(identity, properties, tokens); + if (ticket != null) + { + return HandleRequestResult.Success(ticket); + } + else + { + return HandleRequestResult.Fail("Failed to retrieve user information from remote server.", properties); + } } } @@ -177,7 +178,7 @@ protected virtual async Task ExchangeCodeAsync(string code, var response = await Backchannel.SendAsync(requestMessage, Context.RequestAborted); if (response.IsSuccessStatusCode) { - var payload = JObject.Parse(await response.Content.ReadAsStringAsync()); + var payload = JsonDocument.Parse(await response.Content.ReadAsStringAsync()); return OAuthTokenResponse.Success(payload); } else diff --git a/src/Security/Authentication/OAuth/src/OAuthTokenResponse.cs b/src/Security/Authentication/OAuth/src/OAuthTokenResponse.cs index aa4026b009eb..3e30da4f79e8 100644 --- a/src/Security/Authentication/OAuth/src/OAuthTokenResponse.cs +++ b/src/Security/Authentication/OAuth/src/OAuthTokenResponse.cs @@ -2,19 +2,19 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using Newtonsoft.Json.Linq; +using System.Text.Json; namespace Microsoft.AspNetCore.Authentication.OAuth { - public class OAuthTokenResponse + public class OAuthTokenResponse : IDisposable { - private OAuthTokenResponse(JObject response) + private OAuthTokenResponse(JsonDocument response) { Response = response; - AccessToken = response.Value("access_token"); - TokenType = response.Value("token_type"); - RefreshToken = response.Value("refresh_token"); - ExpiresIn = response.Value("expires_in"); + AccessToken = response.GetString("access_token"); + TokenType = response.GetString("token_type"); + RefreshToken = response.GetString("refresh_token"); + ExpiresIn = response.GetString("expires_in"); } private OAuthTokenResponse(Exception error) @@ -22,7 +22,7 @@ private OAuthTokenResponse(Exception error) Error = error; } - public static OAuthTokenResponse Success(JObject response) + public static OAuthTokenResponse Success(JsonDocument response) { return new OAuthTokenResponse(response); } @@ -32,11 +32,16 @@ public static OAuthTokenResponse Failed(Exception error) return new OAuthTokenResponse(error); } - public JObject Response { get; set; } + public void Dispose() + { + Response?.Dispose(); + } + + public JsonDocument Response { get; set; } public string AccessToken { get; set; } public string TokenType { get; set; } public string RefreshToken { get; set; } public string ExpiresIn { get; set; } public Exception Error { get; set; } } -} \ No newline at end of file +} diff --git a/src/Security/Authentication/OpenIdConnect/samples/OpenIdConnect.AzureAdSample/Properties/launchSettings.json b/src/Security/Authentication/OpenIdConnect/samples/OpenIdConnect.AzureAdSample/Properties/launchSettings.json new file mode 100644 index 000000000000..889821c21dc7 --- /dev/null +++ b/src/Security/Authentication/OpenIdConnect/samples/OpenIdConnect.AzureAdSample/Properties/launchSettings.json @@ -0,0 +1,27 @@ +{ + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "https://localhost:44318/", + "sslPort": 44318 + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "SocialSample": { + "commandName": "Project", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "applicationUrl": "https://localhost:44318/" + } + } +} diff --git a/src/Security/Authentication/OpenIdConnect/samples/OpenIdConnectSample/Properties/launchSettings.json b/src/Security/Authentication/OpenIdConnect/samples/OpenIdConnectSample/Properties/launchSettings.json new file mode 100644 index 000000000000..889821c21dc7 --- /dev/null +++ b/src/Security/Authentication/OpenIdConnect/samples/OpenIdConnectSample/Properties/launchSettings.json @@ -0,0 +1,27 @@ +{ + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "https://localhost:44318/", + "sslPort": 44318 + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "SocialSample": { + "commandName": "Project", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "applicationUrl": "https://localhost:44318/" + } + } +} diff --git a/src/Security/Authentication/OpenIdConnect/samples/OpenIdConnectSample/Startup.cs b/src/Security/Authentication/OpenIdConnect/samples/OpenIdConnectSample/Startup.cs index a2b5a9a5cad9..7311f63d6797 100644 --- a/src/Security/Authentication/OpenIdConnect/samples/OpenIdConnectSample/Startup.cs +++ b/src/Security/Authentication/OpenIdConnect/samples/OpenIdConnectSample/Startup.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Net.Http; using System.Text.Encodings.Web; +using System.Text.Json; using System.Threading.Tasks; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication.Cookies; @@ -16,7 +17,6 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Microsoft.IdentityModel.Protocols.OpenIdConnect; -using Newtonsoft.Json.Linq; namespace OpenIdConnectSample { @@ -208,12 +208,12 @@ await WriteHtmlAsync(response, async res => var tokenResponse = await options.Backchannel.PostAsync(metadata.TokenEndpoint, content, context.RequestAborted); tokenResponse.EnsureSuccessStatusCode(); - var payload = JObject.Parse(await tokenResponse.Content.ReadAsStringAsync()); + var payload = JsonDocument.Parse(await tokenResponse.Content.ReadAsStringAsync()); // Persist the new acess token - props.UpdateTokenValue("access_token", payload.Value("access_token")); - props.UpdateTokenValue("refresh_token", payload.Value("refresh_token")); - if (int.TryParse(payload.Value("expires_in"), NumberStyles.Integer, CultureInfo.InvariantCulture, out var seconds)) + props.UpdateTokenValue("access_token", payload.GetString("access_token")); + props.UpdateTokenValue("refresh_token", payload.GetString("refresh_token")); + if (payload.RootElement.TryGetProperty("expires_in", out var property) && property.TryGetInt32(out var seconds)) { var expiresAt = DateTimeOffset.UtcNow + TimeSpan.FromSeconds(seconds); props.UpdateTokenValue("expires_at", expiresAt.ToString("o", CultureInfo.InvariantCulture)); diff --git a/src/Security/Authentication/OpenIdConnect/src/Events/UserInformationReceivedContext.cs b/src/Security/Authentication/OpenIdConnect/src/Events/UserInformationReceivedContext.cs index 0b855eaf3930..29deb9acbe94 100644 --- a/src/Security/Authentication/OpenIdConnect/src/Events/UserInformationReceivedContext.cs +++ b/src/Security/Authentication/OpenIdConnect/src/Events/UserInformationReceivedContext.cs @@ -2,9 +2,9 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Security.Claims; +using System.Text.Json; using Microsoft.AspNetCore.Http; using Microsoft.IdentityModel.Protocols.OpenIdConnect; -using Newtonsoft.Json.Linq; namespace Microsoft.AspNetCore.Authentication.OpenIdConnect { @@ -16,6 +16,6 @@ public UserInformationReceivedContext(HttpContext context, AuthenticationScheme public OpenIdConnectMessage ProtocolMessage { get; set; } - public JObject User { get; set; } + public JsonDocument User { get; set; } } } diff --git a/src/Security/Authentication/OpenIdConnect/src/OpenIdConnectHandler.cs b/src/Security/Authentication/OpenIdConnect/src/OpenIdConnectHandler.cs index 982376387307..ed6e22d38f8d 100644 --- a/src/Security/Authentication/OpenIdConnect/src/OpenIdConnectHandler.cs +++ b/src/Security/Authentication/OpenIdConnect/src/OpenIdConnectHandler.cs @@ -12,6 +12,7 @@ using System.Security.Cryptography; using System.Text; using System.Text.Encodings.Web; +using System.Text.Json; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; @@ -20,7 +21,6 @@ using Microsoft.IdentityModel.Protocols.OpenIdConnect; using Microsoft.IdentityModel.Tokens; using Microsoft.Net.Http.Headers; -using Newtonsoft.Json.Linq; namespace Microsoft.AspNetCore.Authentication.OpenIdConnect { @@ -853,42 +853,46 @@ protected virtual async Task GetUserInformationAsync( responseMessage.EnsureSuccessStatusCode(); var userInfoResponse = await responseMessage.Content.ReadAsStringAsync(); - JObject user; + JsonDocument user; var contentType = responseMessage.Content.Headers.ContentType; if (contentType.MediaType.Equals("application/json", StringComparison.OrdinalIgnoreCase)) { - user = JObject.Parse(userInfoResponse); + user = JsonDocument.Parse(userInfoResponse); } else if (contentType.MediaType.Equals("application/jwt", StringComparison.OrdinalIgnoreCase)) { var userInfoEndpointJwt = new JwtSecurityToken(userInfoResponse); - user = JObject.FromObject(userInfoEndpointJwt.Payload); + user = JsonDocument.Parse(userInfoEndpointJwt.Payload.SerializeToJson()); } else { return HandleRequestResult.Fail("Unknown response type: " + contentType.MediaType, properties); } - var userInformationReceivedContext = await RunUserInformationReceivedEventAsync(principal, properties, message, user); - if (userInformationReceivedContext.Result != null) + using (user) { - return userInformationReceivedContext.Result; - } - principal = userInformationReceivedContext.Principal; - properties = userInformationReceivedContext.Properties; - user = userInformationReceivedContext.User; - - Options.ProtocolValidator.ValidateUserInfoResponse(new OpenIdConnectProtocolValidationContext() - { - UserInfoEndpointResponse = userInfoResponse, - ValidatedIdToken = jwt, - }); + var userInformationReceivedContext = await RunUserInformationReceivedEventAsync(principal, properties, message, user); + if (userInformationReceivedContext.Result != null) + { + return userInformationReceivedContext.Result; + } + principal = userInformationReceivedContext.Principal; + properties = userInformationReceivedContext.Properties; + using (var updatedUser = userInformationReceivedContext.User) + { + Options.ProtocolValidator.ValidateUserInfoResponse(new OpenIdConnectProtocolValidationContext() + { + UserInfoEndpointResponse = userInfoResponse, + ValidatedIdToken = jwt, + }); - var identity = (ClaimsIdentity)principal.Identity; + var identity = (ClaimsIdentity)principal.Identity; - foreach (var action in Options.ClaimActions) - { - action.Run(user, identity, ClaimsIssuer); + foreach (var action in Options.ClaimActions) + { + action.Run(user, identity, ClaimsIssuer); + } + } } return HandleRequestResult.Success(new AuthenticationTicket(principal, properties, Scheme.Name)); @@ -1144,7 +1148,7 @@ private async Task RunTokenResponseReceivedEventAs return context; } - private async Task RunUserInformationReceivedEventAsync(ClaimsPrincipal principal, AuthenticationProperties properties, OpenIdConnectMessage message, JObject user) + private async Task RunUserInformationReceivedEventAsync(ClaimsPrincipal principal, AuthenticationProperties properties, OpenIdConnectMessage message, JsonDocument user) { Logger.UserInformationReceived(user.ToString()); diff --git a/src/Security/Authentication/OpenIdConnect/src/UniqueJsonKeyClaimAction.cs b/src/Security/Authentication/OpenIdConnect/src/UniqueJsonKeyClaimAction.cs index 132885b3caf3..dccf2eb6779f 100644 --- a/src/Security/Authentication/OpenIdConnect/src/UniqueJsonKeyClaimAction.cs +++ b/src/Security/Authentication/OpenIdConnect/src/UniqueJsonKeyClaimAction.cs @@ -1,10 +1,10 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.IdentityModel.Tokens.Jwt; using System.Security.Claims; +using System.Text.Json; using Microsoft.AspNetCore.Authentication.OAuth.Claims; -using Newtonsoft.Json.Linq; namespace Microsoft.AspNetCore.Authentication.OpenIdConnect.Claims { @@ -27,9 +27,9 @@ public UniqueJsonKeyClaimAction(string claimType, string valueType, string jsonK } /// - public override void Run(JObject userData, ClaimsIdentity identity, string issuer) + public override void Run(JsonDocument userData, ClaimsIdentity identity, string issuer) { - var value = userData?.Value(JsonKey); + var value = userData?.GetString(JsonKey); if (string.IsNullOrEmpty(value)) { // Not found diff --git a/src/Security/Authentication/Twitter/src/TwitterCreatingTicketContext.cs b/src/Security/Authentication/Twitter/src/TwitterCreatingTicketContext.cs index 67f28d5297af..5440d66fb1b1 100644 --- a/src/Security/Authentication/Twitter/src/TwitterCreatingTicketContext.cs +++ b/src/Security/Authentication/Twitter/src/TwitterCreatingTicketContext.cs @@ -1,10 +1,9 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System; using System.Security.Claims; +using System.Text.Json; using Microsoft.AspNetCore.Http; -using Newtonsoft.Json.Linq; namespace Microsoft.AspNetCore.Authentication.Twitter { @@ -36,14 +35,14 @@ public TwitterCreatingTicketContext( string screenName, string accessToken, string accessTokenSecret, - JObject user) + JsonDocument user) : base(context, scheme, options) { UserId = userId; ScreenName = screenName; AccessToken = accessToken; AccessTokenSecret = accessTokenSecret; - User = user ?? new JObject(); + User = user ?? JsonDocument.Parse(string.Empty); Principal = principal; Properties = properties; } @@ -70,8 +69,8 @@ public TwitterCreatingTicketContext( /// /// Gets the JSON-serialized user or an empty - /// if it is not available. + /// if it is not available. /// - public JObject User { get; } + public JsonDocument User { get; } } } diff --git a/src/Security/Authentication/Twitter/src/TwitterHandler.cs b/src/Security/Authentication/Twitter/src/TwitterHandler.cs index 01f80dc72f9f..aa2dcf93b638 100644 --- a/src/Security/Authentication/Twitter/src/TwitterHandler.cs +++ b/src/Security/Authentication/Twitter/src/TwitterHandler.cs @@ -9,13 +9,13 @@ using System.Security.Cryptography; using System.Text; using System.Text.Encodings.Web; +using System.Text.Json; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.WebUtilities; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Microsoft.Extensions.Primitives; -using Newtonsoft.Json.Linq; namespace Microsoft.AspNetCore.Authentication.Twitter { @@ -99,7 +99,7 @@ protected override async Task HandleRemoteAuthenticateAsync }, ClaimsIssuer); - JObject user = null; + JsonDocument user = null; if (Options.RetrieveUserDetails) { user = await RetrieveUserDetailsAsync(accessToken, identity); @@ -113,11 +113,13 @@ protected override async Task HandleRemoteAuthenticateAsync }); } - return HandleRequestResult.Success(await CreateTicketAsync(identity, properties, accessToken, user)); + var ticket = await CreateTicketAsync(identity, properties, accessToken, user); + user?.Dispose(); + return HandleRequestResult.Success(ticket); } protected virtual async Task CreateTicketAsync( - ClaimsIdentity identity, AuthenticationProperties properties, AccessToken token, JObject user) + ClaimsIdentity identity, AuthenticationProperties properties, AccessToken token, JsonDocument user) { foreach (var action in Options.ClaimActions) { @@ -275,7 +277,7 @@ private async Task ObtainAccessTokenAsync(RequestToken token, strin } // https://dev.twitter.com/rest/reference/get/account/verify_credentials - private async Task RetrieveUserDetailsAsync(AccessToken accessToken, ClaimsIdentity identity) + private async Task RetrieveUserDetailsAsync(AccessToken accessToken, ClaimsIdentity identity) { Logger.RetrieveUserDetails(); @@ -288,7 +290,7 @@ private async Task RetrieveUserDetailsAsync(AccessToken accessToken, Cl } var responseText = await response.Content.ReadAsStringAsync(); - var result = JObject.Parse(responseText); + var result = JsonDocument.Parse(responseText); return result; } diff --git a/src/Security/Authentication/WsFederation/samples/WsFedSample/Properties/launchSettings.json b/src/Security/Authentication/WsFederation/samples/WsFedSample/Properties/launchSettings.json new file mode 100644 index 000000000000..889821c21dc7 --- /dev/null +++ b/src/Security/Authentication/WsFederation/samples/WsFedSample/Properties/launchSettings.json @@ -0,0 +1,27 @@ +{ + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "https://localhost:44318/", + "sslPort": 44318 + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "SocialSample": { + "commandName": "Project", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "applicationUrl": "https://localhost:44318/" + } + } +} diff --git a/src/Security/Authentication/samples/SocialSample/Properties/launchSettings.json b/src/Security/Authentication/samples/SocialSample/Properties/launchSettings.json new file mode 100644 index 000000000000..889821c21dc7 --- /dev/null +++ b/src/Security/Authentication/samples/SocialSample/Properties/launchSettings.json @@ -0,0 +1,27 @@ +{ + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "https://localhost:44318/", + "sslPort": 44318 + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "SocialSample": { + "commandName": "Project", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "applicationUrl": "https://localhost:44318/" + } + } +} diff --git a/src/Security/Authentication/samples/SocialSample/Startup.cs b/src/Security/Authentication/samples/SocialSample/Startup.cs index b5032072bd6b..c4f1884d8c39 100644 --- a/src/Security/Authentication/samples/SocialSample/Startup.cs +++ b/src/Security/Authentication/samples/SocialSample/Startup.cs @@ -6,6 +6,7 @@ using System.Net.Http.Headers; using System.Security.Claims; using System.Text.Encodings.Web; +using System.Text.Json; using System.Threading.Tasks; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication.Cookies; @@ -21,7 +22,6 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; -using Newtonsoft.Json.Linq; namespace SocialSample { @@ -207,7 +207,7 @@ public void ConfigureServices(IServiceCollection services) var response = await context.Backchannel.SendAsync(request, context.HttpContext.RequestAborted); response.EnsureSuccessStatusCode(); - var user = JObject.Parse(await response.Content.ReadAsStringAsync()); + var user = JsonDocument.Parse(await response.Content.ReadAsStringAsync()); context.RunClaimActions(user); } @@ -332,16 +332,16 @@ public void Configure(IApplicationBuilder app) var refreshResponse = await options.Backchannel.PostAsync(options.TokenEndpoint, content, context.RequestAborted); refreshResponse.EnsureSuccessStatusCode(); - var payload = JObject.Parse(await refreshResponse.Content.ReadAsStringAsync()); + var payload = JsonDocument.Parse(await refreshResponse.Content.ReadAsStringAsync()); // Persist the new acess token - authProperties.UpdateTokenValue("access_token", payload.Value("access_token")); - refreshToken = payload.Value("refresh_token"); + authProperties.UpdateTokenValue("access_token", payload.GetString("access_token")); + refreshToken = payload.GetString("refresh_token"); if (!string.IsNullOrEmpty(refreshToken)) { authProperties.UpdateTokenValue("refresh_token", refreshToken); } - if (int.TryParse(payload.Value("expires_in"), NumberStyles.Integer, CultureInfo.InvariantCulture, out var seconds)) + if (payload.RootElement.TryGetProperty("expires_in", out var property) && property.TryGetInt32(out var seconds)) { var expiresAt = DateTimeOffset.UtcNow + TimeSpan.FromSeconds(seconds); authProperties.UpdateTokenValue("expires_at", expiresAt.ToString("o", CultureInfo.InvariantCulture)); @@ -368,10 +368,10 @@ public void Configure(IApplicationBuilder app) }.ToQueryString(); var refreshResponse = await options.Backchannel.GetStringAsync(options.TokenEndpoint + query); - var payload = JObject.Parse(refreshResponse); + var payload = JsonDocument.Parse(refreshResponse); - authProperties.UpdateTokenValue("access_token", payload.Value("access_token")); - if (int.TryParse(payload.Value("expires_in"), NumberStyles.Integer, CultureInfo.InvariantCulture, out var seconds)) + authProperties.UpdateTokenValue("access_token", payload.GetString("access_token")); + if (payload.RootElement.TryGetProperty("expires_in", out var property) && property.TryGetInt32(out var seconds)) { var expiresAt = DateTimeOffset.UtcNow + TimeSpan.FromSeconds(seconds); authProperties.UpdateTokenValue("expires_at", expiresAt.ToString("o", CultureInfo.InvariantCulture)); @@ -485,12 +485,12 @@ private Task GetOAuthOptionsAsync(HttpContext context, string curr throw new NotImplementedException(currentAuthType); } - private async Task PrintRefreshedTokensAsync(HttpResponse response, JObject payload, AuthenticationProperties authProperties) + private async Task PrintRefreshedTokensAsync(HttpResponse response, JsonDocument payload, AuthenticationProperties authProperties) { response.ContentType = "text/html"; await response.WriteAsync(""); await response.WriteAsync("Refreshed.
"); - await response.WriteAsync(HtmlEncoder.Default.Encode(payload.ToString()).Replace(",", ",
") + "
"); + await response.WriteAsync(HtmlEncoder.Default.Encode(payload.RootElement.ToString()).Replace(",", ",
") + "
"); await response.WriteAsync("
Tokens:
"); diff --git a/src/Security/Authentication/samples/SocialSample/web.config b/src/Security/Authentication/samples/SocialSample/web.config index f7ac679334bc..e149af83da85 100644 --- a/src/Security/Authentication/samples/SocialSample/web.config +++ b/src/Security/Authentication/samples/SocialSample/web.config @@ -4,6 +4,8 @@ - + + + \ No newline at end of file diff --git a/src/Security/Authentication/test/ClaimActionTests.cs b/src/Security/Authentication/test/ClaimActionTests.cs index b083e9d76dbe..b990fc597d1b 100644 --- a/src/Security/Authentication/test/ClaimActionTests.cs +++ b/src/Security/Authentication/test/ClaimActionTests.cs @@ -1,14 +1,11 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System; -using System.IO; using System.Linq; using System.Security.Claims; -using Microsoft.AspNetCore.Testing.xunit; -using Xunit; +using System.Text.Json; using Microsoft.AspNetCore.Authentication.OAuth.Claims; -using Newtonsoft.Json.Linq; +using Xunit; namespace Microsoft.AspNetCore.Authentication { @@ -17,10 +14,7 @@ public class ClaimActionTests [Fact] public void CanMapSingleValueUserDataToClaim() { - var userData = new JObject - { - ["name"] = "test" - }; + var userData = JsonDocument.Parse("{ \"name\": \"test\" }"); var identity = new ClaimsIdentity(); @@ -34,10 +28,7 @@ public void CanMapSingleValueUserDataToClaim() [Fact] public void CanMapArrayValueUserDataToClaims() { - var userData = new JObject - { - ["role"] = new JArray { "role1", "role2" } - }; + var userData = JsonDocument.Parse("{ \"role\": [ \"role1\", \"role2\" ] }"); var identity = new ClaimsIdentity(); @@ -55,11 +46,7 @@ public void CanMapArrayValueUserDataToClaims() [Fact] public void MapAllSucceeds() { - var userData = new JObject - { - ["name0"] = "value0", - ["name1"] = "value1", - }; + var userData = JsonDocument.Parse("{ \"name0\": \"value0\", \"name1\": \"value1\" }"); var identity = new ClaimsIdentity(); var action = new MapAllClaimsAction(); @@ -74,11 +61,7 @@ public void MapAllSucceeds() [Fact] public void MapAllAllowesDulicateKeysWithUniqueValues() { - var userData = new JObject - { - ["name0"] = "value0", - ["name1"] = "value1", - }; + var userData = JsonDocument.Parse("{ \"name0\": \"value0\", \"name1\": \"value1\" }"); var identity = new ClaimsIdentity(); identity.AddClaim(new Claim("name0", "value2")); @@ -93,11 +76,7 @@ public void MapAllAllowesDulicateKeysWithUniqueValues() [Fact] public void MapAllSkipsDuplicateValues() { - var userData = new JObject - { - ["name0"] = "value0", - ["name1"] = "value1", - }; + var userData = JsonDocument.Parse("{ \"name0\": \"value0\", \"name1\": \"value1\" }"); var identity = new ClaimsIdentity(); identity.AddClaim(new Claim("name0", "value0")); diff --git a/src/Security/Authentication/test/FacebookTests.cs b/src/Security/Authentication/test/FacebookTests.cs index 964af2ce7285..bd50a86de683 100644 --- a/src/Security/Authentication/test/FacebookTests.cs +++ b/src/Security/Authentication/test/FacebookTests.cs @@ -10,7 +10,6 @@ using Microsoft.AspNetCore.TestHost; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging.Abstractions; -using Newtonsoft.Json; using System; using System.Linq; using System.Net; @@ -325,10 +324,7 @@ public async Task CustomUserInfoEndpointHasValidGraphQuery() if (req.RequestUri.GetComponents(UriComponents.SchemeAndServer | UriComponents.Path, UriFormat.UriEscaped) == FacebookDefaults.TokenEndpoint) { var res = new HttpResponseMessage(HttpStatusCode.OK); - var graphResponse = JsonConvert.SerializeObject(new - { - access_token = "TestAuthToken" - }); + var graphResponse = "{ \"access_token\": \"TestAuthToken\" }"; res.Content = new StringContent(graphResponse, Encoding.UTF8); return res; } @@ -337,11 +333,7 @@ public async Task CustomUserInfoEndpointHasValidGraphQuery() { finalUserInfoEndpoint = req.RequestUri.ToString(); var res = new HttpResponseMessage(HttpStatusCode.OK); - var graphResponse = JsonConvert.SerializeObject(new - { - id = "TestProfileId", - name = "TestName" - }); + var graphResponse = "{ \"id\": \"TestProfileId\", \"name\": \"TestName\" }"; res.Content = new StringContent(graphResponse, Encoding.UTF8); return res; } diff --git a/src/Security/Authentication/test/GoogleTests.cs b/src/Security/Authentication/test/GoogleTests.cs index 6559c1aacc0e..4d8dc500f9d3 100644 --- a/src/Security/Authentication/test/GoogleTests.cs +++ b/src/Security/Authentication/test/GoogleTests.cs @@ -9,7 +9,6 @@ using Microsoft.AspNetCore.WebUtilities; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging.Abstractions; -using Newtonsoft.Json; using System; using System.Collections.Generic; using System.Linq; @@ -972,7 +971,7 @@ private HttpMessageHandler CreateBackchannel() private static HttpResponseMessage ReturnJsonResponse(object content, HttpStatusCode code = HttpStatusCode.OK) { var res = new HttpResponseMessage(code); - var text = JsonConvert.SerializeObject(content); + var text = Newtonsoft.Json.JsonConvert.SerializeObject(content); res.Content = new StringContent(text, Encoding.UTF8, "application/json"); return res; } diff --git a/src/Security/Authentication/test/MicrosoftAccountTests.cs b/src/Security/Authentication/test/MicrosoftAccountTests.cs index 4005b50efeca..c13f4bf51b01 100644 --- a/src/Security/Authentication/test/MicrosoftAccountTests.cs +++ b/src/Security/Authentication/test/MicrosoftAccountTests.cs @@ -9,7 +9,6 @@ using Microsoft.AspNetCore.TestHost; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging.Abstractions; -using Newtonsoft.Json; using System; using System.Linq; using System.Net; @@ -302,7 +301,7 @@ private static TestServer CreateServer(Action configure private static HttpResponseMessage ReturnJsonResponse(object content) { var res = new HttpResponseMessage(HttpStatusCode.OK); - var text = JsonConvert.SerializeObject(content); + var text = Newtonsoft.Json.JsonConvert.SerializeObject(content); res.Content = new StringContent(text, Encoding.UTF8, "application/json"); return res; } From c613e542091e6f50201b23f7b3bcf75931ac97a5 Mon Sep 17 00:00:00 2001 From: Chris R Date: Tue, 29 Jan 2019 13:07:05 -0800 Subject: [PATCH 2/9] Use the pipe --- .../JwtBearer/samples/JwtBearerSample/Startup.cs | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/src/Security/Authentication/JwtBearer/samples/JwtBearerSample/Startup.cs b/src/Security/Authentication/JwtBearer/samples/JwtBearerSample/Startup.cs index bd1d3710980e..1999cea1d9de 100644 --- a/src/Security/Authentication/JwtBearer/samples/JwtBearerSample/Startup.cs +++ b/src/Security/Authentication/JwtBearer/samples/JwtBearerSample/Startup.cs @@ -104,20 +104,13 @@ public void Configure(IApplicationBuilder app) { response.ContentType = "application/json"; response.Headers[HeaderNames.CacheControl] = "no-cache"; - await SerializeAsync(Todos, response.Body); + Serialize(Todos, response.BodyPipe); + await response.BodyPipe.FlushAsync(); } }); }); } - private async Task SerializeAsync(IList todos, Stream stream) - { - var pipe = new StreamPipeWriter(stream); - Serialize(todos, pipe); - await pipe.FlushAsync(); - pipe.Complete(); - } - private void Serialize(IList todos, IBufferWriter output) { var writer = new Utf8JsonWriter(output); From 7691abfef411c9aa08f753e59545912a67d3b250 Mon Sep 17 00:00:00 2001 From: Chris R Date: Tue, 29 Jan 2019 14:05:58 -0800 Subject: [PATCH 3/9] Add null array value --- src/Security/Authentication/test/ClaimActionTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Security/Authentication/test/ClaimActionTests.cs b/src/Security/Authentication/test/ClaimActionTests.cs index b990fc597d1b..d42e786dd812 100644 --- a/src/Security/Authentication/test/ClaimActionTests.cs +++ b/src/Security/Authentication/test/ClaimActionTests.cs @@ -28,7 +28,7 @@ public void CanMapSingleValueUserDataToClaim() [Fact] public void CanMapArrayValueUserDataToClaims() { - var userData = JsonDocument.Parse("{ \"role\": [ \"role1\", \"role2\" ] }"); + var userData = JsonDocument.Parse("{ \"role\": [ \"role1\", null, \"role2\" ] }"); var identity = new ClaimsIdentity(); From dc101b9f9a7cdd07f43d83145f1b868992566742 Mon Sep 17 00:00:00 2001 From: Chris R Date: Tue, 29 Jan 2019 15:15:57 -0800 Subject: [PATCH 4/9] Empty doc --- .../OAuth/src/Events/OAuthCreatingTicketContext.cs | 2 +- .../Authentication/Twitter/src/TwitterCreatingTicketContext.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Security/Authentication/OAuth/src/Events/OAuthCreatingTicketContext.cs b/src/Security/Authentication/OAuth/src/Events/OAuthCreatingTicketContext.cs index 6c3a43c071c4..887eb3101667 100644 --- a/src/Security/Authentication/OAuth/src/Events/OAuthCreatingTicketContext.cs +++ b/src/Security/Authentication/OAuth/src/Events/OAuthCreatingTicketContext.cs @@ -33,7 +33,7 @@ public OAuthCreatingTicketContext( OAuthOptions options, HttpClient backchannel, OAuthTokenResponse tokens) - : this(principal, properties, context, scheme, options, backchannel, tokens, user: JsonDocument.Parse(string.Empty)) + : this(principal, properties, context, scheme, options, backchannel, tokens, user: JsonDocument.Parse("{}")) { } /// diff --git a/src/Security/Authentication/Twitter/src/TwitterCreatingTicketContext.cs b/src/Security/Authentication/Twitter/src/TwitterCreatingTicketContext.cs index 5440d66fb1b1..de18558053e0 100644 --- a/src/Security/Authentication/Twitter/src/TwitterCreatingTicketContext.cs +++ b/src/Security/Authentication/Twitter/src/TwitterCreatingTicketContext.cs @@ -42,7 +42,7 @@ public TwitterCreatingTicketContext( ScreenName = screenName; AccessToken = accessToken; AccessTokenSecret = accessTokenSecret; - User = user ?? JsonDocument.Parse(string.Empty); + User = user ?? JsonDocument.Parse("{}"); Principal = principal; Properties = properties; } From e8b0210a48c1bb24ebc1d20d20f5b30b3e091ec1 Mon Sep 17 00:00:00 2001 From: Chris R Date: Wed, 30 Jan 2019 12:34:28 -0800 Subject: [PATCH 5/9] More dispose --- .../samples/JwtBearerSample/Startup.cs | 9 ++- .../src/Events/OAuthCreatingTicketContext.cs | 21 ------- .../Authentication/OAuth/src/OAuthHandler.cs | 9 ++- .../samples/OpenIdConnectSample/Startup.cs | 42 ++++++------- .../samples/SocialSample/Startup.cs | 60 ++++++++++--------- 5 files changed, 65 insertions(+), 76 deletions(-) diff --git a/src/Security/Authentication/JwtBearer/samples/JwtBearerSample/Startup.cs b/src/Security/Authentication/JwtBearer/samples/JwtBearerSample/Startup.cs index 1999cea1d9de..f0faeefc6894 100644 --- a/src/Security/Authentication/JwtBearer/samples/JwtBearerSample/Startup.cs +++ b/src/Security/Authentication/JwtBearer/samples/JwtBearerSample/Startup.cs @@ -96,9 +96,12 @@ public void Configure(IApplicationBuilder app) { var reader = new StreamReader(context.Request.Body); var body = await reader.ReadToEndAsync(); - var obj = JsonDocument.Parse(body).RootElement; - var todo = new Todo() { Description = obj.GetProperty("Description").ToString(), Owner = context.User.Identity.Name }; - Todos.Add(todo); + using (var json = JsonDocument.Parse(body)) + { + var obj = json.RootElement; + var todo = new Todo() { Description = obj.GetProperty("Description").GetString(), Owner = context.User.Identity.Name }; + Todos.Add(todo); + } } else { diff --git a/src/Security/Authentication/OAuth/src/Events/OAuthCreatingTicketContext.cs b/src/Security/Authentication/OAuth/src/Events/OAuthCreatingTicketContext.cs index 887eb3101667..1d9da136ccaa 100644 --- a/src/Security/Authentication/OAuth/src/Events/OAuthCreatingTicketContext.cs +++ b/src/Security/Authentication/OAuth/src/Events/OAuthCreatingTicketContext.cs @@ -15,27 +15,6 @@ namespace Microsoft.AspNetCore.Authentication.OAuth /// public class OAuthCreatingTicketContext : ResultContext { - /// - /// Initializes a new . - /// - /// The . - /// The . - /// The HTTP environment. - /// The authentication scheme. - /// The options used by the authentication middleware. - /// The HTTP client used by the authentication middleware - /// The tokens returned from the token endpoint. - public OAuthCreatingTicketContext( - ClaimsPrincipal principal, - AuthenticationProperties properties, - HttpContext context, - AuthenticationScheme scheme, - OAuthOptions options, - HttpClient backchannel, - OAuthTokenResponse tokens) - : this(principal, properties, context, scheme, options, backchannel, tokens, user: JsonDocument.Parse("{}")) - { } - /// /// Initializes a new . /// diff --git a/src/Security/Authentication/OAuth/src/OAuthHandler.cs b/src/Security/Authentication/OAuth/src/OAuthHandler.cs index 3b4f4437cb37..40079456c40e 100644 --- a/src/Security/Authentication/OAuth/src/OAuthHandler.cs +++ b/src/Security/Authentication/OAuth/src/OAuthHandler.cs @@ -199,9 +199,12 @@ private static async Task Display(HttpResponseMessage response) protected virtual async Task CreateTicketAsync(ClaimsIdentity identity, AuthenticationProperties properties, OAuthTokenResponse tokens) { - var context = new OAuthCreatingTicketContext(new ClaimsPrincipal(identity), properties, Context, Scheme, Options, Backchannel, tokens); - await Events.CreatingTicket(context); - return new AuthenticationTicket(context.Principal, context.Properties, Scheme.Name); + using (var user = JsonDocument.Parse("{}")) + { + var context = new OAuthCreatingTicketContext(new ClaimsPrincipal(identity), properties, Context, Scheme, Options, Backchannel, tokens, user); + await Events.CreatingTicket(context); + return new AuthenticationTicket(context.Principal, context.Properties, Scheme.Name); + } } protected override async Task HandleChallengeAsync(AuthenticationProperties properties) diff --git a/src/Security/Authentication/OpenIdConnect/samples/OpenIdConnectSample/Startup.cs b/src/Security/Authentication/OpenIdConnect/samples/OpenIdConnectSample/Startup.cs index 7311f63d6797..ebecabda3355 100644 --- a/src/Security/Authentication/OpenIdConnect/samples/OpenIdConnectSample/Startup.cs +++ b/src/Security/Authentication/OpenIdConnect/samples/OpenIdConnectSample/Startup.cs @@ -208,30 +208,32 @@ await WriteHtmlAsync(response, async res => var tokenResponse = await options.Backchannel.PostAsync(metadata.TokenEndpoint, content, context.RequestAborted); tokenResponse.EnsureSuccessStatusCode(); - var payload = JsonDocument.Parse(await tokenResponse.Content.ReadAsStringAsync()); - - // Persist the new acess token - props.UpdateTokenValue("access_token", payload.GetString("access_token")); - props.UpdateTokenValue("refresh_token", payload.GetString("refresh_token")); - if (payload.RootElement.TryGetProperty("expires_in", out var property) && property.TryGetInt32(out var seconds)) + using (var payload = JsonDocument.Parse(await tokenResponse.Content.ReadAsStringAsync())) { - var expiresAt = DateTimeOffset.UtcNow + TimeSpan.FromSeconds(seconds); - props.UpdateTokenValue("expires_at", expiresAt.ToString("o", CultureInfo.InvariantCulture)); - } - await context.SignInAsync(user, props); - await WriteHtmlAsync(response, async res => - { - await res.WriteAsync($"

Refreshed.

"); - await res.WriteAsync("Refresh tokens"); - await res.WriteAsync("Home"); + // Persist the new acess token + props.UpdateTokenValue("access_token", payload.GetString("access_token")); + props.UpdateTokenValue("refresh_token", payload.GetString("refresh_token")); + if (payload.RootElement.TryGetProperty("expires_in", out var property) && property.TryGetInt32(out var seconds)) + { + var expiresAt = DateTimeOffset.UtcNow + TimeSpan.FromSeconds(seconds); + props.UpdateTokenValue("expires_at", expiresAt.ToString("o", CultureInfo.InvariantCulture)); + } + await context.SignInAsync(user, props); - await res.WriteAsync("

Tokens:

"); - await WriteTableHeader(res, new string[] { "Token Type", "Value" }, props.GetTokens().Select(token => new string[] { token.Name, token.Value })); + await WriteHtmlAsync(response, async res => + { + await res.WriteAsync($"

Refreshed.

"); + await res.WriteAsync("Refresh tokens"); + await res.WriteAsync("Home"); - await res.WriteAsync("

Payload:

"); - await res.WriteAsync(HtmlEncoder.Default.Encode(payload.ToString()).Replace(",", ",
") + "
"); - }); + await res.WriteAsync("

Tokens:

"); + await WriteTableHeader(res, new string[] { "Token Type", "Value" }, props.GetTokens().Select(token => new string[] { token.Name, token.Value })); + + await res.WriteAsync("

Payload:

"); + await res.WriteAsync(HtmlEncoder.Default.Encode(payload.ToString()).Replace(",", ",
") + "
"); + }); + } return; } diff --git a/src/Security/Authentication/samples/SocialSample/Startup.cs b/src/Security/Authentication/samples/SocialSample/Startup.cs index c4f1884d8c39..a25733c76f91 100644 --- a/src/Security/Authentication/samples/SocialSample/Startup.cs +++ b/src/Security/Authentication/samples/SocialSample/Startup.cs @@ -207,9 +207,10 @@ public void ConfigureServices(IServiceCollection services) var response = await context.Backchannel.SendAsync(request, context.HttpContext.RequestAborted); response.EnsureSuccessStatusCode(); - var user = JsonDocument.Parse(await response.Content.ReadAsStringAsync()); - - context.RunClaimActions(user); + using (var user = JsonDocument.Parse(await response.Content.ReadAsStringAsync())) + { + context.RunClaimActions(user); + } } }; }); @@ -332,24 +333,25 @@ public void Configure(IApplicationBuilder app) var refreshResponse = await options.Backchannel.PostAsync(options.TokenEndpoint, content, context.RequestAborted); refreshResponse.EnsureSuccessStatusCode(); - var payload = JsonDocument.Parse(await refreshResponse.Content.ReadAsStringAsync()); - - // Persist the new acess token - authProperties.UpdateTokenValue("access_token", payload.GetString("access_token")); - refreshToken = payload.GetString("refresh_token"); - if (!string.IsNullOrEmpty(refreshToken)) - { - authProperties.UpdateTokenValue("refresh_token", refreshToken); - } - if (payload.RootElement.TryGetProperty("expires_in", out var property) && property.TryGetInt32(out var seconds)) + using (var payload = JsonDocument.Parse(await refreshResponse.Content.ReadAsStringAsync())) { - var expiresAt = DateTimeOffset.UtcNow + TimeSpan.FromSeconds(seconds); - authProperties.UpdateTokenValue("expires_at", expiresAt.ToString("o", CultureInfo.InvariantCulture)); - } - await context.SignInAsync(user, authProperties); - - await PrintRefreshedTokensAsync(response, payload, authProperties); + // Persist the new acess token + authProperties.UpdateTokenValue("access_token", payload.GetString("access_token")); + refreshToken = payload.GetString("refresh_token"); + if (!string.IsNullOrEmpty(refreshToken)) + { + authProperties.UpdateTokenValue("refresh_token", refreshToken); + } + if (payload.RootElement.TryGetProperty("expires_in", out var property) && property.TryGetInt32(out var seconds)) + { + var expiresAt = DateTimeOffset.UtcNow + TimeSpan.FromSeconds(seconds); + authProperties.UpdateTokenValue("expires_at", expiresAt.ToString("o", CultureInfo.InvariantCulture)); + } + await context.SignInAsync(user, authProperties); + + await PrintRefreshedTokensAsync(response, payload, authProperties); + } return; } // https://developers.facebook.com/docs/facebook-login/access-tokens/expiration-and-extension @@ -368,18 +370,18 @@ public void Configure(IApplicationBuilder app) }.ToQueryString(); var refreshResponse = await options.Backchannel.GetStringAsync(options.TokenEndpoint + query); - var payload = JsonDocument.Parse(refreshResponse); - - authProperties.UpdateTokenValue("access_token", payload.GetString("access_token")); - if (payload.RootElement.TryGetProperty("expires_in", out var property) && property.TryGetInt32(out var seconds)) + using (var payload = JsonDocument.Parse(refreshResponse)) { - var expiresAt = DateTimeOffset.UtcNow + TimeSpan.FromSeconds(seconds); - authProperties.UpdateTokenValue("expires_at", expiresAt.ToString("o", CultureInfo.InvariantCulture)); + authProperties.UpdateTokenValue("access_token", payload.GetString("access_token")); + if (payload.RootElement.TryGetProperty("expires_in", out var property) && property.TryGetInt32(out var seconds)) + { + var expiresAt = DateTimeOffset.UtcNow + TimeSpan.FromSeconds(seconds); + authProperties.UpdateTokenValue("expires_at", expiresAt.ToString("o", CultureInfo.InvariantCulture)); + } + await context.SignInAsync(user, authProperties); + + await PrintRefreshedTokensAsync(response, payload, authProperties); } - await context.SignInAsync(user, authProperties); - - await PrintRefreshedTokensAsync(response, payload, authProperties); - return; } From dcdc9a09460d5155f5736c0a342f13eb07a0deb5 Mon Sep 17 00:00:00 2001 From: Chris R Date: Wed, 30 Jan 2019 14:24:09 -0800 Subject: [PATCH 6/9] Prefer JsonElement --- .../Core/src/JsonDocumentAuthExtensions.cs | 5 +++- .../Facebook/src/FacebookHandler.cs | 2 +- .../Google/src/GoogleHandler.cs | 3 +-- .../src/MicrosoftAccountHandler.cs | 2 +- .../Authentication/OAuth/src/ClaimAction.cs | 2 +- .../src/ClaimActionCollectionMapExtensions.cs | 4 ++-- .../OAuth/src/CustomJsonClaimAction.cs | 10 +++----- .../OAuth/src/DeleteClaimAction.cs | 2 +- .../src/Events/OAuthCreatingTicketContext.cs | 18 ++++---------- .../OAuth/src/JsonKeyClaimAction.cs | 4 ++-- .../OAuth/src/JsonSubKeyClaimAction.cs | 6 ++--- .../OAuth/src/MapAllClaimsAction.cs | 8 ++----- .../Authentication/OAuth/src/OAuthHandler.cs | 2 +- .../samples/OpenIdConnectSample/Startup.cs | 1 - .../OpenIdConnect/src/OpenIdConnectHandler.cs | 11 +++++---- .../src/UniqueJsonKeyClaimAction.cs | 4 ++-- .../src/TwitterCreatingTicketContext.cs | 8 +++---- .../Twitter/src/TwitterHandler.cs | 24 ++++++++++++------- .../samples/SocialSample/Startup.cs | 2 +- .../Authentication/test/ClaimActionTests.cs | 10 ++++---- .../Authentication/test/GoogleTests.cs | 1 - 21 files changed, 60 insertions(+), 69 deletions(-) diff --git a/src/Security/Authentication/Core/src/JsonDocumentAuthExtensions.cs b/src/Security/Authentication/Core/src/JsonDocumentAuthExtensions.cs index 2ecaf0d81571..330e02c54916 100644 --- a/src/Security/Authentication/Core/src/JsonDocumentAuthExtensions.cs +++ b/src/Security/Authentication/Core/src/JsonDocumentAuthExtensions.cs @@ -8,7 +8,10 @@ namespace Microsoft.AspNetCore.Authentication public static class JsonDocumentAuthExtensions { public static string GetString(this JsonDocument document, string key) => - document.RootElement.TryGetProperty(key, out var property) + document.RootElement.GetString(key); + + public static string GetString(this JsonElement element, string key) => + element.TryGetProperty(key, out var property) ? property.ToString() : null; } } diff --git a/src/Security/Authentication/Facebook/src/FacebookHandler.cs b/src/Security/Authentication/Facebook/src/FacebookHandler.cs index c825d33560fa..7fe350385e80 100644 --- a/src/Security/Authentication/Facebook/src/FacebookHandler.cs +++ b/src/Security/Authentication/Facebook/src/FacebookHandler.cs @@ -43,7 +43,7 @@ protected override async Task CreateTicketAsync(ClaimsIden using (var payload = JsonDocument.Parse(await response.Content.ReadAsStringAsync())) { - var context = new OAuthCreatingTicketContext(new ClaimsPrincipal(identity), properties, Context, Scheme, Options, Backchannel, tokens, payload); + var context = new OAuthCreatingTicketContext(new ClaimsPrincipal(identity), properties, Context, Scheme, Options, Backchannel, tokens, payload.RootElement); context.RunClaimActions(); await Events.CreatingTicket(context); return new AuthenticationTicket(context.Principal, context.Properties, Scheme.Name); diff --git a/src/Security/Authentication/Google/src/GoogleHandler.cs b/src/Security/Authentication/Google/src/GoogleHandler.cs index 2f9fcf106c9a..7d1df746f038 100644 --- a/src/Security/Authentication/Google/src/GoogleHandler.cs +++ b/src/Security/Authentication/Google/src/GoogleHandler.cs @@ -39,8 +39,7 @@ protected override async Task CreateTicketAsync( using (var payload = JsonDocument.Parse(await response.Content.ReadAsStringAsync())) { - - var context = new OAuthCreatingTicketContext(new ClaimsPrincipal(identity), properties, Context, Scheme, Options, Backchannel, tokens, payload); + var context = new OAuthCreatingTicketContext(new ClaimsPrincipal(identity), properties, Context, Scheme, Options, Backchannel, tokens, payload.RootElement); context.RunClaimActions(); await Events.CreatingTicket(context); return new AuthenticationTicket(context.Principal, context.Properties, Scheme.Name); diff --git a/src/Security/Authentication/MicrosoftAccount/src/MicrosoftAccountHandler.cs b/src/Security/Authentication/MicrosoftAccount/src/MicrosoftAccountHandler.cs index 38194bbe83b1..910da8d9146e 100644 --- a/src/Security/Authentication/MicrosoftAccount/src/MicrosoftAccountHandler.cs +++ b/src/Security/Authentication/MicrosoftAccount/src/MicrosoftAccountHandler.cs @@ -32,7 +32,7 @@ protected override async Task CreateTicketAsync(ClaimsIden using (var payload = JsonDocument.Parse(await response.Content.ReadAsStringAsync())) { - var context = new OAuthCreatingTicketContext(new ClaimsPrincipal(identity), properties, Context, Scheme, Options, Backchannel, tokens, payload); + var context = new OAuthCreatingTicketContext(new ClaimsPrincipal(identity), properties, Context, Scheme, Options, Backchannel, tokens, payload.RootElement); context.RunClaimActions(); await Events.CreatingTicket(context); return new AuthenticationTicket(context.Principal, context.Properties, Scheme.Name); diff --git a/src/Security/Authentication/OAuth/src/ClaimAction.cs b/src/Security/Authentication/OAuth/src/ClaimAction.cs index cab03e679dbb..8d324bc8413b 100644 --- a/src/Security/Authentication/OAuth/src/ClaimAction.cs +++ b/src/Security/Authentication/OAuth/src/ClaimAction.cs @@ -37,6 +37,6 @@ public ClaimAction(string claimType, string valueType) /// The source data to examine. This value may be null. /// The identity to add Claims to. /// The value to use for Claim.Issuer when creating a Claim. - public abstract void Run(JsonDocument userData, ClaimsIdentity identity, string issuer); + public abstract void Run(JsonElement userData, ClaimsIdentity identity, string issuer); } } diff --git a/src/Security/Authentication/OAuth/src/ClaimActionCollectionMapExtensions.cs b/src/Security/Authentication/OAuth/src/ClaimActionCollectionMapExtensions.cs index 6fa625075f6b..f123785691f1 100644 --- a/src/Security/Authentication/OAuth/src/ClaimActionCollectionMapExtensions.cs +++ b/src/Security/Authentication/OAuth/src/ClaimActionCollectionMapExtensions.cs @@ -69,7 +69,7 @@ public static void MapJsonSubKey(this ClaimActionCollection collection, string c /// /// The value to use for Claim.Type when creating a Claim. /// The Func that will be called to select value from the given json user data. - public static void MapCustomJson(this ClaimActionCollection collection, string claimType, Func resolver) + public static void MapCustomJson(this ClaimActionCollection collection, string claimType, Func resolver) { collection.MapCustomJson(claimType, ClaimValueTypes.String, resolver); } @@ -82,7 +82,7 @@ public static void MapCustomJson(this ClaimActionCollection collection, string c /// The value to use for Claim.Type when creating a Claim. /// The value to use for Claim.ValueType when creating a Claim. /// The Func that will be called to select value from the given json user data. - public static void MapCustomJson(this ClaimActionCollection collection, string claimType, string valueType, Func resolver) + public static void MapCustomJson(this ClaimActionCollection collection, string claimType, string valueType, Func resolver) { collection.Add(new CustomJsonClaimAction(claimType, valueType, resolver)); } diff --git a/src/Security/Authentication/OAuth/src/CustomJsonClaimAction.cs b/src/Security/Authentication/OAuth/src/CustomJsonClaimAction.cs index 6d344a200698..ee307a97ae89 100644 --- a/src/Security/Authentication/OAuth/src/CustomJsonClaimAction.cs +++ b/src/Security/Authentication/OAuth/src/CustomJsonClaimAction.cs @@ -18,7 +18,7 @@ public class CustomJsonClaimAction : ClaimAction /// The value to use for Claim.Type when creating a Claim. /// The value to use for Claim.ValueType when creating a Claim. /// The Func that will be called to select value from the given json user data. - public CustomJsonClaimAction(string claimType, string valueType, Func resolver) + public CustomJsonClaimAction(string claimType, string valueType, Func resolver) : base(claimType, valueType) { Resolver = resolver; @@ -27,15 +27,11 @@ public CustomJsonClaimAction(string claimType, string valueType, Func /// The Func that will be called to select value from the given json user data. ///
- public Func Resolver { get; } + public Func Resolver { get; } /// - public override void Run(JsonDocument userData, ClaimsIdentity identity, string issuer) + public override void Run(JsonElement userData, ClaimsIdentity identity, string issuer) { - if (userData == null) - { - return; - } var value = Resolver(userData); if (!string.IsNullOrEmpty(value)) { diff --git a/src/Security/Authentication/OAuth/src/DeleteClaimAction.cs b/src/Security/Authentication/OAuth/src/DeleteClaimAction.cs index 3fefa8bf8c47..e12454bffad6 100644 --- a/src/Security/Authentication/OAuth/src/DeleteClaimAction.cs +++ b/src/Security/Authentication/OAuth/src/DeleteClaimAction.cs @@ -22,7 +22,7 @@ public DeleteClaimAction(string claimType) } /// - public override void Run(JsonDocument userData, ClaimsIdentity identity, string issuer) + public override void Run(JsonElement userData, ClaimsIdentity identity, string issuer) { foreach (var claim in identity.FindAll(ClaimType).ToList()) { diff --git a/src/Security/Authentication/OAuth/src/Events/OAuthCreatingTicketContext.cs b/src/Security/Authentication/OAuth/src/Events/OAuthCreatingTicketContext.cs index 1d9da136ccaa..f534fac2f18c 100644 --- a/src/Security/Authentication/OAuth/src/Events/OAuthCreatingTicketContext.cs +++ b/src/Security/Authentication/OAuth/src/Events/OAuthCreatingTicketContext.cs @@ -34,7 +34,7 @@ public OAuthCreatingTicketContext( OAuthOptions options, HttpClient backchannel, OAuthTokenResponse tokens, - JsonDocument user) + JsonElement user) : base(context, scheme, options) { if (backchannel == null) @@ -47,11 +47,6 @@ public OAuthCreatingTicketContext( throw new ArgumentNullException(nameof(tokens)); } - if (user == null) - { - throw new ArgumentNullException(nameof(user)); - } - TokenResponse = tokens; Backchannel = backchannel; User = user; @@ -61,9 +56,9 @@ public OAuthCreatingTicketContext( /// /// Gets the JSON-serialized user or an empty - /// if it is not available. + /// if it is not available. /// - public JsonDocument User { get; } + public JsonElement User { get; } /// /// Gets the token response returned by the authentication service. @@ -115,13 +110,8 @@ public TimeSpan? ExpiresIn public void RunClaimActions() => RunClaimActions(User); - public void RunClaimActions(JsonDocument userData) + public void RunClaimActions(JsonElement userData) { - if (userData == null) - { - throw new ArgumentNullException(nameof(userData)); - } - foreach (var action in Options.ClaimActions) { action.Run(userData, Identity, Options.ClaimsIssuer ?? Scheme.Name); diff --git a/src/Security/Authentication/OAuth/src/JsonKeyClaimAction.cs b/src/Security/Authentication/OAuth/src/JsonKeyClaimAction.cs index 340c2cd64238..464bcb6e225e 100644 --- a/src/Security/Authentication/OAuth/src/JsonKeyClaimAction.cs +++ b/src/Security/Authentication/OAuth/src/JsonKeyClaimAction.cs @@ -30,9 +30,9 @@ public JsonKeyClaimAction(string claimType, string valueType, string jsonKey) public string JsonKey { get; } /// - public override void Run(JsonDocument userData, ClaimsIdentity identity, string issuer) + public override void Run(JsonElement userData, ClaimsIdentity identity, string issuer) { - if (userData == null || !userData.RootElement.TryGetProperty(JsonKey, out var value)) + if (!userData.TryGetProperty(JsonKey, out var value)) { return; } diff --git a/src/Security/Authentication/OAuth/src/JsonSubKeyClaimAction.cs b/src/Security/Authentication/OAuth/src/JsonSubKeyClaimAction.cs index b4bfb1d39fc0..4d3b134d44f0 100644 --- a/src/Security/Authentication/OAuth/src/JsonSubKeyClaimAction.cs +++ b/src/Security/Authentication/OAuth/src/JsonSubKeyClaimAction.cs @@ -32,7 +32,7 @@ public JsonSubKeyClaimAction(string claimType, string valueType, string jsonKey, public string SubKey { get; } /// - public override void Run(JsonDocument userData, ClaimsIdentity identity, string issuer) + public override void Run(JsonElement userData, ClaimsIdentity identity, string issuer) { var value = GetValue(userData, JsonKey, SubKey); if (!string.IsNullOrEmpty(value)) @@ -42,9 +42,9 @@ public override void Run(JsonDocument userData, ClaimsIdentity identity, string } // Get the given subProperty from a property. - private static string GetValue(JsonDocument userData, string propertyName, string subProperty) + private static string GetValue(JsonElement userData, string propertyName, string subProperty) { - if (userData != null && userData.RootElement.TryGetProperty(propertyName, out var value) + if (userData.TryGetProperty(propertyName, out var value) && value.Type == JsonValueType.Object && value.TryGetProperty(subProperty, out value)) { return value.ToString(); diff --git a/src/Security/Authentication/OAuth/src/MapAllClaimsAction.cs b/src/Security/Authentication/OAuth/src/MapAllClaimsAction.cs index b8aad5a49309..3c14aa77e7c4 100644 --- a/src/Security/Authentication/OAuth/src/MapAllClaimsAction.cs +++ b/src/Security/Authentication/OAuth/src/MapAllClaimsAction.cs @@ -17,13 +17,9 @@ public MapAllClaimsAction() : base("All", ClaimValueTypes.String) { } - public override void Run(JsonDocument userData, ClaimsIdentity identity, string issuer) + public override void Run(JsonElement userData, ClaimsIdentity identity, string issuer) { - if (userData == null) - { - return; - } - foreach (var pair in userData.RootElement.EnumerateObject()) + foreach (var pair in userData.EnumerateObject()) { var claimValue = userData.GetString(pair.Name); diff --git a/src/Security/Authentication/OAuth/src/OAuthHandler.cs b/src/Security/Authentication/OAuth/src/OAuthHandler.cs index 40079456c40e..0d2cf140663a 100644 --- a/src/Security/Authentication/OAuth/src/OAuthHandler.cs +++ b/src/Security/Authentication/OAuth/src/OAuthHandler.cs @@ -201,7 +201,7 @@ protected virtual async Task CreateTicketAsync(ClaimsIdent { using (var user = JsonDocument.Parse("{}")) { - var context = new OAuthCreatingTicketContext(new ClaimsPrincipal(identity), properties, Context, Scheme, Options, Backchannel, tokens, user); + var context = new OAuthCreatingTicketContext(new ClaimsPrincipal(identity), properties, Context, Scheme, Options, Backchannel, tokens, user.RootElement); await Events.CreatingTicket(context); return new AuthenticationTicket(context.Principal, context.Properties, Scheme.Name); } diff --git a/src/Security/Authentication/OpenIdConnect/samples/OpenIdConnectSample/Startup.cs b/src/Security/Authentication/OpenIdConnect/samples/OpenIdConnectSample/Startup.cs index ebecabda3355..10719a67be82 100644 --- a/src/Security/Authentication/OpenIdConnect/samples/OpenIdConnectSample/Startup.cs +++ b/src/Security/Authentication/OpenIdConnect/samples/OpenIdConnectSample/Startup.cs @@ -210,7 +210,6 @@ await WriteHtmlAsync(response, async res => using (var payload = JsonDocument.Parse(await tokenResponse.Content.ReadAsStringAsync())) { - // Persist the new acess token props.UpdateTokenValue("access_token", payload.GetString("access_token")); props.UpdateTokenValue("refresh_token", payload.GetString("refresh_token")); diff --git a/src/Security/Authentication/OpenIdConnect/src/OpenIdConnectHandler.cs b/src/Security/Authentication/OpenIdConnect/src/OpenIdConnectHandler.cs index ed6e22d38f8d..8348b9832a1a 100644 --- a/src/Security/Authentication/OpenIdConnect/src/OpenIdConnectHandler.cs +++ b/src/Security/Authentication/OpenIdConnect/src/OpenIdConnectHandler.cs @@ -712,10 +712,13 @@ protected override async Task HandleRemoteAuthenticateAsync } else { - var identity = (ClaimsIdentity)user.Identity; - foreach (var action in Options.ClaimActions) + using (var payload = JsonDocument.Parse("{}")) { - action.Run(null, identity, ClaimsIssuer); + var identity = (ClaimsIdentity)user.Identity; + foreach (var action in Options.ClaimActions) + { + action.Run(payload.RootElement, identity, ClaimsIssuer); + } } } @@ -890,7 +893,7 @@ protected virtual async Task GetUserInformationAsync( foreach (var action in Options.ClaimActions) { - action.Run(user, identity, ClaimsIssuer); + action.Run(user.RootElement, identity, ClaimsIssuer); } } } diff --git a/src/Security/Authentication/OpenIdConnect/src/UniqueJsonKeyClaimAction.cs b/src/Security/Authentication/OpenIdConnect/src/UniqueJsonKeyClaimAction.cs index dccf2eb6779f..762146b72ca3 100644 --- a/src/Security/Authentication/OpenIdConnect/src/UniqueJsonKeyClaimAction.cs +++ b/src/Security/Authentication/OpenIdConnect/src/UniqueJsonKeyClaimAction.cs @@ -27,9 +27,9 @@ public UniqueJsonKeyClaimAction(string claimType, string valueType, string jsonK } /// - public override void Run(JsonDocument userData, ClaimsIdentity identity, string issuer) + public override void Run(JsonElement userData, ClaimsIdentity identity, string issuer) { - var value = userData?.GetString(JsonKey); + var value = userData.GetString(JsonKey); if (string.IsNullOrEmpty(value)) { // Not found diff --git a/src/Security/Authentication/Twitter/src/TwitterCreatingTicketContext.cs b/src/Security/Authentication/Twitter/src/TwitterCreatingTicketContext.cs index de18558053e0..b95abef0b098 100644 --- a/src/Security/Authentication/Twitter/src/TwitterCreatingTicketContext.cs +++ b/src/Security/Authentication/Twitter/src/TwitterCreatingTicketContext.cs @@ -35,14 +35,14 @@ public TwitterCreatingTicketContext( string screenName, string accessToken, string accessTokenSecret, - JsonDocument user) + JsonElement user) : base(context, scheme, options) { UserId = userId; ScreenName = screenName; AccessToken = accessToken; AccessTokenSecret = accessTokenSecret; - User = user ?? JsonDocument.Parse("{}"); + User = user; Principal = principal; Properties = properties; } @@ -69,8 +69,8 @@ public TwitterCreatingTicketContext( /// /// Gets the JSON-serialized user or an empty - /// if it is not available. + /// if it is not available. /// - public JsonDocument User { get; } + public JsonElement User { get; } } } diff --git a/src/Security/Authentication/Twitter/src/TwitterHandler.cs b/src/Security/Authentication/Twitter/src/TwitterHandler.cs index aa2dcf93b638..e963d7a27e88 100644 --- a/src/Security/Authentication/Twitter/src/TwitterHandler.cs +++ b/src/Security/Authentication/Twitter/src/TwitterHandler.cs @@ -99,27 +99,33 @@ protected override async Task HandleRemoteAuthenticateAsync }, ClaimsIssuer); - JsonDocument user = null; + JsonDocument user; if (Options.RetrieveUserDetails) { user = await RetrieveUserDetailsAsync(accessToken, identity); } + else + { + user = JsonDocument.Parse("{}"); + } - if (Options.SaveTokens) + using (user) { - properties.StoreTokens(new [] { + if (Options.SaveTokens) + { + properties.StoreTokens(new[] { new AuthenticationToken { Name = "access_token", Value = accessToken.Token }, new AuthenticationToken { Name = "access_token_secret", Value = accessToken.TokenSecret } - }); - } + }); + } - var ticket = await CreateTicketAsync(identity, properties, accessToken, user); - user?.Dispose(); - return HandleRequestResult.Success(ticket); + var ticket = await CreateTicketAsync(identity, properties, accessToken, user.RootElement); + return HandleRequestResult.Success(ticket); + } } protected virtual async Task CreateTicketAsync( - ClaimsIdentity identity, AuthenticationProperties properties, AccessToken token, JsonDocument user) + ClaimsIdentity identity, AuthenticationProperties properties, AccessToken token, JsonElement user) { foreach (var action in Options.ClaimActions) { diff --git a/src/Security/Authentication/samples/SocialSample/Startup.cs b/src/Security/Authentication/samples/SocialSample/Startup.cs index a25733c76f91..0ff6156c0b5f 100644 --- a/src/Security/Authentication/samples/SocialSample/Startup.cs +++ b/src/Security/Authentication/samples/SocialSample/Startup.cs @@ -209,7 +209,7 @@ public void ConfigureServices(IServiceCollection services) using (var user = JsonDocument.Parse(await response.Content.ReadAsStringAsync())) { - context.RunClaimActions(user); + context.RunClaimActions(user.RootElement); } } }; diff --git a/src/Security/Authentication/test/ClaimActionTests.cs b/src/Security/Authentication/test/ClaimActionTests.cs index d42e786dd812..1ebe08dd7bd3 100644 --- a/src/Security/Authentication/test/ClaimActionTests.cs +++ b/src/Security/Authentication/test/ClaimActionTests.cs @@ -19,7 +19,7 @@ public void CanMapSingleValueUserDataToClaim() var identity = new ClaimsIdentity(); var action = new JsonKeyClaimAction("name", "name", "name"); - action.Run(userData, identity, "iss"); + action.Run(userData.RootElement, identity, "iss"); Assert.Equal("name", identity.FindFirst("name").Type); Assert.Equal("test", identity.FindFirst("name").Value); @@ -33,7 +33,7 @@ public void CanMapArrayValueUserDataToClaims() var identity = new ClaimsIdentity(); var action = new JsonKeyClaimAction("role", "role", "role"); - action.Run(userData, identity, "iss"); + action.Run(userData.RootElement, identity, "iss"); var roleClaims = identity.FindAll("role").ToList(); Assert.Equal(2, roleClaims.Count); @@ -50,7 +50,7 @@ public void MapAllSucceeds() var identity = new ClaimsIdentity(); var action = new MapAllClaimsAction(); - action.Run(userData, identity, "iss"); + action.Run(userData.RootElement, identity, "iss"); Assert.Equal("name0", identity.FindFirst("name0").Type); Assert.Equal("value0", identity.FindFirst("name0").Value); @@ -67,7 +67,7 @@ public void MapAllAllowesDulicateKeysWithUniqueValues() identity.AddClaim(new Claim("name0", "value2")); identity.AddClaim(new Claim("name1", "value3")); var action = new MapAllClaimsAction(); - action.Run(userData, identity, "iss"); + action.Run(userData.RootElement, identity, "iss"); Assert.Equal(2, identity.FindAll("name0").Count()); Assert.Equal(2, identity.FindAll("name1").Count()); @@ -82,7 +82,7 @@ public void MapAllSkipsDuplicateValues() identity.AddClaim(new Claim("name0", "value0")); identity.AddClaim(new Claim("name1", "value1")); var action = new MapAllClaimsAction(); - action.Run(userData, identity, "iss"); + action.Run(userData.RootElement, identity, "iss"); Assert.Single(identity.FindAll("name0")); Assert.Single(identity.FindAll("name1")); diff --git a/src/Security/Authentication/test/GoogleTests.cs b/src/Security/Authentication/test/GoogleTests.cs index 4d8dc500f9d3..8d5e635fd085 100644 --- a/src/Security/Authentication/test/GoogleTests.cs +++ b/src/Security/Authentication/test/GoogleTests.cs @@ -703,7 +703,6 @@ public async Task ValidateAuthenticatedContext() { OnCreatingTicket = context => { - Assert.NotNull(context.User); Assert.Equal("Test Access Token", context.AccessToken); Assert.Equal("Test Refresh Token", context.RefreshToken); Assert.Equal(TimeSpan.FromSeconds(3600), context.ExpiresIn); From 8e74456454ed419f5c96af33e9e67f30a78d92bd Mon Sep 17 00:00:00 2001 From: Chris R Date: Wed, 30 Jan 2019 15:40:19 -0800 Subject: [PATCH 7/9] Remove JsonDocument.GetString --- .../Core/src/JsonDocumentAuthExtensions.cs | 3 --- .../Authentication/OAuth/src/OAuthTokenResponse.cs | 9 +++++---- .../OpenIdConnect/samples/OpenIdConnectSample/Startup.cs | 4 ++-- .../Authentication/samples/SocialSample/Startup.cs | 6 +++--- 4 files changed, 10 insertions(+), 12 deletions(-) diff --git a/src/Security/Authentication/Core/src/JsonDocumentAuthExtensions.cs b/src/Security/Authentication/Core/src/JsonDocumentAuthExtensions.cs index 330e02c54916..54b8f8e42fef 100644 --- a/src/Security/Authentication/Core/src/JsonDocumentAuthExtensions.cs +++ b/src/Security/Authentication/Core/src/JsonDocumentAuthExtensions.cs @@ -7,9 +7,6 @@ namespace Microsoft.AspNetCore.Authentication { public static class JsonDocumentAuthExtensions { - public static string GetString(this JsonDocument document, string key) => - document.RootElement.GetString(key); - public static string GetString(this JsonElement element, string key) => element.TryGetProperty(key, out var property) ? property.ToString() : null; diff --git a/src/Security/Authentication/OAuth/src/OAuthTokenResponse.cs b/src/Security/Authentication/OAuth/src/OAuthTokenResponse.cs index 3e30da4f79e8..15eaf71eb14b 100644 --- a/src/Security/Authentication/OAuth/src/OAuthTokenResponse.cs +++ b/src/Security/Authentication/OAuth/src/OAuthTokenResponse.cs @@ -11,10 +11,11 @@ public class OAuthTokenResponse : IDisposable private OAuthTokenResponse(JsonDocument response) { Response = response; - AccessToken = response.GetString("access_token"); - TokenType = response.GetString("token_type"); - RefreshToken = response.GetString("refresh_token"); - ExpiresIn = response.GetString("expires_in"); + var root = response.RootElement; + AccessToken = root.GetString("access_token"); + TokenType = root.GetString("token_type"); + RefreshToken = root.GetString("refresh_token"); + ExpiresIn = root.GetString("expires_in"); } private OAuthTokenResponse(Exception error) diff --git a/src/Security/Authentication/OpenIdConnect/samples/OpenIdConnectSample/Startup.cs b/src/Security/Authentication/OpenIdConnect/samples/OpenIdConnectSample/Startup.cs index 10719a67be82..e47a1c86293a 100644 --- a/src/Security/Authentication/OpenIdConnect/samples/OpenIdConnectSample/Startup.cs +++ b/src/Security/Authentication/OpenIdConnect/samples/OpenIdConnectSample/Startup.cs @@ -211,8 +211,8 @@ await WriteHtmlAsync(response, async res => using (var payload = JsonDocument.Parse(await tokenResponse.Content.ReadAsStringAsync())) { // Persist the new acess token - props.UpdateTokenValue("access_token", payload.GetString("access_token")); - props.UpdateTokenValue("refresh_token", payload.GetString("refresh_token")); + props.UpdateTokenValue("access_token", payload.RootElement.GetString("access_token")); + props.UpdateTokenValue("refresh_token", payload.RootElement.GetString("refresh_token")); if (payload.RootElement.TryGetProperty("expires_in", out var property) && property.TryGetInt32(out var seconds)) { var expiresAt = DateTimeOffset.UtcNow + TimeSpan.FromSeconds(seconds); diff --git a/src/Security/Authentication/samples/SocialSample/Startup.cs b/src/Security/Authentication/samples/SocialSample/Startup.cs index 0ff6156c0b5f..3921ab5f36a8 100644 --- a/src/Security/Authentication/samples/SocialSample/Startup.cs +++ b/src/Security/Authentication/samples/SocialSample/Startup.cs @@ -337,8 +337,8 @@ public void Configure(IApplicationBuilder app) { // Persist the new acess token - authProperties.UpdateTokenValue("access_token", payload.GetString("access_token")); - refreshToken = payload.GetString("refresh_token"); + authProperties.UpdateTokenValue("access_token", payload.RootElement.GetString("access_token")); + refreshToken = payload.RootElement.GetString("refresh_token"); if (!string.IsNullOrEmpty(refreshToken)) { authProperties.UpdateTokenValue("refresh_token", refreshToken); @@ -372,7 +372,7 @@ public void Configure(IApplicationBuilder app) var refreshResponse = await options.Backchannel.GetStringAsync(options.TokenEndpoint + query); using (var payload = JsonDocument.Parse(refreshResponse)) { - authProperties.UpdateTokenValue("access_token", payload.GetString("access_token")); + authProperties.UpdateTokenValue("access_token", payload.RootElement.GetString("access_token")); if (payload.RootElement.TryGetProperty("expires_in", out var property) && property.TryGetInt32(out var seconds)) { var expiresAt = DateTimeOffset.UtcNow + TimeSpan.FromSeconds(seconds); From 50aa4e8e4b8a6dd9dee9cb098c2e11340d5f68ae Mon Sep 17 00:00:00 2001 From: Chris R Date: Thu, 31 Jan 2019 10:52:49 -0800 Subject: [PATCH 8/9] Remove double lookup --- src/Security/Authentication/OAuth/src/MapAllClaimsAction.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Security/Authentication/OAuth/src/MapAllClaimsAction.cs b/src/Security/Authentication/OAuth/src/MapAllClaimsAction.cs index 3c14aa77e7c4..46c6f0cb7668 100644 --- a/src/Security/Authentication/OAuth/src/MapAllClaimsAction.cs +++ b/src/Security/Authentication/OAuth/src/MapAllClaimsAction.cs @@ -21,7 +21,7 @@ public override void Run(JsonElement userData, ClaimsIdentity identity, string i { foreach (var pair in userData.EnumerateObject()) { - var claimValue = userData.GetString(pair.Name); + var claimValue = pair.Value.ToString(); // Avoid adding a claim if there's a duplicate name and value. This often happens in OIDC when claims are // retrieved both from the id_token and from the user-info endpoint. From bee3e86e19d1ecd71bf2d679bb052642e59061ad Mon Sep 17 00:00:00 2001 From: Chris R Date: Thu, 31 Jan 2019 16:05:25 -0800 Subject: [PATCH 9/9] Fix musicstore --- .../MusicStore/ForTesting/Mocks/Facebook/TestFacebookEvents.cs | 2 +- .../MusicStore/ForTesting/Mocks/Google/TestGoogleEvents.cs | 1 - .../Mocks/MicrosoftAccount/TestMicrosoftAccountEvents.cs | 3 +-- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/MusicStore/samples/MusicStore/ForTesting/Mocks/Facebook/TestFacebookEvents.cs b/src/MusicStore/samples/MusicStore/ForTesting/Mocks/Facebook/TestFacebookEvents.cs index cf8bb10d3796..98c6a3500ef7 100644 --- a/src/MusicStore/samples/MusicStore/ForTesting/Mocks/Facebook/TestFacebookEvents.cs +++ b/src/MusicStore/samples/MusicStore/ForTesting/Mocks/Facebook/TestFacebookEvents.cs @@ -21,7 +21,7 @@ internal static Task OnCreatingTicket(OAuthCreatingTicketContext context) Helpers.ThrowIfConditionFailed(() => context.Identity.FindFirst(ClaimTypes.NameIdentifier)?.Value == "Id", ""); Helpers.ThrowIfConditionFailed(() => context.Identity.FindFirst("urn:facebook:link")?.Value == "https://www.facebook.com/myLink", ""); Helpers.ThrowIfConditionFailed(() => context.Identity.FindFirst(ClaimTypes.Name)?.Value == "AspnetvnextTest AspnetvnextTest", ""); - Helpers.ThrowIfConditionFailed(() => context.User.SelectToken("id").ToString() == context.Identity.FindFirst(ClaimTypes.NameIdentifier)?.Value, ""); + Helpers.ThrowIfConditionFailed(() => context.User.GetString("id") == context.Identity.FindFirst(ClaimTypes.NameIdentifier)?.Value, ""); Helpers.ThrowIfConditionFailed(() => context.ExpiresIn.Value == TimeSpan.FromSeconds(100), ""); Helpers.ThrowIfConditionFailed(() => context.AccessToken == "ValidAccessToken", ""); context.Principal.Identities.First().AddClaim(new Claim("ManageStore", "false")); diff --git a/src/MusicStore/samples/MusicStore/ForTesting/Mocks/Google/TestGoogleEvents.cs b/src/MusicStore/samples/MusicStore/ForTesting/Mocks/Google/TestGoogleEvents.cs index 4dec753c30de..f5af6cf9ae00 100644 --- a/src/MusicStore/samples/MusicStore/ForTesting/Mocks/Google/TestGoogleEvents.cs +++ b/src/MusicStore/samples/MusicStore/ForTesting/Mocks/Google/TestGoogleEvents.cs @@ -23,7 +23,6 @@ internal static Task OnCreatingTicket(OAuthCreatingTicketContext context) Helpers.ThrowIfConditionFailed(() => context.Identity.FindFirst(ClaimTypes.Surname)?.Value == "AspnetvnextTest", "FamilyName is not valid"); Helpers.ThrowIfConditionFailed(() => context.Identity.FindFirst(ClaimTypes.Name)?.Value == "AspnetvnextTest AspnetvnextTest", "Name is not valid"); Helpers.ThrowIfConditionFailed(() => context.ExpiresIn.Value == TimeSpan.FromSeconds(1200), "ExpiresIn is not valid"); - Helpers.ThrowIfConditionFailed(() => context.User != null, "User object is not valid"); context.Principal.Identities.First().AddClaim(new Claim("ManageStore", "false")); } diff --git a/src/MusicStore/samples/MusicStore/ForTesting/Mocks/MicrosoftAccount/TestMicrosoftAccountEvents.cs b/src/MusicStore/samples/MusicStore/ForTesting/Mocks/MicrosoftAccount/TestMicrosoftAccountEvents.cs index 3639a7bb278b..45bea42b0457 100644 --- a/src/MusicStore/samples/MusicStore/ForTesting/Mocks/MicrosoftAccount/TestMicrosoftAccountEvents.cs +++ b/src/MusicStore/samples/MusicStore/ForTesting/Mocks/MicrosoftAccount/TestMicrosoftAccountEvents.cs @@ -23,8 +23,7 @@ internal static Task OnCreatingTicket(OAuthCreatingTicketContext context) Helpers.ThrowIfConditionFailed(() => context.Identity.FindFirst(ClaimTypes.NameIdentifier)?.Value == "fccf9a24999f4f4f", "Id is not valid"); Helpers.ThrowIfConditionFailed(() => context.Identity.FindFirst(ClaimTypes.Name)?.Value == "AspnetvnextTest AspnetvnextTest", "Name is not valid"); Helpers.ThrowIfConditionFailed(() => context.ExpiresIn.Value == TimeSpan.FromSeconds(3600), "ExpiresIn is not valid"); - Helpers.ThrowIfConditionFailed(() => context.User != null, "User object is not valid"); - Helpers.ThrowIfConditionFailed(() => context.Identity.FindFirst(ClaimTypes.NameIdentifier)?.Value == context.User.SelectToken("id").ToString(), "User id is not valid"); + Helpers.ThrowIfConditionFailed(() => context.Identity.FindFirst(ClaimTypes.NameIdentifier)?.Value == context.User.GetString("id"), "User id is not valid"); context.Principal.Identities.First().AddClaim(new Claim("ManageStore", "false")); }