From ce7dfb3a9e90b6e2522abe1f559d5aa501dd1761 Mon Sep 17 00:00:00 2001 From: Trojaner Date: Thu, 8 Oct 2020 06:45:54 +0300 Subject: [PATCH] Refactor code --- .../ExternalLoginController.cs | 17 ++- src/{ => Database}/AppInMemoryDbContext.cs | 2 +- .../Models/GetPlayerSummariesResponse.cs | 11 ++ src/Profile/Models/Player.cs | 58 ++++++++++ src/Profile/Models/SteamResponse.cs | 10 ++ src/Profile/SteamProfileService.cs | 80 +++++++++++++ src/ProfileService.cs | 109 ------------------ src/Program.cs | 1 - src/Startup.cs | 9 +- 9 files changed, 173 insertions(+), 124 deletions(-) rename src/{ => Controllers}/ExternalLoginController.cs (87%) rename src/{ => Database}/AppInMemoryDbContext.cs (90%) create mode 100644 src/Profile/Models/GetPlayerSummariesResponse.cs create mode 100644 src/Profile/Models/Player.cs create mode 100644 src/Profile/Models/SteamResponse.cs create mode 100644 src/Profile/SteamProfileService.cs delete mode 100644 src/ProfileService.cs diff --git a/src/ExternalLoginController.cs b/src/Controllers/ExternalLoginController.cs similarity index 87% rename from src/ExternalLoginController.cs rename to src/Controllers/ExternalLoginController.cs index 35be28b..67b54ec 100644 --- a/src/ExternalLoginController.cs +++ b/src/Controllers/ExternalLoginController.cs @@ -6,7 +6,7 @@ using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; -namespace SteamOpenIdConnectProvider +namespace SteamOpenIdConnectProvider.Controllers { [AllowAnonymous] [Route("[action]")] @@ -28,20 +28,20 @@ namespace SteamOpenIdConnectProvider [HttpGet] - public async Task ExternalLogin(string returnUrl = null) + public Task ExternalLogin(string returnUrl = null) { - string provider = "Steam"; + const string provider = "Steam"; - // Request a redirect to the external login provider. var redirectUrl = Url.Action("ExternalLoginCallback", new { returnUrl }); var properties = _signInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl); - return new ChallengeResult(provider, properties); + return Task.FromResult(new ChallengeResult(provider, properties)); } [HttpGet] public async Task ExternalLoginCallback(string returnUrl = null, string remoteError = null) { - returnUrl = returnUrl ?? Url.Content("~/"); + returnUrl ??= Url.Content("~/"); + if (remoteError != null) { throw new Exception($"Error from external provider: {remoteError}"); @@ -53,7 +53,6 @@ namespace SteamOpenIdConnectProvider throw new Exception($"Error loading external login information."); } - // Sign in the user with this external login provider if the user already has a login. var externalLoginResult = await _signInManager.ExternalLoginSignInAsync(info.LoginProvider, info.ProviderKey, isPersistent: false, bypassTwoFactor: true); if (externalLoginResult.Succeeded) { @@ -61,13 +60,13 @@ namespace SteamOpenIdConnectProvider return LocalRedirect(returnUrl); } - var userName = info.Principal.FindFirstValue(ClaimTypes.Name); var userId = info.Principal.FindFirstValue(ClaimTypes.NameIdentifier); - + var user = new IdentityUser { UserName = userName, Id = userId }; _userManager.UserValidators.Clear(); + var result = await _userManager.CreateAsync(user); if (result.Succeeded) { diff --git a/src/AppInMemoryDbContext.cs b/src/Database/AppInMemoryDbContext.cs similarity index 90% rename from src/AppInMemoryDbContext.cs rename to src/Database/AppInMemoryDbContext.cs index aab7aeb..0284829 100644 --- a/src/AppInMemoryDbContext.cs +++ b/src/Database/AppInMemoryDbContext.cs @@ -2,7 +2,7 @@ using Microsoft.AspNetCore.Identity.EntityFrameworkCore; using Microsoft.EntityFrameworkCore; -namespace SteamOpenIdConnectProvider +namespace SteamOpenIdConnectProvider.Database { // This is completely in-memory, we do not need a persistent store. public class AppInMemoryDbContext : IdentityDbContext diff --git a/src/Profile/Models/GetPlayerSummariesResponse.cs b/src/Profile/Models/GetPlayerSummariesResponse.cs new file mode 100644 index 0000000..12cb571 --- /dev/null +++ b/src/Profile/Models/GetPlayerSummariesResponse.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace SteamOpenIdConnectProvider.Profile.Models +{ + public sealed class GetPlayerSummariesResponse + { + [JsonProperty("players")] + public ICollection Players { get; set; } + } +} \ No newline at end of file diff --git a/src/Profile/Models/Player.cs b/src/Profile/Models/Player.cs new file mode 100644 index 0000000..74af6d6 --- /dev/null +++ b/src/Profile/Models/Player.cs @@ -0,0 +1,58 @@ +using Newtonsoft.Json; + +namespace SteamOpenIdConnectProvider.Profile.Models +{ + public sealed class Player + { + [JsonProperty("steamid")] + public ulong SteamId { get; set; } + + [JsonProperty("communityvisibilitystate")] + public int CommunityVisibilityState { get; set; } + + [JsonProperty("profilestate")] + public int ProfileState { get; set; } + + [JsonProperty("personaname")] + public string PersonaName { get; set; } + + [JsonProperty("commentpermission")] + public int CommentPermission { get; set; } + + [JsonProperty("profileurl")] + public string ProfileUrl { get; set; } + + [JsonProperty("avatar")] + public string Avatar { get; set; } + + [JsonProperty("avatarmedium")] + public string AvatarMedium { get; set; } + + [JsonProperty("avatarfull")] + public string AvatarFull { get; set; } + + [JsonProperty("avatarhash")] + public string AvatarHash { get; set; } + + [JsonProperty("lastlogoff")] + public int LastLogoff { get; set; } + + [JsonProperty("personastate")] + public int PersonaState { get; set; } + + [JsonProperty("realname")] + public string RealName { get; set; } + + [JsonProperty("primaryclanid")] + public ulong PrimaryClanId { get; set; } + + [JsonProperty("timecreated")] + public int TimeCreated { get; set; } + + [JsonProperty("personastateflags")] + public int PersonaStateFlags { get; set; } + + [JsonProperty("loccountrycode")] + public string LocCountryCode { get; set; } + } +} \ No newline at end of file diff --git a/src/Profile/Models/SteamResponse.cs b/src/Profile/Models/SteamResponse.cs new file mode 100644 index 0000000..2b43afb --- /dev/null +++ b/src/Profile/Models/SteamResponse.cs @@ -0,0 +1,10 @@ +using Newtonsoft.Json; + +namespace SteamOpenIdConnectProvider.Profile.Models +{ + public sealed class SteamResponse + { + [JsonProperty("response")] + public T Response { get; set; } + } +} \ No newline at end of file diff --git a/src/Profile/SteamProfileService.cs b/src/Profile/SteamProfileService.cs new file mode 100644 index 0000000..f5461ed --- /dev/null +++ b/src/Profile/SteamProfileService.cs @@ -0,0 +1,80 @@ +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Security.Claims; +using System.Threading.Tasks; +using IdentityServer4.Extensions; +using IdentityServer4.Models; +using IdentityServer4.Services; +using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.Configuration; +using Newtonsoft.Json; +using SteamOpenIdConnectProvider.Profile.Models; + +namespace SteamOpenIdConnectProvider.Profile +{ + public class SteamProfileService : IProfileService + { + private readonly HttpClient _httpClient; + private readonly IConfiguration _configuration; + private readonly IUserClaimsPrincipalFactory _claimsFactory; + private readonly UserManager _userManager; + + private async Task GetPlayerSummariesAsync(IEnumerable steamIds) + { + const string baseurl = "https://api.steampowered.com/ISteamUser/GetPlayerSummaries/v0002"; + + var applicationKey = _configuration["Authentication:Steam:ApplicationKey"]; + var url = $"{baseurl}/?key={applicationKey}&steamids={string.Join(',', steamIds)}"; + + var res = await _httpClient.GetStringAsync(url); + var response = JsonConvert.DeserializeObject>(res); + return response.Response; + } + + public SteamProfileService( + UserManager userManager, + IUserClaimsPrincipalFactory claimsFactory, + IConfiguration configuration, + HttpClient httpClient) + { + _userManager = userManager; + _claimsFactory = claimsFactory; + _configuration = configuration; + _httpClient = httpClient; + } + + public async Task GetProfileDataAsync(ProfileDataRequestContext context) + { + var sub = context.Subject.GetSubjectId(); + var user = await _userManager.FindByIdAsync(sub); + var principal = await _claimsFactory.CreateAsync(user); + + var claims = principal.Claims.ToList(); + claims = claims.Where(claim => context.RequestedClaimTypes.Contains(claim.Type)).ToList(); + + const string steamUrl = "https://steamcommunity.com/openid/id/"; + var steamId = sub.Substring(steamUrl.Length); + + var userSummary = await GetPlayerSummariesAsync(new[] { steamId }); + var player = userSummary.Players.FirstOrDefault(); + + if (player != null) + { + claims.Add(new Claim("picture", player.AvatarFull)); + claims.Add(new Claim("nickname", player.PersonaName)); + claims.Add(new Claim("given_name", player.RealName)); + claims.Add(new Claim("website", player.ProfileUrl)); + } + + context.IssuedClaims = claims; + } + + public async Task IsActiveAsync(IsActiveContext context) + { + var sub = context.Subject.GetSubjectId(); + var user = await _userManager.FindByIdAsync(sub); + context.IsActive = user != null; + } + } +} \ No newline at end of file diff --git a/src/ProfileService.cs b/src/ProfileService.cs deleted file mode 100644 index aab5783..0000000 --- a/src/ProfileService.cs +++ /dev/null @@ -1,109 +0,0 @@ -using IdentityServer4.Models; -using IdentityServer4.Extensions; -using IdentityServer4.Services; -using System; -using System.Linq; -using System.Security.Claims; -using System.Threading.Tasks; -using System.Collections.Generic; -using System.Net.Http; -using Newtonsoft.Json; -using Microsoft.AspNetCore.Identity; -using Microsoft.Extensions.Configuration; - -namespace IdentityServer.Services -{ - public class ProfileService : IProfileService - { - private IConfiguration _configuration; - private readonly IUserClaimsPrincipalFactory _claimsFactory; - private readonly UserManager _userManager; - - private async Task FetchUserSummary(string steamid64) - { - using (var httpClient = new HttpClient()) - { - - var baseurl = "https://api.steampowered.com/ISteamUser/GetPlayerSummaries/v0002"; - var applicationKey = _configuration["Authentication:Steam:ApplicationKey"]; - var url = $"{baseurl}/?key={applicationKey}&steamids={steamid64}"; - - var res = await httpClient.GetStringAsync(url); - var json = JsonConvert.DeserializeObject(res); - return json; - } - } - - public class Player { - public string steamid { get; set; } - public int communityvisibilitystate { get; set; } - public int profilestate { get; set; } - public string personaname { get; set; } - public int commentpermission { get; set; } - public string profileurl { get; set; } - public string avatar { get; set; } - public string avatarmedium { get; set; } - public string avatarfull { get; set; } - public string avatarhash { get; set; } - public int lastlogoff { get; set; } - public int personastate { get; set; } - public string realname { get; set; } - public string primaryclanid { get; set; } - public int timecreated { get; set; } - public int personastateflags { get; set; } - public string loccountrycode { get; set; } - } - - public class Response { - public List players { get; set; } - } - - public class IPlayerSummary { - public Response response { get; set; } - } - - public ProfileService( - UserManager userManager, - IUserClaimsPrincipalFactory claimsFactory, - IConfiguration configuration - ) - { - _userManager = userManager; - _claimsFactory = claimsFactory; - _configuration = configuration; - } - - public async Task GetProfileDataAsync(ProfileDataRequestContext context) - { - var sub = context.Subject.GetSubjectId(); - var user = await _userManager.FindByIdAsync(sub); - var principal = await _claimsFactory.CreateAsync(user); - - var claims = principal.Claims.ToList(); - claims = claims.Where(claim => context.RequestedClaimTypes.Contains(claim.Type)).ToList(); - - var steamurl = "https://steamcommunity.com/openid/id/"; - var steamid64 = sub.Substring(steamurl.Length); - - var userSummary = await FetchUserSummary(steamid64); - var player = userSummary.response.players[0]; - - if (player != null) - { - claims.Add(new Claim("picture", player.avatarfull)); - claims.Add(new Claim("nickname", player.personaname)); - claims.Add(new Claim("given_name", player.realname)); - claims.Add(new Claim("website", player.profileurl)); - } - - context.IssuedClaims = claims; - } - - public async Task IsActiveAsync(IsActiveContext context) - { - var sub = context.Subject.GetSubjectId(); - var user = await _userManager.FindByIdAsync(sub); - context.IsActive = user != null; - } - } -} \ No newline at end of file diff --git a/src/Program.cs b/src/Program.cs index 6bad0dd..c345b04 100644 --- a/src/Program.cs +++ b/src/Program.cs @@ -19,7 +19,6 @@ namespace SteamOpenIdConnectProvider .WriteTo.Console(outputTemplate: "[{Timestamp:HH:mm:ss} {Level}] {SourceContext}{NewLine}{Message:lj}{NewLine}{Exception}{NewLine}", theme: AnsiConsoleTheme.Literate) .CreateLogger(); - CreateWebHostBuilder(args).Build().Run(); } diff --git a/src/Startup.cs b/src/Startup.cs index 9aea9e8..44ee044 100644 --- a/src/Startup.cs +++ b/src/Startup.cs @@ -1,4 +1,5 @@ using System; +using System.Net.Http; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; @@ -9,7 +10,8 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using IdentityServer4.Services; -using IdentityServer.Services; +using SteamOpenIdConnectProvider.Database; +using SteamOpenIdConnectProvider.Profile; namespace SteamOpenIdConnectProvider { @@ -29,8 +31,7 @@ namespace SteamOpenIdConnectProvider .AddNewtonsoftJson() .SetCompatibilityVersion(CompatibilityVersion.Version_3_0); - services.AddSingleton(Configuration); - + services.AddSingleton(Configuration); services.AddDbContext(options => options.UseInMemoryDatabase("default")); @@ -56,7 +57,7 @@ namespace SteamOpenIdConnectProvider .AddDeveloperSigningCredential(true) .AddInMemoryIdentityResources(IdentityServerConfig.GetIdentityResources()); - services.AddScoped(); + services.AddHttpClient(); services.AddAuthentication() .AddCookie(options =>