diff --git a/.github/test/oauth2/import/fts-realm.json b/.github/test/oauth2/import/fts-realm.json index 64873161..628db778 100644 --- a/.github/test/oauth2/import/fts-realm.json +++ b/.github/test/oauth2/import/fts-realm.json @@ -145,9 +145,112 @@ }, "clients": [ { - "id": "34ed2b5c-6e6d-4477-bfa2-f9c36f4f6133", + "id": "ee7a80f4-da2f-4bc8-962b-369c9fd36b1c", "clientId": "fts-client", - "name": "FTS Client", + "name": "FTS CD Client", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "secret": "tIQfOvBuhyR1dw9OQ3E4tCeTvcHtiW84", + "redirectUris": [ + "http://localhost:8080/*" + ], + "webOrigins": [ + "http://localhost:8080" + ], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": true, + "serviceAccountsEnabled": true, + "publicClient": false, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "realm_client": "false", + "access.token.lifespan": "1800", + "client.secret.creation.time": "1737365263", + "backchannel.logout.session.required": "true", + "backchannel.logout.revoke.offline.tokens": "false" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": true, + "nodeReRegistrationTimeout": -1, + "protocolMappers": [ + { + "id": "2787d9e7-4f4a-4443-8efd-b83b392c8a2e", + "name": "Client IP Address", + "protocol": "openid-connect", + "protocolMapper": "oidc-usersessionmodel-note-mapper", + "consentRequired": false, + "config": { + "user.session.note": "clientAddress", + "id.token.claim": "true", + "introspection.token.claim": "true", + "access.token.claim": "true", + "claim.name": "clientAddress", + "jsonType.label": "String" + } + }, + { + "id": "911d7a9a-0015-4e18-8f75-9b29711b23b0", + "name": "Client Host", + "protocol": "openid-connect", + "protocolMapper": "oidc-usersessionmodel-note-mapper", + "consentRequired": false, + "config": { + "user.session.note": "clientHost", + "id.token.claim": "true", + "introspection.token.claim": "true", + "access.token.claim": "true", + "claim.name": "clientHost", + "jsonType.label": "String" + } + }, + { + "id": "08117557-9386-4a7e-b9b2-c5c67305ade9", + "name": "Client ID", + "protocol": "openid-connect", + "protocolMapper": "oidc-usersessionmodel-note-mapper", + "consentRequired": false, + "config": { + "user.session.note": "client_id", + "id.token.claim": "true", + "introspection.token.claim": "true", + "access.token.claim": "true", + "claim.name": "client_id", + "jsonType.label": "String" + } + } + ], + "defaultClientScopes": [ + "web-origins", + "acr", + "profile", + "roles", + "basic", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "organization", + "offline_access", + "microprofile-jwt" + ], + "access": { + "view": true, + "configure": true, + "manage": true + } + }, + { + "id": "34ed2b5c-6e6d-4477-bfa2-f9c36f4f6133", + "clientId": "cd-client", + "name": "FTS CD Client", "surrogateAuthRequired": false, "enabled": true, "alwaysDisplayInConsole": false, @@ -246,13 +349,119 @@ "configure": true, "manage": true } + }, + { + "id": "f03b64d9-c57b-4061-a781-52e29bf05084", + "clientId": "rd-client", + "name": "FTS RD Client", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "secret": "tIQfOvBuhyR1dw9OQ3E4tCeTvcHtiW84", + "redirectUris": [ + "http://localhost:8080/*" + ], + "webOrigins": [ + "http://localhost:8080" + ], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": true, + "serviceAccountsEnabled": true, + "publicClient": false, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "realm_client": "false", + "access.token.lifespan": "1800", + "client.secret.creation.time": "1737365263", + "backchannel.logout.session.required": "true", + "backchannel.logout.revoke.offline.tokens": "false" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": true, + "nodeReRegistrationTimeout": -1, + "protocolMappers": [ + { + "id": "08f5928a-1673-4d08-bf8d-5fc52b37ea2d", + "name": "Client IP Address", + "protocol": "openid-connect", + "protocolMapper": "oidc-usersessionmodel-note-mapper", + "consentRequired": false, + "config": { + "user.session.note": "clientAddress", + "id.token.claim": "true", + "introspection.token.claim": "true", + "access.token.claim": "true", + "claim.name": "clientAddress", + "jsonType.label": "String" + } + }, + { + "id": "49404a86-a5aa-4dd9-b847-a8474bbd0841", + "name": "Client Host", + "protocol": "openid-connect", + "protocolMapper": "oidc-usersessionmodel-note-mapper", + "consentRequired": false, + "config": { + "user.session.note": "clientHost", + "id.token.claim": "true", + "introspection.token.claim": "true", + "access.token.claim": "true", + "claim.name": "clientHost", + "jsonType.label": "String" + } + }, + { + "id": "32165832-73ef-4a51-8a8c-75310171c9e4", + "name": "Client ID", + "protocol": "openid-connect", + "protocolMapper": "oidc-usersessionmodel-note-mapper", + "consentRequired": false, + "config": { + "user.session.note": "client_id", + "id.token.claim": "true", + "introspection.token.claim": "true", + "access.token.claim": "true", + "claim.name": "client_id", + "jsonType.label": "String" + } + } + ], + "defaultClientScopes": [ + "web-origins", + "acr", + "profile", + "roles", + "basic", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "organization", + "offline_access", + "microprofile-jwt" + ], + "access": { + "view": true, + "configure": true, + "manage": true + } } ], "roles": { "client": { "fts-client": [ { - "name": "client" + "name": "cd-client" + }, + { + "name": "rd-client" } ] } @@ -260,25 +469,44 @@ "clientScopeMappings": { "fts-client": [ { - "client": "fts-client", + "client": "cd-client", "roles": [ - "client" + "cd-client" + ] + }, + { + "client": "rd-client", + "roles": [ + "rd-client" ] } ] }, "users": [ { - "username": "service-account-fts-client", + "username": "service-account-cd-client", + "enabled": true, + "serviceAccountClientId": "cd-client", + "clientRoles": { + "fts-client": [ + "cd-client" + ] + }, + "realmRoles": [ + "default-roles-cd" + ] + }, + { + "username": "service-account-rd-client", "enabled": true, - "serviceAccountClientId": "fts-client", + "serviceAccountClientId": "rd-client", "clientRoles": { "fts-client": [ - "client" + "rd-client" ] }, "realmRoles": [ - "default-roles-fts" + "default-roles-rd" ] } ] diff --git a/clinical-domain-agent/application-auth:basic.yaml b/clinical-domain-agent/application-auth:basic.yaml index 5f76599e..828aeaef 100644 --- a/clinical-domain-agent/application-auth:basic.yaml +++ b/clinical-domain-agent/application-auth:basic.yaml @@ -5,7 +5,7 @@ security: - username: client password: "{bcrypt}$2a$10$4i1TQpnBlcKOdUYO9O850.jJ8yGO8x9fQuu/l3Ki3HXgv0t9NOr4y" # password: "{noop}2mXA742aw7CGaLU6" - role: client + role: cd-client test: webclient: diff --git a/clinical-domain-agent/application-auth:oauth2.yaml b/clinical-domain-agent/application-auth:oauth2.yaml index 32c58580..0fc7435b 100644 --- a/clinical-domain-agent/application-auth:oauth2.yaml +++ b/clinical-domain-agent/application-auth:oauth2.yaml @@ -11,7 +11,7 @@ spring: registration: agent: authorization-grant-type: client_credentials - client-id: fts-client + client-id: cd-client client-secret: tIQfOvBuhyR1dw9OQ3E4tCeTvcHtiW84 provider: keycloak provider: diff --git a/clinical-domain-agent/src/main/resources/application.yaml b/clinical-domain-agent/src/main/resources/application.yaml index 90e916ad..d8ee4bd4 100644 --- a/clinical-domain-agent/src/main/resources/application.yaml +++ b/clinical-domain-agent/src/main/resources/application.yaml @@ -14,7 +14,7 @@ management: security: endpoints: - path: /api/v2/** - role: client + role: cd-client runner: maxSendConcurrency: 32 diff --git a/clinical-domain-agent/src/test/resources/application.yaml b/clinical-domain-agent/src/test/resources/application.yaml index a259197f..fa290383 100644 --- a/clinical-domain-agent/src/test/resources/application.yaml +++ b/clinical-domain-agent/src/test/resources/application.yaml @@ -34,7 +34,7 @@ test: security: endpoints: - path: /api/v2/** - role: client + role: cd-client logging.level: care.smith.fts: DEBUG diff --git a/research-domain-agent/application-auth:basic.yaml b/research-domain-agent/application-auth:basic.yaml index 401a41bb..d1ba7e56 100644 --- a/research-domain-agent/application-auth:basic.yaml +++ b/research-domain-agent/application-auth:basic.yaml @@ -5,7 +5,7 @@ security: - username: cd-agent password: "{bcrypt}$2a$10$kUT57nDMEPtigO3BtsD/UeQMLsBDsOwu4iFVAEcgucPbD1zGaHI5y" # password: "{noop}bdfXkmQQIQLEkvVq" - role: client + role: cd-client test: webclient: diff --git a/research-domain-agent/application-auth:oauth2.yaml b/research-domain-agent/application-auth:oauth2.yaml index 32c58580..f0381525 100644 --- a/research-domain-agent/application-auth:oauth2.yaml +++ b/research-domain-agent/application-auth:oauth2.yaml @@ -11,7 +11,7 @@ spring: registration: agent: authorization-grant-type: client_credentials - client-id: fts-client + client-id: rd-client client-secret: tIQfOvBuhyR1dw9OQ3E4tCeTvcHtiW84 provider: keycloak provider: diff --git a/research-domain-agent/src/test/resources/application.yaml b/research-domain-agent/src/test/resources/application.yaml index 99d50f91..c079a7f1 100644 --- a/research-domain-agent/src/test/resources/application.yaml +++ b/research-domain-agent/src/test/resources/application.yaml @@ -28,7 +28,7 @@ test: security: endpoints: - path: /api/v2/** - role: client + role: rd-client logging.level: care.smith.fts: DEBUG diff --git a/test-util/src/main/java/care/smith/fts/test/TestWebClientFactory.java b/test-util/src/main/java/care/smith/fts/test/TestWebClientFactory.java index 954bb958..3ffaf731 100644 --- a/test-util/src/main/java/care/smith/fts/test/TestWebClientFactory.java +++ b/test-util/src/main/java/care/smith/fts/test/TestWebClientFactory.java @@ -35,6 +35,9 @@ public WebClient webClient(String baseUrl) { } public WebClient webClient(String baseUrl, String clientName) { + + log.debug("TestWebClientFactory baseurl: {}, clientName: {}", baseUrl, clientName); + return config .findConfigurationEntry(clientName) .map(c -> factory.create(new HttpClientConfig(baseUrl, c.auth(), c.ssl()))) diff --git a/trust-center-agent/application-auth:basic.yaml b/trust-center-agent/application-auth:basic.yaml index 67d48eb6..1ea31903 100644 --- a/trust-center-agent/application-auth:basic.yaml +++ b/trust-center-agent/application-auth:basic.yaml @@ -5,11 +5,11 @@ security: - username: cd-agent password: "{bcrypt}$2a$10$S7FXGqbbci2YOjBAAaeC9.KaTP8sZ2Hyi5d3aub1L..oe3L2kqv/K" # password: "{noop}Aj6cloJYsTpu+op+" - role: client + role: cd-client - username: rd-agent password: "{bcrypt}$2a$10$m0kteW3J47snneNzGTzkzeAtGo8FfODkmPP0uLXOz8uRvkc5Lqwme" # password: "{noop}1J5MhEhhiGh33dgt" - role: client + role: rd-client test: webclient: diff --git a/trust-center-agent/application-auth:oauth2.yaml b/trust-center-agent/application-auth:oauth2.yaml index 7d0f97bd..912980e1 100644 --- a/trust-center-agent/application-auth:oauth2.yaml +++ b/trust-center-agent/application-auth:oauth2.yaml @@ -9,9 +9,14 @@ spring: oauth2: client: registration: - agent: + cd-agent: authorization-grant-type: client_credentials - client-id: fts-client + client-id: cd-client + client-secret: tIQfOvBuhyR1dw9OQ3E4tCeTvcHtiW84 + provider: keycloak + rd-agent: + authorization-grant-type: client_credentials + client-id: rd-client client-secret: tIQfOvBuhyR1dw9OQ3E4tCeTvcHtiW84 provider: keycloak provider: @@ -23,8 +28,8 @@ test: cd-agent: auth: oauth2: - registration: agent + registration: cd-agent rd-agent: auth: oauth2: - registration: agent + registration: rd-agent diff --git a/trust-center-agent/src/main/resources/application.yaml b/trust-center-agent/src/main/resources/application.yaml index 3f856a09..20ad9ea9 100644 --- a/trust-center-agent/src/main/resources/application.yaml +++ b/trust-center-agent/src/main/resources/application.yaml @@ -13,9 +13,9 @@ management: security: endpoints: - path: /api/v2/cd/** - role: client + role: cd-client - path: /api/v2/rd/** - role: client + role: rd-client deIdentification: keystoreUrl: redis://keystore:6379 diff --git a/trust-center-agent/src/test/java/care/smith/fts/tca/rest/DeIdentificationControllerIT.java b/trust-center-agent/src/test/java/care/smith/fts/tca/rest/DeIdentificationControllerIT.java index e30d1347..0bc9cdba 100644 --- a/trust-center-agent/src/test/java/care/smith/fts/tca/rest/DeIdentificationControllerIT.java +++ b/trust-center-agent/src/test/java/care/smith/fts/tca/rest/DeIdentificationControllerIT.java @@ -50,9 +50,12 @@ class DeIdentificationControllerIT extends BaseIT { private static WebClient rdClient; @BeforeAll - static void setUp(@LocalServerPort int port, @Autowired TestWebClientFactory factory) { - cdClient = factory.webClient("https://localhost:" + port, "cd-agent"); - rdClient = factory.webClient("https://localhost:" + port, "rd-agent"); + static void setUp( + @LocalServerPort int cd_port, + @LocalServerPort int rd_port, + @Autowired TestWebClientFactory factory) { + cdClient = factory.webClient("https://localhost:" + cd_port, "cd-agent"); + rdClient = factory.webClient("https://localhost:" + rd_port, "rd-agent"); } @Test diff --git a/trust-center-agent/src/test/resources/application.yaml b/trust-center-agent/src/test/resources/application.yaml index 75168fd0..628cc87d 100644 --- a/trust-center-agent/src/test/resources/application.yaml +++ b/trust-center-agent/src/test/resources/application.yaml @@ -23,9 +23,9 @@ logging.level: security: endpoints: - path: /api/v2/cd/** - role: client + role: cd-client - path: /api/v2/rd/** - role: client + role: rd-client spring: main: diff --git a/util/src/main/java/care/smith/fts/util/AgentConfiguration.java b/util/src/main/java/care/smith/fts/util/AgentConfiguration.java index 9e085fa4..114b4266 100644 --- a/util/src/main/java/care/smith/fts/util/AgentConfiguration.java +++ b/util/src/main/java/care/smith/fts/util/AgentConfiguration.java @@ -4,11 +4,13 @@ import ca.uhn.fhir.context.FhirContext; import care.smith.fts.util.auth.HttpServerAuthConfig; +import care.smith.fts.util.auth.OAuth2ConfigurationExistsCondition; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import java.net.http.HttpClient; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Primary; @@ -47,7 +49,7 @@ public ObjectMapper defaultObjectMapper() { } @Bean - @ConditionalOnProperty(name = "spring.security.oauth2.client.registration.agent.provider") + @Conditional(OAuth2ConfigurationExistsCondition.class) public ReactiveOAuth2AuthorizedClientManager authorizedClientManager( ReactiveClientRegistrationRepository clientRegistrationRepository, ReactiveOAuth2AuthorizedClientService authorizedClientService) { diff --git a/util/src/main/java/care/smith/fts/util/auth/HttpClientOAuth2Auth.java b/util/src/main/java/care/smith/fts/util/auth/HttpClientOAuth2Auth.java index 1ef3af44..d5fd8d00 100644 --- a/util/src/main/java/care/smith/fts/util/auth/HttpClientOAuth2Auth.java +++ b/util/src/main/java/care/smith/fts/util/auth/HttpClientOAuth2Auth.java @@ -2,7 +2,7 @@ import care.smith.fts.util.auth.HttpClientOAuth2Auth.Config; import lombok.extern.slf4j.Slf4j; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Conditional; import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientManager; import org.springframework.security.oauth2.client.web.reactive.function.client.ServerOAuth2AuthorizedClientExchangeFilterFunction; import org.springframework.stereotype.Component; @@ -11,8 +11,7 @@ /** OAuth2 Authentication using oauth2 client credentials flow. */ @Slf4j @Component -// @ConditionalOnProperty(prefix = "spring.security.oauth2") -@ConditionalOnProperty(name = "spring.security.oauth2.client.registration.agent.provider") +@Conditional(OAuth2ConfigurationExistsCondition.class) public class HttpClientOAuth2Auth implements HttpClientAuth { private final ReactiveOAuth2AuthorizedClientManager clientManager; diff --git a/util/src/main/java/care/smith/fts/util/auth/OAuth2ConfigurationExistsCondition.java b/util/src/main/java/care/smith/fts/util/auth/OAuth2ConfigurationExistsCondition.java new file mode 100644 index 00000000..270beeec --- /dev/null +++ b/util/src/main/java/care/smith/fts/util/auth/OAuth2ConfigurationExistsCondition.java @@ -0,0 +1,20 @@ +package care.smith.fts.util.auth; + +import java.util.Arrays; +import org.springframework.context.annotation.Condition; +import org.springframework.context.annotation.ConditionContext; +import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.core.env.EnumerablePropertySource; +import org.springframework.core.type.AnnotatedTypeMetadata; + +public class OAuth2ConfigurationExistsCondition implements Condition { + @Override + public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { + ConfigurableEnvironment env = (ConfigurableEnvironment) context.getEnvironment(); + return env.getPropertySources().stream() + .filter(ps -> ps instanceof EnumerablePropertySource) + .map(ps -> (EnumerablePropertySource) ps) + .flatMap(ps -> Arrays.stream(ps.getPropertyNames())) + .anyMatch(prop -> prop.startsWith("spring.security.oauth2")); + } +} diff --git a/util/src/test/java/care/smith/fts/util/auth/HttpServerAuthConfigTest.java b/util/src/test/java/care/smith/fts/util/auth/HttpServerAuthConfigTest.java index 74ef3cdc..99b5edc9 100644 --- a/util/src/test/java/care/smith/fts/util/auth/HttpServerAuthConfigTest.java +++ b/util/src/test/java/care/smith/fts/util/auth/HttpServerAuthConfigTest.java @@ -14,7 +14,6 @@ void noUserDetailsForNoneAuth() { var config = new HttpServerAuthConfig(); var reactiveUserDetailsService = config.userDetailsService(); - assertThat(reactiveUserDetailsService).isNull(); } diff --git a/util/src/test/java/care/smith/fts/util/auth/HttpServerOAuth2AuthTest.java b/util/src/test/java/care/smith/fts/util/auth/HttpServerOAuth2AuthTest.java index aa00ba7e..b775967e 100644 --- a/util/src/test/java/care/smith/fts/util/auth/HttpServerOAuth2AuthTest.java +++ b/util/src/test/java/care/smith/fts/util/auth/HttpServerOAuth2AuthTest.java @@ -3,24 +3,32 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.*; +import java.util.List; +import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.Test; import org.springframework.security.access.SecurityConfig; import org.springframework.security.config.web.server.ServerHttpSecurity; import org.springframework.security.web.server.SecurityWebFilterChain; import org.springframework.security.web.server.authentication.AuthenticationWebFilter; +import org.springframework.security.web.server.authentication.ServerAuthenticationConverter; import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher; +import org.springframework.web.server.WebFilter; +@Slf4j class HttpServerOAuth2AuthTest { HttpServerOAuth2Auth auth = new HttpServerOAuth2Auth("issuer", "clientId"); @Test void configure() { var security = ServerHttpSecurity.http(); - var result = auth.configure(security).build(); - assertThat(result.getWebFilters().collectList().block()) + var chain = auth.configure(security).build(); + + var filters = chain.getWebFilters().collectList().block(); + + assertThat(filters) .filteredOn(filter -> filter instanceof AuthenticationWebFilter) - .hasSize(1); + .isNotEmpty(); } @Test