diff --git a/application/pom.xml b/application/pom.xml
index 245ee9b7..6912d25c 100644
--- a/application/pom.xml
+++ b/application/pom.xml
@@ -12,7 +12,7 @@
org.springframework.boot
spring-boot-starter-parent
- 3.3.2
+ 3.3.5
../
it.smartcommunitylabdhub
@@ -71,6 +71,11 @@
spring-security-oauth2-resource-server
${spring-security.version}
+
+ org.springframework.security
+ spring-security-oauth2-client
+ ${spring-security.version}
+
org.springframework.security
spring-security-oauth2-jose
@@ -270,6 +275,16 @@
dh-authorization
${revision}
+
+ it.smartcommunitylabdhub
+ credentials-provider-minio
+ ${revision}
+
+
+ it.smartcommunitylabdhub
+ credentials-provider-db
+ ${revision}
+
diff --git a/application/src/main/java/it/smartcommunitylabdhub/core/components/config/CoreConfig.java b/application/src/main/java/it/smartcommunitylabdhub/core/components/config/CoreConfig.java
new file mode 100644
index 00000000..458b571c
--- /dev/null
+++ b/application/src/main/java/it/smartcommunitylabdhub/core/components/config/CoreConfig.java
@@ -0,0 +1,52 @@
+/**
+ * Copyright 2025 the original author or authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package it.smartcommunitylabdhub.core.components.config;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import it.smartcommunitylabdhub.commons.infrastructure.AbstractConfiguration;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import lombok.ToString;
+
+@Getter
+@Setter
+@ToString
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class CoreConfig extends AbstractConfiguration {
+
+ @JsonProperty("endpoint")
+ private String endpoint;
+
+ @JsonProperty("name")
+ private String name;
+
+ @JsonProperty("version")
+ private String version;
+
+ @JsonProperty("api_level")
+ private String level;
+
+ @JsonProperty("api_version")
+ private String api;
+}
diff --git a/application/src/main/java/it/smartcommunitylabdhub/core/components/config/CoreConfigProvider.java b/application/src/main/java/it/smartcommunitylabdhub/core/components/config/CoreConfigProvider.java
new file mode 100644
index 00000000..d9ee5506
--- /dev/null
+++ b/application/src/main/java/it/smartcommunitylabdhub/core/components/config/CoreConfigProvider.java
@@ -0,0 +1,57 @@
+/**
+ * Copyright 2025 the original author or authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package it.smartcommunitylabdhub.core.components.config;
+
+import it.smartcommunitylabdhub.commons.config.ApplicationProperties;
+import it.smartcommunitylabdhub.commons.infrastructure.ConfigurationProvider;
+import it.smartcommunitylabdhub.core.components.config.CoreConfig.CoreConfigBuilder;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import org.springframework.util.Assert;
+
+@Service
+@Slf4j
+public class CoreConfigProvider implements ConfigurationProvider {
+
+ private CoreConfig config;
+
+ public CoreConfigProvider(ApplicationProperties properties) {
+ Assert.notNull(properties, "properties can not be null");
+
+ log.debug("Build configuration for provider...");
+
+ //build config
+ CoreConfigBuilder builder = CoreConfig
+ .builder()
+ .endpoint(properties.getEndpoint())
+ .name(properties.getName())
+ .version(properties.getVersion())
+ .level(properties.getLevel())
+ .api(properties.getApi());
+
+ this.config = builder.build();
+
+ if (log.isTraceEnabled()) {
+ log.trace("config: {}", config.toJson());
+ }
+ }
+
+ @Override
+ public CoreConfig getConfig() {
+ return config;
+ }
+}
diff --git a/application/src/main/java/it/smartcommunitylabdhub/core/components/run/states/RunStateBuilt.java b/application/src/main/java/it/smartcommunitylabdhub/core/components/run/states/RunStateBuilt.java
index 4705faa8..f72f26e1 100644
--- a/application/src/main/java/it/smartcommunitylabdhub/core/components/run/states/RunStateBuilt.java
+++ b/application/src/main/java/it/smartcommunitylabdhub/core/components/run/states/RunStateBuilt.java
@@ -1,22 +1,21 @@
package it.smartcommunitylabdhub.core.components.run.states;
-import it.smartcommunitylabdhub.authorization.services.JwtTokenService;
+import it.smartcommunitylabdhub.authorization.model.UserAuthentication;
+import it.smartcommunitylabdhub.authorization.services.CredentialsService;
import it.smartcommunitylabdhub.commons.accessors.spec.RunSpecAccessor;
-import it.smartcommunitylabdhub.commons.config.SecurityProperties;
+import it.smartcommunitylabdhub.commons.infrastructure.Credentials;
import it.smartcommunitylabdhub.commons.infrastructure.RunRunnable;
import it.smartcommunitylabdhub.commons.infrastructure.SecuredRunnable;
import it.smartcommunitylabdhub.commons.models.enums.State;
+import it.smartcommunitylabdhub.core.components.security.UserAuthenticationHelper;
import it.smartcommunitylabdhub.core.fsm.RunContext;
import it.smartcommunitylabdhub.core.fsm.RunEvent;
import it.smartcommunitylabdhub.fsm.FsmState;
import it.smartcommunitylabdhub.fsm.Transition;
-import java.io.Serializable;
import java.util.List;
import java.util.Optional;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.security.core.Authentication;
-import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
@Slf4j
@@ -24,10 +23,7 @@
public class RunStateBuilt implements FsmState.Builder {
@Autowired
- JwtTokenService jwtTokenService;
-
- @Autowired
- SecurityProperties securityProperties;
+ CredentialsService credentialsService;
public FsmState build() {
//define state
@@ -49,16 +45,14 @@ public FsmState build() {
Optional runnable = Optional.ofNullable(context.runtime.run(context.run));
runnable.ifPresent(r -> {
//extract auth from security context to inflate secured credentials
- //TODO refactor properly
- if (r instanceof SecuredRunnable) {
- // check that auth is enabled via securityProperties
- Authentication auth = SecurityContextHolder.getContext().getAuthentication();
- if (auth != null && securityProperties.isRequired()) {
- Serializable credentials = jwtTokenService.generateCredentials(auth);
- if (credentials != null) {
- ((SecuredRunnable) r).setCredentials(credentials);
- }
- }
+ UserAuthentication> auth = UserAuthenticationHelper.getUserAuthentication();
+ if (auth != null && r instanceof SecuredRunnable) {
+ //get credentials from providers
+ List credentials = credentialsService.getCredentials(
+ (UserAuthentication>) auth
+ );
+
+ ((SecuredRunnable) r).setCredentials(credentials);
}
});
diff --git a/application/src/main/java/it/smartcommunitylabdhub/core/components/security/UserAuthenticationHelper.java b/application/src/main/java/it/smartcommunitylabdhub/core/components/security/UserAuthenticationHelper.java
new file mode 100644
index 00000000..c42fe058
--- /dev/null
+++ b/application/src/main/java/it/smartcommunitylabdhub/core/components/security/UserAuthenticationHelper.java
@@ -0,0 +1,39 @@
+package it.smartcommunitylabdhub.core.components.security;
+
+import it.smartcommunitylabdhub.authorization.model.UserAuthentication;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder;
+
+public class UserAuthenticationHelper {
+
+ public static UserAuthentication> getUserAuthentication() {
+ Authentication auth = SecurityContextHolder.getContext().getAuthentication();
+
+ if (auth == null) {
+ return null;
+ }
+
+ if (auth instanceof UserAuthentication) {
+ return (UserAuthentication>) auth;
+ }
+
+ // //workaround: inflate basic auth tokens
+ // //TODO define authManager to produce proper authentication
+ // if (auth instanceof UsernamePasswordAuthenticationToken) {
+ // UserAuthentication user = new UserAuthentication<>(
+ // (UsernamePasswordAuthenticationToken) auth,
+ // auth.getName(),
+ // auth.getAuthorities()
+ // );
+
+ // //update context
+ // SecurityContextHolder.getContext().setAuthentication(user);
+
+ // return user;
+ // }
+
+ return null;
+ }
+
+ private UserAuthenticationHelper() {}
+}
diff --git a/application/src/main/java/it/smartcommunitylabdhub/core/config/SecurityConfig.java b/application/src/main/java/it/smartcommunitylabdhub/core/config/SecurityConfig.java
index d3c76326..36bf852d 100644
--- a/application/src/main/java/it/smartcommunitylabdhub/core/config/SecurityConfig.java
+++ b/application/src/main/java/it/smartcommunitylabdhub/core/config/SecurityConfig.java
@@ -1,20 +1,20 @@
package it.smartcommunitylabdhub.core.config;
-import com.nimbusds.jose.JOSEException;
-import com.nimbusds.jose.jwk.JWK;
-import com.nimbusds.jose.jwk.RSAKey;
+import it.smartcommunitylabdhub.authorization.UserAuthenticationManager;
+import it.smartcommunitylabdhub.authorization.UserAuthenticationManagerBuilder;
import it.smartcommunitylabdhub.authorization.config.KeyStoreConfig;
import it.smartcommunitylabdhub.authorization.services.AuthorizableAwareEntityService;
+import it.smartcommunitylabdhub.authorization.services.JwtTokenService;
import it.smartcommunitylabdhub.commons.config.ApplicationProperties;
import it.smartcommunitylabdhub.commons.config.SecurityProperties;
-import it.smartcommunitylabdhub.commons.config.SecurityProperties.JwtAuthenticationProperties;
+import it.smartcommunitylabdhub.commons.config.SecurityProperties.OidcAuthenticationProperties;
import it.smartcommunitylabdhub.commons.models.project.Project;
import jakarta.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.HashSet;
import java.util.List;
-import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import lombok.extern.slf4j.Slf4j;
@@ -26,7 +26,8 @@
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.domain.AuditorAware;
import org.springframework.security.authentication.AbstractAuthenticationToken;
-import org.springframework.security.authentication.ProviderManager;
+import org.springframework.security.authentication.AuthenticationProvider;
+import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
@@ -36,6 +37,12 @@
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.security.crypto.factory.PasswordEncoderFactories;
+import org.springframework.security.oauth2.client.registration.ClientRegistration;
+import org.springframework.security.oauth2.client.registration.ClientRegistrations;
+import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository;
+import org.springframework.security.oauth2.core.AuthorizationGrantType;
+import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.security.oauth2.core.DelegatingOAuth2TokenValidator;
import org.springframework.security.oauth2.core.OAuth2TokenValidator;
import org.springframework.security.oauth2.core.oidc.IdTokenClaimNames;
@@ -45,13 +52,13 @@
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.jwt.JwtValidators;
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
-import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationProvider;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.access.AccessDeniedHandlerImpl;
import org.springframework.security.web.authentication.Http403ForbiddenEntryPoint;
+import org.springframework.security.web.savedrequest.HttpSessionRequestCache;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.util.StringUtils;
@@ -91,8 +98,17 @@ public class SecurityConfig {
@Autowired
KeyStoreConfig keyStoreConfig;
+ @Autowired(required = false)
+ JwtTokenService jwtTokenService;
+
+ // @Autowired
+ // AuthorizableAwareEntityService projectAuthHelper;
+
+ // @Autowired
+ // List providers;
+
@Autowired
- AuthorizableAwareEntityService projectAuthHelper;
+ UserAuthenticationManagerBuilder authenticationManagerBuilder;
@Bean("apiSecurityFilterChain")
public SecurityFilterChain apiSecurityFilterChain(HttpSecurity http) throws Exception {
@@ -118,43 +134,32 @@ public SecurityFilterChain apiSecurityFilterChain(HttpSecurity http) throws Exce
});
//authentication (when configured)
- if (properties.isRequired()) {
- //always enable internal jwt auth provider
- JwtAuthenticationProvider coreJwtAuthProvider = new JwtAuthenticationProvider(
- coreJwtDecoder(
- applicationProperties.getEndpoint(),
- applicationProperties.getName(),
- keyStoreConfig.getJWKSetKeyStore().getJwk()
- )
- );
- coreJwtAuthProvider.setJwtAuthenticationConverter(
- coreJwtAuthenticationConverter("authorities", projectAuthHelper)
- );
+ if (properties.isRequired() && jwtTokenService != null) {
+ List authProviders = new ArrayList<>();
- // Create authentication Manager
- securityChain.oauth2ResourceServer(oauth2 ->
- oauth2.jwt(jwt -> jwt.authenticationManager(new ProviderManager(coreJwtAuthProvider)))
- );
-
- if (properties.isJwtAuthEnabled()) {
- JwtAuthenticationProperties jwtProps = properties.getJwt();
- // rebuild auth manager to include external jwt provider
- JwtAuthenticationProvider externalJwtAuthProvider = new JwtAuthenticationProvider(
- externalJwtDecoder(jwtProps.getIssuerUri(), jwtProps.getAudience())
- );
+ // always enable internal jwt auth provider
+ JwtAuthenticationProvider coreJwtAuthProvider = new JwtAuthenticationProvider(jwtTokenService.getDecoder());
+ coreJwtAuthProvider.setJwtAuthenticationConverter(jwtTokenService.getAuthenticationConverter());
+ authProviders.add(coreJwtAuthProvider);
- externalJwtAuthProvider.setJwtAuthenticationConverter(
- externalJwtAuthenticationConverter(jwtProps.getUsername(), jwtProps.getClaim(), projectAuthHelper)
- );
-
- securityChain.oauth2ResourceServer(oauth2 ->
- oauth2.jwt(jwt ->
- jwt.authenticationManager(new ProviderManager(coreJwtAuthProvider, externalJwtAuthProvider))
- )
+ //enable basic if required
+ if (properties.isBasicAuthEnabled()) {
+ DaoAuthenticationProvider daoProvider = new DaoAuthenticationProvider();
+ daoProvider.setUserDetailsService(
+ userDetailsService(properties.getBasic().getUsername(), properties.getBasic().getPassword())
);
+ daoProvider.setPasswordEncoder(PasswordEncoderFactories.createDelegatingPasswordEncoder());
+ authProviders.add(daoProvider);
}
+ // Create authentication Manager
+ UserAuthenticationManager authManager = authenticationManagerBuilder.build(authProviders);
+
+ securityChain.authenticationManager(authManager);
+ securityChain.oauth2ResourceServer(oauth2 -> oauth2.jwt(jwt -> jwt.authenticationManager(authManager)));
+
//enable basic if required
+ //NOTE: we need to it now to use our authenticationManager
if (properties.isBasicAuthEnabled()) {
securityChain
.httpBasic(basic -> basic.authenticationEntryPoint(new Http403ForbiddenEntryPoint()))
@@ -182,10 +187,11 @@ public SecurityFilterChain apiSecurityFilterChain(HttpSecurity http) throws Exce
return securityChain.build();
}
- @Bean("authSecurityFilterChain")
- public SecurityFilterChain authSecurityFilterChain(HttpSecurity http) throws Exception {
+ @Bean("tokenSecurityFilterChain")
+ public SecurityFilterChain tokenSecurityFilterChain(HttpSecurity http) throws Exception {
+ //token chain
HttpSecurity securityChain = http
- .securityMatcher(getAuthRequestMatcher())
+ .securityMatcher(new AntPathRequestMatcher("/auth/token"))
.authorizeHttpRequests(auth -> {
auth.requestMatchers(getAuthRequestMatcher()).hasRole("USER").anyRequest().authenticated();
})
@@ -193,32 +199,69 @@ public SecurityFilterChain authSecurityFilterChain(HttpSecurity http) throws Exc
.requestCache(requestCache -> requestCache.disable())
//disable csrf
.csrf(csrf -> csrf.disable())
- // we don't want a session for these endpoints, each request should be evaluated
+ // disable session for token requests
.sessionManagement(management -> management.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
- // allow cors
+ // always allow cors
securityChain.cors(cors -> {
- if (StringUtils.hasText(corsOrigins)) {
- cors.configurationSource(corsConfigurationSource(corsOrigins));
- } else {
- cors.disable();
- }
+ cors.configurationSource(corsConfigurationSource("*"));
});
- //authentication (when configured)
- if (StringUtils.hasText(clientId) && StringUtils.hasText(clientSecret)) {
- //enable basic
+ //enable anonymous auth, we'll double check auth in granters
+ securityChain.anonymous(anon -> anon.authorities(List.of(new SimpleGrantedAuthority("ROLE_USER"))));
+
+ //enable basic if required (client auth)
+ //NOTE: configure first to avoid injecting user auth manager for basic
+ if (StringUtils.hasText(clientId) && StringUtils.hasText(clientId)) {
+ //client basic auth flow
securityChain
.httpBasic(basic -> basic.authenticationEntryPoint(new Http403ForbiddenEntryPoint()))
.userDetailsService(userDetailsService(clientId, clientSecret));
}
- //assign both USER and ADMIN to anon user to bypass all scoped permission checks
- securityChain.anonymous(anon -> {
- anon.authorities("ROLE_USER", "ROLE_ADMIN");
- anon.principal("anonymous");
+ securityChain.exceptionHandling(handling -> {
+ handling
+ .authenticationEntryPoint(new Http403ForbiddenEntryPoint())
+ .accessDeniedHandler(new AccessDeniedHandlerImpl()); // use 403
});
+ return securityChain.build();
+ }
+
+ @Bean("userinfoSecurityFilterChain")
+ public SecurityFilterChain userinfoSecurityFilterChain(HttpSecurity http) throws Exception {
+ //userinfo chain
+ HttpSecurity securityChain = http
+ .securityMatcher(new AntPathRequestMatcher("/auth/userinfo"))
+ .authorizeHttpRequests(auth -> {
+ auth.requestMatchers(getAuthRequestMatcher()).hasRole("USER").anyRequest().authenticated();
+ })
+ // disable request cache
+ .requestCache(requestCache -> requestCache.disable())
+ //disable csrf
+ .csrf(csrf -> csrf.disable())
+ // disable session
+ .sessionManagement(management -> management.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
+
+ // always allow cors
+ securityChain.cors(cors -> {
+ cors.configurationSource(corsConfigurationSource("*"));
+ });
+
+ //disable anonymous auth
+ securityChain.anonymous(anon -> anon.disable());
+
+ //authentication (when configured)
+ if (properties.isOidcAuthEnabled() && jwtTokenService != null) {
+ // enable internal jwt auth provider
+ JwtAuthenticationProvider coreJwtAuthProvider = new JwtAuthenticationProvider(jwtTokenService.getDecoder());
+ coreJwtAuthProvider.setJwtAuthenticationConverter(jwtTokenService.getAuthenticationConverter());
+ UserAuthenticationManager authManager = authenticationManagerBuilder.build(coreJwtAuthProvider);
+
+ securityChain.authenticationManager(authManager);
+ securityChain.oauth2ResourceServer(oauth2 -> oauth2.jwt(jwt -> jwt.authenticationManager(authManager)));
+ }
+
securityChain.exceptionHandling(handling -> {
handling
.authenticationEntryPoint(new Http403ForbiddenEntryPoint())
@@ -228,6 +271,110 @@ public SecurityFilterChain authSecurityFilterChain(HttpSecurity http) throws Exc
return securityChain.build();
}
+ @Bean("authorizeSecurityFilterChain")
+ public SecurityFilterChain authorizeSecurityFilterChain(HttpSecurity http) throws Exception {
+ //token chain
+ HttpSecurity securityChain = http
+ .securityMatcher(getAuthRequestMatcher())
+ .authorizeHttpRequests(auth -> {
+ auth.requestMatchers(getAuthRequestMatcher()).hasRole("USER").anyRequest().authenticated();
+ })
+ // enable request cache IN SESSION
+ .requestCache(requestCache -> requestCache.requestCache(new HttpSessionRequestCache()))
+ //disable csrf
+ .csrf(csrf -> csrf.disable())
+ // we need session to handle auth flows
+ .sessionManagement(management -> management.sessionCreationPolicy(SessionCreationPolicy.ALWAYS));
+
+ // allow cors
+ securityChain.cors(cors -> {
+ if (StringUtils.hasText(corsOrigins)) {
+ cors.configurationSource(corsConfigurationSource(corsOrigins));
+ } else {
+ cors.disable();
+ }
+ });
+
+ //disable anonymous auth, to authenticate we *need* valid credentials!
+ securityChain.anonymous(anon -> anon.disable());
+
+ //enable upstream oidc
+ if (properties.isOidcAuthEnabled()) {
+ OidcAuthenticationProperties props = properties.getOidc();
+ //we support a single static client
+ String registrationId = "oidc";
+ ClientRegistration.Builder client = ClientRegistrations
+ .fromIssuerLocation(props.getIssuerUri())
+ .registrationId(registrationId)
+ .clientName(props.getClientName())
+ .clientId(props.getClientId())
+ .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
+ .redirectUri("{baseUrl}/auth/code/" + registrationId)
+ .scope(props.getScope())
+ .userNameAttributeName(
+ StringUtils.hasText(props.getUsernameAttributeName())
+ ? props.getUsernameAttributeName()
+ : IdTokenClaimNames.SUB
+ );
+
+ if (StringUtils.hasText(props.getClientSecret())) {
+ //use secret
+ client
+ .clientSecret(clientSecret)
+ .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC);
+ } else {
+ //use PKCE
+ client.clientAuthenticationMethod(ClientAuthenticationMethod.NONE);
+ }
+
+ InMemoryClientRegistrationRepository repository = new InMemoryClientRegistrationRepository(client.build());
+
+ //register provider for authorize chain
+ securityChain.oauth2Login(oauth2 -> {
+ oauth2.clientRegistrationRepository(repository);
+ oauth2.authorizationEndpoint(endpoint -> endpoint.baseUri("/auth/authorization"));
+ oauth2.redirectionEndpoint(endpoint -> endpoint.baseUri("/auth/code/*"));
+ oauth2.userInfoEndpoint(userInfo ->
+ userInfo.userAuthoritiesMapper(authorities ->
+ Collections.singleton(new SimpleGrantedAuthority("ROLE_USER"))
+ )
+ );
+ });
+ // //add entryPoint towards provider
+ // securityChain.exceptionHandling(handling -> {
+ // handling
+ // .authenticationEntryPoint(
+ // new LoginUrlAuthenticationEntryPoint("/auth/authorization/" + registrationId)
+ // )
+ // .accessDeniedHandler(new AccessDeniedHandlerImpl()); // use 403
+ // });
+ }
+
+ return securityChain.build();
+ }
+
+ @Bean("wellKnownSecurityFilterChain")
+ public SecurityFilterChain wellKnownSecurityFilterChain(HttpSecurity http) throws Exception {
+ return http
+ .securityMatcher(new AntPathRequestMatcher("/.well-known/**"))
+ .authorizeHttpRequests(auth -> {
+ auth.anyRequest().permitAll();
+ })
+ // disable request cache
+ .requestCache(requestCache -> requestCache.disable())
+ //disable csrf
+ .csrf(csrf -> csrf.disable())
+ // we don't want a session for these endpoints, each request should be evaluated
+ .sessionManagement(management -> management.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
+ // enable frame options
+ .headers(headers -> headers.frameOptions(frame -> frame.sameOrigin()))
+ // always allow cors
+ .cors(cors -> {
+ cors.configurationSource(corsConfigurationSource("*"));
+ })
+ .build();
+ }
+
@Bean("h2SecurityFilterChain")
public SecurityFilterChain h2SecurityFilterChain(HttpSecurity http) throws Exception {
return http
@@ -316,87 +463,88 @@ public static UserDetailsService userDetailsService(String username, String pass
* Internal auth via JWT
*/
- public static JwtDecoder coreJwtDecoder(String issuer, String audience, JWK jwk) throws JOSEException {
- //we support only RSA keys
- if (!(jwk instanceof RSAKey)) {
- throw new IllegalArgumentException("the provided key is not suitable for token authentication");
- }
-
- NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withPublicKey(jwk.toRSAKey().toRSAPublicKey()).build();
-
- OAuth2TokenValidator withIssuer = JwtValidators.createDefaultWithIssuer(issuer);
-
- OAuth2TokenValidator audienceValidator = new JwtClaimValidator>(
- JwtClaimNames.AUD,
- (aud -> aud != null && aud.contains(audience))
- );
-
- //access tokens *do not contain* at_hash, those are refresh
- OAuth2TokenValidator accessTokenValidator = new JwtClaimValidator(
- IdTokenClaimNames.AT_HASH,
- (Objects::isNull)
- );
-
- OAuth2TokenValidator validator = new DelegatingOAuth2TokenValidator<>(
- withIssuer,
- audienceValidator,
- accessTokenValidator
- );
- jwtDecoder.setJwtValidator(validator);
-
- return jwtDecoder;
- }
-
- public static JwtAuthenticationConverter coreJwtAuthenticationConverter(
- String claim,
- AuthorizableAwareEntityService projectAuthHelper
- ) {
- JwtAuthenticationConverter converter = new JwtAuthenticationConverter();
- converter.setJwtGrantedAuthoritiesConverter((Jwt source) -> {
- if (source == null) return null;
-
- Set authorities = new HashSet<>();
- authorities.add(new SimpleGrantedAuthority("ROLE_USER"));
-
- if (StringUtils.hasText(claim) && source.hasClaim(claim)) {
- List roles = source.getClaimAsStringList(claim);
- if (roles != null) {
- roles.forEach(r -> {
- //use as is
- authorities.add(new SimpleGrantedAuthority(r));
- });
- }
- }
-
- //refresh project authorities via helper
- if (projectAuthHelper != null && StringUtils.hasText(source.getSubject())) {
- String username = source.getSubject();
-
- //inject roles from ownership of projects
- projectAuthHelper
- .findIdsByCreatedBy(username)
- .forEach(p -> {
- //derive a scoped ADMIN role
- authorities.add(new SimpleGrantedAuthority(p + ":ROLE_ADMIN"));
- });
-
- //inject roles from sharing of projects
- projectAuthHelper
- .findIdsBySharedTo(username)
- .forEach(p -> {
- //derive a scoped USER role
- //TODO make configurable?
- authorities.add(new SimpleGrantedAuthority(p + ":ROLE_USER"));
- });
- }
-
- return authorities;
- });
- return converter;
- }
+ // public static JwtDecoder coreJwtDecoder(String issuer, String audience, JWK jwk) throws JOSEException {
+ // //we support only RSA keys
+ // if (!(jwk instanceof RSAKey)) {
+ // throw new IllegalArgumentException("the provided key is not suitable for token authentication");
+ // }
+
+ // NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withPublicKey(jwk.toRSAKey().toRSAPublicKey()).build();
+
+ // OAuth2TokenValidator withIssuer = JwtValidators.createDefaultWithIssuer(issuer);
+
+ // OAuth2TokenValidator audienceValidator = new JwtClaimValidator>(
+ // JwtClaimNames.AUD,
+ // (aud -> aud != null && aud.contains(audience))
+ // );
+
+ // //access tokens *do not contain* at_hash, those are refresh
+ // OAuth2TokenValidator accessTokenValidator = new JwtClaimValidator(
+ // IdTokenClaimNames.AT_HASH,
+ // (Objects::isNull)
+ // );
+
+ // OAuth2TokenValidator validator = new DelegatingOAuth2TokenValidator<>(
+ // withIssuer,
+ // audienceValidator,
+ // accessTokenValidator
+ // );
+ // jwtDecoder.setJwtValidator(validator);
+
+ // return jwtDecoder;
+ // }
+
+ // public static JwtAuthenticationConverter coreJwtAuthenticationConverter(
+ // String claim,
+ // AuthorizableAwareEntityService projectAuthHelper
+ // ) {
+ // JwtAuthenticationConverter converter = new JwtAuthenticationConverter();
+ // converter.setJwtGrantedAuthoritiesConverter((Jwt source) -> {
+ // if (source == null) return null;
+
+ // Set authorities = new HashSet<>();
+ // authorities.add(new SimpleGrantedAuthority("ROLE_USER"));
+
+ // if (StringUtils.hasText(claim) && source.hasClaim(claim)) {
+ // List roles = source.getClaimAsStringList(claim);
+ // if (roles != null) {
+ // roles.forEach(r -> {
+ // //use as is
+ // authorities.add(new SimpleGrantedAuthority(r));
+ // });
+ // }
+ // }
+
+ // //refresh project authorities via helper
+ // if (projectAuthHelper != null && StringUtils.hasText(source.getSubject())) {
+ // String username = source.getSubject();
+
+ // //inject roles from ownership of projects
+ // projectAuthHelper
+ // .findIdsByCreatedBy(username)
+ // .forEach(p -> {
+ // //derive a scoped ADMIN role
+ // authorities.add(new SimpleGrantedAuthority(p + ":ROLE_ADMIN"));
+ // });
+
+ // //inject roles from sharing of projects
+ // projectAuthHelper
+ // .findIdsBySharedTo(username)
+ // .forEach(p -> {
+ // //derive a scoped USER role
+ // //TODO make configurable?
+ // authorities.add(new SimpleGrantedAuthority(p + ":ROLE_USER"));
+ // });
+ // }
+
+ // return authorities;
+ // });
+ // return converter;
+ // }
/**
* External auth via JWT
+ * TODO move config to service
*/
public static JwtDecoder externalJwtDecoder(String issuer, String audience) {
NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withIssuerLocation(issuer).build();
diff --git a/application/src/main/java/it/smartcommunitylabdhub/core/controllers/ConfigurationEndpoint.java b/application/src/main/java/it/smartcommunitylabdhub/core/controllers/ConfigurationEndpoint.java
new file mode 100644
index 00000000..146c69ef
--- /dev/null
+++ b/application/src/main/java/it/smartcommunitylabdhub/core/controllers/ConfigurationEndpoint.java
@@ -0,0 +1,56 @@
+package it.smartcommunitylabdhub.core.controllers;
+
+import it.smartcommunitylabdhub.commons.config.ApplicationProperties;
+import it.smartcommunitylabdhub.commons.config.SecurityProperties;
+import it.smartcommunitylabdhub.commons.infrastructure.ConfigurationProvider;
+import java.io.Serializable;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+public class ConfigurationEndpoint {
+
+ @Autowired
+ private List providers;
+
+ @Autowired
+ private ApplicationProperties applicationProperties;
+
+ @Autowired
+ private SecurityProperties securityProperties;
+
+ @Value("${jwt.cache-control}")
+ private String cacheControl;
+
+ //cache, we don't expect config to be mutable!
+ private Map config = null;
+
+ @GetMapping(value = { "/.well-known/configuration" })
+ public ResponseEntity