diff --git a/core/src/main/java/org/fao/geonet/kernel/security/jwtheaders/JwtHeadersConfiguration.java b/core/src/main/java/org/fao/geonet/kernel/security/jwtheaders/JwtHeadersConfiguration.java new file mode 100644 index 00000000000..3465b6d61d4 --- /dev/null +++ b/core/src/main/java/org/fao/geonet/kernel/security/jwtheaders/JwtHeadersConfiguration.java @@ -0,0 +1,275 @@ +package org.fao.geonet.kernel.security.jwtheaders; + +import org.fao.geonet.kernel.security.SecurityProviderConfiguration; +import org.geoserver.security.config.RoleSource; +import org.geoserver.security.jwtheaders.JwtConfiguration; + +public class JwtHeadersConfiguration implements SecurityProviderConfiguration { + + + public JwtHeadersConfiguration() { + jwtConfiguration = new JwtConfiguration(); + } + + + public LoginType loginType = LoginType.AUTOLOGIN; + + /** + * true -> update the DB with the information from OIDC (don't allow user to edit profile in the UI) + * false -> don't update the DB (user must edit profile in UI). + */ + public boolean updateProfile =true; + + /** + * true -> update the DB (user's group) with the information from OIDC (don't allow admin to edit user's groups in the UI) + * false -> don't update the DB (admin must edit groups in UI). + */ + public boolean updateGroup = true; + + + + + // getters/setters + + + public boolean isUpdateProfile() { + return updateProfile; + } + + public void setUpdateProfile(boolean updateProfile) { + this.updateProfile = updateProfile; + } + + public boolean isUpdateGroup() { + return updateGroup; + } + + public void setUpdateGroup(boolean updateGroup) { + this.updateGroup = updateGroup; + } + + + + //---- abstract class methods + + + @Override + public String getLoginType() { + return loginType.toString(); + } + + @Override + public String getSecurityProvider() { + return "JWT-HEADERS"; + } + + @Override + public boolean isUserProfileUpdateEnabled() { + // If updating profile from the security provider then disable the profile updates in the interface + return !updateProfile; + } + + @Override + public boolean isUserGroupUpdateEnabled() { + // If updating group from the security provider then disable the group updates in the interface + return !updateGroup; + } + + //======================================================================== + + protected JwtConfiguration jwtConfiguration; + + + public org.geoserver.security.jwtheaders.JwtConfiguration getJwtConfiguration() { + return jwtConfiguration; + } + + public void setJwtConfiguration( + org.geoserver.security.jwtheaders.JwtConfiguration jwtConfiguration) { + jwtConfiguration = jwtConfiguration; + } + + //======================================================================== + + /** what formats we support for roles in the header. */ + public enum JWTHeaderRoleSource implements RoleSource { + JSON, + JWT; + + @Override + public boolean equals(RoleSource other) { + return other != null && other.toString().equals(toString()); + } + } + + //======================================================================== + + + + public String getRoleSource() { + var val = jwtConfiguration.getJwtHeaderRoleSource(); + return val; + } + + public void setRoleSource(String roleSource) { + jwtConfiguration.setJwtHeaderRoleSource(roleSource); + } + + + + // --------------------------------------------------------------------- + +// public JwtConfiguration.UserNameHeaderFormat getUserNameFormatChoice() { +// return jwtConfiguration.getUserNameFormatChoice(); +// } +// +// public void setUserNameFormatChoice( +// JwtConfiguration.UserNameHeaderFormat userNameFormatChoice) { +// jwtConfiguration.setUserNameFormatChoice(userNameFormatChoice); +// } + + public String getUserNameFormat() { + if (jwtConfiguration.getUserNameFormatChoice() == null) { + return null; + } + return jwtConfiguration.getUserNameFormatChoice().toString(); + } + + public void setUserNameFormat(String userNameFormat) { + if (userNameFormat == null) { + jwtConfiguration.setUserNameFormatChoice(null); + return; + } + var choice = JwtConfiguration.UserNameHeaderFormat.valueOf(userNameFormat); + jwtConfiguration.setUserNameFormatChoice(choice); + } + + public String getUserNameJsonPath() { + return jwtConfiguration.getUserNameJsonPath(); + } + + public void setUserNameJsonPath(String userNameJsonPath) { + jwtConfiguration.setUserNameJsonPath(userNameJsonPath); + } + + public String getRolesHeaderName() { + return jwtConfiguration.getRolesHeaderName(); + } + + public void setRolesHeaderName(String rolesHeaderName) { + jwtConfiguration.setRolesHeaderName(rolesHeaderName); + } + + public String getRolesJsonPath() { + return jwtConfiguration.getRolesJsonPath(); + } + + public void setRolesJsonPath(String rolesJsonPath) { + jwtConfiguration.setRolesJsonPath(rolesJsonPath); + } + + public String getRoleConverterString() { + return jwtConfiguration.getRoleConverterString(); + } + + public void setRoleConverterString(String roleConverterString) { + jwtConfiguration.setRoleConverterString(roleConverterString); + } + + public boolean isOnlyExternalListedRoles() { + return jwtConfiguration.isOnlyExternalListedRoles(); + } + + public void setOnlyExternalListedRoles(boolean onlyExternalListedRoles) { + jwtConfiguration.setOnlyExternalListedRoles(onlyExternalListedRoles); + } + + public boolean isValidateToken() { + return jwtConfiguration.isValidateToken(); + } + + public void setValidateToken(boolean validateToken) { + jwtConfiguration.setValidateToken(validateToken); + } + + public boolean isValidateTokenExpiry() { + return jwtConfiguration.isValidateTokenExpiry(); + } + + public void setValidateTokenExpiry(boolean validateTokenExpiry) { + jwtConfiguration.setValidateTokenExpiry(validateTokenExpiry); + } + + public boolean isValidateTokenSignature() { + return jwtConfiguration.isValidateTokenSignature(); + } + + public void setValidateTokenSignature(boolean validateTokenSignature) { + jwtConfiguration.setValidateTokenSignature(validateTokenSignature); + } + + public String getValidateTokenSignatureURL() { + return jwtConfiguration.getValidateTokenSignatureURL(); + } + + public void setValidateTokenSignatureURL(String validateTokenSignatureURL) { + jwtConfiguration.setValidateTokenSignatureURL(validateTokenSignatureURL); + } + + public boolean isValidateTokenAgainstURL() { + return jwtConfiguration.isValidateTokenAgainstURL(); + } + + public void setValidateTokenAgainstURL(boolean validateTokenAgainstURL) { + jwtConfiguration.setValidateTokenAgainstURL(validateTokenAgainstURL); + } + + public String getValidateTokenAgainstURLEndpoint() { + return jwtConfiguration.getValidateTokenAgainstURLEndpoint(); + } + + public void setValidateTokenAgainstURLEndpoint(String validateTokenAgainstURLEndpoint) { + jwtConfiguration.setValidateTokenAgainstURLEndpoint(validateTokenAgainstURLEndpoint); + } + + public boolean isValidateSubjectWithEndpoint() { + return jwtConfiguration.isValidateSubjectWithEndpoint(); + } + + public void setValidateSubjectWithEndpoint(boolean validateSubjectWithEndpoint) { + jwtConfiguration.setValidateSubjectWithEndpoint(validateSubjectWithEndpoint); + } + + public boolean isValidateTokenAudience() { + return jwtConfiguration.isValidateTokenAudience(); + } + + public void setValidateTokenAudience(boolean validateTokenAudience) { + jwtConfiguration.setValidateTokenAudience(validateTokenAudience); + } + + public String getValidateTokenAudienceClaimName() { + return jwtConfiguration.getValidateTokenAudienceClaimName(); + } + + public void setValidateTokenAudienceClaimName(String validateTokenAudienceClaimName) { + jwtConfiguration.setValidateTokenAudienceClaimName(validateTokenAudienceClaimName); + } + + public String getValidateTokenAudienceClaimValue() { + return jwtConfiguration.getValidateTokenAudienceClaimValue(); + } + + public void setValidateTokenAudienceClaimValue(String validateTokenAudienceClaimValue) { + jwtConfiguration.setValidateTokenAudienceClaimValue(validateTokenAudienceClaimValue); + } + + public String getUserNameHeaderAttributeName() { + return jwtConfiguration.getUserNameHeaderAttributeName(); + } + + public void setUserNameHeaderAttributeName(String userNameHeaderAttributeName) { + jwtConfiguration.setUserNameHeaderAttributeName(userNameHeaderAttributeName); + } + +} diff --git a/web/src/main/webapp/WEB-INF/config-security/config-security-jwt-headers-overrides.properties b/web/src/main/webapp/WEB-INF/config-security/config-security-jwt-headers-overrides.properties new file mode 100644 index 00000000000..d52ebdc0ae1 --- /dev/null +++ b/web/src/main/webapp/WEB-INF/config-security/config-security-jwt-headers-overrides.properties @@ -0,0 +1,47 @@ +# Copyright (C) 2024 Food and Agriculture Organization of the +# United Nations (FAO-UN), United Nations World Food Programme (WFP) +# and United Nations Environment Programme (UNEP) +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or (at +# your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA +# +# Contact: Jeroen Ticheler - FAO - Viale delle Terme di Caracalla 2, +# Rome - Italy. email: geonetwork@osgeo.org + +jwtheadersConfiguration.UserNameHeaderAttributeName=${JWTHEADERS_UserNameHeaderFormat:OIDC_id_token_payload} +jwtheadersConfiguration.UserNameFormat=${JWTHEADERS_UserNameFormat:JSON} +jwtheadersConfiguration.UserNameJsonPath=${JWTHEADERS_UserNameJsonPath:preferred_username} + + +jwtheadersConfiguration.RolesJsonPath=${JWTHEADERS_RolesJsonPath:resource_access.live-key2.roles} +jwtheadersConfiguration.RolesHeaderName=${JWTHEADERS_RolesHeaderName:OIDC_id_token_payload} +jwtheadersConfiguration.RoleSource=${JWTHEADERS_JwtHeaderRoleSource:JSON} + +jwtheadersConfiguration.RoleConverterString=${JWTHEADERS_RoleConverterString:"GeonetworkAdministrator=ADMINISTRATOR"} +jwtheadersConfiguration.OnlyExternalListedRoles=${JWTHEADERS_OnlyExternalListedRoles:true} + +jwtheadersConfiguration.ValidateToken=${JWTHEADERS_ValidateToken:false} + +jwtheadersConfiguration.ValidateTokenAgainstURL=${JWTHEADERS_ValidateTokenAgainstURL:true} +jwtheadersConfiguration.ValidateTokenAgainstURLEndpoint=${JWTHEADERS_ValidateTokenAgainstURLEndpoint:} +jwtheadersConfiguration.ValidateSubjectWithEndpoint=${JWTHEADERS_ValidateSubjectWithEndpoint:true} + +jwtheadersConfiguration.ValidateTokenAudience=${JWTHEADERS_ValidateTokenAudience:true} +jwtheadersConfiguration.ValidateTokenAudienceClaimName=${JWTHEADERS_ValidateTokenAudienceClaimName:""} +jwtheadersConfiguration.ValidateTokenAudienceClaimValue=${JWTHEADERS_ValidateTokenAudienceClaimValue:""} + +jwtheadersConfiguration.ValidateTokenSignature=${JWTHEADERS_ValidateTokenSignature:true} +jwtheadersConfiguration.ValidateTokenSignatureURL=${JWTHEADERS_ValidateTokenSignatureURL:""} + + diff --git a/web/src/main/webapp/WEB-INF/config-security/config-security-jwt-headers.xml b/web/src/main/webapp/WEB-INF/config-security/config-security-jwt-headers.xml new file mode 100644 index 00000000000..692ef46622f --- /dev/null +++ b/web/src/main/webapp/WEB-INF/config-security/config-security-jwt-headers.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + diff --git a/web/src/main/webapp/WEB-INF/config-security/config-security.xml b/web/src/main/webapp/WEB-INF/config-security/config-security.xml index 817d79df5ed..1918022707a 100644 --- a/web/src/main/webapp/WEB-INF/config-security/config-security.xml +++ b/web/src/main/webapp/WEB-INF/config-security/config-security.xml @@ -54,6 +54,8 @@ keycloak - Keycloak security (see config-security-keycloak.xml for more details) openidconnect - OAUTH2 Open ID Connect (see config-security-openidconnect.xml and -overrides.properties for details) + openidconnect can be used instead of the keycloak provider + jwt-headers - Support for JSON/JWT headers for username & roles + Access Token validation + + (see config-security-jwt-headers.xml and -overrides.properties for details) ldap - ldap security (see config-security-ldap.xml for more details) ldap-recursive - ldap-recursive security (see config-security-ldap-recursive.xml for more details) ecas - ecas security (see config-security-ecas.xml for more details)