From 25dfcb30c1e346fe9a85209de6ebd4af9a555850 Mon Sep 17 00:00:00 2001 From: Mark Ettema Date: Sun, 9 May 2021 17:02:07 +0700 Subject: [PATCH 01/26] Prefer .sln in src/ --- .../SteamOpenIdConnectProvider.sln | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename SteamOpenIdConnectProvider.sln => src/SteamOpenIdConnectProvider.sln (91%) diff --git a/SteamOpenIdConnectProvider.sln b/src/SteamOpenIdConnectProvider.sln similarity index 91% rename from SteamOpenIdConnectProvider.sln rename to src/SteamOpenIdConnectProvider.sln index 0d0559d..b66f6c6 100644 --- a/SteamOpenIdConnectProvider.sln +++ b/src/SteamOpenIdConnectProvider.sln @@ -3,7 +3,7 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 16 VisualStudioVersion = 16.0.28803.202 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SteamOpenIdConnectProvider", "src\SteamOpenIdConnectProvider.csproj", "{39261E1A-B9AB-45C1-991E-B88ED3E076A2}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SteamOpenIdConnectProvider", "SteamOpenIdConnectProvider.csproj", "{39261E1A-B9AB-45C1-991E-B88ED3E076A2}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution From b48c3510829f4fa6035848b74277f99764708c17 Mon Sep 17 00:00:00 2001 From: Mark Ettema Date: Sun, 9 May 2021 17:02:36 +0700 Subject: [PATCH 02/26] Small restructure Dockerfile --- Dockerfile | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/Dockerfile b/Dockerfile index 11e1348..3388020 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,17 +1,22 @@ FROM mcr.microsoft.com/dotnet/core/sdk:3.1 AS build + WORKDIR /src -COPY ["src/SteamOpenIdConnectProvider.csproj", "SteamOpenIdConnectProvider/"] -RUN dotnet restore "SteamOpenIdConnectProvider/SteamOpenIdConnectProvider.csproj" -COPY ["src/", "SteamOpenIdConnectProvider/"] -WORKDIR "/src/SteamOpenIdConnectProvider" -RUN dotnet build "SteamOpenIdConnectProvider.csproj" -c Release -o /app + +# Copy the project file to create layer with packages +COPY src/SteamOpenIdConnectProvider.csproj . +RUN dotnet restore ./SteamOpenIdConnectProvider.csproj + +# Copy the rest of the source +COPY src/* . +RUN dotnet build ./SteamOpenIdConnectProvider.csproj -c Release -o /app FROM build AS publish -RUN dotnet publish "SteamOpenIdConnectProvider.csproj" -c Release -o /app +RUN dotnet publish ./SteamOpenIdConnectProvider.csproj -c Release -o /app FROM mcr.microsoft.com/dotnet/core/aspnet:3.1 AS base WORKDIR /app COPY --from=publish /app . EXPOSE 80 + HEALTHCHECK CMD curl --fail http://localhost/health || exit 1 -ENTRYPOINT ["dotnet", "SteamOpenIdConnectProvider.dll"] +ENTRYPOINT ["dotnet", "SteamOpenIdConnectProvider.dll"] \ No newline at end of file From 2d5a4ce3ac7289381640eaecb72c3e887a19c042 Mon Sep 17 00:00:00 2001 From: Mark Ettema Date: Sun, 9 May 2021 17:02:59 +0700 Subject: [PATCH 03/26] Remove unnecessary --- src/SteamOpenIdConnectProvider.csproj | 1 - 1 file changed, 1 deletion(-) diff --git a/src/SteamOpenIdConnectProvider.csproj b/src/SteamOpenIdConnectProvider.csproj index 1b3243f..34b6d46 100644 --- a/src/SteamOpenIdConnectProvider.csproj +++ b/src/SteamOpenIdConnectProvider.csproj @@ -13,7 +13,6 @@ - From ab2e3718bf3e24ab436dc518fde1b252213f65f3 Mon Sep 17 00:00:00 2001 From: Mark Ettema Date: Sun, 9 May 2021 17:36:59 +0700 Subject: [PATCH 04/26] Adding roslyn .editorconfig --- .editorconfig | 279 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 279 insertions(+) create mode 100644 .editorconfig diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..cd895da --- /dev/null +++ b/.editorconfig @@ -0,0 +1,279 @@ +# EditorConfig is awesome: https://EditorConfig.org + +# top-most EditorConfig file +root = true + +# Don't use tabs for indentation. +[*] +indent_style = space +# (Please don't specify an indent_size here; that has too many unintended consequences.) + +# Code files +[*.{cs,csx,vb,vbx}] +indent_size = 4 +insert_final_newline = true +charset = utf-8-bom + +# XML project files +[*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,projitems,shproj}] +indent_size = 2 + +# XML config files +[*.{props,targets,ruleset,config,nuspec,resx,vsixmanifest,vsct}] +indent_size = 2 + +# JSON files +[*.json] +indent_size = 2 + +# Powershell files +[*.ps1] +indent_size = 2 + +# Shell script files +[*.sh] +end_of_line = lf +indent_size = 2 + +# Dotnet code style settings: +[*.{cs,vb}] + +# IDE0055: Fix formatting +dotnet_diagnostic.IDE0055.severity = warning + +# Sort using and Import directives with System.* appearing first +dotnet_sort_system_directives_first = true +dotnet_separate_import_directive_groups = false +# Avoid "this." and "Me." if not necessary +dotnet_style_qualification_for_field = false:refactoring +dotnet_style_qualification_for_property = false:refactoring +dotnet_style_qualification_for_method = false:refactoring +dotnet_style_qualification_for_event = false:refactoring + +# Use language keywords instead of framework type names for type references +dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion +dotnet_style_predefined_type_for_member_access = true:suggestion + +# Suggest more modern language features when available +dotnet_style_object_initializer = true:suggestion +dotnet_style_collection_initializer = true:suggestion +dotnet_style_coalesce_expression = true:suggestion +dotnet_style_null_propagation = true:suggestion +dotnet_style_explicit_tuple_names = true:suggestion + +# Non-private static fields are PascalCase +dotnet_naming_rule.non_private_static_fields_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.non_private_static_fields_should_be_pascal_case.symbols = non_private_static_fields +dotnet_naming_rule.non_private_static_fields_should_be_pascal_case.style = non_private_static_field_style + +dotnet_naming_symbols.non_private_static_fields.applicable_kinds = field +dotnet_naming_symbols.non_private_static_fields.applicable_accessibilities = public, protected, internal, protected_internal, private_protected +dotnet_naming_symbols.non_private_static_fields.required_modifiers = static + +dotnet_naming_style.non_private_static_field_style.capitalization = pascal_case + +# Non-private readonly fields are PascalCase +dotnet_naming_rule.non_private_readonly_fields_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.non_private_readonly_fields_should_be_pascal_case.symbols = non_private_readonly_fields +dotnet_naming_rule.non_private_readonly_fields_should_be_pascal_case.style = non_private_readonly_field_style + +dotnet_naming_symbols.non_private_readonly_fields.applicable_kinds = field +dotnet_naming_symbols.non_private_readonly_fields.applicable_accessibilities = public, protected, internal, protected_internal, private_protected +dotnet_naming_symbols.non_private_readonly_fields.required_modifiers = readonly + +dotnet_naming_style.non_private_readonly_field_style.capitalization = pascal_case + +# Constants are PascalCase +dotnet_naming_rule.constants_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.constants_should_be_pascal_case.symbols = constants +dotnet_naming_rule.constants_should_be_pascal_case.style = constant_style + +dotnet_naming_symbols.constants.applicable_kinds = field, local +dotnet_naming_symbols.constants.required_modifiers = const + +dotnet_naming_style.constant_style.capitalization = pascal_case + +# Static fields are camelCase and start with s_ +dotnet_naming_rule.static_fields_should_be_camel_case.severity = suggestion +dotnet_naming_rule.static_fields_should_be_camel_case.symbols = static_fields +dotnet_naming_rule.static_fields_should_be_camel_case.style = static_field_style + +dotnet_naming_symbols.static_fields.applicable_kinds = field +dotnet_naming_symbols.static_fields.required_modifiers = static + +dotnet_naming_style.static_field_style.capitalization = camel_case +dotnet_naming_style.static_field_style.required_prefix = s_ + +# Instance fields are camelCase and start with _ +dotnet_naming_rule.instance_fields_should_be_camel_case.severity = suggestion +dotnet_naming_rule.instance_fields_should_be_camel_case.symbols = instance_fields +dotnet_naming_rule.instance_fields_should_be_camel_case.style = instance_field_style + +dotnet_naming_symbols.instance_fields.applicable_kinds = field + +dotnet_naming_style.instance_field_style.capitalization = camel_case +dotnet_naming_style.instance_field_style.required_prefix = _ + +# Locals and parameters are camelCase +dotnet_naming_rule.locals_should_be_camel_case.severity = suggestion +dotnet_naming_rule.locals_should_be_camel_case.symbols = locals_and_parameters +dotnet_naming_rule.locals_should_be_camel_case.style = camel_case_style + +dotnet_naming_symbols.locals_and_parameters.applicable_kinds = parameter, local + +dotnet_naming_style.camel_case_style.capitalization = camel_case + +# Local functions are PascalCase +dotnet_naming_rule.local_functions_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.local_functions_should_be_pascal_case.symbols = local_functions +dotnet_naming_rule.local_functions_should_be_pascal_case.style = local_function_style + +dotnet_naming_symbols.local_functions.applicable_kinds = local_function + +dotnet_naming_style.local_function_style.capitalization = pascal_case + +# By default, name items with PascalCase +dotnet_naming_rule.members_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.members_should_be_pascal_case.symbols = all_members +dotnet_naming_rule.members_should_be_pascal_case.style = pascal_case_style + +dotnet_naming_symbols.all_members.applicable_kinds = * + +dotnet_naming_style.pascal_case_style.capitalization = pascal_case + +# error RS2008: Enable analyzer release tracking for the analyzer project containing rule '{0}' +dotnet_diagnostic.RS2008.severity = none + +# IDE0073: File header +dotnet_diagnostic.IDE0073.severity = warning +file_header_template = Licensed to the .NET Foundation under one or more agreements.\nThe .NET Foundation licenses this file to you under the MIT license.\nSee the LICENSE file in the project root for more information. + +# IDE0035: Remove unreachable code +dotnet_diagnostic.IDE0035.severity = warning + +# IDE0036: Order modifiers +dotnet_diagnostic.IDE0036.severity = warning + +# IDE0043: Format string contains invalid placeholder +dotnet_diagnostic.IDE0043.severity = warning + +# IDE0044: Make field readonly +dotnet_diagnostic.IDE0044.severity = warning + +# RS0016: Only enable if API files are present +dotnet_public_api_analyzer.require_api_files = true + +# CSharp code style settings: +[*.cs] +# Newline settings +csharp_new_line_before_open_brace = all +csharp_new_line_before_else = true +csharp_new_line_before_catch = true +csharp_new_line_before_finally = true +csharp_new_line_before_members_in_object_initializers = true +csharp_new_line_before_members_in_anonymous_types = true +csharp_new_line_between_query_expression_clauses = true + +# Indentation preferences +csharp_indent_block_contents = true +csharp_indent_braces = false +csharp_indent_case_contents = true +csharp_indent_case_contents_when_block = true +csharp_indent_switch_labels = true +csharp_indent_labels = flush_left + +# Prefer "var" everywhere +csharp_style_var_for_built_in_types = true:suggestion +csharp_style_var_when_type_is_apparent = true:suggestion +csharp_style_var_elsewhere = true:suggestion + +# Prefer method-like constructs to have a block body +csharp_style_expression_bodied_methods = false:none +csharp_style_expression_bodied_constructors = false:none +csharp_style_expression_bodied_operators = false:none + +# Prefer property-like constructs to have an expression-body +csharp_style_expression_bodied_properties = true:none +csharp_style_expression_bodied_indexers = true:none +csharp_style_expression_bodied_accessors = true:none + +# Suggest more modern language features when available +csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion +csharp_style_pattern_matching_over_as_with_null_check = true:suggestion +csharp_style_inlined_variable_declaration = true:suggestion +csharp_style_throw_expression = true:suggestion +csharp_style_conditional_delegate_call = true:suggestion + +# Space preferences +csharp_space_after_cast = false +csharp_space_after_colon_in_inheritance_clause = true +csharp_space_after_comma = true +csharp_space_after_dot = false +csharp_space_after_keywords_in_control_flow_statements = true +csharp_space_after_semicolon_in_for_statement = true +csharp_space_around_binary_operators = before_and_after +csharp_space_around_declaration_statements = do_not_ignore +csharp_space_before_colon_in_inheritance_clause = true +csharp_space_before_comma = false +csharp_space_before_dot = false +csharp_space_before_open_square_brackets = false +csharp_space_before_semicolon_in_for_statement = false +csharp_space_between_empty_square_brackets = false +csharp_space_between_method_call_empty_parameter_list_parentheses = false +csharp_space_between_method_call_name_and_opening_parenthesis = false +csharp_space_between_method_call_parameter_list_parentheses = false +csharp_space_between_method_declaration_empty_parameter_list_parentheses = false +csharp_space_between_method_declaration_name_and_open_parenthesis = false +csharp_space_between_method_declaration_parameter_list_parentheses = false +csharp_space_between_parentheses = false +csharp_space_between_square_brackets = false + +# Blocks are allowed +csharp_prefer_braces = true:silent +csharp_preserve_single_line_blocks = true +csharp_preserve_single_line_statements = true + +[src/CodeStyle/**.{cs,vb}] +# warning RS0005: Do not use generic CodeAction.Create to create CodeAction +dotnet_diagnostic.RS0005.severity = none + +[src/{Analyzers,CodeStyle,Features,Workspaces,EditorFeatures,VisualStudio}/**/*.{cs,vb}] + +# IDE0011: Add braces +csharp_prefer_braces = when_multiline:warning +# NOTE: We need the below severity entry for Add Braces due to https://github.com/dotnet/roslyn/issues/44201 +dotnet_diagnostic.IDE0011.severity = warning + +# IDE0040: Add accessibility modifiers +dotnet_diagnostic.IDE0040.severity = warning + +# CONSIDER: Are IDE0051 and IDE0052 too noisy to be warnings for IDE editing scenarios? Should they be made build-only warnings? +# IDE0051: Remove unused private member +dotnet_diagnostic.IDE0051.severity = warning + +# IDE0052: Remove unread private member +dotnet_diagnostic.IDE0052.severity = warning + +# IDE0059: Unnecessary assignment to a value +dotnet_diagnostic.IDE0059.severity = warning + +# IDE0060: Remove unused parameter +dotnet_diagnostic.IDE0060.severity = warning + +# CA1012: Abstract types should not have public constructors +dotnet_diagnostic.CA1012.severity = warning + +# CA1822: Make member static +dotnet_diagnostic.CA1822.severity = warning + +# Prefer "var" everywhere +dotnet_diagnostic.IDE0007.severity = warning +csharp_style_var_for_built_in_types = true:warning +csharp_style_var_when_type_is_apparent = true:warning +csharp_style_var_elsewhere = true:warning + +[src/{VisualStudio}/**/*.{cs,vb}] +# CA1822: Make member static +# Not enforced as a build 'warning' for 'VisualStudio' layer due to large number of false positives from https://github.com/dotnet/roslyn-analyzers/issues/3857 and https://github.com/dotnet/roslyn-analyzers/issues/3858 +# Additionally, there is a risk of accidentally breaking an internal API that partners rely on though IVT. +dotnet_diagnostic.CA1822.severity = suggestion \ No newline at end of file From 94de2a33636547a0262b80411d9bddbde8f82470 Mon Sep 17 00:00:00 2001 From: Mark Ettema Date: Sun, 9 May 2021 20:35:44 +0700 Subject: [PATCH 05/26] Remove roslyn specific header termplate --- .editorconfig | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.editorconfig b/.editorconfig index cd895da..892890e 100644 --- a/.editorconfig +++ b/.editorconfig @@ -144,10 +144,6 @@ dotnet_naming_style.pascal_case_style.capitalization = pascal_case # error RS2008: Enable analyzer release tracking for the analyzer project containing rule '{0}' dotnet_diagnostic.RS2008.severity = none -# IDE0073: File header -dotnet_diagnostic.IDE0073.severity = warning -file_header_template = Licensed to the .NET Foundation under one or more agreements.\nThe .NET Foundation licenses this file to you under the MIT license.\nSee the LICENSE file in the project root for more information. - # IDE0035: Remove unreachable code dotnet_diagnostic.IDE0035.severity = warning From 62dd979a80a9ae6c1fbe500d79f03f0adfbc347c Mon Sep 17 00:00:00 2001 From: Mark Ettema Date: Sun, 9 May 2021 20:36:41 +0700 Subject: [PATCH 06/26] Updated packages and remove Newtonsoft.Json in favor of System.Text.Json --- .../Models/GetPlayerSummariesResponse.cs | 4 +- src/Profile/Models/Player.cs | 37 ++++++++++--------- src/Profile/Models/SteamResponse.cs | 4 +- src/Profile/SteamProfileService.cs | 4 +- src/Startup.cs | 1 - src/SteamOpenIdConnectProvider.csproj | 23 +++++------- 6 files changed, 35 insertions(+), 38 deletions(-) diff --git a/src/Profile/Models/GetPlayerSummariesResponse.cs b/src/Profile/Models/GetPlayerSummariesResponse.cs index 12cb571..5cb0846 100644 --- a/src/Profile/Models/GetPlayerSummariesResponse.cs +++ b/src/Profile/Models/GetPlayerSummariesResponse.cs @@ -1,11 +1,11 @@ using System.Collections.Generic; -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace SteamOpenIdConnectProvider.Profile.Models { public sealed class GetPlayerSummariesResponse { - [JsonProperty("players")] + [JsonPropertyName("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 index 74af6d6..2873c3d 100644 --- a/src/Profile/Models/Player.cs +++ b/src/Profile/Models/Player.cs @@ -1,58 +1,59 @@ -using Newtonsoft.Json; +using System.Text.Json; +using System.Text.Json.Serialization; namespace SteamOpenIdConnectProvider.Profile.Models { public sealed class Player { - [JsonProperty("steamid")] + [JsonPropertyName("steamid")] public ulong SteamId { get; set; } - [JsonProperty("communityvisibilitystate")] + [JsonPropertyName("communityvisibilitystate")] public int CommunityVisibilityState { get; set; } - [JsonProperty("profilestate")] + [JsonPropertyName("profilestate")] public int ProfileState { get; set; } - [JsonProperty("personaname")] + [JsonPropertyName("personaname")] public string PersonaName { get; set; } - [JsonProperty("commentpermission")] + [JsonPropertyName("commentpermission")] public int CommentPermission { get; set; } - [JsonProperty("profileurl")] + [JsonPropertyName("profileurl")] public string ProfileUrl { get; set; } - [JsonProperty("avatar")] + [JsonPropertyName("avatar")] public string Avatar { get; set; } - [JsonProperty("avatarmedium")] + [JsonPropertyName("avatarmedium")] public string AvatarMedium { get; set; } - [JsonProperty("avatarfull")] + [JsonPropertyName("avatarfull")] public string AvatarFull { get; set; } - [JsonProperty("avatarhash")] + [JsonPropertyName("avatarhash")] public string AvatarHash { get; set; } - [JsonProperty("lastlogoff")] + [JsonPropertyName("lastlogoff")] public int LastLogoff { get; set; } - [JsonProperty("personastate")] + [JsonPropertyName("personastate")] public int PersonaState { get; set; } - [JsonProperty("realname")] + [JsonPropertyName("realname")] public string RealName { get; set; } - [JsonProperty("primaryclanid")] + [JsonPropertyName("primaryclanid")] public ulong PrimaryClanId { get; set; } - [JsonProperty("timecreated")] + [JsonPropertyName("timecreated")] public int TimeCreated { get; set; } - [JsonProperty("personastateflags")] + [JsonPropertyName("personastateflags")] public int PersonaStateFlags { get; set; } - [JsonProperty("loccountrycode")] + [JsonPropertyName("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 index 2b43afb..7799ea6 100644 --- a/src/Profile/Models/SteamResponse.cs +++ b/src/Profile/Models/SteamResponse.cs @@ -1,10 +1,10 @@ -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace SteamOpenIdConnectProvider.Profile.Models { public sealed class SteamResponse { - [JsonProperty("response")] + [JsonPropertyName("response")] public T Response { get; set; } } } \ No newline at end of file diff --git a/src/Profile/SteamProfileService.cs b/src/Profile/SteamProfileService.cs index 795f8b9..9800ded 100644 --- a/src/Profile/SteamProfileService.cs +++ b/src/Profile/SteamProfileService.cs @@ -2,13 +2,13 @@ using System.Collections.Generic; using System.Linq; using System.Net.Http; using System.Security.Claims; +using System.Text.Json; 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 @@ -28,7 +28,7 @@ namespace SteamOpenIdConnectProvider.Profile var url = $"{baseurl}/?key={applicationKey}&steamids={string.Join(',', steamIds)}"; var res = await _httpClient.GetStringAsync(url); - var response = JsonConvert.DeserializeObject>(res); + var response = JsonSerializer.Deserialize>(res); return response.Response; } diff --git a/src/Startup.cs b/src/Startup.cs index 7a98d35..f056b07 100644 --- a/src/Startup.cs +++ b/src/Startup.cs @@ -29,7 +29,6 @@ namespace SteamOpenIdConnectProvider public void ConfigureServices(IServiceCollection services) { services.AddControllers() - .AddNewtonsoftJson() .SetCompatibilityVersion(CompatibilityVersion.Version_3_0); services.AddSingleton(Configuration); diff --git a/src/SteamOpenIdConnectProvider.csproj b/src/SteamOpenIdConnectProvider.csproj index 34b6d46..293fe6e 100644 --- a/src/SteamOpenIdConnectProvider.csproj +++ b/src/SteamOpenIdConnectProvider.csproj @@ -1,27 +1,24 @@  - netcoreapp3.1 + net5.0 InProcess 9f8cb9ce-f696-422f-901a-563af9413684 8 - - - - - + + + + - - - - - - + + + + - + From e8520a6128b98ce44a87de71c2c2ae68b142c7a7 Mon Sep 17 00:00:00 2001 From: Mark Ettema Date: Sun, 9 May 2021 20:37:16 +0700 Subject: [PATCH 07/26] Automatic formatting changes --- src/Controllers/ExternalLoginController.cs | 4 ++-- src/IdentityServerConfig.cs | 4 ++-- src/Profile/SteamProfileService.cs | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Controllers/ExternalLoginController.cs b/src/Controllers/ExternalLoginController.cs index 67b54ec..86e1435 100644 --- a/src/Controllers/ExternalLoginController.cs +++ b/src/Controllers/ExternalLoginController.cs @@ -41,7 +41,7 @@ namespace SteamOpenIdConnectProvider.Controllers public async Task ExternalLoginCallback(string returnUrl = null, string remoteError = null) { returnUrl ??= Url.Content("~/"); - + if (remoteError != null) { throw new Exception($"Error from external provider: {remoteError}"); @@ -62,7 +62,7 @@ namespace SteamOpenIdConnectProvider.Controllers 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(); diff --git a/src/IdentityServerConfig.cs b/src/IdentityServerConfig.cs index fec35aa..16f80f1 100644 --- a/src/IdentityServerConfig.cs +++ b/src/IdentityServerConfig.cs @@ -1,6 +1,6 @@ -using System; +using System; using System.Collections.Generic; -using System.Linq; +using System.Linq; using IdentityServer4; using IdentityServer4.Models; diff --git a/src/Profile/SteamProfileService.cs b/src/Profile/SteamProfileService.cs index 9800ded..72195c8 100644 --- a/src/Profile/SteamProfileService.cs +++ b/src/Profile/SteamProfileService.cs @@ -35,7 +35,7 @@ namespace SteamOpenIdConnectProvider.Profile public SteamProfileService( UserManager userManager, IUserClaimsPrincipalFactory claimsFactory, - IConfiguration configuration, + IConfiguration configuration, HttpClient httpClient) { _userManager = userManager; From 03947fdbdc279587bca7a4fb954064c2ef9bcb81 Mon Sep 17 00:00:00 2001 From: Mark Ettema Date: Sun, 9 May 2021 21:20:57 +0700 Subject: [PATCH 08/26] Added classes for the Configuration Separated the logic in domains --- src/Domains/Common/HostingConfig.cs | 16 +++++++ .../IdentityServer}/AppInMemoryDbContext.cs | 2 +- .../IdentityServerConfigFactory.cs} | 17 +++---- src/Domains/IdentityServer/OpenIdConfig.cs | 18 ++++++++ src/Domains/Steam/Constants.cs | 14 ++++++ .../Steam}/GetPlayerSummariesResponse.cs | 3 +- .../Models => Domains/Steam}/Player.cs | 0 src/Domains/Steam/SteamConfig.cs | 14 ++++++ .../Steam}/SteamProfileService.cs | 44 +++++++++---------- .../Models => Domains/Steam}/SteamResponse.cs | 2 +- src/Startup.cs | 34 +++++++------- 11 files changed, 113 insertions(+), 51 deletions(-) create mode 100644 src/Domains/Common/HostingConfig.cs rename src/{Database => Domains/IdentityServer}/AppInMemoryDbContext.cs (87%) rename src/{IdentityServerConfig.cs => Domains/IdentityServer/IdentityServerConfigFactory.cs} (64%) create mode 100644 src/Domains/IdentityServer/OpenIdConfig.cs create mode 100644 src/Domains/Steam/Constants.cs rename src/{Profile/Models => Domains/Steam}/GetPlayerSummariesResponse.cs (70%) rename src/{Profile/Models => Domains/Steam}/Player.cs (100%) create mode 100644 src/Domains/Steam/SteamConfig.cs rename src/{Profile => Domains/Steam}/SteamProfileService.cs (79%) rename src/{Profile/Models => Domains/Steam}/SteamResponse.cs (76%) diff --git a/src/Domains/Common/HostingConfig.cs b/src/Domains/Common/HostingConfig.cs new file mode 100644 index 0000000..d63f374 --- /dev/null +++ b/src/Domains/Common/HostingConfig.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace SteamOpenIdConnectProvider.Domains.Common +{ + public class HostingConfig + { + public static readonly string Key = "Hosting"; + + public string BasePath { get; set; } + + public string PublicOrigin { get; set; } + } +} diff --git a/src/Database/AppInMemoryDbContext.cs b/src/Domains/IdentityServer/AppInMemoryDbContext.cs similarity index 87% rename from src/Database/AppInMemoryDbContext.cs rename to src/Domains/IdentityServer/AppInMemoryDbContext.cs index 0284829..7450f17 100644 --- a/src/Database/AppInMemoryDbContext.cs +++ b/src/Domains/IdentityServer/AppInMemoryDbContext.cs @@ -2,7 +2,7 @@ using Microsoft.AspNetCore.Identity.EntityFrameworkCore; using Microsoft.EntityFrameworkCore; -namespace SteamOpenIdConnectProvider.Database +namespace SteamOpenIdConnectProvider.Domains.IdentityServer { // This is completely in-memory, we do not need a persistent store. public class AppInMemoryDbContext : IdentityDbContext diff --git a/src/IdentityServerConfig.cs b/src/Domains/IdentityServer/IdentityServerConfigFactory.cs similarity index 64% rename from src/IdentityServerConfig.cs rename to src/Domains/IdentityServer/IdentityServerConfigFactory.cs index 16f80f1..8a95f5f 100644 --- a/src/IdentityServerConfig.cs +++ b/src/Domains/IdentityServer/IdentityServerConfigFactory.cs @@ -3,29 +3,30 @@ using System.Collections.Generic; using System.Linq; using IdentityServer4; using IdentityServer4.Models; +using SteamOpenIdConnectProvider.Domains.IdentityServer; -namespace SteamOpenIdConnectProvider +namespace SteamOpenIdConnectProvider.Models.IdentityServer { - public class IdentityServerConfig + public static class IdentityServerConfigFactory { - public static IEnumerable GetClients(string clientId, string secret, string redirectUri, string logoutRedirectUri) + public static IEnumerable GetClients(OpenIdConfig config) { yield return new Client { - ClientId = clientId, - ClientName = "Proxy Client", + ClientId = config.ClientID, + ClientName = config.ClientName, AllowedGrantTypes = GrantTypes.Code, RequireConsent = false, ClientSecrets = { - new Secret(secret.Sha256()) + new Secret(config.ClientSecret.Sha256()) }, // where to redirect to after login - RedirectUris = redirectUri.Split(",").Select(x => x.Trim()).ToArray(), + RedirectUris = config.RedirectUri.Split(",").Select(x => x.Trim()).ToArray(), // where to redirect to after logout - PostLogoutRedirectUris = { logoutRedirectUri }, + PostLogoutRedirectUris = { config.PostLogoutRedirectUri }, RequirePkce = false, AllowedScopes = new List { diff --git a/src/Domains/IdentityServer/OpenIdConfig.cs b/src/Domains/IdentityServer/OpenIdConfig.cs new file mode 100644 index 0000000..8bf8f2f --- /dev/null +++ b/src/Domains/IdentityServer/OpenIdConfig.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace SteamOpenIdConnectProvider.Domains.IdentityServer +{ + public class OpenIdConfig + { + public static readonly string Key = "OpenID"; + + public string ClientID { get; set; } + public string ClientSecret { get; set; } + public string RedirectUri { get; set; } + public string PostLogoutRedirectUri { get; set; } + public string ClientName { get; set; } = "Proxy Client"; + } +} diff --git a/src/Domains/Steam/Constants.cs b/src/Domains/Steam/Constants.cs new file mode 100644 index 0000000..4863d1b --- /dev/null +++ b/src/Domains/Steam/Constants.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace SteamOpenIdConnectProvider.Domains.Steam +{ + public static class Constants + { + public static readonly string OpenIdUrl = "https://steamcommunity.com/openid/id/"; + public static readonly string ApiUrl = "https://api.steampowered.com/"; + public static readonly string GetPlayerSummariesUrl = ApiUrl + "ISteamUser/GetPlayerSummaries/v0002"; + } +} diff --git a/src/Profile/Models/GetPlayerSummariesResponse.cs b/src/Domains/Steam/GetPlayerSummariesResponse.cs similarity index 70% rename from src/Profile/Models/GetPlayerSummariesResponse.cs rename to src/Domains/Steam/GetPlayerSummariesResponse.cs index 5cb0846..0cf516a 100644 --- a/src/Profile/Models/GetPlayerSummariesResponse.cs +++ b/src/Domains/Steam/GetPlayerSummariesResponse.cs @@ -1,7 +1,8 @@ using System.Collections.Generic; using System.Text.Json.Serialization; +using SteamOpenIdConnectProvider.Profile.Models; -namespace SteamOpenIdConnectProvider.Profile.Models +namespace SteamOpenIdConnectProvider.Domains.Steam { public sealed class GetPlayerSummariesResponse { diff --git a/src/Profile/Models/Player.cs b/src/Domains/Steam/Player.cs similarity index 100% rename from src/Profile/Models/Player.cs rename to src/Domains/Steam/Player.cs diff --git a/src/Domains/Steam/SteamConfig.cs b/src/Domains/Steam/SteamConfig.cs new file mode 100644 index 0000000..150d484 --- /dev/null +++ b/src/Domains/Steam/SteamConfig.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace SteamOpenIdConnectProvider.Domains.Steam +{ + public class SteamConfig + { + public static readonly string Key = "Steam"; + + public string ApplicationKey { get; internal set; } + } +} diff --git a/src/Profile/SteamProfileService.cs b/src/Domains/Steam/SteamProfileService.cs similarity index 79% rename from src/Profile/SteamProfileService.cs rename to src/Domains/Steam/SteamProfileService.cs index 72195c8..9592945 100644 --- a/src/Profile/SteamProfileService.cs +++ b/src/Domains/Steam/SteamProfileService.cs @@ -8,39 +8,28 @@ using IdentityServer4.Extensions; using IdentityServer4.Models; using IdentityServer4.Services; using Microsoft.AspNetCore.Identity; -using Microsoft.Extensions.Configuration; -using SteamOpenIdConnectProvider.Profile.Models; +using Microsoft.Extensions.Options; +using SteamOpenIdConnectProvider.Domains.Steam; +using SteamOpenIdConnectProvider.Models.Steam; -namespace SteamOpenIdConnectProvider.Profile +namespace SteamOpenIdConnectProvider.Services { public class SteamProfileService : IProfileService { private readonly HttpClient _httpClient; - private readonly IConfiguration _configuration; + private readonly SteamConfig _config; 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 = JsonSerializer.Deserialize>(res); - return response.Response; - } - public SteamProfileService( UserManager userManager, IUserClaimsPrincipalFactory claimsFactory, - IConfiguration configuration, + IOptions config, HttpClient httpClient) { _userManager = userManager; _claimsFactory = claimsFactory; - _configuration = configuration; + _config = config.Value; _httpClient = httpClient; } @@ -53,8 +42,7 @@ namespace SteamOpenIdConnectProvider.Profile 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 steamId = sub.Substring(Constants.OpenIdUrl.Length); var userSummary = await GetPlayerSummariesAsync(new[] { steamId }); var player = userSummary.Players.FirstOrDefault(); @@ -70,6 +58,13 @@ namespace SteamOpenIdConnectProvider.Profile context.IssuedClaims = claims; } + public async Task IsActiveAsync(IsActiveContext context) + { + var sub = context.Subject.GetSubjectId(); + var user = await _userManager.FindByIdAsync(sub); + context.IsActive = user != null; + } + private void AddClaim(List claims, string type, string value) { if (!string.IsNullOrEmpty(value)) @@ -78,11 +73,12 @@ namespace SteamOpenIdConnectProvider.Profile } } - public async Task IsActiveAsync(IsActiveContext context) + private async Task GetPlayerSummariesAsync(IEnumerable steamIds) { - var sub = context.Subject.GetSubjectId(); - var user = await _userManager.FindByIdAsync(sub); - context.IsActive = user != null; + var url = $"{Constants.GetPlayerSummariesUrl}/?key={_config.ApplicationKey}&steamids={string.Join(',', steamIds)}"; + var res = await _httpClient.GetStringAsync(url); + var response = JsonSerializer.Deserialize>(res); + return response.Response; } } } \ No newline at end of file diff --git a/src/Profile/Models/SteamResponse.cs b/src/Domains/Steam/SteamResponse.cs similarity index 76% rename from src/Profile/Models/SteamResponse.cs rename to src/Domains/Steam/SteamResponse.cs index 7799ea6..f6647fb 100644 --- a/src/Profile/Models/SteamResponse.cs +++ b/src/Domains/Steam/SteamResponse.cs @@ -1,6 +1,6 @@ using System.Text.Json.Serialization; -namespace SteamOpenIdConnectProvider.Profile.Models +namespace SteamOpenIdConnectProvider.Models.Steam { public sealed class SteamResponse { diff --git a/src/Startup.cs b/src/Startup.cs index f056b07..4cd0cb4 100644 --- a/src/Startup.cs +++ b/src/Startup.cs @@ -12,8 +12,11 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using IdentityServer4.Services; using Microsoft.AspNetCore.HttpOverrides; -using SteamOpenIdConnectProvider.Database; -using SteamOpenIdConnectProvider.Profile; +using SteamOpenIdConnectProvider.Services; +using SteamOpenIdConnectProvider.Models.IdentityServer; +using SteamOpenIdConnectProvider.Domains.Common; +using SteamOpenIdConnectProvider.Domains.IdentityServer; +using SteamOpenIdConnectProvider.Domains.Steam; namespace SteamOpenIdConnectProvider { @@ -31,7 +34,6 @@ namespace SteamOpenIdConnectProvider services.AddControllers() .SetCompatibilityVersion(CompatibilityVersion.Version_3_0); - services.AddSingleton(Configuration); services.AddDbContext(options => options.UseInMemoryDatabase("default")); @@ -42,21 +44,21 @@ namespace SteamOpenIdConnectProvider .AddEntityFrameworkStores() .AddDefaultTokenProviders(); + var openIdConfig = Configuration.GetSection(OpenIdConfig.Key).Get(); services.AddIdentityServer(options => { options.UserInteraction.LoginUrl = "/ExternalLogin"; }) .AddAspNetIdentity() - .AddInMemoryClients(IdentityServerConfig.GetClients( - Configuration["OpenID:ClientID"], - Configuration["OpenID:ClientSecret"], - Configuration["OpenID:RedirectUri"], - Configuration["OpenID:PostLogoutRedirectUri"])) + .AddInMemoryClients(IdentityServerConfigFactory.GetClients(openIdConfig)) .AddInMemoryPersistedGrants() .AddDeveloperSigningCredential(true) - .AddInMemoryIdentityResources(IdentityServerConfig.GetIdentityResources()); + .AddInMemoryIdentityResources(IdentityServerConfigFactory.GetIdentityResources()); - services.AddHttpClient(); + var steamConfig = Configuration.GetSection(SteamConfig.Key).Get(); + services + .Configure(Configuration.GetSection(SteamConfig.Key)) + .AddHttpClient(); services.AddAuthentication() .AddCookie(options => @@ -66,7 +68,7 @@ namespace SteamOpenIdConnectProvider }) .AddSteam(options => { - options.ApplicationKey = Configuration["Authentication:Steam:ApplicationKey"]; + options.ApplicationKey = steamConfig.ApplicationKey; }); services.AddHealthChecks() @@ -80,18 +82,18 @@ namespace SteamOpenIdConnectProvider app.UseDeveloperExceptionPage(); } - if (!string.IsNullOrEmpty(Configuration["Hosting:PathBase"])) + var hostingConfig = Configuration.GetSection(HostingConfig.Key).Get(); + if (!string.IsNullOrEmpty(hostingConfig.BasePath)) { - app.UsePathBase(Configuration["Hosting:PathBase"]); + app.UsePathBase(hostingConfig.BasePath); } app.UseCookiePolicy(); app.Use(async (ctx, next) => { - var origin = Configuration["Hosting:PublicOrigin"]; - if (!string.IsNullOrEmpty(origin)) + if (!string.IsNullOrEmpty(hostingConfig.PublicOrigin)) { - ctx.SetIdentityServerOrigin(origin); + ctx.SetIdentityServerOrigin(hostingConfig.PublicOrigin); } await next(); From 897b693263181586184c0e2fd0467a4490b95a74 Mon Sep 17 00:00:00 2001 From: Mark Ettema Date: Mon, 10 May 2021 07:46:43 +0700 Subject: [PATCH 09/26] Moving Dockerfile Using .Net 5 images Starting to setup test environment with docker-compose --- docker-compose.yml | 19 +++++++++++++++++++ Dockerfile => src/Dockerfile | 8 ++++---- src/appsettings.json | 5 ++++- 3 files changed, 27 insertions(+), 5 deletions(-) create mode 100644 docker-compose.yml rename Dockerfile => src/Dockerfile (75%) diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..64afbe6 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,19 @@ +version: '2' +services: + keycloak: + image: jboss/keycloak + container_name: keycloak + environment: + KEYCLOAK_USER: admin + KEYCLOAK_PASSWORD: changeit + ports: + - 8080:8080 + + steamidp: + image: neothor/steam-openid-connect-provider + build: ./src + container_name: steamidp + ports: + - 80:80 + links: + - keycloak \ No newline at end of file diff --git a/Dockerfile b/src/Dockerfile similarity index 75% rename from Dockerfile rename to src/Dockerfile index 3388020..3842055 100644 --- a/Dockerfile +++ b/src/Dockerfile @@ -1,19 +1,19 @@ -FROM mcr.microsoft.com/dotnet/core/sdk:3.1 AS build +FROM mcr.microsoft.com/dotnet/sdk:5.0 AS build WORKDIR /src # Copy the project file to create layer with packages -COPY src/SteamOpenIdConnectProvider.csproj . +COPY SteamOpenIdConnectProvider.csproj . RUN dotnet restore ./SteamOpenIdConnectProvider.csproj # Copy the rest of the source -COPY src/* . +COPY . . RUN dotnet build ./SteamOpenIdConnectProvider.csproj -c Release -o /app FROM build AS publish RUN dotnet publish ./SteamOpenIdConnectProvider.csproj -c Release -o /app -FROM mcr.microsoft.com/dotnet/core/aspnet:3.1 AS base +FROM mcr.microsoft.com/dotnet/aspnet:5.0 WORKDIR /app COPY --from=publish /app . EXPOSE 80 diff --git a/src/appsettings.json b/src/appsettings.json index 5582ca6..2b96863 100644 --- a/src/appsettings.json +++ b/src/appsettings.json @@ -4,14 +4,17 @@ "Default": "Warning" } }, + "AllowedHosts": "*", "OpenID": { "ClientID": "proxy", "ClientSecret": "secret", "RedirectUri": "http://localhost:8080/auth/realms/master/broker/steam/endpoint", "PostLogoutRedirectUri": "" }, - "AllowedHosts": "*", "Hosting": { "PathBase": "" + }, + "Steam": { + "ApplicationKey": "secret" } } \ No newline at end of file From a003e250fa2217d686f3130466a56ecd2f22d40f Mon Sep 17 00:00:00 2001 From: Mark Ettema Date: Tue, 11 May 2021 07:06:54 +0700 Subject: [PATCH 10/26] Re-use constant --- docker-compose.yml | 2 +- src/Startup.cs | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 64afbe6..444adb9 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -10,7 +10,7 @@ services: - 8080:8080 steamidp: - image: neothor/steam-openid-connect-provider + image: neothor/steam-openid-connect-provider:develop build: ./src container_name: steamidp ports: diff --git a/src/Startup.cs b/src/Startup.cs index 4cd0cb4..44ce6a5 100644 --- a/src/Startup.cs +++ b/src/Startup.cs @@ -1,5 +1,4 @@ using System; -using System.Net.Http; using IdentityServer4.Extensions; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; @@ -72,7 +71,7 @@ namespace SteamOpenIdConnectProvider }); services.AddHealthChecks() - .AddUrlGroup(new Uri("https://steamcommunity.com/openid"), "Steam"); + .AddUrlGroup(new Uri(Constants.OpenIdUrl), "Steam"); } public void Configure(IApplicationBuilder app, IWebHostEnvironment env) From f14bf5e6e2b36148413bbe1bf3835be79adf7898 Mon Sep 17 00:00:00 2001 From: Mark Ettema Date: Tue, 11 May 2021 07:42:16 +0700 Subject: [PATCH 11/26] Use postgresql for faster startup --- .gitignore | 6 ++++++ docker-compose.yml | 22 ++++++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/.gitignore b/.gitignore index 4bda13b..ca3816f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,7 @@ + +## For secrets in through docker env variables +docker-compose.secrets.yml + ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. ## @@ -330,3 +334,5 @@ ASALocalRun/ # MFractors (Xamarin productivity tool) working folder .mfractor/ + +dist/ \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 444adb9..4709acd 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,13 +1,35 @@ version: '2' +volumes: + postgres_data: + driver: local + services: + postgres: + image: postgres + container_name: postgres + volumes: + - postgres_data:/var/lib/postgresql/data + environment: + POSTGRES_DB: keycloak + POSTGRES_USER: keycloak + POSTGRES_PASSWORD: password + keycloak: image: jboss/keycloak container_name: keycloak environment: + DB_VENDOR: POSTGRES + DB_ADDR: postgres + DB_DATABASE: keycloak + DB_USER: keycloak + DB_SCHEMA: public + DB_PASSWORD: password KEYCLOAK_USER: admin KEYCLOAK_PASSWORD: changeit ports: - 8080:8080 + links: + - postgres steamidp: image: neothor/steam-openid-connect-provider:develop From 425d7d1d85b8093ba6fabc41599b7c5d9168c26d Mon Sep 17 00:00:00 2001 From: Mark Ettema Date: Thu, 13 May 2021 12:05:27 +0700 Subject: [PATCH 12/26] The userName claim is not set through steam. Fallback to id as username for now --- src/Controllers/ExternalLoginController.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/Controllers/ExternalLoginController.cs b/src/Controllers/ExternalLoginController.cs index 86e1435..b97747f 100644 --- a/src/Controllers/ExternalLoginController.cs +++ b/src/Controllers/ExternalLoginController.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using System.Security.Claims; using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; @@ -63,6 +64,16 @@ namespace SteamOpenIdConnectProvider.Controllers var userName = info.Principal.FindFirstValue(ClaimTypes.Name); var userId = info.Principal.FindFirstValue(ClaimTypes.NameIdentifier); + if (string.IsNullOrEmpty(userId)) + { + throw new ArgumentNullException(nameof(userId), $"No claim found for {ClaimTypes.NameIdentifier}"); + } + + if (string.IsNullOrEmpty(userName)) + { + userName = userId.Split('/').Last(); + } + var user = new IdentityUser { UserName = userName, Id = userId }; _userManager.UserValidators.Clear(); From 3cd36724c9a34bca64acf8a0329899b3611e4e03 Mon Sep 17 00:00:00 2001 From: Mark Ettema Date: Sat, 15 May 2021 11:42:14 +0700 Subject: [PATCH 13/26] Added nginx proxy with SSL support. TODO keycloak truststore --- dev.local.plantuml | 29 ++++++++++++++++++++++++++++ docker-compose.yml | 45 +++++++++++++++++++++++++++++++++++++------- proxy_ssl.conf | 21 +++++++++++++++++++++ src/appsettings.json | 2 +- 4 files changed, 89 insertions(+), 8 deletions(-) create mode 100644 dev.local.plantuml create mode 100644 proxy_ssl.conf diff --git a/dev.local.plantuml b/dev.local.plantuml new file mode 100644 index 0000000..131a2a5 --- /dev/null +++ b/dev.local.plantuml @@ -0,0 +1,29 @@ +@startuml + +node Host { + component Browser + + node Docker { + component Proxy + component Keycloak + component SteamIdp + component Postgresql + + Proxy --> Keycloak: keycloak (http) + Proxy --> SteamIdp: steamidp (http) + Keycloak --> Postgresql: postgres + + Proxy <-- Keycloak: dev.local (https) + } + + component SteamIdpDev as "SteamIdp" +} + +cloud Internet { + component Steam +} + +Browser --> Proxy: dev.local (https) +Proxy ..> SteamIdpDev: host.docker.internal (http) + +@enduml \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 4709acd..328323c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,9 +1,34 @@ +# Create the file docker-compose.secrets.yml and add +# ``` +# version: '2' +# services: +# proxy: +# volumes: +# - "/dev.local.crt:/tmp/dev.local.crt" +# - "/dev.local.key:/tmp/dev.local.key" +# steamidp: +# environment: +# Steam__ApplicationKey: +# ``` +# +# Than start with `docker compose -f .\docker-compose.yml -f .\docker-compose.secrets.yml up` version: '2' volumes: postgres_data: driver: local services: + proxy: + image: nginx + container_name: proxy + volumes: + - "./proxy_ssl.conf:/etc/nginx/conf.d/proxy_ssl.conf" + ports: + - 443:443 + links: + - keycloak + - steamidp + postgres: image: postgres container_name: postgres @@ -26,16 +51,22 @@ services: DB_PASSWORD: password KEYCLOAK_USER: admin KEYCLOAK_PASSWORD: changeit - ports: - - 8080:8080 + PROXY_ADDRESS_FORWARDING: "true" links: - - postgres + - postgres + extra_hosts: + - "dev.local:host-gateway" + steamidp: image: neothor/steam-openid-connect-provider:develop build: ./src - container_name: steamidp - ports: - - 80:80 + container_name: steamidp links: - - keycloak \ No newline at end of file + - keycloak + environment: + OpenID__ClientID: keycloak + OpenID__ClientName: keycloak + OpenId__ClientSecret: keycloak + OpenID__RedirectUri: https://dev.local/auth/realms/dev/broker/steam/endpoint + Hosting__BasePath: /steam \ No newline at end of file diff --git a/proxy_ssl.conf b/proxy_ssl.conf new file mode 100644 index 0000000..2f9d45d --- /dev/null +++ b/proxy_ssl.conf @@ -0,0 +1,21 @@ +server { + listen 443 ssl; + ssl_certificate /tmp/dev.local.crt; + ssl_certificate_key /tmp/dev.local.key; + + proxy_set_header X-Forwarded-For $proxy_protocol_addr; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header Host $host; + + location / { + return 301 https://$host/auth/realms/dev/account; + } + + location /auth { + proxy_pass http://keycloak:8080; + } + + location /steam { + proxy_pass http://steamidp:80; + } +} \ No newline at end of file diff --git a/src/appsettings.json b/src/appsettings.json index 2b96863..9c45c2d 100644 --- a/src/appsettings.json +++ b/src/appsettings.json @@ -8,7 +8,7 @@ "OpenID": { "ClientID": "proxy", "ClientSecret": "secret", - "RedirectUri": "http://localhost:8080/auth/realms/master/broker/steam/endpoint", + "RedirectUri": "http://localhost:8080/auth/realms/dev/broker/steam-dev/endpoint", "PostLogoutRedirectUri": "" }, "Hosting": { From 59bd22567ecfc3ac12c093141f482eb3d1fcf39a Mon Sep 17 00:00:00 2001 From: Mark Ettema Date: Sat, 15 May 2021 18:27:57 +0700 Subject: [PATCH 14/26] Adding Dockerfiles for supporting services. Updating keycloak container to be able to add SSL cert --- develop/keycloak/Dockerfile | 19 +++++++++++++++++++ develop/keycloak/add-to-truststore.sh | 10 ++++++++++ develop/proxy/Dockerfile | 3 +++ .../proxy/proxy_ssl.conf | 0 docker-compose.yml | 14 +++++++++----- 5 files changed, 41 insertions(+), 5 deletions(-) create mode 100644 develop/keycloak/Dockerfile create mode 100644 develop/keycloak/add-to-truststore.sh create mode 100644 develop/proxy/Dockerfile rename proxy_ssl.conf => develop/proxy/proxy_ssl.conf (100%) diff --git a/develop/keycloak/Dockerfile b/develop/keycloak/Dockerfile new file mode 100644 index 0000000..cbc4d2a --- /dev/null +++ b/develop/keycloak/Dockerfile @@ -0,0 +1,19 @@ +FROM jboss/keycloak + +USER root + +# Install sudo and utils to configure jboss user +RUN microdnf update -y && \ + microdnf install -y sudo shadow-utils passwd && \ + microdnf clean all + +# 'Fix' jboss user, add to sudoers +RUN usermod --password jboss jboss && \ + usermod -aG wheel jboss && \ + sed -i 's/# includedir/includedir/' /etc/sudoers && \ + echo "jboss ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers.d/jboss + +ADD ./add-to-truststore.sh /opt/jboss/startup-scripts/add-to-truststore.sh +RUN chmod +x /opt/jboss/startup-scripts/add-to-truststore.sh + +USER jboss \ No newline at end of file diff --git a/develop/keycloak/add-to-truststore.sh b/develop/keycloak/add-to-truststore.sh new file mode 100644 index 0000000..5499c3f --- /dev/null +++ b/develop/keycloak/add-to-truststore.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash + +CACERTS=$(readlink -e $(dirname $(readlink -e $(which keytool)))"/../lib/security/cacerts") + +sudo keytool \ + -import -trustcacerts \ + -alias "dev.local.crt" -file /tmp/dev.local.crt \ + -keystore ${CACERTS} \ + -storepass changeit \ + -noprompt \ No newline at end of file diff --git a/develop/proxy/Dockerfile b/develop/proxy/Dockerfile new file mode 100644 index 0000000..275df43 --- /dev/null +++ b/develop/proxy/Dockerfile @@ -0,0 +1,3 @@ +FROM nginx + +ADD ./develop/proxy/proxy_ssl.conf /etc/nginx/conf.d/proxy_ssl.conf \ No newline at end of file diff --git a/proxy_ssl.conf b/develop/proxy/proxy_ssl.conf similarity index 100% rename from proxy_ssl.conf rename to develop/proxy/proxy_ssl.conf diff --git a/docker-compose.yml b/docker-compose.yml index 328323c..aedde3a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -6,6 +6,11 @@ # volumes: # - "/dev.local.crt:/tmp/dev.local.crt" # - "/dev.local.key:/tmp/dev.local.key" +# +# keycloak: +# volumes: +# - "/dev.local.crt:/tmp/dev.local.crt" +# # steamidp: # environment: # Steam__ApplicationKey: @@ -19,10 +24,9 @@ volumes: services: proxy: - image: nginx + image: neothor/proxy:develop + build: ./develop/proxy container_name: proxy - volumes: - - "./proxy_ssl.conf:/etc/nginx/conf.d/proxy_ssl.conf" ports: - 443:443 links: @@ -40,7 +44,8 @@ services: POSTGRES_PASSWORD: password keycloak: - image: jboss/keycloak + image: neothor/keycloak:develop + build: ./develop/keycloak container_name: keycloak environment: DB_VENDOR: POSTGRES @@ -56,7 +61,6 @@ services: - postgres extra_hosts: - "dev.local:host-gateway" - steamidp: image: neothor/steam-openid-connect-provider:develop From 79ae1458f9b6e2d4aad01b68881febb20dcb1ed2 Mon Sep 17 00:00:00 2001 From: Mark Ettema Date: Sat, 15 May 2021 18:35:28 +0700 Subject: [PATCH 15/26] Correct the path --- develop/proxy/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/develop/proxy/Dockerfile b/develop/proxy/Dockerfile index 275df43..0947519 100644 --- a/develop/proxy/Dockerfile +++ b/develop/proxy/Dockerfile @@ -1,3 +1,3 @@ FROM nginx -ADD ./develop/proxy/proxy_ssl.conf /etc/nginx/conf.d/proxy_ssl.conf \ No newline at end of file +ADD ./proxy_ssl.conf /etc/nginx/conf.d/proxy_ssl.conf \ No newline at end of file From 8cbd103532806a0a989a817378734d08b3dc3ade Mon Sep 17 00:00:00 2001 From: Mark Ettema Date: Mon, 17 May 2021 08:34:14 +0700 Subject: [PATCH 16/26] Trying to setup with debugging in visual studio --- develop/proxy/Dockerfile | 8 +++++++- .../{proxy_ssl.conf => proxy_ssl.conf.template} | 8 ++++---- docker-compose.debug.yml | 7 +++++++ docker-compose.secrets.yml.template | 14 ++++++++++++++ src/appsettings.Development.json | 2 +- src/appsettings.json | 7 ++++--- 6 files changed, 37 insertions(+), 9 deletions(-) rename develop/proxy/{proxy_ssl.conf => proxy_ssl.conf.template} (65%) create mode 100644 docker-compose.debug.yml create mode 100644 docker-compose.secrets.yml.template diff --git a/develop/proxy/Dockerfile b/develop/proxy/Dockerfile index 0947519..38c3a8e 100644 --- a/develop/proxy/Dockerfile +++ b/develop/proxy/Dockerfile @@ -1,3 +1,9 @@ FROM nginx -ADD ./proxy_ssl.conf /etc/nginx/conf.d/proxy_ssl.conf \ No newline at end of file +ENV SSL_CERT=/tmp/dev.local.crt +ENV SSL_KEY=/tmp/dev.local.key + +ENV KEYCLOAK_URI=http://keycloak:8080 +ENV STEAMIDP_URI=http://steamidp:80 + +ADD ./proxy_ssl.conf.template /etc/nginx/templates/ \ No newline at end of file diff --git a/develop/proxy/proxy_ssl.conf b/develop/proxy/proxy_ssl.conf.template similarity index 65% rename from develop/proxy/proxy_ssl.conf rename to develop/proxy/proxy_ssl.conf.template index 2f9d45d..00ff768 100644 --- a/develop/proxy/proxy_ssl.conf +++ b/develop/proxy/proxy_ssl.conf.template @@ -1,7 +1,7 @@ server { listen 443 ssl; - ssl_certificate /tmp/dev.local.crt; - ssl_certificate_key /tmp/dev.local.key; + ssl_certificate ${SSL_CERT}; + ssl_certificate_key ${SSL_KEY}; proxy_set_header X-Forwarded-For $proxy_protocol_addr; proxy_set_header X-Forwarded-Proto $scheme; @@ -12,10 +12,10 @@ server { } location /auth { - proxy_pass http://keycloak:8080; + proxy_pass ${KEYCLOAK_URI}; } location /steam { - proxy_pass http://steamidp:80; + proxy_pass ${STEAMIDP_URI}; } } \ No newline at end of file diff --git a/docker-compose.debug.yml b/docker-compose.debug.yml new file mode 100644 index 0000000..85b2a37 --- /dev/null +++ b/docker-compose.debug.yml @@ -0,0 +1,7 @@ +version: '2' +services: + proxy: + environment: + STEAMIDP_URI: https://steamidp-debugger:5001 + extra_hosts: + - "steamidp-debugger:host-gateway" \ No newline at end of file diff --git a/docker-compose.secrets.yml.template b/docker-compose.secrets.yml.template new file mode 100644 index 0000000..c32387a --- /dev/null +++ b/docker-compose.secrets.yml.template @@ -0,0 +1,14 @@ +version: '2' +services: + proxy: + volumes: + - "/dev.local.crt:/tmp/dev.local.crt" + - "/dev.local.key:/tmp/dev.local.key" + + keycloak: + volumes: + - "/dev.local.crt:/tmp/dev.local.crt" + + steamidp: + environment: + Steam__ApplicationKey: \ No newline at end of file diff --git a/src/appsettings.Development.json b/src/appsettings.Development.json index 9988482..35c654c 100644 --- a/src/appsettings.Development.json +++ b/src/appsettings.Development.json @@ -7,6 +7,6 @@ } }, "Hosting": { - "PathBase": "/test" + "PathBase": "/steam" } } diff --git a/src/appsettings.json b/src/appsettings.json index 9c45c2d..bba5e09 100644 --- a/src/appsettings.json +++ b/src/appsettings.json @@ -6,9 +6,10 @@ }, "AllowedHosts": "*", "OpenID": { - "ClientID": "proxy", - "ClientSecret": "secret", - "RedirectUri": "http://localhost:8080/auth/realms/dev/broker/steam-dev/endpoint", + "ClientID": "keycloak", + "ClientSecret": "keycloak", + "ClientName": "keycloak", + "RedirectUri": "https://dev.local/auth/realms/dev/broker/steam/endpoint", "PostLogoutRedirectUri": "" }, "Hosting": { From c12ce382d85b0fd85d9afa26131c055b9f52b7d8 Mon Sep 17 00:00:00 2001 From: Mark Ettema Date: Thu, 20 May 2021 21:22:01 +0700 Subject: [PATCH 17/26] Added request logging to see the actual requests to the SteamOpenIdConnectProvider Fixed config variable names Updated Player with strings instead of ulong --- .gitattributes | 1 + docker-compose.yml | 19 ------------------- src/Domains/Steam/Player.cs | 8 ++++---- src/Domains/Steam/SteamConfig.cs | 2 +- src/Program.cs | 3 ++- src/Startup.cs | 5 +++++ src/appsettings.Development.json | 9 +-------- src/appsettings.json | 7 +------ 8 files changed, 15 insertions(+), 39 deletions(-) diff --git a/.gitattributes b/.gitattributes index 1ff0c42..ef82812 100644 --- a/.gitattributes +++ b/.gitattributes @@ -2,6 +2,7 @@ # Set default behavior to automatically normalize line endings. ############################################################################### * text=auto +*.sh text eol=lf ############################################################################### # Set default behavior for command prompt diff. diff --git a/docker-compose.yml b/docker-compose.yml index aedde3a..9a2f38d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,22 +1,3 @@ -# Create the file docker-compose.secrets.yml and add -# ``` -# version: '2' -# services: -# proxy: -# volumes: -# - "/dev.local.crt:/tmp/dev.local.crt" -# - "/dev.local.key:/tmp/dev.local.key" -# -# keycloak: -# volumes: -# - "/dev.local.crt:/tmp/dev.local.crt" -# -# steamidp: -# environment: -# Steam__ApplicationKey: -# ``` -# -# Than start with `docker compose -f .\docker-compose.yml -f .\docker-compose.secrets.yml up` version: '2' volumes: postgres_data: diff --git a/src/Domains/Steam/Player.cs b/src/Domains/Steam/Player.cs index 2873c3d..6ce9ae6 100644 --- a/src/Domains/Steam/Player.cs +++ b/src/Domains/Steam/Player.cs @@ -1,4 +1,4 @@ -using System.Text.Json; +using System.Text.Json; using System.Text.Json.Serialization; namespace SteamOpenIdConnectProvider.Profile.Models @@ -6,7 +6,7 @@ namespace SteamOpenIdConnectProvider.Profile.Models public sealed class Player { [JsonPropertyName("steamid")] - public ulong SteamId { get; set; } + public string SteamId { get; set; } [JsonPropertyName("communityvisibilitystate")] public int CommunityVisibilityState { get; set; } @@ -45,7 +45,7 @@ namespace SteamOpenIdConnectProvider.Profile.Models public string RealName { get; set; } [JsonPropertyName("primaryclanid")] - public ulong PrimaryClanId { get; set; } + public string PrimaryClanId { get; set; } [JsonPropertyName("timecreated")] public int TimeCreated { get; set; } @@ -56,4 +56,4 @@ namespace SteamOpenIdConnectProvider.Profile.Models [JsonPropertyName("loccountrycode")] public string LocCountryCode { get; set; } } -} \ No newline at end of file +} diff --git a/src/Domains/Steam/SteamConfig.cs b/src/Domains/Steam/SteamConfig.cs index 150d484..6a7e40e 100644 --- a/src/Domains/Steam/SteamConfig.cs +++ b/src/Domains/Steam/SteamConfig.cs @@ -9,6 +9,6 @@ namespace SteamOpenIdConnectProvider.Domains.Steam { public static readonly string Key = "Steam"; - public string ApplicationKey { get; internal set; } + public string ApplicationKey { get; set; } } } diff --git a/src/Program.cs b/src/Program.cs index c345b04..ed6d99a 100644 --- a/src/Program.cs +++ b/src/Program.cs @@ -15,8 +15,9 @@ namespace SteamOpenIdConnectProvider .MinimumLevel.Override("Microsoft", LogEventLevel.Warning) .MinimumLevel.Override("System", LogEventLevel.Warning) .MinimumLevel.Override("Microsoft.AspNetCore.Authentication", LogEventLevel.Information) + .MinimumLevel.Override("Microsoft.AspNetCore", LogEventLevel.Warning) .Enrich.FromLogContext() - .WriteTo.Console(outputTemplate: "[{Timestamp:HH:mm:ss} {Level}] {SourceContext}{NewLine}{Message:lj}{NewLine}{Exception}{NewLine}", theme: AnsiConsoleTheme.Literate) + .WriteTo.Console() .CreateLogger(); CreateWebHostBuilder(args).Build().Run(); diff --git a/src/Startup.cs b/src/Startup.cs index 44ce6a5..290c599 100644 --- a/src/Startup.cs +++ b/src/Startup.cs @@ -16,6 +16,9 @@ using SteamOpenIdConnectProvider.Models.IdentityServer; using SteamOpenIdConnectProvider.Domains.Common; using SteamOpenIdConnectProvider.Domains.IdentityServer; using SteamOpenIdConnectProvider.Domains.Steam; +using System.IO; +using System.Text; +using Serilog; namespace SteamOpenIdConnectProvider { @@ -87,6 +90,8 @@ namespace SteamOpenIdConnectProvider app.UsePathBase(hostingConfig.BasePath); } + app.UseSerilogRequestLogging(); + app.UseCookiePolicy(); app.Use(async (ctx, next) => { diff --git a/src/appsettings.Development.json b/src/appsettings.Development.json index 35c654c..2ada604 100644 --- a/src/appsettings.Development.json +++ b/src/appsettings.Development.json @@ -1,12 +1,5 @@ { - "Logging": { - "LogLevel": { - "Default": "Debug", - "System": "Information", - "Microsoft": "Information" - } - }, "Hosting": { - "PathBase": "/steam" + "BasePath": "/steam" } } diff --git a/src/appsettings.json b/src/appsettings.json index bba5e09..d6ba347 100644 --- a/src/appsettings.json +++ b/src/appsettings.json @@ -1,9 +1,4 @@ { - "Logging": { - "LogLevel": { - "Default": "Warning" - } - }, "AllowedHosts": "*", "OpenID": { "ClientID": "keycloak", @@ -13,7 +8,7 @@ "PostLogoutRedirectUri": "" }, "Hosting": { - "PathBase": "" + "BasePath": "" }, "Steam": { "ApplicationKey": "secret" From f24607c4c2a0ab13f26edba4dd75684888528e1d Mon Sep 17 00:00:00 2001 From: Mark Ettema Date: Mon, 24 May 2021 09:46:48 +0700 Subject: [PATCH 18/26] Got logout working in a hacky way Added keycloak dev config --- develop/keycloak/dev-realm.json | 1787 +++++++++++++++++ src/Controllers/ExternalLoginController.cs | 36 +- .../IdentityServerConfigFactory.cs | 10 +- src/Domains/IdentityServer/OpenIdConfig.cs | 8 + .../IdentityServer/OpenIdStandardClaims.cs | 30 + src/Domains/Steam/SteamProfileService.cs | 14 +- src/Startup.cs | 17 +- src/appsettings.json | 4 +- 8 files changed, 1885 insertions(+), 21 deletions(-) create mode 100644 develop/keycloak/dev-realm.json create mode 100644 src/Domains/IdentityServer/OpenIdStandardClaims.cs diff --git a/develop/keycloak/dev-realm.json b/develop/keycloak/dev-realm.json new file mode 100644 index 0000000..91eb7e2 --- /dev/null +++ b/develop/keycloak/dev-realm.json @@ -0,0 +1,1787 @@ +{ + "id": "dev", + "realm": "dev", + "notBefore": 0, + "defaultSignatureAlgorithm": "RS256", + "revokeRefreshToken": false, + "refreshTokenMaxReuse": 0, + "accessTokenLifespan": 300, + "accessTokenLifespanForImplicitFlow": 900, + "ssoSessionIdleTimeout": 1800, + "ssoSessionMaxLifespan": 36000, + "ssoSessionIdleTimeoutRememberMe": 0, + "ssoSessionMaxLifespanRememberMe": 0, + "offlineSessionIdleTimeout": 2592000, + "offlineSessionMaxLifespanEnabled": false, + "offlineSessionMaxLifespan": 5184000, + "clientSessionIdleTimeout": 0, + "clientSessionMaxLifespan": 0, + "clientOfflineSessionIdleTimeout": 0, + "clientOfflineSessionMaxLifespan": 0, + "accessCodeLifespan": 60, + "accessCodeLifespanUserAction": 300, + "accessCodeLifespanLogin": 1800, + "actionTokenGeneratedByAdminLifespan": 43200, + "actionTokenGeneratedByUserLifespan": 300, + "oauth2DeviceCodeLifespan": 600, + "oauth2DevicePollingInterval": 5, + "enabled": true, + "sslRequired": "external", + "registrationAllowed": false, + "registrationEmailAsUsername": false, + "rememberMe": false, + "verifyEmail": false, + "loginWithEmailAllowed": true, + "duplicateEmailsAllowed": false, + "resetPasswordAllowed": false, + "editUsernameAllowed": false, + "bruteForceProtected": false, + "permanentLockout": false, + "maxFailureWaitSeconds": 900, + "minimumQuickLoginWaitSeconds": 60, + "waitIncrementSeconds": 60, + "quickLoginCheckMilliSeconds": 1000, + "maxDeltaTimeSeconds": 43200, + "failureFactor": 30, + "defaultRole": { + "id": "e213aa88-c64e-4688-be18-6befd10a896f", + "name": "default-roles-dev", + "description": "${role_default-roles}", + "composite": true, + "clientRole": false, + "containerId": "dev" + }, + "requiredCredentials": [ + "password" + ], + "otpPolicyType": "totp", + "otpPolicyAlgorithm": "HmacSHA1", + "otpPolicyInitialCounter": 0, + "otpPolicyDigits": 6, + "otpPolicyLookAheadWindow": 1, + "otpPolicyPeriod": 30, + "otpSupportedApplications": [ + "FreeOTP", + "Google Authenticator" + ], + "webAuthnPolicyRpEntityName": "keycloak", + "webAuthnPolicySignatureAlgorithms": [ + "ES256" + ], + "webAuthnPolicyRpId": "", + "webAuthnPolicyAttestationConveyancePreference": "not specified", + "webAuthnPolicyAuthenticatorAttachment": "not specified", + "webAuthnPolicyRequireResidentKey": "not specified", + "webAuthnPolicyUserVerificationRequirement": "not specified", + "webAuthnPolicyCreateTimeout": 0, + "webAuthnPolicyAvoidSameAuthenticatorRegister": false, + "webAuthnPolicyAcceptableAaguids": [], + "webAuthnPolicyPasswordlessRpEntityName": "keycloak", + "webAuthnPolicyPasswordlessSignatureAlgorithms": [ + "ES256" + ], + "webAuthnPolicyPasswordlessRpId": "", + "webAuthnPolicyPasswordlessAttestationConveyancePreference": "not specified", + "webAuthnPolicyPasswordlessAuthenticatorAttachment": "not specified", + "webAuthnPolicyPasswordlessRequireResidentKey": "not specified", + "webAuthnPolicyPasswordlessUserVerificationRequirement": "not specified", + "webAuthnPolicyPasswordlessCreateTimeout": 0, + "webAuthnPolicyPasswordlessAvoidSameAuthenticatorRegister": false, + "webAuthnPolicyPasswordlessAcceptableAaguids": [], + "clientProfiles": { + "profiles": [] + }, + "clientPolicies": { + "policies": [ + { + "name": "builtin-default-policy", + "builtin": true, + "enable": false + } + ] + }, + "scopeMappings": [ + { + "clientScope": "offline_access", + "roles": [ + "offline_access" + ] + } + ], + "clientScopeMappings": { + "account": [ + { + "client": "account-console", + "roles": [ + "manage-account" + ] + } + ] + }, + "clients": [ + { + "id": "015f65aa-1d2b-484c-9846-2c1e24cdf32e", + "clientId": "account", + "name": "${client_account}", + "rootUrl": "${authBaseUrl}", + "baseUrl": "/realms/dev/account/", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [ + "/realms/dev/account/*" + ], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": true, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": {}, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "defaultClientScopes": [ + "web-origins", + "profile", + "roles", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "43f7defb-7fa1-4c39-88d7-438a9b152563", + "clientId": "account-console", + "name": "${client_account-console}", + "rootUrl": "${authBaseUrl}", + "baseUrl": "/realms/dev/account/", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [ + "/realms/dev/account/*" + ], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": true, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "pkce.code.challenge.method": "S256" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "protocolMappers": [ + { + "id": "3e8967f9-8e91-4c5d-8b06-ef9b424dfb45", + "name": "audience resolve", + "protocol": "openid-connect", + "protocolMapper": "oidc-audience-resolve-mapper", + "consentRequired": false, + "config": {} + } + ], + "defaultClientScopes": [ + "web-origins", + "profile", + "roles", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "31cdbd7e-d641-4650-973e-75ae93ad9b0e", + "clientId": "admin-cli", + "name": "${client_admin-cli}", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": false, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": true, + "serviceAccountsEnabled": false, + "publicClient": true, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": {}, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "defaultClientScopes": [ + "web-origins", + "profile", + "roles", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "4f2f4ade-2496-470c-bb56-a584da1efa07", + "clientId": "broker", + "name": "${client_broker}", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": true, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": false, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": {}, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "defaultClientScopes": [ + "web-origins", + "profile", + "roles", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "b1c685e0-b330-425c-a4d8-7b528b7957fb", + "clientId": "realm-management", + "name": "${client_realm-management}", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": true, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": false, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": {}, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "defaultClientScopes": [ + "web-origins", + "profile", + "roles", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "032b6141-9340-4795-b47e-6ce9b0377bdb", + "clientId": "security-admin-console", + "name": "${client_security-admin-console}", + "rootUrl": "${authAdminUrl}", + "baseUrl": "/admin/dev/console/", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [ + "/admin/dev/console/*" + ], + "webOrigins": [ + "+" + ], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": true, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "pkce.code.challenge.method": "S256" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "protocolMappers": [ + { + "id": "c8805da0-194b-4361-b961-d7934849f48c", + "name": "locale", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "locale", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "locale", + "jsonType.label": "String" + } + } + ], + "defaultClientScopes": [ + "web-origins", + "profile", + "roles", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + } + ], + "clientScopes": [ + { + "id": "0436adc3-a1ae-408d-b200-030e00c5bc07", + "name": "profile", + "description": "OpenID Connect built-in scope: profile", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "true", + "consent.screen.text": "${profileScopeConsentText}" + }, + "protocolMappers": [ + { + "id": "3f20c76c-6215-4344-888e-b7d61b69af70", + "name": "given name", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "firstName", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "given_name", + "jsonType.label": "String" + } + }, + { + "id": "44cbf50c-ca6f-46a6-b95a-8c29e420e9fe", + "name": "username", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "username", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "preferred_username", + "jsonType.label": "String" + } + }, + { + "id": "d1f0a11c-8bd2-4d8d-8c1b-4ba40bca9d51", + "name": "updated at", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "updatedAt", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "updated_at", + "jsonType.label": "String" + } + }, + { + "id": "727d0335-3f41-412b-a62e-5858a78b58d0", + "name": "family name", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "lastName", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "family_name", + "jsonType.label": "String" + } + }, + { + "id": "265a8134-bce4-41f8-ab50-e1b08ec12ecf", + "name": "website", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "website", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "website", + "jsonType.label": "String" + } + }, + { + "id": "1798cf58-6302-4f50-bb43-db0754d3a7c8", + "name": "nickname", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "nickname", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "nickname", + "jsonType.label": "String" + } + }, + { + "id": "84535028-529d-48a5-b517-38c3d2083776", + "name": "profile", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "profile", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "profile", + "jsonType.label": "String" + } + }, + { + "id": "805aa9fb-d305-4a58-b114-ab1a51518f18", + "name": "birthdate", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "birthdate", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "birthdate", + "jsonType.label": "String" + } + }, + { + "id": "47b4b43f-aa19-4352-9e84-f02b64377b27", + "name": "middle name", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "middleName", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "middle_name", + "jsonType.label": "String" + } + }, + { + "id": "a9f17bbe-56cc-4208-a971-b0bce597ec67", + "name": "zoneinfo", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "zoneinfo", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "zoneinfo", + "jsonType.label": "String" + } + }, + { + "id": "b302846b-f739-4566-8092-32436b1fd3b5", + "name": "gender", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "gender", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "gender", + "jsonType.label": "String" + } + }, + { + "id": "e9286ba1-25ba-4ed6-bf0d-3382c7e54133", + "name": "locale", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "locale", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "locale", + "jsonType.label": "String" + } + }, + { + "id": "e88f9947-2012-4171-ba79-2a7cfb071e72", + "name": "full name", + "protocol": "openid-connect", + "protocolMapper": "oidc-full-name-mapper", + "consentRequired": false, + "config": { + "id.token.claim": "true", + "access.token.claim": "true", + "userinfo.token.claim": "true" + } + }, + { + "id": "22d76b1d-528b-4eee-8873-5e0cd8a9f156", + "name": "picture", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "picture", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "picture", + "jsonType.label": "String" + } + } + ] + }, + { + "id": "04c470b8-0ab4-4f70-8fe0-2f02fe4d6dfb", + "name": "role_list", + "description": "SAML role list", + "protocol": "saml", + "attributes": { + "consent.screen.text": "${samlRoleListScopeConsentText}", + "display.on.consent.screen": "true" + }, + "protocolMappers": [ + { + "id": "1bd9c2fb-3a76-49f8-9511-b71aa728c9a5", + "name": "role list", + "protocol": "saml", + "protocolMapper": "saml-role-list-mapper", + "consentRequired": false, + "config": { + "single": "false", + "attribute.nameformat": "Basic", + "attribute.name": "Role" + } + } + ] + }, + { + "id": "6b479a64-0127-46be-9e34-46eaad12a49b", + "name": "address", + "description": "OpenID Connect built-in scope: address", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "true", + "consent.screen.text": "${addressScopeConsentText}" + }, + "protocolMappers": [ + { + "id": "64a1555f-ac9f-4fad-aa20-b63976b7c8e8", + "name": "address", + "protocol": "openid-connect", + "protocolMapper": "oidc-address-mapper", + "consentRequired": false, + "config": { + "user.attribute.formatted": "formatted", + "user.attribute.country": "country", + "user.attribute.postal_code": "postal_code", + "userinfo.token.claim": "true", + "user.attribute.street": "street", + "id.token.claim": "true", + "user.attribute.region": "region", + "access.token.claim": "true", + "user.attribute.locality": "locality" + } + } + ] + }, + { + "id": "d48dfbcb-cfb6-436b-84cd-e2d03363a6fc", + "name": "web-origins", + "description": "OpenID Connect scope for add allowed web origins to the access token", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "false", + "display.on.consent.screen": "false", + "consent.screen.text": "" + }, + "protocolMappers": [ + { + "id": "f6c3e4fc-2039-4443-819d-abd4ec458e98", + "name": "allowed web origins", + "protocol": "openid-connect", + "protocolMapper": "oidc-allowed-origins-mapper", + "consentRequired": false, + "config": {} + } + ] + }, + { + "id": "3b8b9e0c-3afc-415a-b550-91780ec44af1", + "name": "offline_access", + "description": "OpenID Connect built-in scope: offline_access", + "protocol": "openid-connect", + "attributes": { + "consent.screen.text": "${offlineAccessScopeConsentText}", + "display.on.consent.screen": "true" + } + }, + { + "id": "198ce177-b1b6-4364-9380-cc4d4f64615b", + "name": "roles", + "description": "OpenID Connect scope for add user roles to the access token", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "false", + "display.on.consent.screen": "true", + "consent.screen.text": "${rolesScopeConsentText}" + }, + "protocolMappers": [ + { + "id": "b6c011b9-e953-4a2e-90f1-fd96a2ab6827", + "name": "audience resolve", + "protocol": "openid-connect", + "protocolMapper": "oidc-audience-resolve-mapper", + "consentRequired": false, + "config": {} + }, + { + "id": "12c25a87-7670-4f59-913c-39c69ef9f306", + "name": "realm roles", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-realm-role-mapper", + "consentRequired": false, + "config": { + "user.attribute": "foo", + "access.token.claim": "true", + "claim.name": "realm_access.roles", + "jsonType.label": "String", + "multivalued": "true" + } + }, + { + "id": "47e27457-e550-4df8-b49d-18e0e898f836", + "name": "client roles", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-client-role-mapper", + "consentRequired": false, + "config": { + "user.attribute": "foo", + "access.token.claim": "true", + "claim.name": "resource_access.${client_id}.roles", + "jsonType.label": "String", + "multivalued": "true" + } + } + ] + }, + { + "id": "fafef84a-3bb8-49e7-83f2-b64208cf43ac", + "name": "phone", + "description": "OpenID Connect built-in scope: phone", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "true", + "consent.screen.text": "${phoneScopeConsentText}" + }, + "protocolMappers": [ + { + "id": "57c538c1-2544-4302-b040-38722c73335e", + "name": "phone number verified", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "phoneNumberVerified", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "phone_number_verified", + "jsonType.label": "boolean" + } + }, + { + "id": "a0bf0b01-a914-4fd6-a2bf-af5b12ddf231", + "name": "phone number", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "phoneNumber", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "phone_number", + "jsonType.label": "String" + } + } + ] + }, + { + "id": "2cbb508b-e085-4ff3-a4d5-394fcd16a6ef", + "name": "microprofile-jwt", + "description": "Microprofile - JWT built-in scope", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "false" + }, + "protocolMappers": [ + { + "id": "aaf8fa54-2467-4b16-888c-5f41b0cc87b5", + "name": "groups", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-realm-role-mapper", + "consentRequired": false, + "config": { + "multivalued": "true", + "user.attribute": "foo", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "groups", + "jsonType.label": "String" + } + }, + { + "id": "37ea9623-505e-4e84-afd4-d2397f986f4f", + "name": "upn", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "username", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "upn", + "jsonType.label": "String" + } + } + ] + }, + { + "id": "57f57cc2-ac22-4ecf-aa2f-05c5fbee7061", + "name": "email", + "description": "OpenID Connect built-in scope: email", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "true", + "consent.screen.text": "${emailScopeConsentText}" + }, + "protocolMappers": [ + { + "id": "e8f29966-d46a-43e6-a845-69402f621183", + "name": "email verified", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "emailVerified", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "email_verified", + "jsonType.label": "boolean" + } + }, + { + "id": "dd38103c-ed2b-4845-a12c-42d261fb90e4", + "name": "email", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "email", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "email", + "jsonType.label": "String" + } + } + ] + } + ], + "defaultDefaultClientScopes": [ + "role_list", + "profile", + "email", + "roles", + "web-origins" + ], + "defaultOptionalClientScopes": [ + "offline_access", + "address", + "phone", + "microprofile-jwt" + ], + "browserSecurityHeaders": { + "contentSecurityPolicyReportOnly": "", + "xContentTypeOptions": "nosniff", + "xRobotsTag": "none", + "xFrameOptions": "SAMEORIGIN", + "contentSecurityPolicy": "frame-src 'self'; frame-ancestors 'self'; object-src 'none';", + "xXSSProtection": "1; mode=block", + "strictTransportSecurity": "max-age=31536000; includeSubDomains" + }, + "smtpServer": {}, + "eventsEnabled": false, + "eventsListeners": [ + "jboss-logging" + ], + "enabledEventTypes": [], + "adminEventsEnabled": false, + "adminEventsDetailsEnabled": false, + "identityProviders": [ + { + "alias": "steam", + "internalId": "4371eef8-d3d8-42fa-aa7a-1b08052bb007", + "providerId": "oidc", + "enabled": true, + "updateProfileFirstLoginMode": "on", + "trustEmail": false, + "storeToken": false, + "addReadTokenRoleOnCreate": false, + "authenticateByDefault": false, + "linkOnly": false, + "firstBrokerLoginFlowAlias": "first broker login", + "config": { + "validateSignature": "true", + "userInfoUrl": "https://dev.local/steam/connect/userinfo", + "tokenUrl": "https://dev.local/steam/connect/token", + "clientId": "keycloak", + "jwksUrl": "https://dev.local/steam/.well-known/openid-configuration/jwks", + "issuer": "https://dev.local/steam", + "useJwksUrl": "true", + "authorizationUrl": "https://dev.local/steam/connect/authorize", + "clientAuthMethod": "client_secret_basic", + "logoutUrl": "https://dev.local/steam/connect/endsession", + "syncMode": "FORCE", + "clientSecret": "**********", + "allowedClockSkew": "10", + "defaultScope": "openid profile" + } + } + ], + "identityProviderMappers": [ + { + "id": "5bb308fe-c9a9-4565-8851-614628cac5b1", + "name": "picture", + "identityProviderAlias": "steam", + "identityProviderMapper": "oidc-user-attribute-idp-mapper", + "config": { + "syncMode": "INHERIT", + "claim": "picture", + "user.attribute": "picture" + } + }, + { + "id": "cfee9d15-0c9b-49b1-9d6a-a3118715be97", + "name": "website", + "identityProviderAlias": "steam", + "identityProviderMapper": "oidc-user-attribute-idp-mapper", + "config": { + "syncMode": "INHERIT", + "claim": "website", + "user.attribute": "website" + } + } + ], + "components": { + "org.keycloak.services.clientregistration.policy.ClientRegistrationPolicy": [ + { + "id": "cf4793d9-960b-4c1a-82cd-1456b09ffebe", + "name": "Max Clients Limit", + "providerId": "max-clients", + "subType": "anonymous", + "subComponents": {}, + "config": { + "max-clients": [ + "200" + ] + } + }, + { + "id": "0ea56136-cc82-42c0-9cfc-751e63aaecee", + "name": "Allowed Protocol Mapper Types", + "providerId": "allowed-protocol-mappers", + "subType": "anonymous", + "subComponents": {}, + "config": { + "allowed-protocol-mapper-types": [ + "saml-user-property-mapper", + "oidc-address-mapper", + "oidc-usermodel-property-mapper", + "saml-role-list-mapper", + "oidc-sha256-pairwise-sub-mapper", + "saml-user-attribute-mapper", + "oidc-usermodel-attribute-mapper", + "oidc-full-name-mapper" + ] + } + }, + { + "id": "cbd298fe-6251-47d5-83df-7346912a5aad", + "name": "Allowed Client Scopes", + "providerId": "allowed-client-templates", + "subType": "authenticated", + "subComponents": {}, + "config": { + "allow-default-scopes": [ + "true" + ] + } + }, + { + "id": "ff5c15ec-f38c-405f-aefe-1bae8a0352e1", + "name": "Allowed Client Scopes", + "providerId": "allowed-client-templates", + "subType": "anonymous", + "subComponents": {}, + "config": { + "allow-default-scopes": [ + "true" + ] + } + }, + { + "id": "e45c806d-6794-4507-acb0-316b45f17901", + "name": "Allowed Protocol Mapper Types", + "providerId": "allowed-protocol-mappers", + "subType": "authenticated", + "subComponents": {}, + "config": { + "allowed-protocol-mapper-types": [ + "oidc-full-name-mapper", + "oidc-address-mapper", + "saml-user-property-mapper", + "oidc-usermodel-attribute-mapper", + "saml-user-attribute-mapper", + "oidc-sha256-pairwise-sub-mapper", + "oidc-usermodel-property-mapper", + "saml-role-list-mapper" + ] + } + }, + { + "id": "a8deab80-df62-4e17-913b-6767624275bd", + "name": "Consent Required", + "providerId": "consent-required", + "subType": "anonymous", + "subComponents": {}, + "config": {} + }, + { + "id": "3b0c05c2-5f1e-4eac-aa00-14d7c8e9c488", + "name": "Trusted Hosts", + "providerId": "trusted-hosts", + "subType": "anonymous", + "subComponents": {}, + "config": { + "host-sending-registration-request-must-match": [ + "true" + ], + "client-uris-must-match": [ + "true" + ] + } + }, + { + "id": "9cc2c69f-40ef-4013-a777-6b58ae85ffa6", + "name": "Full Scope Disabled", + "providerId": "scope", + "subType": "anonymous", + "subComponents": {}, + "config": {} + } + ], + "org.keycloak.keys.KeyProvider": [ + { + "id": "510f8359-39e1-46f4-a844-b868c8b8da93", + "name": "aes-generated", + "providerId": "aes-generated", + "subComponents": {}, + "config": { + "priority": [ + "100" + ] + } + }, + { + "id": "ac2f383b-eb4d-460f-8b0a-f6c5d9cba979", + "name": "hmac-generated", + "providerId": "hmac-generated", + "subComponents": {}, + "config": { + "priority": [ + "100" + ], + "algorithm": [ + "HS256" + ] + } + }, + { + "id": "d27999f5-3645-401e-ad3c-d5f57dd6704c", + "name": "rsa-generated", + "providerId": "rsa-generated", + "subComponents": {}, + "config": { + "priority": [ + "100" + ] + } + } + ] + }, + "internationalizationEnabled": false, + "supportedLocales": [], + "authenticationFlows": [ + { + "id": "332c7de2-7ba1-4cc0-be24-559f32ee51e4", + "alias": "Account verification options", + "description": "Method with which to verity the existing account", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "idp-email-verification", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticatorFlow": true, + "requirement": "ALTERNATIVE", + "priority": 20, + "flowAlias": "Verify Existing Account by Re-authentication", + "userSetupAllowed": false, + "autheticatorFlow": true + } + ] + }, + { + "id": "c9f45b1f-4ab1-42f4-8206-b354d0a42f9d", + "alias": "Authentication Options", + "description": "Authentication options.", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "basic-auth", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "basic-auth-otp", + "authenticatorFlow": false, + "requirement": "DISABLED", + "priority": 20, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "auth-spnego", + "authenticatorFlow": false, + "requirement": "DISABLED", + "priority": 30, + "userSetupAllowed": false, + "autheticatorFlow": false + } + ] + }, + { + "id": "3a37ad54-e016-497f-8dde-9fd91d811ebe", + "alias": "Browser - Conditional OTP", + "description": "Flow to determine if the OTP is required for the authentication", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "conditional-user-configured", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "auth-otp-form", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "userSetupAllowed": false, + "autheticatorFlow": false + } + ] + }, + { + "id": "69c205d1-8648-46fb-a524-38faf5de8355", + "alias": "Direct Grant - Conditional OTP", + "description": "Flow to determine if the OTP is required for the authentication", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "conditional-user-configured", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "direct-grant-validate-otp", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "userSetupAllowed": false, + "autheticatorFlow": false + } + ] + }, + { + "id": "7f0300d2-c749-4fab-8a40-a0f436e9b941", + "alias": "First broker login - Conditional OTP", + "description": "Flow to determine if the OTP is required for the authentication", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "conditional-user-configured", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "auth-otp-form", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "userSetupAllowed": false, + "autheticatorFlow": false + } + ] + }, + { + "id": "c00cca79-b557-4acf-87e6-f888537e8003", + "alias": "Handle Existing Account", + "description": "Handle what to do if there is existing account with same email/username like authenticated identity provider", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "idp-confirm-link", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticatorFlow": true, + "requirement": "REQUIRED", + "priority": 20, + "flowAlias": "Account verification options", + "userSetupAllowed": false, + "autheticatorFlow": true + } + ] + }, + { + "id": "5a97e90c-c3a7-45b3-8ce1-7c4b1f823cc3", + "alias": "Reset - Conditional OTP", + "description": "Flow to determine if the OTP should be reset or not. Set to REQUIRED to force.", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "conditional-user-configured", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "reset-otp", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "userSetupAllowed": false, + "autheticatorFlow": false + } + ] + }, + { + "id": "cdb342f7-93fc-4d04-b0bc-e4a7c6f59cc6", + "alias": "User creation or linking", + "description": "Flow for the existing/non-existing user alternatives", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticatorConfig": "create unique user config", + "authenticator": "idp-create-user-if-unique", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticatorFlow": true, + "requirement": "ALTERNATIVE", + "priority": 20, + "flowAlias": "Handle Existing Account", + "userSetupAllowed": false, + "autheticatorFlow": true + } + ] + }, + { + "id": "a8dcd3bf-72f6-4442-9f37-64d5f5680bde", + "alias": "Verify Existing Account by Re-authentication", + "description": "Reauthentication of existing account", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "idp-username-password-form", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticatorFlow": true, + "requirement": "CONDITIONAL", + "priority": 20, + "flowAlias": "First broker login - Conditional OTP", + "userSetupAllowed": false, + "autheticatorFlow": true + } + ] + }, + { + "id": "973ef407-d084-4675-968f-3244a4f214f7", + "alias": "browser", + "description": "browser based authentication", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "auth-cookie", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "auth-spnego", + "authenticatorFlow": false, + "requirement": "DISABLED", + "priority": 20, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "identity-provider-redirector", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 25, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticatorFlow": true, + "requirement": "ALTERNATIVE", + "priority": 30, + "flowAlias": "forms", + "userSetupAllowed": false, + "autheticatorFlow": true + } + ] + }, + { + "id": "50ff1818-d89a-419d-a1fc-f8f8d2549572", + "alias": "clients", + "description": "Base authentication for clients", + "providerId": "client-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "client-secret", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "client-jwt", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 20, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "client-secret-jwt", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 30, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "client-x509", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 40, + "userSetupAllowed": false, + "autheticatorFlow": false + } + ] + }, + { + "id": "9cfca69a-1d2f-4dc6-a989-4131e8fdc20f", + "alias": "direct grant", + "description": "OpenID Connect Resource Owner Grant", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "direct-grant-validate-username", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "direct-grant-validate-password", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticatorFlow": true, + "requirement": "CONDITIONAL", + "priority": 30, + "flowAlias": "Direct Grant - Conditional OTP", + "userSetupAllowed": false, + "autheticatorFlow": true + } + ] + }, + { + "id": "fbef5afc-bd23-4810-ad18-1b5f07349b49", + "alias": "docker auth", + "description": "Used by Docker clients to authenticate against the IDP", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "docker-http-basic-authenticator", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + } + ] + }, + { + "id": "e47ec7d5-3a66-4d85-9f18-d882b3ff12b4", + "alias": "first broker login", + "description": "Actions taken after first broker login with identity provider account, which is not yet linked to any Keycloak account", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticatorConfig": "review profile config", + "authenticator": "idp-review-profile", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticatorFlow": true, + "requirement": "REQUIRED", + "priority": 20, + "flowAlias": "User creation or linking", + "userSetupAllowed": false, + "autheticatorFlow": true + } + ] + }, + { + "id": "da238f3d-11c7-48bf-8fb5-49313b932c9b", + "alias": "forms", + "description": "Username, password, otp and other auth forms.", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "auth-username-password-form", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticatorFlow": true, + "requirement": "CONDITIONAL", + "priority": 20, + "flowAlias": "Browser - Conditional OTP", + "userSetupAllowed": false, + "autheticatorFlow": true + } + ] + }, + { + "id": "1e088135-b96f-4f4f-a4ec-557c74b1850e", + "alias": "http challenge", + "description": "An authentication flow based on challenge-response HTTP Authentication Schemes", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "no-cookie-redirect", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticatorFlow": true, + "requirement": "REQUIRED", + "priority": 20, + "flowAlias": "Authentication Options", + "userSetupAllowed": false, + "autheticatorFlow": true + } + ] + }, + { + "id": "8e7ea565-adf6-46ac-811e-ba4aa9cc7e96", + "alias": "registration", + "description": "registration flow", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "registration-page-form", + "authenticatorFlow": true, + "requirement": "REQUIRED", + "priority": 10, + "flowAlias": "registration form", + "userSetupAllowed": false, + "autheticatorFlow": true + } + ] + }, + { + "id": "cb590839-546a-4bab-953d-347b1416af36", + "alias": "registration form", + "description": "registration form", + "providerId": "form-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "registration-user-creation", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "registration-profile-action", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 40, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "registration-password-action", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 50, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "registration-recaptcha-action", + "authenticatorFlow": false, + "requirement": "DISABLED", + "priority": 60, + "userSetupAllowed": false, + "autheticatorFlow": false + } + ] + }, + { + "id": "df52563c-689f-4148-82a0-18d0b30af265", + "alias": "reset credentials", + "description": "Reset credentials for a user if they forgot their password or something", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "reset-credentials-choose-user", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "reset-credential-email", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "reset-password", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 30, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticatorFlow": true, + "requirement": "CONDITIONAL", + "priority": 40, + "flowAlias": "Reset - Conditional OTP", + "userSetupAllowed": false, + "autheticatorFlow": true + } + ] + }, + { + "id": "2a7a7244-047b-447b-a6e6-0a9812ab95cc", + "alias": "saml ecp", + "description": "SAML ECP Profile Authentication Flow", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "http-basic-authenticator", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + } + ] + } + ], + "authenticatorConfig": [ + { + "id": "747e1ea6-8438-43a7-9c21-ca5b3448787b", + "alias": "create unique user config", + "config": { + "require.password.update.after.registration": "false" + } + }, + { + "id": "74ab3ebf-67d4-4337-89d7-a17c9bbae187", + "alias": "review profile config", + "config": { + "update.profile.on.first.login": "missing" + } + } + ], + "requiredActions": [ + { + "alias": "CONFIGURE_TOTP", + "name": "Configure OTP", + "providerId": "CONFIGURE_TOTP", + "enabled": true, + "defaultAction": false, + "priority": 10, + "config": {} + }, + { + "alias": "terms_and_conditions", + "name": "Terms and Conditions", + "providerId": "terms_and_conditions", + "enabled": false, + "defaultAction": false, + "priority": 20, + "config": {} + }, + { + "alias": "UPDATE_PASSWORD", + "name": "Update Password", + "providerId": "UPDATE_PASSWORD", + "enabled": true, + "defaultAction": false, + "priority": 30, + "config": {} + }, + { + "alias": "UPDATE_PROFILE", + "name": "Update Profile", + "providerId": "UPDATE_PROFILE", + "enabled": true, + "defaultAction": false, + "priority": 40, + "config": {} + }, + { + "alias": "VERIFY_EMAIL", + "name": "Verify Email", + "providerId": "VERIFY_EMAIL", + "enabled": true, + "defaultAction": false, + "priority": 50, + "config": {} + }, + { + "alias": "delete_account", + "name": "Delete Account", + "providerId": "delete_account", + "enabled": false, + "defaultAction": false, + "priority": 60, + "config": {} + }, + { + "alias": "update_user_locale", + "name": "Update User Locale", + "providerId": "update_user_locale", + "enabled": true, + "defaultAction": false, + "priority": 1000, + "config": {} + } + ], + "browserFlow": "browser", + "registrationFlow": "registration", + "directGrantFlow": "direct grant", + "resetCredentialsFlow": "reset credentials", + "clientAuthenticationFlow": "clients", + "dockerAuthenticationFlow": "docker auth", + "attributes": { + "cibaBackchannelTokenDeliveryMode": "poll", + "cibaExpiresIn": "120", + "cibaAuthRequestedUserHint": "login_hint", + "oauth2DeviceCodeLifespan": "600", + "oauth2DevicePollingInterval": "5", + "cibaInterval": "5" + }, + "keycloakVersion": "13.0.0", + "userManagedAccessAllowed": false +} \ No newline at end of file diff --git a/src/Controllers/ExternalLoginController.cs b/src/Controllers/ExternalLoginController.cs index b97747f..5aa49df 100644 --- a/src/Controllers/ExternalLoginController.cs +++ b/src/Controllers/ExternalLoginController.cs @@ -2,33 +2,47 @@ using System.Linq; using System.Security.Claims; using System.Threading.Tasks; +using IdentityServer4.Events; +using IdentityServer4.Extensions; +using IdentityServer4.Services; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using SteamOpenIdConnectProvider.Domains.IdentityServer; namespace SteamOpenIdConnectProvider.Controllers { [AllowAnonymous] - [Route("[action]")] public class ExternalLoginController : Controller { private readonly SignInManager _signInManager; private readonly UserManager _userManager; + private readonly IIdentityServerInteractionService _interaction; + private readonly IEventService _events; + + private readonly OpenIdConfig _config; private readonly ILogger _logger; public ExternalLoginController( SignInManager signInManager, UserManager userManager, + IIdentityServerInteractionService interaction, + IEventService events, + IOptions config, ILogger logger) { _signInManager = signInManager; _userManager = userManager; + _config = config.Value; _logger = logger; + _interaction = interaction; + _events = events; } - [HttpGet] + [HttpGet("external-login")] public Task ExternalLogin(string returnUrl = null) { const string provider = "Steam"; @@ -38,7 +52,7 @@ namespace SteamOpenIdConnectProvider.Controllers return Task.FromResult(new ChallengeResult(provider, properties)); } - [HttpGet] + [HttpGet("external-login-callback")] public async Task ExternalLoginCallback(string returnUrl = null, string remoteError = null) { returnUrl ??= Url.Content("~/"); @@ -97,5 +111,21 @@ namespace SteamOpenIdConnectProvider.Controllers return BadRequest(); } + + [HttpGet("external-logout")] + public async Task ExternalLogout(string logoutId) + { + var logout = await _interaction.GetLogoutContextAsync(logoutId); + + if (User?.Identity.IsAuthenticated == true) + { + await _signInManager.SignOutAsync(); + await _events.RaiseAsync(new UserLogoutSuccessEvent(User.GetSubjectId(), User.GetDisplayName())); + } + + return Redirect(logout?.PostLogoutRedirectUri ?? + _config.PostLogoutRedirectUris.FirstOrDefault() ?? + Url.Content("~/")); + } } } diff --git a/src/Domains/IdentityServer/IdentityServerConfigFactory.cs b/src/Domains/IdentityServer/IdentityServerConfigFactory.cs index 8a95f5f..3714254 100644 --- a/src/Domains/IdentityServer/IdentityServerConfigFactory.cs +++ b/src/Domains/IdentityServer/IdentityServerConfigFactory.cs @@ -11,7 +11,7 @@ namespace SteamOpenIdConnectProvider.Models.IdentityServer { public static IEnumerable GetClients(OpenIdConfig config) { - yield return new Client + var client = new Client { ClientId = config.ClientID, ClientName = config.ClientName, @@ -23,10 +23,11 @@ namespace SteamOpenIdConnectProvider.Models.IdentityServer }, // where to redirect to after login - RedirectUris = config.RedirectUri.Split(",").Select(x => x.Trim()).ToArray(), + RedirectUris = config.RedirectUris.ToArray(), // where to redirect to after logout - PostLogoutRedirectUris = { config.PostLogoutRedirectUri }, + PostLogoutRedirectUris = config.PostLogoutRedirectUris.ToArray(), + RequirePkce = false, AllowedScopes = new List { @@ -34,6 +35,7 @@ namespace SteamOpenIdConnectProvider.Models.IdentityServer IdentityServerConstants.StandardScopes.Profile, } }; + yield return client; } public static IEnumerable GetIdentityResources() @@ -45,4 +47,4 @@ namespace SteamOpenIdConnectProvider.Models.IdentityServer }; } } -} \ No newline at end of file +} diff --git a/src/Domains/IdentityServer/OpenIdConfig.cs b/src/Domains/IdentityServer/OpenIdConfig.cs index 8bf8f2f..f858529 100644 --- a/src/Domains/IdentityServer/OpenIdConfig.cs +++ b/src/Domains/IdentityServer/OpenIdConfig.cs @@ -14,5 +14,13 @@ namespace SteamOpenIdConnectProvider.Domains.IdentityServer public string RedirectUri { get; set; } public string PostLogoutRedirectUri { get; set; } public string ClientName { get; set; } = "Proxy Client"; + + public IEnumerable RedirectUris => (RedirectUri ?? string.Empty).Split( + new[] { ',', ';' }, + StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries); + + public IEnumerable PostLogoutRedirectUris => (PostLogoutRedirectUri ?? string.Empty).Split( + new[] { ',', ';' }, + StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries); } } diff --git a/src/Domains/IdentityServer/OpenIdStandardClaims.cs b/src/Domains/IdentityServer/OpenIdStandardClaims.cs new file mode 100644 index 0000000..f415a5b --- /dev/null +++ b/src/Domains/IdentityServer/OpenIdStandardClaims.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace SteamOpenIdConnectProvider.Domains.IdentityServer +{ + public static class OpenIdStandardClaims + { + public static readonly string Name = "name"; + public static readonly string GivenName = "given_name"; + public static readonly string FamilyName = "family_name"; + public static readonly string MiddleName = "middle_name"; + public static readonly string Nickname = "nickname"; + public static readonly string PreferredUsername = "preferred_username"; + public static readonly string Profile = "profile"; + public static readonly string Picture = "picture"; + public static readonly string Website = "website"; + public static readonly string Email = "email"; + public static readonly string EmailVerified = "email_verified"; + public static readonly string Gender = "gender"; + public static readonly string BirthDate = "BirthDate"; + public static readonly string Zoneinfo = "zoneinfo"; + public static readonly string Locale = "locale"; + public static readonly string PhoneNumber = "phone_number"; + public static readonly string PhoneNumberVerified = "phone_number_verified"; + public static readonly string Address = "address"; + public static readonly string UpdatedAt = "updated_at"; + } +} diff --git a/src/Domains/Steam/SteamProfileService.cs b/src/Domains/Steam/SteamProfileService.cs index 9592945..64650c0 100644 --- a/src/Domains/Steam/SteamProfileService.cs +++ b/src/Domains/Steam/SteamProfileService.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; using System.Net.Http; using System.Security.Claims; @@ -9,6 +9,7 @@ using IdentityServer4.Models; using IdentityServer4.Services; using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.Options; +using SteamOpenIdConnectProvider.Domains.IdentityServer; using SteamOpenIdConnectProvider.Domains.Steam; using SteamOpenIdConnectProvider.Models.Steam; @@ -49,10 +50,11 @@ namespace SteamOpenIdConnectProvider.Services if (player != null) { - AddClaim(claims, "picture", player.AvatarFull); - AddClaim(claims, "nickname", player.PersonaName); - AddClaim(claims, "given_name", player.RealName); - AddClaim(claims, "website", player.ProfileUrl); + AddClaim(claims, OpenIdStandardClaims.Picture, player.AvatarFull); + AddClaim(claims, OpenIdStandardClaims.Nickname, player.PersonaName); + AddClaim(claims, OpenIdStandardClaims.PreferredUsername, player.PersonaName); + AddClaim(claims, OpenIdStandardClaims.GivenName, player.RealName); + AddClaim(claims, OpenIdStandardClaims.Website, player.ProfileUrl); } context.IssuedClaims = claims; @@ -81,4 +83,4 @@ namespace SteamOpenIdConnectProvider.Services return response.Response; } } -} \ No newline at end of file +} diff --git a/src/Startup.cs b/src/Startup.cs index 290c599..c115356 100644 --- a/src/Startup.cs +++ b/src/Startup.cs @@ -46,20 +46,23 @@ namespace SteamOpenIdConnectProvider .AddEntityFrameworkStores() .AddDefaultTokenProviders(); - var openIdConfig = Configuration.GetSection(OpenIdConfig.Key).Get(); - services.AddIdentityServer(options => + var openIdConfig = Configuration.GetSection(OpenIdConfig.Key); + services + .Configure(openIdConfig) + .AddIdentityServer(options => { - options.UserInteraction.LoginUrl = "/ExternalLogin"; + options.UserInteraction.LoginUrl = "/external-login"; + options.UserInteraction.LogoutUrl = "/external-logout"; }) .AddAspNetIdentity() - .AddInMemoryClients(IdentityServerConfigFactory.GetClients(openIdConfig)) + .AddInMemoryClients(IdentityServerConfigFactory.GetClients(openIdConfig.Get())) .AddInMemoryPersistedGrants() .AddDeveloperSigningCredential(true) .AddInMemoryIdentityResources(IdentityServerConfigFactory.GetIdentityResources()); - var steamConfig = Configuration.GetSection(SteamConfig.Key).Get(); + var steamConfig = Configuration.GetSection(SteamConfig.Key); services - .Configure(Configuration.GetSection(SteamConfig.Key)) + .Configure(steamConfig) .AddHttpClient(); services.AddAuthentication() @@ -70,7 +73,7 @@ namespace SteamOpenIdConnectProvider }) .AddSteam(options => { - options.ApplicationKey = steamConfig.ApplicationKey; + options.ApplicationKey = steamConfig.Get().ApplicationKey; }); services.AddHealthChecks() diff --git a/src/appsettings.json b/src/appsettings.json index d6ba347..f334f2f 100644 --- a/src/appsettings.json +++ b/src/appsettings.json @@ -5,7 +5,9 @@ "ClientSecret": "keycloak", "ClientName": "keycloak", "RedirectUri": "https://dev.local/auth/realms/dev/broker/steam/endpoint", - "PostLogoutRedirectUri": "" + + // TODO: Don't think this is how it suppose to work. + "PostLogoutRedirectUri": "https://dev.local/auth/realms/dev/protocol/openid-connect/logout?initiating_idp=steam" }, "Hosting": { "BasePath": "" From 8470ccfea712c63de17caaf12867281c95dddd95 Mon Sep 17 00:00:00 2001 From: Mark Ettema Date: Mon, 24 May 2021 10:05:01 +0700 Subject: [PATCH 19/26] Create docker-image.yml --- .github/workflows/docker-image.yml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 .github/workflows/docker-image.yml diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml new file mode 100644 index 0000000..7f1ea7f --- /dev/null +++ b/.github/workflows/docker-image.yml @@ -0,0 +1,18 @@ +name: Docker Image CI + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: Build the Docker image + run: docker build src/ --file src/Dockerfile --tag https://ghcr.io/neothor/steam-openid-connect-provider:$(date +%s) From a5988b792a5adb82143347eab56984efa2ea963c Mon Sep 17 00:00:00 2001 From: Mark Ettema Date: Mon, 24 May 2021 10:06:40 +0700 Subject: [PATCH 20/26] Encapsulate tag --- .github/workflows/docker-image.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml index 7f1ea7f..336caf0 100644 --- a/.github/workflows/docker-image.yml +++ b/.github/workflows/docker-image.yml @@ -15,4 +15,4 @@ jobs: steps: - uses: actions/checkout@v2 - name: Build the Docker image - run: docker build src/ --file src/Dockerfile --tag https://ghcr.io/neothor/steam-openid-connect-provider:$(date +%s) + run: docker build src/ --file src/Dockerfile --tag "https://ghcr.io/neothor/steam-openid-connect-provider:$(date +%s)" From d6fd51cb560612620b7c938983ca1f85ce53f98d Mon Sep 17 00:00:00 2001 From: Mark Ettema Date: Mon, 24 May 2021 10:07:38 +0700 Subject: [PATCH 21/26] No proto --- .github/workflows/docker-image.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml index 336caf0..81d25a6 100644 --- a/.github/workflows/docker-image.yml +++ b/.github/workflows/docker-image.yml @@ -15,4 +15,4 @@ jobs: steps: - uses: actions/checkout@v2 - name: Build the Docker image - run: docker build src/ --file src/Dockerfile --tag "https://ghcr.io/neothor/steam-openid-connect-provider:$(date +%s)" + run: docker build src/ --file src/Dockerfile --tag "ghcr.io/neothor/steam-openid-connect-provider:$(date +%s)" From a3f58dc59b1dc4f7ce8e946c62e21e0d4ad7b5a9 Mon Sep 17 00:00:00 2001 From: Mark Ettema Date: Mon, 24 May 2021 10:16:53 +0700 Subject: [PATCH 22/26] =?UTF-8?q?Login=20into=20GitHub=20and=20push=20?= =?UTF-8?q?=F0=9F=A4=9E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/docker-image.yml | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml index 81d25a6..7a12067 100644 --- a/.github/workflows/docker-image.yml +++ b/.github/workflows/docker-image.yml @@ -13,6 +13,22 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - name: Build the Docker image - run: docker build src/ --file src/Dockerfile --tag "ghcr.io/neothor/steam-openid-connect-provider:$(date +%s)" + - + name: Checkout + uses: actions/checkout@v2 + + - + name: Login to GitHub Container Registry + uses: docker/login-action@v1 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + + - + name: Build and push + uses: docker/build-push-action@v2 + with: + context: src/ + push: true + tags: ghcr.io/neothor/steam-openid-connect-provider:$(date +%s) From 01bf36c992aea8fee26395fa31b57a05661dcb94 Mon Sep 17 00:00:00 2001 From: Mark Ettema Date: Mon, 24 May 2021 10:20:29 +0700 Subject: [PATCH 23/26] Create tags --- .github/workflows/docker-image.yml | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml index 7a12067..3e3a97d 100644 --- a/.github/workflows/docker-image.yml +++ b/.github/workflows/docker-image.yml @@ -25,10 +25,27 @@ jobs: username: ${{ github.repository_owner }} password: ${{ secrets.GITHUB_TOKEN }} + - + name: Docker meta + id: meta + uses: docker/metadata-action@v3 + with: + images: | + ghcr.io/neothor/steam-openid-connect-provider + tags: | + type=schedule + type=ref,event=branch + type=ref,event=pr + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + type=semver,pattern={{major}} + type=sha + - name: Build and push uses: docker/build-push-action@v2 with: context: src/ - push: true - tags: ghcr.io/neothor/steam-openid-connect-provider:$(date +%s) + push: ${{ github.event_name != 'pull_request' }} + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} From fdf472f13d2701fbf75c59200fc76699ac68d647 Mon Sep 17 00:00:00 2001 From: Mark Ettema Date: Mon, 24 May 2021 10:38:53 +0700 Subject: [PATCH 24/26] Push on new tags --- .github/workflows/docker-image.yml | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml index 3e3a97d..9bf8575 100644 --- a/.github/workflows/docker-image.yml +++ b/.github/workflows/docker-image.yml @@ -2,16 +2,18 @@ name: Docker Image CI on: push: - branches: [ master ] + branches: + - master + tags: + - '*' + pull_request: - branches: [ master ] - + branches: + - master + jobs: - build: - runs-on: ubuntu-latest - steps: - name: Checkout From 75c7bcb915101b8876433a5a58b42b497a16bad9 Mon Sep 17 00:00:00 2001 From: Mark Ettema Date: Sun, 11 Jul 2021 12:44:57 +0700 Subject: [PATCH 25/26] Added curl --- src/Dockerfile | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Dockerfile b/src/Dockerfile index 3842055..dce0b07 100644 --- a/src/Dockerfile +++ b/src/Dockerfile @@ -14,6 +14,10 @@ FROM build AS publish RUN dotnet publish ./SteamOpenIdConnectProvider.csproj -c Release -o /app FROM mcr.microsoft.com/dotnet/aspnet:5.0 + +RUN apt-get update && \ + apt-get install -y curl + WORKDIR /app COPY --from=publish /app . EXPOSE 80 From 5e62ec372841af6c6630669f0007f3d1fa9946ac Mon Sep 17 00:00:00 2001 From: Mark Ettema Date: Sun, 17 Oct 2021 16:31:28 +0700 Subject: [PATCH 26/26] Adding some logging Added additional claims --- src/Domains/IdentityServer/SteamClaims.cs | 7 ++++ src/Domains/Steam/SteamProfileService.cs | 16 ++++++++ src/Program.cs | 47 ++++++++++++++++------- src/Startup.cs | 4 ++ src/SteamOpenIdConnectProvider.csproj | 2 +- 5 files changed, 61 insertions(+), 15 deletions(-) create mode 100644 src/Domains/IdentityServer/SteamClaims.cs diff --git a/src/Domains/IdentityServer/SteamClaims.cs b/src/Domains/IdentityServer/SteamClaims.cs new file mode 100644 index 0000000..e557d46 --- /dev/null +++ b/src/Domains/IdentityServer/SteamClaims.cs @@ -0,0 +1,7 @@ +namespace SteamOpenIdConnectProvider.Domains.IdentityServer +{ + public static class SteamClaims + { + public static readonly string SteamId = "steam_id"; + } +} diff --git a/src/Domains/Steam/SteamProfileService.cs b/src/Domains/Steam/SteamProfileService.cs index 64650c0..a2f9465 100644 --- a/src/Domains/Steam/SteamProfileService.cs +++ b/src/Domains/Steam/SteamProfileService.cs @@ -8,6 +8,7 @@ using IdentityServer4.Extensions; using IdentityServer4.Models; using IdentityServer4.Services; using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using SteamOpenIdConnectProvider.Domains.IdentityServer; using SteamOpenIdConnectProvider.Domains.Steam; @@ -20,16 +21,19 @@ namespace SteamOpenIdConnectProvider.Services private readonly HttpClient _httpClient; private readonly SteamConfig _config; private readonly IUserClaimsPrincipalFactory _claimsFactory; + private readonly ILogger _logger; private readonly UserManager _userManager; public SteamProfileService( UserManager userManager, IUserClaimsPrincipalFactory claimsFactory, IOptions config, + ILogger logger, HttpClient httpClient) { _userManager = userManager; _claimsFactory = claimsFactory; + _logger = logger; _config = config.Value; _httpClient = httpClient; } @@ -44,6 +48,7 @@ namespace SteamOpenIdConnectProvider.Services claims = claims.Where(claim => context.RequestedClaimTypes.Contains(claim.Type)).ToList(); var steamId = sub.Substring(Constants.OpenIdUrl.Length); + AddClaim(claims, SteamClaims.SteamId, steamId); var userSummary = await GetPlayerSummariesAsync(new[] { steamId }); var player = userSummary.Players.FirstOrDefault(); @@ -57,6 +62,17 @@ namespace SteamOpenIdConnectProvider.Services AddClaim(claims, OpenIdStandardClaims.Website, player.ProfileUrl); } + if (_logger.IsEnabled(LogLevel.Debug)) + { + foreach (var claim in claims) + { + _logger.LogDebug("Issued claim {claim}:{value} for {principle}", + claim.Type, + claim.Value, + principal.Identity.Name); + } + } + context.IssuedClaims = claims; } diff --git a/src/Program.cs b/src/Program.cs index ed6d99a..c7cbf68 100644 --- a/src/Program.cs +++ b/src/Program.cs @@ -1,5 +1,7 @@ -using Microsoft.AspNetCore; +using System; +using Microsoft.AspNetCore; using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Hosting; using Serilog; using Serilog.Events; using Serilog.Sinks.SystemConsole.Themes; @@ -8,25 +10,42 @@ namespace SteamOpenIdConnectProvider { public class Program { - public static void Main(string[] args) + public static int Main(string[] args) { Log.Logger = new LoggerConfiguration() - .MinimumLevel.Debug() - .MinimumLevel.Override("Microsoft", LogEventLevel.Warning) - .MinimumLevel.Override("System", LogEventLevel.Warning) - .MinimumLevel.Override("Microsoft.AspNetCore.Authentication", LogEventLevel.Information) - .MinimumLevel.Override("Microsoft.AspNetCore", LogEventLevel.Warning) + .MinimumLevel.Override("Microsoft", LogEventLevel.Information) .Enrich.FromLogContext() .WriteTo.Console() - .CreateLogger(); + .CreateBootstrapLogger(); - CreateWebHostBuilder(args).Build().Run(); + try + { + Log.Information("Starting web host"); + CreateHostBuilder(args).Build().Run(); + return 0; + } + catch (Exception ex) + { + Log.Fatal(ex, "Host terminated unexpectedly"); + return 1; + } + finally + { + Log.CloseAndFlush(); + } } - public static IWebHostBuilder CreateWebHostBuilder(string[] args) => - WebHost.CreateDefaultBuilder(args) - .UseKestrel() - .UseSerilog() - .UseStartup(); + public static IHostBuilder CreateHostBuilder(string[] args) => + Host.CreateDefaultBuilder(args) + .UseSerilog((context, services, configuration) => configuration + .ReadFrom.Configuration(context.Configuration) + .ReadFrom.Services(services) + .MinimumLevel.Is(context.HostingEnvironment.IsDevelopment() ? LogEventLevel.Debug : LogEventLevel.Information) + .Enrich.FromLogContext() + .WriteTo.Console(theme: AnsiConsoleTheme.Code)) + .ConfigureWebHostDefaults(webBuilder => + { + webBuilder.UseStartup(); + }); } } diff --git a/src/Startup.cs b/src/Startup.cs index c115356..b8c16cd 100644 --- a/src/Startup.cs +++ b/src/Startup.cs @@ -19,6 +19,7 @@ using SteamOpenIdConnectProvider.Domains.Steam; using System.IO; using System.Text; using Serilog; +using Microsoft.Extensions.Logging; namespace SteamOpenIdConnectProvider { @@ -82,8 +83,11 @@ namespace SteamOpenIdConnectProvider public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { + var logger = app.ApplicationServices.GetRequiredService>(); + if (env.IsDevelopment()) { + logger.LogWarning("Starting up in development mode"); app.UseDeveloperExceptionPage(); } diff --git a/src/SteamOpenIdConnectProvider.csproj b/src/SteamOpenIdConnectProvider.csproj index 293fe6e..71c43d3 100644 --- a/src/SteamOpenIdConnectProvider.csproj +++ b/src/SteamOpenIdConnectProvider.csproj @@ -19,7 +19,7 @@ - +