diff --git a/doc/en/user/source/community/jwt-headers/configuration.rst b/doc/en/user/source/community/jwt-headers/configuration.rst index 2380f1b7f3d..b1e567a1644 100644 --- a/doc/en/user/source/community/jwt-headers/configuration.rst +++ b/doc/en/user/source/community/jwt-headers/configuration.rst @@ -156,6 +156,12 @@ For example, a conversion map like `GeoserverAdministrator=ROLE_ADMINISTRATOR` w In our example, the user has two roles "GeoserverAdministrator" and "GeonetworkAdministrator". If the "Only allow External Roles that are explicitly named above" is checked, then GeoServer will only see the "ROLE_ADMINISTRATOR" role. If unchecked, it will see "ROLE_ADMINISTRATOR" and "GeonetworkAdministrator". In neither case will it see the converted "GeoserverAdministrator" roles. +You can also have multiple GeoServer roles from one external (OIDC) role. For example, this role conversion: + +`GeoserverAdministrator=ROLE_ADMINISTRATOR;GeoserverAdministrator=ADMIN` + +Will give users with the OIDC role `GeoserverAdministrator` two GeoServer roles - `ROLE_ADMINISTRATOR` and `ADMIN`. + JWT Validation ^^^^^^^^^^^^^^ diff --git a/src/community/jwt-headers/gs-jwt-headers/src/main/java/org/geoserver/security/jwtheaders/filter/GeoServerJwtHeadersFilterConfig.java b/src/community/jwt-headers/gs-jwt-headers/src/main/java/org/geoserver/security/jwtheaders/filter/GeoServerJwtHeadersFilterConfig.java index c9cbb245fa9..c36202c677e 100644 --- a/src/community/jwt-headers/gs-jwt-headers/src/main/java/org/geoserver/security/jwtheaders/filter/GeoServerJwtHeadersFilterConfig.java +++ b/src/community/jwt-headers/gs-jwt-headers/src/main/java/org/geoserver/security/jwtheaders/filter/GeoServerJwtHeadersFilterConfig.java @@ -116,7 +116,12 @@ public SecurityConfig clone(boolean allowEnvParametrization) { /** what formats we support for roles in the header. */ public enum JWTHeaderRoleSource implements RoleSource { JSON, - JWT; + JWT, + + // From: PreAuthenticatedUserNameFilterConfig + Header, + UserGroupService, + RoleService; @Override public boolean equals(RoleSource other) { diff --git a/src/community/jwt-headers/gs-jwt-headers/src/main/java/org/geoserver/security/jwtheaders/web/JwtHeadersAuthFilterPanel.html b/src/community/jwt-headers/gs-jwt-headers/src/main/java/org/geoserver/security/jwtheaders/web/JwtHeadersAuthFilterPanel.html index 1c242dee94d..434614ef2b4 100644 --- a/src/community/jwt-headers/gs-jwt-headers/src/main/java/org/geoserver/security/jwtheaders/web/JwtHeadersAuthFilterPanel.html +++ b/src/community/jwt-headers/gs-jwt-headers/src/main/java/org/geoserver/security/jwtheaders/web/JwtHeadersAuthFilterPanel.html @@ -45,16 +45,21 @@ visibilityDiv.style.display = "none"; } - // When the page is loaded, we hide the username "json path" input if its not needed. - window.addEventListener('load', function () { + function reset() { usernameFormatChanged(); showTokenValidationChanged(); toggleVisible(document.getElementById('validateTokenSignature'),'validateTokenSignatureURLDiv'); toggleVisible(document.getElementById('validateTokenAgainstURL'),'validateTokenAgainstURLDiv'); toggleVisible(document.getElementById('validateTokenAudience'),'validateTokenAudienceDiv'); + } - + // When the page is loaded, we hide the username "json path" input if its not needed. + window.addEventListener('load', function () { + reset(); }); + + // when creating a new jwt headers filter, we need to "kick" it. + setTimeout(reset,100); diff --git a/src/community/jwt-headers/jwt-headers-util/src/main/java/org/geoserver/security/jwtheaders/JwtConfiguration.java b/src/community/jwt-headers/jwt-headers-util/src/main/java/org/geoserver/security/jwtheaders/JwtConfiguration.java index 9ce5317b6f5..547df788a0b 100644 --- a/src/community/jwt-headers/jwt-headers-util/src/main/java/org/geoserver/security/jwtheaders/JwtConfiguration.java +++ b/src/community/jwt-headers/jwt-headers-util/src/main/java/org/geoserver/security/jwtheaders/JwtConfiguration.java @@ -1,7 +1,13 @@ +/* (c) 2024 Open Source Geospatial Foundation - all rights reserved + * This code is licensed under the GPL 2.0 license, available at the root + * application directory. + */ package org.geoserver.security.jwtheaders; import java.io.Serializable; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; public class JwtConfiguration implements Serializable { @@ -58,8 +64,8 @@ public class JwtConfiguration implements Serializable { // convert string of the form: // "externalRoleName1=GeoServerRoleName1;externalRoleName2=GeoServerRoleName2" // To a Map - public Map getRoleConverterAsMap() { - Map result = new HashMap<>(); + public Map> getRoleConverterAsMap() { + Map> result = new HashMap<>(); if (roleConverterString == null || roleConverterString.isBlank()) return result; // empty @@ -72,7 +78,13 @@ public Map getRoleConverterAsMap() { String key = goodCharacters(keyValue[0]); String val = goodCharacters(keyValue[1]); if (key.isBlank() || val.isBlank()) continue; - result.put(key, val); + if (!result.containsKey(key)) { + var list = new ArrayList(); + list.add(val); + result.put(key, list); + } else { + result.get(key).add(val); + } } return result; } diff --git a/src/community/jwt-headers/jwt-headers-util/src/main/java/org/geoserver/security/jwtheaders/roles/RoleConverter.java b/src/community/jwt-headers/jwt-headers-util/src/main/java/org/geoserver/security/jwtheaders/roles/RoleConverter.java index ba5ba46ca02..4a3672e3850 100644 --- a/src/community/jwt-headers/jwt-headers-util/src/main/java/org/geoserver/security/jwtheaders/roles/RoleConverter.java +++ b/src/community/jwt-headers/jwt-headers-util/src/main/java/org/geoserver/security/jwtheaders/roles/RoleConverter.java @@ -16,7 +16,7 @@ */ public class RoleConverter { - Map conversionMap; + Map> conversionMap; boolean externalNameMustBeListed; @@ -39,11 +39,11 @@ public List convert(List externalRoles) { if (externalRoles == null) return result; // empty for (String externalRole : externalRoles) { - String gsRole = conversionMap.get(externalRole); + List gsRole = conversionMap.get(externalRole); if (gsRole == null && !externalNameMustBeListed) { result.add(externalRole); } else if (gsRole != null) { - result.add(gsRole); + result.addAll(gsRole); } } return result; diff --git a/src/community/jwt-headers/jwt-headers-util/src/main/java/org/geoserver/security/jwtheaders/token/TokenValidator.java b/src/community/jwt-headers/jwt-headers-util/src/main/java/org/geoserver/security/jwtheaders/token/TokenValidator.java index fc62834d330..dfcbfb4da80 100644 --- a/src/community/jwt-headers/jwt-headers-util/src/main/java/org/geoserver/security/jwtheaders/token/TokenValidator.java +++ b/src/community/jwt-headers/jwt-headers-util/src/main/java/org/geoserver/security/jwtheaders/token/TokenValidator.java @@ -31,6 +31,10 @@ public TokenValidator(JwtConfiguration config) { public void validate(String accessToken) throws Exception { + accessToken = accessToken.replaceFirst("^Bearer", ""); + accessToken = accessToken.replaceFirst("^bearer", ""); + accessToken = accessToken.trim(); + if (!jwtHeadersConfig.isValidateToken()) { return; } diff --git a/src/community/jwt-headers/jwt-headers-util/src/main/java/org/geoserver/security/jwtheaders/username/JwtHeaderUserNameExtractor.java b/src/community/jwt-headers/jwt-headers-util/src/main/java/org/geoserver/security/jwtheaders/username/JwtHeaderUserNameExtractor.java index fb7f0c38998..39e32a7d02e 100644 --- a/src/community/jwt-headers/jwt-headers-util/src/main/java/org/geoserver/security/jwtheaders/username/JwtHeaderUserNameExtractor.java +++ b/src/community/jwt-headers/jwt-headers-util/src/main/java/org/geoserver/security/jwtheaders/username/JwtHeaderUserNameExtractor.java @@ -34,7 +34,12 @@ public static Object getClaim(Map map, String path) { // if this is trivial (single item in pathList), return the value. // otherwise, go into the map one level (pathList[0]) and recurse on the result. private static Object getClaim(Map map, List pathList) { - if (pathList.size() == 1) return map.get(pathList.get(0)); + if (map == null) { + return null; + } + if (pathList.size() == 1) { + return map.get(pathList.get(0)); + } String first = pathList.get(0); pathList.remove(0); diff --git a/src/community/jwt-headers/jwt-headers-util/src/test/java/org/geoserver/security/jwtheaders/roles/JwtHeadersRolesExtractorTest.java b/src/community/jwt-headers/jwt-headers-util/src/test/java/org/geoserver/security/jwtheaders/roles/JwtHeadersRolesExtractorTest.java index 610fbc0a010..085791a5261 100644 --- a/src/community/jwt-headers/jwt-headers-util/src/test/java/org/geoserver/security/jwtheaders/roles/JwtHeadersRolesExtractorTest.java +++ b/src/community/jwt-headers/jwt-headers-util/src/test/java/org/geoserver/security/jwtheaders/roles/JwtHeadersRolesExtractorTest.java @@ -39,6 +39,24 @@ public void testSimpleJwt() throws ParseException { Assert.assertEquals("GeoserverAdministrator", roles.get(0)); } + /** + * Test Tokens that start with "Bearer ". + * + * @throws ParseException + */ + @Test + public void testSimpleJwtBearer() throws ParseException { + String accessToken = + "Bearer eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICItWEdld190TnFwaWRrYTl2QXNJel82WEQtdnJmZDVyMlNWTWkwcWMyR1lNIn0.eyJleHAiOjE3MDcxNTMxNDYsImlhdCI6MTcwNzE1Mjg0NiwiYXV0aF90aW1lIjoxNzA3MTUyNjQ1LCJqdGkiOiJlMzhjY2ZmYy0zMWNjLTQ0NmEtYmU1Yy04MjliNDE0NTkyZmQiLCJpc3MiOiJodHRwczovL2xvZ2luLWxpdmUtZGV2Lmdlb2NhdC5saXZlL3JlYWxtcy9kYXZlLXRlc3QyIiwiYXVkIjoiYWNjb3VudCIsInN1YiI6ImVhMzNlM2NjLWYwZTEtNDIxOC04OWNiLThkNDhjMjdlZWUzZCIsInR5cCI6IkJlYXJlciIsImF6cCI6ImxpdmUta2V5MiIsIm5vbmNlIjoiQldzc2M3cTBKZ0tHZC1OdFc1QlFhVlROMkhSa25LQmVIY0ZMTHZ5OXpYSSIsInNlc3Npb25fc3RhdGUiOiIxY2FiZmU1NC1lOWU0LTRjMmMtODQwNy03NTZiMjczZmFmZmIiLCJhY3IiOiIwIiwicmVhbG1fYWNjZXNzIjp7InJvbGVzIjpbImRlZmF1bHQtcm9sZXMtZGF2ZS10ZXN0MiIsIm9mZmxpbmVfYWNjZXNzIiwidW1hX2F1dGhvcml6YXRpb24iXX0sInJlc291cmNlX2FjY2VzcyI6eyJsaXZlLWtleTIiOnsicm9sZXMiOlsiR2Vvc2VydmVyQWRtaW5pc3RyYXRvciJdfSwiYWNjb3VudCI6eyJyb2xlcyI6WyJtYW5hZ2UtYWNjb3VudCIsIm1hbmFnZS1hY2NvdW50LWxpbmtzIiwidmlldy1wcm9maWxlIl19fSwic2NvcGUiOiJvcGVuaWQgcGhvbmUgb2ZmbGluZV9hY2Nlc3MgbWljcm9wcm9maWxlLWp3dCBwcm9maWxlIGFkZHJlc3MgZW1haWwiLCJzaWQiOiIxY2FiZmU1NC1lOWU0LTRjMmMtODQwNy03NTZiMjczZmFmZmIiLCJ1cG4iOiJkYXZpZC5ibGFzYnlAZ2VvY2F0Lm5ldCIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwiYWRkcmVzcyI6e30sIm5hbWUiOiJkYXZpZCBibGFzYnkiLCJncm91cHMiOlsiZGVmYXVsdC1yb2xlcy1kYXZlLXRlc3QyIiwib2ZmbGluZV9hY2Nlc3MiLCJ1bWFfYXV0aG9yaXphdGlvbiJdLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJkYXZpZC5ibGFzYnlAZ2VvY2F0Lm5ldCIsImdpdmVuX25hbWUiOiJkYXZpZCIsImZhbWlseV9uYW1lIjoiYmxhc2J5IiwiZW1haWwiOiJkYXZpZC5ibGFzYnlAZ2VvY2F0Lm5ldCJ9.fHzXd7oISnqWb09ah9wikfP2UOBeiOA3vd_aDg3Bw-xcfv9aD3CWhAK5FUDPYSPyj4whAcknZbUgUzcm0qkaI8V_aS65F3Fug4jt4nC9YPL4zMSJ5an4Dp6jlQ3OQhrKFn4FwaoW61ndMmScsZZWEQyj6gzHnn5cknqySB26tVydT6q57iTO7KQFcXRdbXd6GWIoFGS-ud9XzxQMUdNfYmsDD7e6hoWhe9PJD9Zq4KT6JN13hUU4Dos-Z5SBHjRa6ieHoOe9gqkjKyA1jT1NU42Nqr-mTV-ql22nAoXuplpvOYc5-09-KDDzSDuVKFwLCNMN3ZyRF1wWuydJeU-gOQ"; + + List roles = + getExtractor(JWT.toString(), "", "resource_access.live-key2.roles") + .getRoles(accessToken).stream() + .collect(Collectors.toList()); + Assert.assertEquals(1, roles.size()); + Assert.assertEquals("GeoserverAdministrator", roles.get(0)); + } + @Test public void testSimpleJson() throws ParseException { String json = diff --git a/src/community/jwt-headers/jwt-headers-util/src/test/java/org/geoserver/security/jwtheaders/roles/RoleConverterTest.java b/src/community/jwt-headers/jwt-headers-util/src/test/java/org/geoserver/security/jwtheaders/roles/RoleConverterTest.java index e9bf5553180..0153c9bb28e 100644 --- a/src/community/jwt-headers/jwt-headers-util/src/test/java/org/geoserver/security/jwtheaders/roles/RoleConverterTest.java +++ b/src/community/jwt-headers/jwt-headers-util/src/test/java/org/geoserver/security/jwtheaders/roles/RoleConverterTest.java @@ -24,7 +24,7 @@ public void testParseNull() { JwtConfiguration config = new JwtConfiguration(); config.setRoleConverterString(null); - Map map = config.getRoleConverterAsMap(); + Map> map = config.getRoleConverterAsMap(); Assert.assertEquals(0, map.size()); config.setRoleConverterString(""); @@ -50,18 +50,18 @@ public void testParseSimple() { JwtConfiguration config = new JwtConfiguration(); config.setRoleConverterString("a=b"); - Map map = config.getRoleConverterAsMap(); + Map> map = config.getRoleConverterAsMap(); Assert.assertEquals(1, map.size()); Assert.assertTrue(map.containsKey("a")); - Assert.assertEquals("b", map.get("a")); + Assert.assertEquals(Arrays.asList("b"), map.get("a")); config.setRoleConverterString("a=b;c=d"); map = config.getRoleConverterAsMap(); Assert.assertEquals(2, map.size()); Assert.assertTrue(map.containsKey("a")); - Assert.assertEquals("b", map.get("a")); + Assert.assertEquals(Arrays.asList("b"), map.get("a")); Assert.assertTrue(map.containsKey("c")); - Assert.assertEquals("d", map.get("c")); + Assert.assertEquals(Arrays.asList("d"), map.get("c")); } /** @@ -75,24 +75,24 @@ public void testParseBad() { // bad format config.setRoleConverterString("a=b;c=;d"); - Map map = config.getRoleConverterAsMap(); + Map> map = config.getRoleConverterAsMap(); Assert.assertEquals(1, map.size()); Assert.assertTrue(map.containsKey("a")); - Assert.assertEquals("b", map.get("a")); + Assert.assertEquals(Arrays.asList("b"), map.get("a")); // bad chars config.setRoleConverterString("a= b** ;c=**;d"); map = config.getRoleConverterAsMap(); Assert.assertEquals(1, map.size()); Assert.assertTrue(map.containsKey("a")); - Assert.assertEquals("b", map.get("a")); + Assert.assertEquals(Arrays.asList("b"), map.get("a")); // removes html tags config.setRoleConverterString("a= - - - - - - - - - - - - - - - - - - - -
- - -
- - - -
- - \ No newline at end of file diff --git a/src/community/jwt-headers/src/main/java/org/geoserver/security/jwtheaders/web/JwtHeadersAuthFilterPanel.html b/src/community/jwt-headers/src/main/java/org/geoserver/security/jwtheaders/web/JwtHeadersAuthFilterPanel.html deleted file mode 100644 index 9ef8a29979b..00000000000 --- a/src/community/jwt-headers/src/main/java/org/geoserver/security/jwtheaders/web/JwtHeadersAuthFilterPanel.html +++ /dev/null @@ -1,188 +0,0 @@ - - - - - - - - - -
- - - - - - -
- - -
- -
- - -
- -
- - - - - -
- - -
- -
-
- - -
- -
- - -
-
- - -
-
- - -
-
- -
-
- - -
-
- - -
- -
-
- - -
- -
-
- - -
-
- - -
-
- -
- -
- -
- -
- - -
-
- - -
-
- - \ No newline at end of file diff --git a/src/community/jwt-headers/src/main/java/org/geoserver/security/jwtheaders/web/JwtHeadersAuthFilterPanel.java b/src/community/jwt-headers/src/main/java/org/geoserver/security/jwtheaders/web/JwtHeadersAuthFilterPanel.java deleted file mode 100644 index 290e7f750a0..00000000000 --- a/src/community/jwt-headers/src/main/java/org/geoserver/security/jwtheaders/web/JwtHeadersAuthFilterPanel.java +++ /dev/null @@ -1,98 +0,0 @@ -/* (c) 2024 Open Source Geospatial Foundation - all rights reserved - * This code is licensed under the GPL 2.0 license, available at the root - * application directory. - */ - -package org.geoserver.security.jwtheaders.web; - -import static org.geoserver.security.jwtheaders.filter.GeoServerJwtHeadersFilterConfig.JWTHeaderRoleSource.JSON; -import static org.geoserver.security.jwtheaders.filter.GeoServerJwtHeadersFilterConfig.JWTHeaderRoleSource.JWT; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import org.apache.wicket.markup.html.form.CheckBox; -import org.apache.wicket.markup.html.form.DropDownChoice; -import org.apache.wicket.markup.html.form.TextField; -import org.apache.wicket.markup.html.panel.Panel; -import org.apache.wicket.model.IModel; -import org.apache.wicket.model.Model; -import org.geoserver.security.config.PreAuthenticatedUserNameFilterConfig; -import org.geoserver.security.config.RoleSource; -import org.geoserver.security.jwtheaders.filter.GeoServerJwtHeadersFilterConfig; -import org.geoserver.security.web.auth.PreAuthenticatedUserNameFilterPanel; -import org.geoserver.security.web.auth.RoleSourceChoiceRenderer; -import org.geoserver.web.wicket.HelpLink; - -/** Jwt Headers auth panel Wicket */ -public class JwtHeadersAuthFilterPanel - extends PreAuthenticatedUserNameFilterPanel { - - protected DropDownChoice - userNameFormatChoice; - - public JwtHeadersAuthFilterPanel(String id, IModel model) { - super(id, model); - - add(new HelpLink("UsernameHelp", this).setDialog(dialog)); - add(new TextField("userNameHeaderAttributeName").setRequired(true)); - - add(new TextField("userNameJsonPath").setRequired(false)); - - add(new CheckBox("validateToken").setRequired(false)); - add(new HelpLink("validateTokenHelp", this).setDialog(dialog)); - - add(new CheckBox("validateTokenExpiry").setRequired(false)); - - add(new CheckBox("validateTokenSignature").setRequired(false)); - add(new TextField("validateTokenSignatureURL").setRequired(false)); - - add(new CheckBox("validateTokenAgainstURL").setRequired(false)); - add(new TextField("validateTokenAgainstURLEndpoint").setRequired(false)); - add(new CheckBox("validateSubjectWithEndpoint").setRequired(false)); - - add(new CheckBox("validateTokenAudience").setRequired(false)); - add(new TextField("validateTokenAudienceClaimName").setRequired(false)); - add(new TextField("validateTokenAudienceClaimValue").setRequired(false)); - - userNameFormatChoice = - new DropDownChoice( - "userNameFormatChoice", - Arrays.asList( - GeoServerJwtHeadersFilterConfig.UserNameHeaderFormat.values()), - new UserNameFormatChoiceRenderer()); - - add(userNameFormatChoice); - } - - @Override - protected DropDownChoice createRoleSourceDropDown() { - List sources = - new ArrayList<>( - Arrays.asList( - GeoServerJwtHeadersFilterConfig.JWTHeaderRoleSource.values())); - sources.addAll( - Arrays.asList( - PreAuthenticatedUserNameFilterConfig.PreAuthenticatedUserNameRoleSource - .values())); - return new DropDownChoice<>("roleSource", sources, new RoleSourceChoiceRenderer()); - } - - @Override - protected Panel getRoleSourcePanel(RoleSource model) { - if (JSON.equals(model) || JWT.equals(model)) { - return new JsonClaimPanel("panel"); - } - return super.getRoleSourcePanel(model); - } - - static class JsonClaimPanel extends Panel { - public JsonClaimPanel(String id) { - super(id, new Model<>()); - add(new TextField("rolesJsonPath").setRequired(true)); - add(new TextField("rolesHeaderName").setRequired(true)); - add(new TextField("roleConverterString").setRequired(false)); - add(new CheckBox("onlyExternalListedRoles").setRequired(false)); - } - } -} diff --git a/src/community/jwt-headers/src/main/java/org/geoserver/security/jwtheaders/web/JwtHeadersAuthFilterPanelInfo.java b/src/community/jwt-headers/src/main/java/org/geoserver/security/jwtheaders/web/JwtHeadersAuthFilterPanelInfo.java deleted file mode 100644 index fa4d51417a5..00000000000 --- a/src/community/jwt-headers/src/main/java/org/geoserver/security/jwtheaders/web/JwtHeadersAuthFilterPanelInfo.java +++ /dev/null @@ -1,23 +0,0 @@ -/* (c) 2024 Open Source Geospatial Foundation - all rights reserved - * This code is licensed under the GPL 2.0 license, available at the root - * application directory. - */ - -package org.geoserver.security.jwtheaders.web; - -import org.geoserver.security.filter.GeoServerRequestHeaderAuthenticationFilter; -import org.geoserver.security.jwtheaders.filter.GeoServerJwtHeadersFilter; -import org.geoserver.security.jwtheaders.filter.GeoServerJwtHeadersFilterConfig; -import org.geoserver.security.web.auth.AuthenticationFilterPanelInfo; - -/** Configuration panel extension for {@link GeoServerRequestHeaderAuthenticationFilter}. */ -public class JwtHeadersAuthFilterPanelInfo - extends AuthenticationFilterPanelInfo< - GeoServerJwtHeadersFilterConfig, JwtHeadersAuthFilterPanel> { - - public JwtHeadersAuthFilterPanelInfo() { - setComponentClass(JwtHeadersAuthFilterPanel.class); - setServiceClass(GeoServerJwtHeadersFilter.class); - setServiceConfigClass(GeoServerJwtHeadersFilterConfig.class); - } -} diff --git a/src/community/jwt-headers/src/main/java/org/geoserver/security/jwtheaders/web/UserNameFormatChoiceRenderer.java b/src/community/jwt-headers/src/main/java/org/geoserver/security/jwtheaders/web/UserNameFormatChoiceRenderer.java deleted file mode 100644 index 62f48d3a9d7..00000000000 --- a/src/community/jwt-headers/src/main/java/org/geoserver/security/jwtheaders/web/UserNameFormatChoiceRenderer.java +++ /dev/null @@ -1,29 +0,0 @@ -/* (c) 2024 Open Source Geospatial Foundation - all rights reserved - * This code is licensed under the GPL 2.0 license, available at the root - * application directory. - */ - -package org.geoserver.security.jwtheaders.web; - -import org.apache.wicket.Application; -import org.apache.wicket.markup.html.form.ChoiceRenderer; -import org.geoserver.security.jwtheaders.filter.GeoServerJwtHeadersFilterConfig; - -/** Wicket support class for the UserNameHeaderFormat displayed on the config page. */ -public class UserNameFormatChoiceRenderer - extends ChoiceRenderer { - - /** serialVersionUID */ - private static final long serialVersionUID = 1L; - - @Override - public Object getDisplayValue(GeoServerJwtHeadersFilterConfig.UserNameHeaderFormat rs) { - String key = "UserNameHeaderFormat." + rs.toString(); - return Application.get().getResourceSettings().getLocalizer().getString(key, null); - } - - @Override - public String getIdValue(GeoServerJwtHeadersFilterConfig.UserNameHeaderFormat rs, int index) { - return rs.toString(); - } -} diff --git a/src/community/jwt-headers/src/main/resources/GeoServerApplication.properties b/src/community/jwt-headers/src/main/resources/GeoServerApplication.properties deleted file mode 100644 index 3a67fe5d85f..00000000000 --- a/src/community/jwt-headers/src/main/resources/GeoServerApplication.properties +++ /dev/null @@ -1,43 +0,0 @@ -JwtHeadersAuthFilterPanel.short=JWT Headers -JwtHeadersAuthFilterPanel.title=HTTP JWT Request Header Authentication -JwtHeadersAuthFilterPanel.description=Authenticates by checking existence of a JWT HTTP request header - -org.geoserver.security.jwtheaders.filter.GeoServerJwtHeadersFilter.name=HTTP JWT Header -org.geoserver.security.jwtheaders.filter.GeoServerJwtHeadersFilter.title=HTTP request JWT header authentication -JwtHeadersAuthFilterPanel.userNameHeaderAttributeName=Request header attribute for User Name -JwtHeadersAuthFilterPanel.UsernameHeader=User Name Extraction From Header -JwtHeadersAuthFilterPanel.UsernameHelp.title=User Name Extraction from HTTP Headers -JwtHeadersAuthFilterPanel.UsernameHelp=\ -Where to extract the User Name from the HTTP request. -RoleSource.JWT=Header Containing JWT -RoleSource.JSON=Header Containing JSON String -JwtHeadersAuthFilterPanel.jsonPath=JSON Path (i.e. property1.property2) -JwtHeadersAuthFilterPanel.rolesHeaderName=Request Header attribute for Roles -UserNameHeaderFormat.STRING=Simple String -UserNameHeaderFormat.JSON=JSON -UserNameHeaderFormat.JWT=JWT -JwtHeadersAuthFilterPanel.formatUserName=Format the Header value is in -JwtHeadersAuthFilterPanel.userNamePath=JSON path for the User Name -JwtHeadersAuthFilterPanel.roleConverterTitle=Role Converter Map from External Roles to Geoserver Roles -JwtHeadersAuthFilterPanel.roleConverterHint=eg. ExternalRole1=GeoServerRole1;ExternalRole2=GeoServerRole2 -JwtHeadersAuthFilterPanel.externalRoleTitle=External Role Name -JwtHeadersAuthFilterPanel.gsRoleTitle=GeoServer Role Name -JwtHeadersAuthFilterPanel.roleConverterTableTitle=Role Conversion Summary -JwtHeadersAuthFilterPanel.roleConverterOnlyListedExternalRoles=Only allow External Roles that are explicitly named above -JwtHeadersAuthFilterPanel.showTokenValidation=Validate JWT (Access Token) -JwtHeadersAuthFilterPanel.validateTokenSignatureText=Validate JWT (Access Token) Signature -JwtHeadersAuthFilterPanel.validateTokenSignatureURL=JSON Web Key Set URL (jwks_uri) -JwtHeadersAuthFilterPanel.validateTokenAgainstURL=Validate JWT (Access Token) Against Endpoint -JwtHeadersAuthFilterPanel.validateTokenAgainstURLText=URL (userinfo_endpoint) -JwtHeadersAuthFilterPanel.validateTokenAgainstURLSubject=Also validate Subject -JwtHeadersAuthFilterPanel.validateTokenAudience=Validate JWT (Access Token) Audience -JwtHeadersAuthFilterPanel.validateTokenAudienceClaimName=Claim Name (usually 'aud', 'azp', or 'appid') -JwtHeadersAuthFilterPanel.validateTokenAudienceClaimValue=Required Claim Value -JwtHeadersAuthFilterPanel.validateTokenHeader=Header Attached JWT Validation -JwtHeadersAuthFilterPanel.validateTokenExpiry=Validate Token Expiry -JwtHeadersAuthFilterPanel.validateTokenHelp= \ -Options for validating a JWT Token.\ -Typically only required for Access Token validation (Bearer Tokens). -JwtHeadersAuthFilterPanel.validateTokenHelp.title=JWT Validation Options - - diff --git a/src/community/jwt-headers/src/main/resources/applicationContext.xml b/src/community/jwt-headers/src/main/resources/applicationContext.xml deleted file mode 100644 index 97b4b7d779c..00000000000 --- a/src/community/jwt-headers/src/main/resources/applicationContext.xml +++ /dev/null @@ -1,34 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/community/jwt-headers/src/test/java/org/geoserver/security/jwtheaders/JwtHeadersIntegrationTest.java b/src/community/jwt-headers/src/test/java/org/geoserver/security/jwtheaders/JwtHeadersIntegrationTest.java deleted file mode 100644 index 1bd347c74ee..00000000000 --- a/src/community/jwt-headers/src/test/java/org/geoserver/security/jwtheaders/JwtHeadersIntegrationTest.java +++ /dev/null @@ -1,344 +0,0 @@ -/* (c) 2024 Open Source Geospatial Foundation - all rights reserved - * This code is licensed under the GPL 2.0 license, available at the root - * application directory. - */ - -package org.geoserver.security.jwtheaders; - -import static org.geoserver.security.jwtheaders.filter.GeoServerJwtHeadersFilterConfig.JWTHeaderRoleSource.JSON; -import static org.geoserver.security.jwtheaders.filter.GeoServerJwtHeadersFilterConfig.JWTHeaderRoleSource.JWT; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; -import java.util.SortedSet; -import java.util.stream.Collectors; -import javax.servlet.Filter; -import javax.servlet.ServletRequestEvent; -import javax.servlet.http.HttpSession; -import org.geoserver.data.test.SystemTestData; -import org.geoserver.platform.GeoServerExtensions; -import org.geoserver.security.*; -import org.geoserver.security.config.SecurityFilterConfig; -import org.geoserver.security.config.SecurityManagerConfig; -import org.geoserver.security.jwtheaders.filter.GeoServerJwtHeadersFilter; -import org.geoserver.security.jwtheaders.filter.GeoServerJwtHeadersFilterConfig; -import org.geoserver.security.jwtheaders.filter.details.JwtHeadersWebAuthenticationDetails; -import org.geoserver.test.GeoServerSystemTestSupport; -import org.junit.Assert; -import org.junit.Test; -import org.springframework.mock.web.MockFilterChain; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.mock.web.MockHttpServletResponse; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.SecurityContext; -import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken; -import org.springframework.security.web.context.HttpRequestResponseHolder; -import org.springframework.security.web.context.HttpSessionSecurityContextRepository; -import org.springframework.web.context.request.RequestContextHolder; -import org.springframework.web.context.request.RequestContextListener; -import org.springframework.web.context.request.ServletRequestAttributes; - -public class JwtHeadersIntegrationTest extends GeoServerSystemTestSupport { - - @Override - protected void onSetUp(SystemTestData testData) throws Exception { - super.onSetUp(testData); - } - - protected GeoServerJwtHeadersFilterConfig injectConfig1() throws Exception { - GeoServerSecurityManager manager = getSecurityManager(); - - SortedSet filters = manager.listFilters(); - if (filters.contains("JwtHeaders1")) { - SecurityFilterConfig config = manager.loadFilterConfig("JwtHeaders1", false); - manager.removeFilter(config); - } - if (filters.contains("JwtHeaders2")) { - SecurityFilterConfig config = manager.loadFilterConfig("JwtHeaders2", false); - manager.removeFilter(config); - } - - GeoServerJwtHeadersFilterConfig filterConfig = new GeoServerJwtHeadersFilterConfig(); - filterConfig.setName("JwtHeaders1"); - - filterConfig.setClassName(GeoServerJwtHeadersFilter.class.getName()); - - // username - filterConfig.setUserNameJsonPath("preferred_username"); - filterConfig.setUserNameFormatChoice( - GeoServerJwtHeadersFilterConfig.UserNameHeaderFormat.JSON); - filterConfig.setUserNameHeaderAttributeName("json-header"); - - // roles - filterConfig.setRoleSource(JSON); - filterConfig.setRoleConverterString("GeoserverAdministrator=ROLE_ADMINISTRATOR"); - filterConfig.setRolesJsonPath("resource_access.live-key2.roles"); - filterConfig.setRolesHeaderName("json-header"); - filterConfig.setOnlyExternalListedRoles(true); - - manager.saveFilter(filterConfig); - - SecurityManagerConfig config = manager.getSecurityConfig(); - GeoServerSecurityFilterChain chain = config.getFilterChain(); - RequestFilterChain www = chain.getRequestChainByName("web"); - // www.setFilterNames("JwtHeaders1", "anonymous"); - www.setFilterNames("JwtHeaders1"); - - manager.saveSecurityConfig(config); - return filterConfig; - } - - protected GeoServerJwtHeadersFilterConfig injectConfig2() throws Exception { - GeoServerSecurityManager manager = getSecurityManager(); - - SortedSet filters = manager.listFilters(); - if (filters.contains("JwtHeaders1")) { - SecurityFilterConfig config = manager.loadFilterConfig("JwtHeaders1", false); - manager.removeFilter(config); - } - if (filters.contains("JwtHeaders2")) { - SecurityFilterConfig config = manager.loadFilterConfig("JwtHeaders2", false); - manager.removeFilter(config); - } - - GeoServerJwtHeadersFilterConfig filterConfig = new GeoServerJwtHeadersFilterConfig(); - filterConfig.setName("JwtHeaders2"); - - filterConfig.setClassName(GeoServerJwtHeadersFilter.class.getName()); - - // username - filterConfig.setUserNameJsonPath("preferred_username"); - filterConfig.setUserNameFormatChoice( - GeoServerJwtHeadersFilterConfig.UserNameHeaderFormat.JWT); - filterConfig.setUserNameHeaderAttributeName("json-header"); - - // roles - filterConfig.setRoleSource(JWT); - filterConfig.setRoleConverterString("GeoserverAdministrator=ROLE_ADMINISTRATOR"); - filterConfig.setRolesJsonPath("resource_access.live-key2.roles"); - filterConfig.setRolesHeaderName("json-header"); - filterConfig.setOnlyExternalListedRoles(true); - - manager.saveFilter(filterConfig); - - SecurityManagerConfig config = manager.getSecurityConfig(); - GeoServerSecurityFilterChain chain = config.getFilterChain(); - RequestFilterChain www = chain.getRequestChainByName("web"); - - www.setFilterNames("JwtHeaders2"); - - manager.saveSecurityConfig(config); - return filterConfig; - } - - /** - * Enable the Spring Security authentication filters, we want the test to be complete and - * realistic - */ - @Override - protected List getFilters() { - - SecurityManagerConfig mconfig = getSecurityManager().getSecurityConfig(); - GeoServerSecurityFilterChain filterChain = mconfig.getFilterChain(); - VariableFilterChain chain = (VariableFilterChain) filterChain.getRequestChainByName("web"); - List result = new ArrayList<>(); - for (String filterName : chain.getCompiledFilterNames()) { - try { - result.add(getSecurityManager().loadFilter(filterName)); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - return result; - } - - String json = - "{\"exp\":1707155912,\"iat\":1707155612,\"jti\":\"888715ae-a79d-4633-83e5-9b97dee02bbc\",\"iss\":\"https://login-live-dev.geocat.live/realms/dave-test2\",\"aud\":\"account\",\"sub\":\"ea33e3cc-f0e1-4218-89cb-8d48c27eee3d\",\"typ\":\"Bearer\",\"azp\":\"live-key2\",\"session_state\":\"ae7796fa-b374-4754-a294-e0eb834b23b5\",\"acr\":\"1\",\"realm_access\":{\"roles\":[\"default-roles-dave-test2\",\"offline_access\",\"uma_authorization\"]},\"resource_access\":{\"live-key2\":{\"roles\":[\"GeoserverAdministrator\"]},\"account\":{\"roles\":[\"manage-account\",\"manage-account-links\",\"view-profile\"]}},\"scope\":\"openidprofileemail\",\"sid\":\"ae7796fa-b374-4754-a294-e0eb834b23b5\",\"email_verified\":false,\"name\":\"davidblasby\",\"preferred_username\":\"david.blasby@geocat.net\",\"given_name\":\"david\",\"family_name\":\"blasby\",\"email\":\"david.blasby@geocat.net\"}"; - - @Test - public void testSimpleJSON() throws Exception { - GeoServerJwtHeadersFilterConfig filterConfig = injectConfig1(); - // mimick user pressing on login button - MockHttpServletRequest webRequest = createRequest("web/"); - MockHttpServletResponse webResponse = executeOnSecurityFilters(webRequest); - - int responseStatus = webResponse.getStatus(); - - Assert.assertTrue(responseStatus > 400); // access denied - - webRequest = createRequest("web/"); - webRequest.addHeader("json-header", json); - - webResponse = executeOnSecurityFilters(webRequest); - - responseStatus = webResponse.getStatus(); - - Assert.assertEquals(responseStatus, 200); // good request - - // get the security context - Authentication auth = getAuthentication(webRequest, webResponse); - - Assert.assertNotNull(auth); - Assert.assertEquals(PreAuthenticatedAuthenticationToken.class, auth.getClass()); - Assert.assertEquals(JwtHeadersWebAuthenticationDetails.class, auth.getDetails().getClass()); - - String authFilterId = - ((JwtHeadersWebAuthenticationDetails) auth.getDetails()).getJwtHeadersConfigId(); - Assert.assertEquals(filterConfig.getId(), authFilterId); - - List roles = - auth.getAuthorities().stream() - .map(x -> x.getAuthority()) - .collect(Collectors.toList()); - Assert.assertEquals(2, roles.size()); - Assert.assertTrue(roles.contains("ROLE_ADMINISTRATOR")); - Assert.assertTrue(roles.contains("ROLE_AUTHENTICATED")); - int tt = 0; - } - - String accessToken = - "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICItWEdld190TnFwaWRrYTl2QXNJel82WEQtdnJmZDVyMlNWTWkwcWMyR1lNIn0.eyJleHAiOjE3MDcxNTMxNDYsImlhdCI6MTcwNzE1Mjg0NiwiYXV0aF90aW1lIjoxNzA3MTUyNjQ1LCJqdGkiOiJlMzhjY2ZmYy0zMWNjLTQ0NmEtYmU1Yy04MjliNDE0NTkyZmQiLCJpc3MiOiJodHRwczovL2xvZ2luLWxpdmUtZGV2Lmdlb2NhdC5saXZlL3JlYWxtcy9kYXZlLXRlc3QyIiwiYXVkIjoiYWNjb3VudCIsInN1YiI6ImVhMzNlM2NjLWYwZTEtNDIxOC04OWNiLThkNDhjMjdlZWUzZCIsInR5cCI6IkJlYXJlciIsImF6cCI6ImxpdmUta2V5MiIsIm5vbmNlIjoiQldzc2M3cTBKZ0tHZC1OdFc1QlFhVlROMkhSa25LQmVIY0ZMTHZ5OXpYSSIsInNlc3Npb25fc3RhdGUiOiIxY2FiZmU1NC1lOWU0LTRjMmMtODQwNy03NTZiMjczZmFmZmIiLCJhY3IiOiIwIiwicmVhbG1fYWNjZXNzIjp7InJvbGVzIjpbImRlZmF1bHQtcm9sZXMtZGF2ZS10ZXN0MiIsIm9mZmxpbmVfYWNjZXNzIiwidW1hX2F1dGhvcml6YXRpb24iXX0sInJlc291cmNlX2FjY2VzcyI6eyJsaXZlLWtleTIiOnsicm9sZXMiOlsiR2Vvc2VydmVyQWRtaW5pc3RyYXRvciJdfSwiYWNjb3VudCI6eyJyb2xlcyI6WyJtYW5hZ2UtYWNjb3VudCIsIm1hbmFnZS1hY2NvdW50LWxpbmtzIiwidmlldy1wcm9maWxlIl19fSwic2NvcGUiOiJvcGVuaWQgcGhvbmUgb2ZmbGluZV9hY2Nlc3MgbWljcm9wcm9maWxlLWp3dCBwcm9maWxlIGFkZHJlc3MgZW1haWwiLCJzaWQiOiIxY2FiZmU1NC1lOWU0LTRjMmMtODQwNy03NTZiMjczZmFmZmIiLCJ1cG4iOiJkYXZpZC5ibGFzYnlAZ2VvY2F0Lm5ldCIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwiYWRkcmVzcyI6e30sIm5hbWUiOiJkYXZpZCBibGFzYnkiLCJncm91cHMiOlsiZGVmYXVsdC1yb2xlcy1kYXZlLXRlc3QyIiwib2ZmbGluZV9hY2Nlc3MiLCJ1bWFfYXV0aG9yaXphdGlvbiJdLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJkYXZpZC5ibGFzYnlAZ2VvY2F0Lm5ldCIsImdpdmVuX25hbWUiOiJkYXZpZCIsImZhbWlseV9uYW1lIjoiYmxhc2J5IiwiZW1haWwiOiJkYXZpZC5ibGFzYnlAZ2VvY2F0Lm5ldCJ9.fHzXd7oISnqWb09ah9wikfP2UOBeiOA3vd_aDg3Bw-xcfv9aD3CWhAK5FUDPYSPyj4whAcknZbUgUzcm0qkaI8V_aS65F3Fug4jt4nC9YPL4zMSJ5an4Dp6jlQ3OQhrKFn4FwaoW61ndMmScsZZWEQyj6gzHnn5cknqySB26tVydT6q57iTO7KQFcXRdbXd6GWIoFGS-ud9XzxQMUdNfYmsDD7e6hoWhe9PJD9Zq4KT6JN13hUU4Dos-Z5SBHjRa6ieHoOe9gqkjKyA1jT1NU42Nqr-mTV-ql22nAoXuplpvOYc5-09-KDDzSDuVKFwLCNMN3ZyRF1wWuydJeU-gOQ"; - - @Test - public void testSimpleJWT() throws Exception { - GeoServerJwtHeadersFilterConfig filterConfig = injectConfig2(); - // mimick user pressing on login button - MockHttpServletRequest webRequest = createRequest("web/"); - MockHttpServletResponse webResponse = executeOnSecurityFilters(webRequest); - - int responseStatus = webResponse.getStatus(); - - Assert.assertTrue(responseStatus > 400); // access denied - - webRequest = createRequest("web/"); - webRequest.addHeader("json-header", accessToken); - - webResponse = executeOnSecurityFilters(webRequest); - - responseStatus = webResponse.getStatus(); - - Assert.assertEquals(responseStatus, 200); // good request - - Authentication auth = getAuthentication(webRequest, webResponse); - - Assert.assertNotNull(auth); - Assert.assertEquals(PreAuthenticatedAuthenticationToken.class, auth.getClass()); - Assert.assertEquals(JwtHeadersWebAuthenticationDetails.class, auth.getDetails().getClass()); - - String authFilterId = - ((JwtHeadersWebAuthenticationDetails) auth.getDetails()).getJwtHeadersConfigId(); - Assert.assertEquals(filterConfig.getId(), authFilterId); - - List roles = - auth.getAuthorities().stream() - .map(x -> x.getAuthority()) - .collect(Collectors.toList()); - Assert.assertEquals(2, roles.size()); - Assert.assertTrue(roles.contains("ROLE_ADMINISTRATOR")); - Assert.assertTrue(roles.contains("ROLE_AUTHENTICATED")); - int tt = 0; - } - - @Test - public void testLogout() throws Exception { - GeoServerJwtHeadersFilterConfig filterConfig = injectConfig2(); - MockHttpServletRequest webRequest = createRequest("web/"); - MockHttpServletResponse webResponse = executeOnSecurityFilters(webRequest); - - // no token, no access - int responseStatus = webResponse.getStatus(); - Assert.assertTrue(responseStatus > 400); - HttpSession session = webRequest.getSession(); - - // add token - should have access - webRequest = createRequest("web/"); - webRequest.setSession(session); - webRequest.addHeader("json-header", accessToken); - - webResponse = executeOnSecurityFilters(webRequest); - responseStatus = webResponse.getStatus(); - Assert.assertEquals(responseStatus, 200); - - session = webRequest.getSession(); - // remove token - should not have access - webRequest = createRequest("web/"); - webRequest.setSession(session); - - webResponse = executeOnSecurityFilters(webRequest); - responseStatus = webResponse.getStatus(); - Assert.assertTrue(responseStatus > 400); - } - - String json2 = - "{\"exp\":1707155912,\"iat\":1707155612,\"jti\":\"888715ae-a79d-4633-83e5-9b97dee02bbc\",\"iss\":\"https://login-live-dev.geocat.live/realms/dave-test2\",\"aud\":\"account\",\"sub\":\"ea33e3cc-f0e1-4218-89cb-8d48c27eee3d\",\"typ\":\"Bearer\",\"azp\":\"live-key2\",\"session_state\":\"ae7796fa-b374-4754-a294-e0eb834b23b5\",\"acr\":\"1\",\"realm_access\":{\"roles\":[\"default-roles-dave-test2\",\"offline_access\",\"uma_authorization\"]},\"resource_access\":{\"live-key2\":{\"roles\":[\"GeoserverAdministrator\"]},\"account\":{\"roles\":[\"manage-account\",\"manage-account-links\",\"view-profile\"]}},\"scope\":\"openidprofileemail\",\"sid\":\"ae7796fa-b374-4754-a294-e0eb834b23b5\",\"email_verified\":false,\"name\":\"davidblasby\",\"preferred_username\":\"david.blasby22@geocat.net\",\"given_name\":\"david\",\"family_name\":\"blasby\",\"email\":\"david.blasby@geocat.net\"}"; - - @Test - public void testUserNameChange() throws Exception { - GeoServerJwtHeadersFilterConfig filterConfig = injectConfig1(); - MockHttpServletRequest webRequest = createRequest("web/"); - MockHttpServletResponse webResponse = executeOnSecurityFilters(webRequest); - - // no token, no access - int responseStatus = webResponse.getStatus(); - Assert.assertTrue(responseStatus > 400); - HttpSession session = webRequest.getSession(); - - // add token - should have access - webRequest = createRequest("web/"); - webRequest.setSession(session); - webRequest.addHeader("json-header", json); - - webResponse = executeOnSecurityFilters(webRequest); - responseStatus = webResponse.getStatus(); - Assert.assertEquals(responseStatus, 200); - - Authentication auth = getAuthentication(webRequest, webResponse); - - Assert.assertEquals("david.blasby@geocat.net", auth.getPrincipal()); - - // use different json with different username! - webRequest = createRequest("web/"); - webRequest.setSession(session); - webRequest.addHeader("json-header", json2); - webResponse = executeOnSecurityFilters(webRequest); - responseStatus = webResponse.getStatus(); - Assert.assertEquals(responseStatus, 200); - - auth = getAuthentication(webRequest, webResponse); - - Assert.assertEquals("david.blasby22@geocat.net", auth.getPrincipal()); - } - - public Authentication getAuthentication( - MockHttpServletRequest request, MockHttpServletResponse response) { - SecurityContext context = - new HttpSessionSecurityContextRepository() - .loadContext(new HttpRequestResponseHolder(request, response)); - Authentication auth = context.getAuthentication(); - return auth; - } - - public HttpSession getSession() { - ServletRequestAttributes attr = - (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes(); - HttpSession session = attr.getRequest().getSession(); - return session; - } - - private MockHttpServletResponse executeOnSecurityFilters(MockHttpServletRequest request) - throws IOException, javax.servlet.ServletException { - // for session local support in Spring - new RequestContextListener() - .requestInitialized(new ServletRequestEvent(request.getServletContext(), request)); - - // run on the - MockFilterChain chain = new MockFilterChain(); - MockHttpServletResponse response = new MockHttpServletResponse(); - GeoServerSecurityFilterChainProxy filterChainProxy = - GeoServerExtensions.bean(GeoServerSecurityFilterChainProxy.class); - filterChainProxy.doFilter(request, response, chain); - - return response; - } -} diff --git a/src/community/jwt-headers/src/test/java/org/geoserver/security/jwtheaders/filter/JwtHeadersFilterTest.java b/src/community/jwt-headers/src/test/java/org/geoserver/security/jwtheaders/filter/JwtHeadersFilterTest.java deleted file mode 100644 index 0a2c5a796bc..00000000000 --- a/src/community/jwt-headers/src/test/java/org/geoserver/security/jwtheaders/filter/JwtHeadersFilterTest.java +++ /dev/null @@ -1,81 +0,0 @@ -/* (c) 2024 Open Source Geospatial Foundation - all rights reserved - * This code is licensed under the GPL 2.0 license, available at the root - * application directory. - */ - -package org.geoserver.security.jwtheaders.filter; - -import java.io.IOException; -import org.geoserver.security.jwtheaders.filter.details.JwtHeadersWebAuthenticationDetails; -import org.geoserver.test.GeoServerAbstractTestSupport; -import org.junit.Test; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.web.authentication.WebAuthenticationDetails; -import org.springframework.util.Assert; - -public class JwtHeadersFilterTest { - - @Test - public void testExistingAuthIsFromThisConfig() throws IOException { - GeoServerJwtHeadersFilterConfig config1 = new GeoServerJwtHeadersFilterConfig(); - config1.setId("abc123"); - GeoServerJwtHeadersFilter filter1 = new GeoServerJwtHeadersFilter(); - filter1.initializeFromConfig(config1); - - GeoServerJwtHeadersFilterConfig config2 = new GeoServerJwtHeadersFilterConfig(); - config2.setId("aaaaaaaaaaa111111111111111"); - GeoServerJwtHeadersFilter filter2 = new GeoServerJwtHeadersFilter(); - filter2.initializeFromConfig(config1); - - // trivial cases - - // no existing auth - Assert.isTrue(!filter1.existingAuthIsFromThisConfig(null)); - // not from us - UsernamePasswordAuthenticationToken auth = - new UsernamePasswordAuthenticationToken(null, null, null); - Assert.isTrue(!filter1.existingAuthIsFromThisConfig(auth)); - - // details, but wrong type - auth.setDetails(new WebAuthenticationDetails(null, null)); - Assert.isTrue(!filter1.existingAuthIsFromThisConfig(auth)); - - // more complex cases - // details is JwtHeaders, but wrong id - auth.setDetails( - new JwtHeadersWebAuthenticationDetails( - config2.getId(), - new GeoServerAbstractTestSupport.GeoServerMockHttpServletRequest("", ""))); - Assert.isTrue(!filter1.existingAuthIsFromThisConfig(auth)); - - // details is JwtHeaders,right id - auth.setDetails( - new JwtHeadersWebAuthenticationDetails( - config1.getId(), - new GeoServerAbstractTestSupport.GeoServerMockHttpServletRequest("", ""))); - Assert.isTrue(filter1.existingAuthIsFromThisConfig(auth)); - } - - @Test - public void testPrincipleHasChanged() throws IOException { - GeoServerJwtHeadersFilterConfig config1 = new GeoServerJwtHeadersFilterConfig(); - config1.setId("abc123"); - GeoServerJwtHeadersFilter filter1 = new GeoServerJwtHeadersFilter(); - filter1.initializeFromConfig(config1); - - // trivial cases - Assert.isTrue(!filter1.principleHasChanged(null, null)); - Assert.isTrue(!filter1.principleHasChanged(null, "aaa")); - - UsernamePasswordAuthenticationToken auth_aaa = - new UsernamePasswordAuthenticationToken("aaa", null, null); - UsernamePasswordAuthenticationToken auth_bbb = - new UsernamePasswordAuthenticationToken("bbb", null, null); - - // aaa->aaa - Assert.isTrue(!filter1.principleHasChanged(auth_aaa, "aaa")); - - /// bbb->aaa - Assert.isTrue(filter1.principleHasChanged(auth_bbb, "aaa")); - } -} diff --git a/src/community/jwt-headers/src/test/java/org/geoserver/security/jwtheaders/roles/JwtHeadersRolesExtractorTest.java b/src/community/jwt-headers/src/test/java/org/geoserver/security/jwtheaders/roles/JwtHeadersRolesExtractorTest.java deleted file mode 100644 index 5e704003300..00000000000 --- a/src/community/jwt-headers/src/test/java/org/geoserver/security/jwtheaders/roles/JwtHeadersRolesExtractorTest.java +++ /dev/null @@ -1,55 +0,0 @@ -/* (c) 2024 Open Source Geospatial Foundation - all rights reserved - * This code is licensed under the GPL 2.0 license, available at the root - * application directory. - */ - -package org.geoserver.security.jwtheaders.roles; - -import static org.geoserver.security.jwtheaders.filter.GeoServerJwtHeadersFilterConfig.JWTHeaderRoleSource.JSON; -import static org.geoserver.security.jwtheaders.filter.GeoServerJwtHeadersFilterConfig.JWTHeaderRoleSource.JWT; - -import java.text.ParseException; -import java.util.List; -import java.util.stream.Collectors; -import org.geoserver.security.impl.GeoServerRole; -import org.geoserver.security.jwtheaders.filter.GeoServerJwtHeadersFilterConfig; -import org.junit.Assert; -import org.junit.Test; - -public class JwtHeadersRolesExtractorTest { - - public JwtHeadersRolesExtractor getExtractor( - GeoServerJwtHeadersFilterConfig.JWTHeaderRoleSource roleSource, - String roleConverterMapString, - String path) { - GeoServerJwtHeadersFilterConfig config = new GeoServerJwtHeadersFilterConfig(); - config.setRoleSource(roleSource); - config.setRoleConverterString(roleConverterMapString); - config.setRolesJsonPath(path); - return new JwtHeadersRolesExtractor(config); - } - - @Test - public void testSimpleJwt() throws ParseException { - String accessToken = - "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICItWEdld190TnFwaWRrYTl2QXNJel82WEQtdnJmZDVyMlNWTWkwcWMyR1lNIn0.eyJleHAiOjE3MDcxNTMxNDYsImlhdCI6MTcwNzE1Mjg0NiwiYXV0aF90aW1lIjoxNzA3MTUyNjQ1LCJqdGkiOiJlMzhjY2ZmYy0zMWNjLTQ0NmEtYmU1Yy04MjliNDE0NTkyZmQiLCJpc3MiOiJodHRwczovL2xvZ2luLWxpdmUtZGV2Lmdlb2NhdC5saXZlL3JlYWxtcy9kYXZlLXRlc3QyIiwiYXVkIjoiYWNjb3VudCIsInN1YiI6ImVhMzNlM2NjLWYwZTEtNDIxOC04OWNiLThkNDhjMjdlZWUzZCIsInR5cCI6IkJlYXJlciIsImF6cCI6ImxpdmUta2V5MiIsIm5vbmNlIjoiQldzc2M3cTBKZ0tHZC1OdFc1QlFhVlROMkhSa25LQmVIY0ZMTHZ5OXpYSSIsInNlc3Npb25fc3RhdGUiOiIxY2FiZmU1NC1lOWU0LTRjMmMtODQwNy03NTZiMjczZmFmZmIiLCJhY3IiOiIwIiwicmVhbG1fYWNjZXNzIjp7InJvbGVzIjpbImRlZmF1bHQtcm9sZXMtZGF2ZS10ZXN0MiIsIm9mZmxpbmVfYWNjZXNzIiwidW1hX2F1dGhvcml6YXRpb24iXX0sInJlc291cmNlX2FjY2VzcyI6eyJsaXZlLWtleTIiOnsicm9sZXMiOlsiR2Vvc2VydmVyQWRtaW5pc3RyYXRvciJdfSwiYWNjb3VudCI6eyJyb2xlcyI6WyJtYW5hZ2UtYWNjb3VudCIsIm1hbmFnZS1hY2NvdW50LWxpbmtzIiwidmlldy1wcm9maWxlIl19fSwic2NvcGUiOiJvcGVuaWQgcGhvbmUgb2ZmbGluZV9hY2Nlc3MgbWljcm9wcm9maWxlLWp3dCBwcm9maWxlIGFkZHJlc3MgZW1haWwiLCJzaWQiOiIxY2FiZmU1NC1lOWU0LTRjMmMtODQwNy03NTZiMjczZmFmZmIiLCJ1cG4iOiJkYXZpZC5ibGFzYnlAZ2VvY2F0Lm5ldCIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwiYWRkcmVzcyI6e30sIm5hbWUiOiJkYXZpZCBibGFzYnkiLCJncm91cHMiOlsiZGVmYXVsdC1yb2xlcy1kYXZlLXRlc3QyIiwib2ZmbGluZV9hY2Nlc3MiLCJ1bWFfYXV0aG9yaXphdGlvbiJdLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJkYXZpZC5ibGFzYnlAZ2VvY2F0Lm5ldCIsImdpdmVuX25hbWUiOiJkYXZpZCIsImZhbWlseV9uYW1lIjoiYmxhc2J5IiwiZW1haWwiOiJkYXZpZC5ibGFzYnlAZ2VvY2F0Lm5ldCJ9.fHzXd7oISnqWb09ah9wikfP2UOBeiOA3vd_aDg3Bw-xcfv9aD3CWhAK5FUDPYSPyj4whAcknZbUgUzcm0qkaI8V_aS65F3Fug4jt4nC9YPL4zMSJ5an4Dp6jlQ3OQhrKFn4FwaoW61ndMmScsZZWEQyj6gzHnn5cknqySB26tVydT6q57iTO7KQFcXRdbXd6GWIoFGS-ud9XzxQMUdNfYmsDD7e6hoWhe9PJD9Zq4KT6JN13hUU4Dos-Z5SBHjRa6ieHoOe9gqkjKyA1jT1NU42Nqr-mTV-ql22nAoXuplpvOYc5-09-KDDzSDuVKFwLCNMN3ZyRF1wWuydJeU-gOQ"; - - List roles = - getExtractor(JWT, "", "resource_access.live-key2.roles").getRoles(accessToken) - .stream() - .collect(Collectors.toList()); - Assert.assertEquals(1, roles.size()); - Assert.assertEquals("GeoserverAdministrator", roles.get(0).getAuthority()); - } - - @Test - public void testSimpleJson() throws ParseException { - String json = - "{\"exp\":1707155912,\"iat\":1707155612,\"jti\":\"888715ae-a79d-4633-83e5-9b97dee02bbc\",\"iss\":\"https://login-live-dev.geocat.live/realms/dave-test2\",\"aud\":\"account\",\"sub\":\"ea33e3cc-f0e1-4218-89cb-8d48c27eee3d\",\"typ\":\"Bearer\",\"azp\":\"live-key2\",\"session_state\":\"ae7796fa-b374-4754-a294-e0eb834b23b5\",\"acr\":\"1\",\"realm_access\":{\"roles\":[\"default-roles-dave-test2\",\"offline_access\",\"uma_authorization\"]},\"resource_access\":{\"live-key2\":{\"roles\":[\"GeoserverAdministrator\"]},\"account\":{\"roles\":[\"manage-account\",\"manage-account-links\",\"view-profile\"]}},\"scope\":\"openidprofileemail\",\"sid\":\"ae7796fa-b374-4754-a294-e0eb834b23b5\",\"email_verified\":false,\"name\":\"davidblasby\",\"preferred_username\":\"david.blasby@geocat.net\",\"given_name\":\"david\",\"family_name\":\"blasby\",\"email\":\"david.blasby@geocat.net\"}"; - List roles = - getExtractor(JSON, "", "resource_access.live-key2.roles").getRoles(json).stream() - .collect(Collectors.toList()); - Assert.assertEquals(1, roles.size()); - Assert.assertEquals("GeoserverAdministrator", roles.get(0).getAuthority()); - } -} diff --git a/src/community/jwt-headers/src/test/java/org/geoserver/security/jwtheaders/roles/RoleConverterTest.java b/src/community/jwt-headers/src/test/java/org/geoserver/security/jwtheaders/roles/RoleConverterTest.java deleted file mode 100644 index 12917b3f375..00000000000 --- a/src/community/jwt-headers/src/test/java/org/geoserver/security/jwtheaders/roles/RoleConverterTest.java +++ /dev/null @@ -1,137 +0,0 @@ -/* (c) 2024 Open Source Geospatial Foundation - all rights reserved - * This code is licensed under the GPL 2.0 license, available at the root - * application directory. - */ - -package org.geoserver.security.jwtheaders.roles; - -import java.util.Arrays; -import java.util.List; -import java.util.Map; -import org.geoserver.security.impl.GeoServerRole; -import org.geoserver.security.jwtheaders.filter.GeoServerJwtHeadersFilterConfig; -import org.junit.Assert; -import org.junit.Test; - -public class RoleConverterTest { - - /** - * tests parsing of the roleConverter string - * format:externalRoleName1=GeoServerRoleName1;externalRoleName2=GeoServerRoleName2" These check - * for empty maps - */ - @Test - public void testParseNull() { - GeoServerJwtHeadersFilterConfig config = new GeoServerJwtHeadersFilterConfig(); - - config.setRoleConverterString(null); - Map map = config.getRoleConverterAsMap(); - Assert.assertEquals(0, map.size()); - - config.setRoleConverterString(""); - map = config.getRoleConverterAsMap(); - Assert.assertEquals(0, map.size()); - - config.setRoleConverterString("adadadfafdasdf"); - map = config.getRoleConverterAsMap(); - Assert.assertEquals(0, map.size()); - - config.setRoleConverterString("adadadfafdasdf="); - map = config.getRoleConverterAsMap(); - Assert.assertEquals(0, map.size()); - } - - /** - * tests parsing of the roleConverter string - * format:externalRoleName1=GeoServerRoleName1;externalRoleName2=GeoServerRoleName2" These - * checks simple (correct) inputs - */ - @Test - public void testParseSimple() { - GeoServerJwtHeadersFilterConfig config = new GeoServerJwtHeadersFilterConfig(); - - config.setRoleConverterString("a=b"); - Map map = config.getRoleConverterAsMap(); - Assert.assertEquals(1, map.size()); - Assert.assertTrue(map.containsKey("a")); - Assert.assertEquals("b", map.get("a")); - - config.setRoleConverterString("a=b;c=d"); - map = config.getRoleConverterAsMap(); - Assert.assertEquals(2, map.size()); - Assert.assertTrue(map.containsKey("a")); - Assert.assertEquals("b", map.get("a")); - Assert.assertTrue(map.containsKey("c")); - Assert.assertEquals("d", map.get("c")); - } - - /** - * tests parsing of the roleConverter string - * format:externalRoleName1=GeoServerRoleName1;externalRoleName2=GeoServerRoleName2" These - * checks bad inputs - */ - @Test - public void testParseBad() { - GeoServerJwtHeadersFilterConfig config = new GeoServerJwtHeadersFilterConfig(); - - // bad format - config.setRoleConverterString("a=b;c=;d"); - Map map = config.getRoleConverterAsMap(); - Assert.assertEquals(1, map.size()); - Assert.assertTrue(map.containsKey("a")); - Assert.assertEquals("b", map.get("a")); - - // bad chars - config.setRoleConverterString("a= b** ;c=**;d"); - map = config.getRoleConverterAsMap(); - Assert.assertEquals(1, map.size()); - Assert.assertTrue(map.containsKey("a")); - Assert.assertEquals("b", map.get("a")); - - // removes html tags - config.setRoleConverterString("a=