From ddf1caaaaac4ca05b5d042deeaaea273ab303429 Mon Sep 17 00:00:00 2001 From: Shaun Walker Date: Thu, 29 Sep 2022 16:32:50 -0400 Subject: [PATCH] fix #2432 - add support for roles as part of external login via OIDC --- Oqtane.Client/Modules/Admin/Users/Index.razor | 16 ++++++++-- .../Resources/Modules/Admin/Users/Index.resx | 32 +++++++++++-------- .../OqtaneServiceCollectionExtensions.cs | 4 +++ ...taneSiteAuthenticationBuilderExtensions.cs | 16 ++++++++++ 4 files changed, 53 insertions(+), 15 deletions(-) diff --git a/Oqtane.Client/Modules/Admin/Users/Index.razor b/Oqtane.Client/Modules/Admin/Users/Index.razor index a8f38030..32233793 100644 --- a/Oqtane.Client/Modules/Admin/Users/Index.razor +++ b/Oqtane.Client/Modules/Admin/Users/Index.razor @@ -286,6 +286,15 @@ else + @if (_providertype == AuthenticationProviderTypes.OpenIDConnect) + { +
+ +
+ +
+
+ }
@@ -385,6 +394,7 @@ else private string _redirecturl; private string _identifierclaimtype; private string _emailclaimtype; + private string _roleclaimtype; private string _domainfilter; private string _createusers; @@ -436,8 +446,9 @@ else _parameters = SettingService.GetSetting(settings, "ExternalLogin:Parameters", ""); _pkce = SettingService.GetSetting(settings, "ExternalLogin:PKCE", "false"); _redirecturl = PageState.Uri.Scheme + "://" + PageState.Alias.Name + "/signin-" + _providertype; - _identifierclaimtype = SettingService.GetSetting(settings, "ExternalLogin:IdentifierClaimType", "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier"); - _emailclaimtype = SettingService.GetSetting(settings, "ExternalLogin:EmailClaimType", "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress"); + _identifierclaimtype = SettingService.GetSetting(settings, "ExternalLogin:IdentifierClaimType", "sub"); + _emailclaimtype = SettingService.GetSetting(settings, "ExternalLogin:EmailClaimType", "email"); + _roleclaimtype = SettingService.GetSetting(settings, "ExternalLogin:RoleClaimType", ""); _domainfilter = SettingService.GetSetting(settings, "ExternalLogin:DomainFilter", ""); _createusers = SettingService.GetSetting(settings, "ExternalLogin:CreateUsers", "true"); @@ -555,6 +566,7 @@ else settings = SettingService.SetSetting(settings, "ExternalLogin:PKCE", _pkce, true); settings = SettingService.SetSetting(settings, "ExternalLogin:IdentifierClaimType", _identifierclaimtype, true); settings = SettingService.SetSetting(settings, "ExternalLogin:EmailClaimType", _emailclaimtype, true); + settings = SettingService.SetSetting(settings, "ExternalLogin:RoleClaimType", _roleclaimtype, true); settings = SettingService.SetSetting(settings, "ExternalLogin:DomainFilter", _domainfilter, true); settings = SettingService.SetSetting(settings, "ExternalLogin:CreateUsers", _createusers, true); diff --git a/Oqtane.Client/Resources/Modules/Admin/Users/Index.resx b/Oqtane.Client/Resources/Modules/Admin/Users/Index.resx index 2568b5a9..90cce059 100644 --- a/Oqtane.Client/Resources/Modules/Admin/Users/Index.resx +++ b/Oqtane.Client/Resources/Modules/Admin/Users/Index.resx @@ -211,25 +211,25 @@ Allow Login? - The Authority Url or Issuer Url associated with the OpenID Connect provider + The authority url or issuer url associated with the identity provider Authority: - The endpoint for obtaining an Authorization Code + The endpoint for obtaining an authorization code Authorization Url: - The Client ID from the provider + The client id for the identity provider Client ID: - The Client Secret from the provider + The client secret for the identity provider Client Secret: @@ -247,7 +247,7 @@ Domain Filter: - The name of the email address claim provided by the provider + The name of the email address claim provided by the identity provider Email Claim: @@ -259,7 +259,7 @@ Lockout Settings - The discovery endpoint for obtaining metadata for this provider. Only specify if the OpenID Connect provider does not use the standard approach (ie. /.well-known/openid-configuration) + The discovery endpoint for obtaining metadata for this identity provider. Only specify if the identity provider does not use the standard approach (ie. /.well-known/openid-configuration) Metadata Url: @@ -268,7 +268,7 @@ Password Settings - Indicate if the provider supports Proof Key for Code Exchange (PKCE) + Indicate if the identity provider supports proof key for code exchange (PKCE) Use PKCE? @@ -286,25 +286,25 @@ Provider Type: - The Redirect Url (or Callback Url) which usually needs to be registered with the provider + The redirect url (or callback url) which usually needs to be registered with the identity provider Redirect Url: - A list of Scopes to request from the provider (separated by commas). If none are specified, standard Scopes will be used by default. + A list of scopes to request from the identity provider (separated by commas). If none are specified, standard Scopes will be used by default. Scopes: - The endpoint for obtaining an Auth Token + The endpoint for obtaining an auth token Token Url: - The endpoint for obtaining user information. This should be an API or Page Url which contains the users email address. + The endpoint for obtaining user information. This should be an API endpoint or page url which contains the users email address. User Info Url: @@ -373,15 +373,21 @@ Last Login - The name of the unique user identifier claim provided by the provider + The name of the unique user identifier claim provided by the identity provider Identifier Claim: - Optionally specify any additional parameters as name/value pairs to send to the provider (separated by commas if there are multiple). + Optionally specify any additional parameters as name/value pairs to send to the identity provider (separated by commas if there are multiple). Parameters: + + Optionally provide the name of the role claim provided by the identity provider. These roles will be used in addition to any internal user roles assigned within the site. + + + Role Claim Type: + \ No newline at end of file diff --git a/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs b/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs index fec61c63..f21f53c9 100644 --- a/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs +++ b/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs @@ -1,5 +1,6 @@ using System; using System.Diagnostics; +using System.IdentityModel.Tokens.Jwt; using System.IO; using System.Linq; using System.Net; @@ -157,6 +158,9 @@ namespace Microsoft.Extensions.DependencyInjection public static IServiceCollection ConfigureOqtaneAuthenticationOptions(this IServiceCollection services, IConfigurationRoot Configuration) { + // prevent remapping of claims + JwtSecurityTokenHandler.DefaultMapInboundClaims = false; + // settings defined in appsettings services.Configure(Configuration); services.Configure(Configuration); diff --git a/Oqtane.Server/Extensions/OqtaneSiteAuthenticationBuilderExtensions.cs b/Oqtane.Server/Extensions/OqtaneSiteAuthenticationBuilderExtensions.cs index 4d28a043..a5d703fd 100644 --- a/Oqtane.Server/Extensions/OqtaneSiteAuthenticationBuilderExtensions.cs +++ b/Oqtane.Server/Extensions/OqtaneSiteAuthenticationBuilderExtensions.cs @@ -56,6 +56,10 @@ namespace Oqtane.Extensions options.ClientId = sitesettings.GetValue("ExternalLogin:ClientId", ""); options.ClientSecret = sitesettings.GetValue("ExternalLogin:ClientSecret", ""); options.UsePkce = bool.Parse(sitesettings.GetValue("ExternalLogin:PKCE", "false")); + if (!string.IsNullOrEmpty(sitesettings.GetValue("ExternalLogin:RoleClaimType", ""))) + { + options.TokenValidationParameters.RoleClaimType = sitesettings.GetValue("ExternalLogin:RoleClaimType", ""); + } options.Scope.Clear(); foreach (var scope in sitesettings.GetValue("ExternalLogin:Scopes", "openid,profile,email").Split(',', StringSplitOptions.RemoveEmptyEntries)) { @@ -230,6 +234,18 @@ namespace Oqtane.Extensions var identity = await ValidateUser(email, id, claims, context.HttpContext); if (identity.Label == ExternalLoginStatus.Success) { + // external roles + if (!string.IsNullOrEmpty(context.HttpContext.GetSiteSettings().GetValue("ExternalLogin:RoleClaimType", ""))) + { + foreach (var claim in context.Principal.Claims.Where(item => item.Type == ClaimTypes.Role)) + { + if (!identity.Claims.Any(item => item.Type == ClaimTypes.Role && item.Value == claim.Value)) + { + identity.AddClaim(new Claim(ClaimTypes.Role, claim.Value)); + } + } + } + identity.AddClaim(new Claim("access_token", context.SecurityToken.RawData)); context.Principal = new ClaimsPrincipal(identity); }