diff --git a/README.md b/README.md index a229c9a697..cd2a08d053 100644 --- a/README.md +++ b/README.md @@ -357,14 +357,15 @@ Gateway auth simply allows the users to authenticate with the service using the ```yml Security: OpenIDConnect: - Example: + Example: # Scheme key: Name of this OIDC scheme (public) Authority: "..." # This is the root of the URL containing the "/.well-known/openid-configuration" endpoint ClientId: "..." ClientSecret: "..." - FriendlyName: "Example Provider" # Friendly name is how this provider is displayed in UIs - ThemeColour: "#ff0000" # (Optional) Hex color code indicating the color used to theme UI elements relating to this provider - ThemeIconUrl: "..." # (Optional) Public URL of an image that can be used to theme UI elements relating to this provider - UsernameClaim: "preferred_username" # (Optional) Name of claim used to set TGS usernames upon registration. By default "preferred_username" is used. Only applies when using OidcStrictMode. + FriendlyName: "Example Provider" # Friendly name is how this provider is displayed in UIs (public) + ThemeColour: "#ff0000" # (Optional) Hex color code indicating the color used to theme UI elements relating to this provider (public) + ThemeIconUrl: "..." # (Optional) Public URL of an image that can be used to theme UI elements relating to this provider (public) + UsernameClaim: "preferred_username" # (Optional) Name of claim used to set TGS usernames upon registration. By default "preferred_username" is used. Only applies when using OidcStrictMode. + GroupIdClaim: "tgstation-server-group-id" # (Options) Name of claim used to set TGS group ID upon logging in. By default "tgstation-server-group-id" is used. Only applies when using OidcStrictMode. ``` - `Security:OidcStrictMode`: Boolean flag that, when `true`, disables password and OAuth logins, password changes, individual permission set assignment, and enables user registration using OpenID Connect providers. The claim name `tgstation-server-group-id` is used to dictate what TGS group users are registered to. diff --git a/build/Version.props b/build/Version.props index acc1797a58..86560c36ad 100644 --- a/build/Version.props +++ b/build/Version.props @@ -4,7 +4,7 @@ 6.15.2 - 5.6.0 + 5.7.0 10.13.0 0.6.0 7.0.0 diff --git a/src/Tgstation.Server.Host/Configuration/OidcConfiguration.cs b/src/Tgstation.Server.Host/Configuration/OidcConfiguration.cs index 3a7062bb44..67380f7e5e 100644 --- a/src/Tgstation.Server.Host/Configuration/OidcConfiguration.cs +++ b/src/Tgstation.Server.Host/Configuration/OidcConfiguration.cs @@ -9,6 +9,11 @@ namespace Tgstation.Server.Host.Configuration /// public sealed class OidcConfiguration { + /// + /// The default value of . + /// + private const string DefaultGroupClaimName = "tgstation-server-group-id"; + /// /// The containing the .well-known endpoint for the provider. /// @@ -48,5 +53,10 @@ namespace Tgstation.Server.Host.Configuration /// The name of the claim used to set the user's name. /// public string UsernameClaim { get; set; } = JwtRegisteredClaimNames.PreferredUsername; + + /// + /// Claim name used to set user groups in OIDC strict mode. + /// + public string GroupIdClaim { get; set; } = DefaultGroupClaimName; } } diff --git a/src/Tgstation.Server.Host/Core/Application.cs b/src/Tgstation.Server.Host/Core/Application.cs index e06c0ff949..c4958767ee 100644 --- a/src/Tgstation.Server.Host/Core/Application.cs +++ b/src/Tgstation.Server.Host/Core/Application.cs @@ -916,6 +916,7 @@ namespace Tgstation.Server.Host.Core .ValidateOidcToken( context, configName, + config.GroupIdClaim, context .HttpContext .RequestAborted); @@ -923,7 +924,7 @@ namespace Tgstation.Server.Host.Core if (securityConfiguration.OidcStrictMode) { options.GetClaimsFromUserInfoEndpoint = true; - options.ClaimActions.MapUniqueJsonKey(AuthenticationContextFactory.TgsGroupIdClaimName, AuthenticationContextFactory.TgsGroupIdClaimName); + options.ClaimActions.MapUniqueJsonKey(config.GroupIdClaim, config.GroupIdClaim); options.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters { NameClaimType = config.UsernameClaim, diff --git a/src/Tgstation.Server.Host/Security/AuthenticationContextFactory.cs b/src/Tgstation.Server.Host/Security/AuthenticationContextFactory.cs index bfa742b840..e4b9e71424 100644 --- a/src/Tgstation.Server.Host/Security/AuthenticationContextFactory.cs +++ b/src/Tgstation.Server.Host/Security/AuthenticationContextFactory.cs @@ -32,11 +32,6 @@ namespace Tgstation.Server.Host.Security /// public const string OpenIDConnectAuthenticationSchemePrefix = $"{OpenIdConnectDefaults.AuthenticationScheme}."; - /// - /// Claim name used to set user groups in OIDC strict mode. - /// - public const string TgsGroupIdClaimName = "tgstation-server-group-id"; - /// /// The the created. /// @@ -233,7 +228,7 @@ namespace Tgstation.Server.Host.Security } /// - public async Task ValidateOidcToken(RemoteAuthenticationContext tokenValidatedContext, string schemeKey, CancellationToken cancellationToken) + public async Task ValidateOidcToken(RemoteAuthenticationContext tokenValidatedContext, string schemeKey, string groupIdClaimName, CancellationToken cancellationToken) { ArgumentNullException.ThrowIfNull(tokenValidatedContext); @@ -270,7 +265,7 @@ namespace Tgstation.Server.Host.Security } else { - var groupClaim = principal.FindFirst(TgsGroupIdClaimName); + var groupClaim = principal.FindFirst(groupIdClaimName); long? groupId; if (groupClaim == default) groupId = null; @@ -278,7 +273,7 @@ namespace Tgstation.Server.Host.Security groupId = groupIdParsed; else { - tokenValidatedContext.Fail($"User has non-numeric '{TgsGroupIdClaimName}' claim!"); + tokenValidatedContext.Fail($"User has non-numeric '{groupIdClaimName}' claim!"); return; } @@ -291,7 +286,7 @@ namespace Tgstation.Server.Host.Security .FirstOrDefaultAsync(cancellationToken) : null; - var missingClaimError = $"User missing '{TgsGroupIdClaimName}' claim!"; + var missingClaimError = $"User missing '{groupIdClaimName}' claim!"; if (connection == default) { var username = principal.Identity?.Name; @@ -311,11 +306,13 @@ namespace Tgstation.Server.Host.Security { tokenValidatedContext.Fail( groupId.HasValue - ? $"'{TgsGroupIdClaimName}' does not point to a valid group!" + ? $"'{groupIdClaimName}' does not point to a valid group!" : missingClaimError); return; } + logger.LogInformation("Registering new user '{name}' via OIDC scheme '{scheme}'", username, schemeKey); + var tgsUser = await databaseContext .Users .GetTgsUser( @@ -353,6 +350,7 @@ namespace Tgstation.Server.Host.Security // group update if (group == null) { + logger.LogDebug("User {id} attempted to login via OIDC scheme '{scheme}' but had no group ID claim ('{groupClaimName}') and will be disabled", user.Id, schemeKey, groupIdClaimName); user.PermissionSet = new PermissionSet { AdministrationRights = AdministrationRights.None, @@ -365,6 +363,7 @@ namespace Tgstation.Server.Host.Security return; } + logger.LogDebug("User {id} mapped to group {groupId} via OIDC login on scheme '{scheme}'", user.Id, groupId, schemeKey); user.Group = group; if (user.PermissionSet != null) databaseContext.PermissionSets.Remove(user.PermissionSet); diff --git a/src/Tgstation.Server.Host/Security/ITokenValidator.cs b/src/Tgstation.Server.Host/Security/ITokenValidator.cs index e7d17f14aa..d53acceaa4 100644 --- a/src/Tgstation.Server.Host/Security/ITokenValidator.cs +++ b/src/Tgstation.Server.Host/Security/ITokenValidator.cs @@ -24,8 +24,9 @@ namespace Tgstation.Server.Host.Security /// /// The for . /// The scheme key being used to login. + /// The name of the used to set the user's group ID. /// The for the operation. /// A representing the running operation. - Task ValidateOidcToken(RemoteAuthenticationContext tokenValidatedContext, string schemeKey, CancellationToken cancellationToken); + Task ValidateOidcToken(RemoteAuthenticationContext tokenValidatedContext, string schemeKey, string groupIdClaimName, CancellationToken cancellationToken); } } diff --git a/src/Tgstation.Server.Host/appsettings.yml b/src/Tgstation.Server.Host/appsettings.yml index add5c212bf..7d952c73d1 100644 --- a/src/Tgstation.Server.Host/appsettings.yml +++ b/src/Tgstation.Server.Host/appsettings.yml @@ -74,6 +74,16 @@ Security: TGForums: # https://tgstation13.org OAuth configuration Keycloak: # Keycloak OAuth configuration. InvisionCommunity: # Invision Community OAuth configuration. +# OpenIDConnect: +# Example: # Scheme key: Name of this OIDC scheme (public) +# Authority: "..." # This is the root of the URL containing the "/.well-known/openid-configuration" endpoint +# ClientId: "..." +# ClientSecret: "..." +# FriendlyName: "Example Provider" # Friendly name is how this provider is displayed in UIs (public) +# ThemeColour: "#ff0000" # (Optional) Hex color code indicating the color used to theme UI elements relating to this provider (public) +# ThemeIconUrl: "..." # (Optional) Public URL of an image that can be used to theme UI elements relating to this provider (public) +# UsernameClaim: "preferred_username" # (Optional) Name of claim used to set TGS usernames upon registration. By default "preferred_username" is used. Only applies when using OidcStrictMode. +# GroupIdClaim: "tgstation-server-group-id" # (Options) Name of claim used to set TGS group ID upon logging in. By default "tgstation-server-group-id" is used. Only applies when using OidcStrictMode. Swarm: # Should be left empty if using swarm mode is not desired # Identifier: # Required: The string identifier of the swarm node # PrivateKey: # Required: The shared API key used for inter-server communication in the swarm. Must be the same secure string on all nodes