From 17381452a69eb873830fe1b465fb5c09a0ef00f5 Mon Sep 17 00:00:00 2001 From: Hylke van der Schaaf Date: Thu, 5 Dec 2024 12:07:22 +0100 Subject: [PATCH] Fixed #2072: Users are always anonymous when combining keycloak, finegrained auth & anonymous access --- CHANGELOG.md | 1 + .../auth/keycloak/KeycloakFilter.java | 9 +++-- .../iosb/ilt/statests/TestSuite.java | 16 +++++---- .../f01auth/FineGrainedAuthTests.java | 36 ++++++++++++++----- .../f01auth/FineGrainedAuthTestsBasic.java | 2 +- .../f01auth/FineGrainedAuthTestsKeycloak.java | 13 +++++-- .../FineGrainedAuthTestsKeycloak11.java | 2 +- .../FineGrainedAuthTestsKeycloakAnon11.java | 31 ++++++++++++++++ .../plugin/projects/ProjectRoleDecoder.java | 2 +- 9 files changed, 89 insertions(+), 23 deletions(-) create mode 100644 FROST-Server.Tests/src/test/java/de/fraunhofer/iosb/ilt/statests/f01auth/FineGrainedAuthTestsKeycloakAnon11.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 71ecf0e21..7c4078369 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ **New Features** **Internal changes & Bugfixes** +* Fixed #2072: Users are always anonymous when combining keycloak, finegrained auth and anonymous access. ## Release version 2.5.0 diff --git a/FROST-Server.Auth.Keycloak/src/main/java/de/fraunhofer/iosb/ilt/frostserver/auth/keycloak/KeycloakFilter.java b/FROST-Server.Auth.Keycloak/src/main/java/de/fraunhofer/iosb/ilt/frostserver/auth/keycloak/KeycloakFilter.java index d9897fdd4..2c9a65eca 100644 --- a/FROST-Server.Auth.Keycloak/src/main/java/de/fraunhofer/iosb/ilt/frostserver/auth/keycloak/KeycloakFilter.java +++ b/FROST-Server.Auth.Keycloak/src/main/java/de/fraunhofer/iosb/ilt/frostserver/auth/keycloak/KeycloakFilter.java @@ -172,7 +172,7 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha HttpServletResponse httpResponse = (HttpServletResponse) response; Role requiredRole = findRequiredRoleForRequest(httpRequest); - if (requiredRole == Role.NONE) { + if (!authenticateOnly && requiredRole == Role.NONE) { chain.doFilter(request, response); return; } @@ -236,6 +236,12 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha } } } + + if (requiredRole == Role.NONE) { + chain.doFilter(request, response); + return; + } + AuthChallenge challenge = authenticator.getChallenge(); if (challenge != null) { LOGGER.debug("Challenge."); @@ -244,7 +250,6 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha return; } catch (IllegalStateException ex) { LOGGER.debug("Challenge failed.", ex); - // Failed the challenge. } } LOGGER.debug("User is not allowed."); diff --git a/FROST-Server.Tests/src/test/java/de/fraunhofer/iosb/ilt/statests/TestSuite.java b/FROST-Server.Tests/src/test/java/de/fraunhofer/iosb/ilt/statests/TestSuite.java index 3e42be0e0..772379ee9 100644 --- a/FROST-Server.Tests/src/test/java/de/fraunhofer/iosb/ilt/statests/TestSuite.java +++ b/FROST-Server.Tests/src/test/java/de/fraunhofer/iosb/ilt/statests/TestSuite.java @@ -85,6 +85,7 @@ import de.fraunhofer.iosb.ilt.statests.f01auth.BasicAuthTests11; import de.fraunhofer.iosb.ilt.statests.f01auth.FineGrainedAuthTestsBasic11; import de.fraunhofer.iosb.ilt.statests.f01auth.FineGrainedAuthTestsKeycloak11; +import de.fraunhofer.iosb.ilt.statests.f01auth.FineGrainedAuthTestsKeycloakAnon11; import de.fraunhofer.iosb.ilt.statests.f01auth.KeyCloakAnonReadTests10; import de.fraunhofer.iosb.ilt.statests.f01auth.KeyCloakAnonReadTests11; import de.fraunhofer.iosb.ilt.statests.f01auth.KeyCloakTests10; @@ -172,22 +173,23 @@ Capability7Tests11.class, MqttCoreTests.class, MqttExtraTests.class, + CustomLinksTests10.class, + CustomLinksTests11.class, + MetadataTests10.class, + MetadataTests11.class, BasicAuthTests10.class, BasicAuthTests11.class, BasicAuthAnonReadTests10.class, BasicAuthAnonReadTests11.class, BasicAuthCryptPwTests10.class, BasicAuthCryptPwTests11.class, - FineGrainedAuthTestsBasic11.class, - FineGrainedAuthTestsKeycloak11.class, KeyCloakTests10.class, KeyCloakTests11.class, KeyCloakAnonReadTests10.class, KeyCloakAnonReadTests11.class, - CustomLinksTests10.class, - CustomLinksTests11.class, - MetadataTests10.class, - MetadataTests11.class, + FineGrainedAuthTestsBasic11.class, + FineGrainedAuthTestsKeycloak11.class, + FineGrainedAuthTestsKeycloakAnon11.class, TestSuite.SuiteFinaliser.class }) @Suite @@ -452,7 +454,7 @@ private void startMqttServer(int key, Map parameters) throws IOE String dbDriver = parameters.getOrDefault(PREFIX_PERSISTENCE + TAG_DB_DRIVER, "org.postgresql.Driver"); properties.put(PREFIX_PERSISTENCE + PersistenceSettings.TAG_IMPLEMENTATION_CLASS, VAL_PERSISTENCE_MANAGER); - properties.put(PREFIX_PERSISTENCE + TAG_AUTO_UPDATE_DATABASE, "true"); + properties.put(PREFIX_PERSISTENCE + TAG_AUTO_UPDATE_DATABASE, "false"); properties.put(PREFIX_PERSISTENCE + TAG_DB_DRIVER, dbDriver); properties.put(PREFIX_PERSISTENCE + TAG_DB_URL, createDbUrl(dbDriver, parameters.get(KEY_DB_NAME))); properties.put(PREFIX_PERSISTENCE + TAG_DB_USERNAME, VAL_PG_USER); diff --git a/FROST-Server.Tests/src/test/java/de/fraunhofer/iosb/ilt/statests/f01auth/FineGrainedAuthTests.java b/FROST-Server.Tests/src/test/java/de/fraunhofer/iosb/ilt/statests/f01auth/FineGrainedAuthTests.java index ec390b786..47d129439 100644 --- a/FROST-Server.Tests/src/test/java/de/fraunhofer/iosb/ilt/statests/f01auth/FineGrainedAuthTests.java +++ b/FROST-Server.Tests/src/test/java/de/fraunhofer/iosb/ilt/statests/f01auth/FineGrainedAuthTests.java @@ -152,6 +152,7 @@ private static String resourceUrl(String path, String name) { private static MqttHelper2 mqttHelperAdminProject1; private static MqttHelper2 mqttHelperAdminProject2; + private final boolean anonymousReadAllowed; private final AuthTestHelper ath; protected static void addCommonProperties(Map properties) { @@ -175,8 +176,9 @@ protected static void addCommonProperties(Map properties) { properties.put("auth.mqtt.topicAllowList", "^/[a-zA-Z0-9_-]+\\((('[^']+')|([0-9]+))\\)/[a-zA-Z0-9_-]+$"); } - public FineGrainedAuthTests(ServerVersion version, Map properties) { + public FineGrainedAuthTests(ServerVersion version, Map properties, boolean anonymousReadAllowed) { super(version, properties); + this.anonymousReadAllowed = anonymousReadAllowed; ath = new AuthTestHelper(serverSettings); } @@ -325,7 +327,11 @@ void test_02a_ReadProjects() { testFilterResults(ADMIN, serviceAdmin, mdlUsers.etProject, "", PROJECTS); testFilterResults(WRITE, serviceWrite, mdlUsers.etProject, "", PROJECTS); testFilterResults(READ, serviceRead, mdlUsers.etProject, "", PROJECTS); - filterForException(ANONYMOUS, serviceAnon, mdlUsers.etProject, "", H401); + if (anonymousReadAllowed) { + testFilterResults(ANONYMOUS, serviceAnon, mdlUsers.etProject, "", PROJECTS); + } else { + filterForException(ANONYMOUS, serviceAnon, mdlUsers.etProject, "", H401); + } testFilterResults(ADMIN_P1, serviceAdminProject1, mdlUsers.etProject, "", PROJECTS); testFilterResults(ADMIN_P2, serviceAdminProject2, mdlUsers.etProject, "", PROJECTS); testFilterResults(OBS_CREATE_P1, serviceObsCreaterProject1, mdlUsers.etProject, "", PROJECTS); @@ -369,7 +375,7 @@ void test_03_ReadUserProjectRole() { testFilterResults(serviceAdmin, mdlUsers.etUserProjectRole, "", USER_PROJECT_ROLES); filterForException(WRITE, serviceWrite, mdlUsers.etUserProjectRole, "", HTTP_CODE_404_NOT_FOUND); filterForException(READ, serviceRead, mdlUsers.etUserProjectRole, "", HTTP_CODE_404_NOT_FOUND); - filterForException(ANONYMOUS, serviceAnon, mdlUsers.etUserProjectRole, "", HTTP_CODE_401_UNAUTHORIZED, H403); + filterForException(ANONYMOUS, serviceAnon, mdlUsers.etUserProjectRole, "", anonymousReadAllowed ? new int[]{H404} : new int[]{H401, H403}); filterForException(ADMIN_P1, serviceAdminProject1, mdlUsers.etUserProjectRole, "", HTTP_CODE_404_NOT_FOUND); filterForException(ADMIN_P2, serviceAdminProject2, mdlUsers.etUserProjectRole, "", HTTP_CODE_404_NOT_FOUND); filterForException(OBS_CREATE_P1, serviceObsCreaterProject1, mdlUsers.etUserProjectRole, "", HTTP_CODE_404_NOT_FOUND); @@ -382,7 +388,11 @@ void test_04a_ReadUser() { testFilterResults(serviceAdmin, mdlUsers.etUser, "", USERS); testFilterResults(serviceWrite, mdlUsers.etUser, "", Utils.getFromList(USERS, 6)); testFilterResults(serviceRead, mdlUsers.etUser, "", Utils.getFromList(USERS, 5)); - filterForException(ANONYMOUS, serviceAnon, mdlUsers.etUser, "", H401, H403); + if (anonymousReadAllowed) { + testFilterResults(ANONYMOUS, serviceAnon, mdlUsers.etUser, "", Collections.emptyList()); + } else { + filterForException(ANONYMOUS, serviceAnon, mdlUsers.etUser, "", H401, H403); + } testFilterResults(serviceAdminProject1, mdlUsers.etUser, "", USERS); testFilterResults(serviceAdminProject2, mdlUsers.etUser, "", USERS); testFilterResults(serviceObsCreaterProject1, mdlUsers.etUser, "", Utils.getFromList(USERS, 3)); @@ -408,7 +418,7 @@ void test_05_ReadRole() { testFilterResults(ADMIN, serviceAdmin, mdlUsers.etRole, "", ROLES); filterForException(WRITE, serviceWrite, mdlUsers.etRole, "", HTTP_CODE_404_NOT_FOUND); filterForException(READ, serviceRead, mdlUsers.etRole, "", HTTP_CODE_404_NOT_FOUND); - filterForException(ANONYMOUS, serviceAnon, mdlUsers.etRole, "", H401, H403); + filterForException(ANONYMOUS, serviceAnon, mdlUsers.etRole, "", anonymousReadAllowed ? new int[]{H404} : new int[]{H401, H403}); filterForException(ADMIN_P1, serviceAdminProject1, mdlUsers.etRole, "", HTTP_CODE_404_NOT_FOUND); filterForException(ADMIN_P2, serviceAdminProject2, mdlUsers.etRole, "", HTTP_CODE_404_NOT_FOUND); filterForException(OBS_CREATE_P1, serviceObsCreaterProject1, mdlUsers.etRole, "", HTTP_CODE_404_NOT_FOUND); @@ -489,7 +499,11 @@ void test_08a_ObservationRead() { testFilterResults(ADMIN, serviceAdmin, mdlSensing.etObservation, "", OBSERVATIONS); testFilterResults(WRITE, serviceWrite, mdlSensing.etObservation, "", OBSERVATIONS); testFilterResults(READ, serviceRead, mdlSensing.etObservation, "", OBSERVATIONS); - filterForException(ANONYMOUS, serviceAnon, mdlSensing.etObservation, "", HTTP_CODE_401_UNAUTHORIZED); + if (anonymousReadAllowed) { + testFilterResults(ANONYMOUS, serviceAnon, mdlSensing.etObservation, "", Collections.emptyList()); + } else { + filterForException(ANONYMOUS, serviceAnon, mdlSensing.etObservation, "", HTTP_CODE_401_UNAUTHORIZED); + } testFilterResults(ADMIN_P1, serviceAdminProject1, mdlSensing.etObservation, "", Utils.getFromList(OBSERVATIONS, 0, 1, 2, 3, 4, 5, 6, 7, 16, 17, 18, 19, 20, 21, 22, 23)); testFilterResults(ADMIN_P2, serviceAdminProject2, mdlSensing.etObservation, "", Utils.getFromList(OBSERVATIONS, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23)); testFilterResults(OBS_CREATE_P1, serviceObsCreaterProject1, mdlSensing.etObservation, "", Utils.getFromList(OBSERVATIONS, 0, 1, 2, 3, 4, 5, 6, 7, 16, 17, 18, 19, 20, 21, 22, 23)); @@ -503,7 +517,11 @@ void test_08b_ObservationReadFilter() { testFilterResults(ADMIN, serviceAdmin, mdlSensing.etObservedProperty, filter, Utils.getFromList(O_PROPS, 0)); testFilterResults(WRITE, serviceWrite, mdlSensing.etObservedProperty, filter, Utils.getFromList(O_PROPS, 0)); testFilterResults(READ, serviceRead, mdlSensing.etObservedProperty, filter, Utils.getFromList(O_PROPS, 0)); - filterForException(ANONYMOUS, serviceAnon, mdlSensing.etObservedProperty, filter, H401); + if (anonymousReadAllowed) { + testFilterResults(ANONYMOUS, serviceAnon, mdlSensing.etObservedProperty, filter, Collections.emptyList()); + } else { + filterForException(ANONYMOUS, serviceAnon, mdlSensing.etObservedProperty, filter, H401); + } testFilterResults(ADMIN_P1, serviceAdminProject1, mdlSensing.etObservedProperty, filter, Utils.getFromList(O_PROPS, 0)); testFilterResults(ADMIN_P2, serviceAdminProject2, mdlSensing.etObservedProperty, filter, Collections.emptyList()); testFilterResults(OBS_CREATE_P1, serviceObsCreaterProject1, mdlSensing.etObservedProperty, filter, Utils.getFromList(O_PROPS, 0)); @@ -518,7 +536,7 @@ void test_08c_DatastreamFromObservationRead() throws ServiceFailureException, UR fetchForCode(ADMIN, serviceAdmin, link, H200); fetchForCode(WRITE, serviceWrite, link, H200); fetchForCode(READ, serviceRead, link, H200); - fetchForCode(ANONYMOUS, serviceAnon, link, H401); + fetchForCode(ANONYMOUS, serviceAnon, link, anonymousReadAllowed ? H404 : H401); fetchForCode(ADMIN_P1, serviceAdminProject1, link, H200); fetchForCode(ADMIN_P2, serviceAdminProject2, link, H404); fetchForCode(OBS_CREATE_P1, serviceObsCreaterProject1, link, H200); @@ -533,7 +551,7 @@ void test_08d_ObservationsFromDatastreamRead() throws ServiceFailureException, U fetchForCode(ADMIN, serviceAdmin, link, H200); fetchForCode(WRITE, serviceWrite, link, H200); fetchForCode(READ, serviceRead, link, H200); - fetchForCode(ANONYMOUS, serviceAnon, link, H401); + fetchForCode(ANONYMOUS, serviceAnon, link, anonymousReadAllowed ? H404 : H401); fetchForCode(ADMIN_P1, serviceAdminProject1, link, H200); fetchForCode(ADMIN_P2, serviceAdminProject2, link, H404); fetchForCode(OBS_CREATE_P1, serviceObsCreaterProject1, link, H200); diff --git a/FROST-Server.Tests/src/test/java/de/fraunhofer/iosb/ilt/statests/f01auth/FineGrainedAuthTestsBasic.java b/FROST-Server.Tests/src/test/java/de/fraunhofer/iosb/ilt/statests/f01auth/FineGrainedAuthTestsBasic.java index 1d73f9d24..e9972dcc4 100644 --- a/FROST-Server.Tests/src/test/java/de/fraunhofer/iosb/ilt/statests/f01auth/FineGrainedAuthTestsBasic.java +++ b/FROST-Server.Tests/src/test/java/de/fraunhofer/iosb/ilt/statests/f01auth/FineGrainedAuthTestsBasic.java @@ -74,7 +74,7 @@ public class FineGrainedAuthTestsBasic extends FineGrainedAuthTests { } public FineGrainedAuthTestsBasic(ServerVersion version) { - super(version, SERVER_PROPERTIES); + super(version, SERVER_PROPERTIES, false); } @Override diff --git a/FROST-Server.Tests/src/test/java/de/fraunhofer/iosb/ilt/statests/f01auth/FineGrainedAuthTestsKeycloak.java b/FROST-Server.Tests/src/test/java/de/fraunhofer/iosb/ilt/statests/f01auth/FineGrainedAuthTestsKeycloak.java index d2c941fa4..38bd48b95 100644 --- a/FROST-Server.Tests/src/test/java/de/fraunhofer/iosb/ilt/statests/f01auth/FineGrainedAuthTestsKeycloak.java +++ b/FROST-Server.Tests/src/test/java/de/fraunhofer/iosb/ilt/statests/f01auth/FineGrainedAuthTestsKeycloak.java @@ -48,6 +48,7 @@ public class FineGrainedAuthTestsKeycloak extends FineGrainedAuthTests { private static final Logger LOGGER = LoggerFactory.getLogger(FineGrainedAuthTestsKeycloak.class.getName()); private static final Map SERVER_PROPERTIES = new LinkedHashMap<>(); + private static final Map SERVER_PROPERTIES_ANON = new LinkedHashMap<>(); static { FineGrainedAuthTests.addCommonProperties(SERVER_PROPERTIES); @@ -68,10 +69,18 @@ public class FineGrainedAuthTestsKeycloak extends FineGrainedAuthTests { SERVER_PROPERTIES.put("auth.authenticateOnly", "true"); SERVER_PROPERTIES.put("auth.registerUserLocally", "true"); SERVER_PROPERTIES.put("auth.userRoleDecoderClass", ProjectRoleDecoder.class.getName()); + + final String dbNameAnon = "fineGrainedAuthKeycloakAnon"; + SERVER_PROPERTIES_ANON.putAll(SERVER_PROPERTIES); + SERVER_PROPERTIES_ANON.put("auth.db.url", TestSuite.createDbUrl(dbDriver, dbNameAnon)); + SERVER_PROPERTIES_ANON.put(KEY_DB_NAME, dbNameAnon); + SERVER_PROPERTIES_ANON.put("auth_allowAnonymousRead", "true"); } - public FineGrainedAuthTestsKeycloak(ServerVersion version) { - super(version, SERVER_PROPERTIES); + public FineGrainedAuthTestsKeycloak(ServerVersion version, boolean anonymousReadAllowed) { + super(version, + anonymousReadAllowed ? SERVER_PROPERTIES_ANON : SERVER_PROPERTIES, + anonymousReadAllowed); } @BeforeEach diff --git a/FROST-Server.Tests/src/test/java/de/fraunhofer/iosb/ilt/statests/f01auth/FineGrainedAuthTestsKeycloak11.java b/FROST-Server.Tests/src/test/java/de/fraunhofer/iosb/ilt/statests/f01auth/FineGrainedAuthTestsKeycloak11.java index b5f16b89a..8940d5d08 100644 --- a/FROST-Server.Tests/src/test/java/de/fraunhofer/iosb/ilt/statests/f01auth/FineGrainedAuthTestsKeycloak11.java +++ b/FROST-Server.Tests/src/test/java/de/fraunhofer/iosb/ilt/statests/f01auth/FineGrainedAuthTestsKeycloak11.java @@ -25,7 +25,7 @@ public class FineGrainedAuthTestsKeycloak11 extends FineGrainedAuthTestsKeycloak { public FineGrainedAuthTestsKeycloak11() { - super(ServerVersion.v_1_1); + super(ServerVersion.v_1_1, false); } } diff --git a/FROST-Server.Tests/src/test/java/de/fraunhofer/iosb/ilt/statests/f01auth/FineGrainedAuthTestsKeycloakAnon11.java b/FROST-Server.Tests/src/test/java/de/fraunhofer/iosb/ilt/statests/f01auth/FineGrainedAuthTestsKeycloakAnon11.java new file mode 100644 index 000000000..7e619ed10 --- /dev/null +++ b/FROST-Server.Tests/src/test/java/de/fraunhofer/iosb/ilt/statests/f01auth/FineGrainedAuthTestsKeycloakAnon11.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2024 Fraunhofer Institut IOSB, Fraunhoferstr. 1, D 76131 + * Karlsruhe, Germany. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ +package de.fraunhofer.iosb.ilt.statests.f01auth; + +import de.fraunhofer.iosb.ilt.statests.ServerVersion; + +/** + * Runs the FineGrained Auth Tests using Keycloak Auth on the v1.1 API. + */ +public class FineGrainedAuthTestsKeycloakAnon11 extends FineGrainedAuthTestsKeycloak { + + public FineGrainedAuthTestsKeycloakAnon11() { + super(ServerVersion.v_1_1, true); + } + +} diff --git a/Plugins/Projects/src/main/java/de/fraunhofer/iosb/ilt/frostserver/plugin/projects/ProjectRoleDecoder.java b/Plugins/Projects/src/main/java/de/fraunhofer/iosb/ilt/frostserver/plugin/projects/ProjectRoleDecoder.java index 732135ff6..cb3862beb 100644 --- a/Plugins/Projects/src/main/java/de/fraunhofer/iosb/ilt/frostserver/plugin/projects/ProjectRoleDecoder.java +++ b/Plugins/Projects/src/main/java/de/fraunhofer/iosb/ilt/frostserver/plugin/projects/ProjectRoleDecoder.java @@ -37,7 +37,7 @@ import org.slf4j.LoggerFactory; /** - * Decodes Roles for the Projects plugsin. + * Decodes Roles for the Projects plugin. */ public class ProjectRoleDecoder implements UserRoleDecoder, ConfigDefaults {