Skip to content
Merged
Prev Previous commit
Next Next commit
Fix PR comments, add tests with RequestLocalization
  • Loading branch information
asiffermann committed May 22, 2019
commit 3bf8c112c633e64c17b0640be1d7a0ef4f81f243
2 changes: 1 addition & 1 deletion .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ dotnet_naming_symbols.constant_fields.required_modifiers = const
[*.cs]

# var preferences
csharp_style_var_for_built_in_types = true:silent
csharp_style_var_for_built_in_types = false:suggestion
csharp_style_var_when_type_is_apparent = true:silent
csharp_style_var_elsewhere = true:silent

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public LinkedInAuthenticationHandler(
protected override async Task<AuthenticationTicket> CreateTicketAsync([NotNull] ClaimsIdentity identity,
[NotNull] AuthenticationProperties properties, [NotNull] OAuthTokenResponse tokens)
{
var address = Options.UserInformationEndpoint;
string address = Options.UserInformationEndpoint;
var fields = Options.Fields
.Where(f => !string.Equals(f, LinkedInAuthenticationConstants.EmailAddressField, StringComparison.OrdinalIgnoreCase))
.ToList();
Expand Down
32 changes: 20 additions & 12 deletions src/AspNet.Security.OAuth.LinkedIn/LinkedInAuthenticationOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,11 @@ public LinkedInAuthenticationOptions()
ClaimActions.MapCustomJson(ClaimTypes.GivenName, user => GetMultiLocaleString(user, ProfileFields.FirstName));
ClaimActions.MapCustomJson(ClaimTypes.Surname, user => GetMultiLocaleString(user, ProfileFields.LastName));
ClaimActions.MapCustomJson(Claims.PictureUrl, user => GetPictureUrls(user)?.LastOrDefault());
ClaimActions.MapCustomJson(Claims.PictureUrls, user => string.Join(",", GetPictureUrls(user)));
ClaimActions.MapCustomJson(Claims.PictureUrls, user =>
{
var urls = GetPictureUrls(user);
return urls == null ? null : string.Join(",", urls);
});
}

/// <summary>
Expand Down Expand Up @@ -82,22 +86,25 @@ public LinkedInAuthenticationOptions()
/// <returns>The property value.</returns>
private string GetMultiLocaleString(JObject user, string propertyName)
{
const string localizedKey = "localized";
if (user[propertyName] == null || user[propertyName][localizedKey] == null)
var property = user[propertyName];
var propertyLocalized = property["localized"];
if (property == null || propertyLocalized == null)
{
return null;
}

var preferredLocale = user[propertyName]["preferredLocale"];
var preferredLocaleKey = preferredLocale == null ? null : $"{preferredLocale["language"]}_{preferredLocale["country"]}";
var values = user[propertyName][localizedKey].ToObject<Dictionary<string, string>>();
var preferredLocale = property["preferredLocale"];
string preferredLocaleKey = preferredLocale == null ? null : $"{preferredLocale.Value<string>("language")}_{preferredLocale.Value<string>("country")}";
var values = propertyLocalized
.Children<JProperty>()
.ToDictionary(p => p.Name, p => p.Value.Value<string>());

return MultiLocaleStringResolver(values, preferredLocaleKey);
}

private string GetFullName(JObject user)
{
var nameParts = new string[]
string[] nameParts = new string[]
{
GetMultiLocaleString(user, ProfileFields.FirstName),
GetMultiLocaleString(user, ProfileFields.LastName)
Expand Down Expand Up @@ -134,15 +141,16 @@ where address.Value<string>("authorizationMethod") == "PUBLIC"
/// <returns>The localized value.</returns>
private static string DefaultMultiLocaleStringResolver(IReadOnlyDictionary<string, string> localizedValues, string preferredLocale)
{
if (!string.IsNullOrEmpty(preferredLocale) && localizedValues.ContainsKey(preferredLocale))
if (!string.IsNullOrEmpty(preferredLocale)
&& localizedValues.TryGetValue(preferredLocale, out string preferredLocaleValue))
{
return localizedValues[preferredLocale];
return preferredLocaleValue;
}

var currentUiKey = Thread.CurrentThread.CurrentUICulture.ToString().Replace('-', '_');
if (localizedValues.ContainsKey(currentUiKey))
string currentUIKey = Thread.CurrentThread.CurrentUICulture.ToString().Replace('-', '_');
if (localizedValues.TryGetValue(currentUIKey, out string currentUIValue))
{
return localizedValues[currentUiKey];
return currentUIValue;
}

return localizedValues.Values.FirstOrDefault();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ private static void Configure<TOptions>(IWebHostBuilder builder, OAuthTests<TOpt
});

// Configure the test application
builder.Configure(ConfigureApplication)
builder.Configure(app => ConfigureApplication(app, tests))
.ConfigureServices(services =>
{
// Allow HTTP requests to external services to be intercepted
Expand All @@ -82,19 +82,21 @@ private static void Configure<TOptions>(IWebHostBuilder builder, OAuthTests<TOpt
});
}

private static void ConfigureApplication(IApplicationBuilder app)
private static void ConfigureApplication<TOptions>(IApplicationBuilder app, OAuthTests<TOptions> tests)
where TOptions : OAuthOptions
{
// Configure a single HTTP resource that challenges the client if unauthenticated
// or returns the logged in user's claims as XML if the request is authenticated.
tests.ConfigureApplication(app);
app.UseAuthentication();

app.Map("/me", childApp => childApp.Run(
async context =>
{
if (context.User.Identity.IsAuthenticated)
{
var xml = IdentityToXmlString(context.User);
var buffer = Encoding.UTF8.GetBytes(xml.ToString());
string xml = IdentityToXmlString(context.User);
byte[] buffer = Encoding.UTF8.GetBytes(xml.ToString());

context.Response.StatusCode = 200;
context.Response.ContentType = "text/xml";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Xunit;
using Xunit.Abstractions;
Expand All @@ -31,10 +32,17 @@ protected internal override void RegisterAuthentication(AuthenticationBuilder bu
builder.AddLinkedIn(options =>
{
ConfigureDefaults(builder, options);
options.Fields.Add(LinkedInAuthenticationConstants.ProfileFields.PictureUrl);
additionalConfiguration?.Invoke(options);
});
}

protected internal override void ConfigureApplication(IApplicationBuilder app)
{
app.UseRequestLocalization(new RequestLocalizationOptions
{
DefaultRequestCulture = new Microsoft.AspNetCore.Localization.RequestCulture("fr-FR"),
});
}

[Theory]
[InlineData(ClaimTypes.NameIdentifier, "1R2RtA")]
Expand All @@ -46,6 +54,7 @@ protected internal override void RegisterAuthentication(AuthenticationBuilder bu
public async Task Can_Sign_In_Using_LinkedIn(string claimType, string claimValue)
{
// Arrange
additionalConfiguration = options => options.Fields.Add(LinkedInAuthenticationConstants.ProfileFields.PictureUrl);
using (var server = CreateTestServer())
{
// Act
Expand All @@ -62,13 +71,31 @@ public async Task Can_Sign_In_Using_LinkedIn(string claimType, string claimValue
[InlineData(ClaimTypes.GivenName, "Frodon")]
[InlineData(ClaimTypes.Surname, "Sacquet")]
public async Task Can_Sign_In_Using_LinkedIn_Localized(string claimType, string claimValue)
{
// Arrange
using (var server = CreateTestServer())
{
// Act
var claims = await AuthenticateUserAsync(server);

// Assert
AssertClaim(claims, claimType, claimValue);
}
}

[Theory]
[InlineData(ClaimTypes.NameIdentifier, "1R2RtA")]
[InlineData(ClaimTypes.Name, "Frodon Sacquet")]
[InlineData(ClaimTypes.GivenName, "Frodon")]
[InlineData(ClaimTypes.Surname, "Sacquet")]
public async Task Can_Sign_In_Using_LinkedIn_Localized_With_Custom_Resolver(string claimType, string claimValue)
{
// Arrange
additionalConfiguration = options => options.MultiLocaleStringResolver = (values, preferredLocale) =>
{
if (values.ContainsKey("fr_FR"))
if (values.TryGetValue("fr_FR", out string value))
{
return values["fr_FR"];
return value;
}

return values.Values.FirstOrDefault();
Expand Down
20 changes: 20 additions & 0 deletions test/AspNet.Security.OAuth.Providers.Tests/LinkedIn/bundle.json
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,26 @@
}
}
},
{
"comment": "https://docs.microsoft.com/en-us/linkedin/consumer/integrations/self-serve/sign-in-with-linkedin",
"uri": "https://api.linkedin.com/v2/me?projection=(id,firstName,lastName)",
"contentFormat": "json",
"contentJson": {
"id": "1R2RtA",
"firstName": {
"localized": {
"en_US": "Frodo",
"fr_FR": "Frodon"
}
},
"lastName": {
"localized": {
"en_US": "Baggins",
"fr_FR": "Sacquet"
}
}
}
},
{
"comment": "https://docs.microsoft.com/en-us/linkedin/consumer/integrations/self-serve/sign-in-with-linkedin",
"uri": "https://api.linkedin.com/v2/emailAddress?q=members&projection=(elements*(handle~))",
Expand Down
9 changes: 9 additions & 0 deletions test/AspNet.Security.OAuth.Providers.Tests/OAuthTests`1.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
using MartinCostello.Logging.XUnit;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.OAuth;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.Extensions.DependencyInjection;
using Shouldly;
Expand Down Expand Up @@ -65,6 +66,14 @@ protected OAuthTests()
/// <param name="builder">The authentication builder to register authentication with.</param>
protected internal abstract void RegisterAuthentication(AuthenticationBuilder builder);

/// <summary>
/// Configures the test server application.
/// Useful to add a middleware like a <see cref="Microsoft.AspNetCore.Localization.RequestLocalizationMiddleware"/> to test
/// localization scenario.
/// </summary>
/// <param name="app">The application.</param>
protected internal virtual void ConfigureApplication(IApplicationBuilder app) { }

/// <summary>
/// Configures the default authentication options.
/// </summary>
Expand Down