diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 00000000..6009c23b
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "frontend/console"]
+ path = frontend/console
+ url = https://github.com/scc-digitalhub/digitalhub-console.git
diff --git a/.vscode/launch.json b/.vscode/launch.json
index ef5630cb..c009dd8e 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -18,6 +18,13 @@
"mainClass": "it.smartcommunitylabdhub.core.CoreApplication",
"projectName": "core",
"vmArgs": "-Dspring.profiles.active=local",
+ },
+ {
+ "type": "chrome",
+ "request": "launch",
+ "name": "Launch Chrome against localhost",
+ "url": "http://localhost:8080",
+ "webRoot": "${workspaceFolder}"
}
]
}
\ No newline at end of file
diff --git a/application/pom.xml b/application/pom.xml
index 5d96676c..8121afc3 100644
--- a/application/pom.xml
+++ b/application/pom.xml
@@ -76,6 +76,10 @@
org.springframework.boot
spring-boot-starter-amqp
+
+ org.springframework.boot
+ spring-boot-starter-thymeleaf
+
org.projectlombok
@@ -237,6 +241,12 @@
dh-runtime-mlrun
${revision}
+
+
+ it.smartcommunitylabdhub
+ dh-console
+ ${revision}
+
diff --git a/application/src/main/java/it/smartcommunitylabdhub/core/config/ApiVersioningMappingConfig.java b/application/src/main/java/it/smartcommunitylabdhub/core/config/ApiVersioningMappingConfig.java
index e75001ff..5b9313f8 100644
--- a/application/src/main/java/it/smartcommunitylabdhub/core/config/ApiVersioningMappingConfig.java
+++ b/application/src/main/java/it/smartcommunitylabdhub/core/config/ApiVersioningMappingConfig.java
@@ -2,14 +2,20 @@
import it.smartcommunitylabdhub.core.config.handlers.VersionedHandlerMapping;
import org.springframework.boot.autoconfigure.web.servlet.WebMvcRegistrations;
+import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
@Configuration
-public class ApiVersioningMappingConfig implements WebMvcRegistrations {
+public class ApiVersioningMappingConfig {
- @Override
- public RequestMappingHandlerMapping getRequestMappingHandlerMapping() {
- return new VersionedHandlerMapping();
+ @Bean
+ public WebMvcRegistrations webMvcRegistrationsHandlerMapping() {
+ return new WebMvcRegistrations() {
+ @Override
+ public RequestMappingHandlerMapping getRequestMappingHandlerMapping() {
+ return new VersionedHandlerMapping();
+ }
+ };
}
}
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 9bac326c..dc9d828a 100644
--- a/application/src/main/java/it/smartcommunitylabdhub/core/config/SecurityConfig.java
+++ b/application/src/main/java/it/smartcommunitylabdhub/core/config/SecurityConfig.java
@@ -2,6 +2,7 @@
import static org.springframework.security.config.Customizer.withDefaults;
+import it.smartcommunitylabdhub.commons.config.SecurityProperties;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
diff --git a/application/src/main/java/it/smartcommunitylabdhub/core/config/WebConfig.java b/application/src/main/java/it/smartcommunitylabdhub/core/config/WebConfig.java
deleted file mode 100644
index a6c6dddf..00000000
--- a/application/src/main/java/it/smartcommunitylabdhub/core/config/WebConfig.java
+++ /dev/null
@@ -1,20 +0,0 @@
-package it.smartcommunitylabdhub.core.config;
-
-import java.util.List;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.data.web.PageableHandlerMethodArgumentResolver;
-import org.springframework.data.web.config.EnableSpringDataWebSupport;
-import org.springframework.web.method.support.HandlerMethodArgumentResolver;
-import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
-
-@Configuration
-@EnableSpringDataWebSupport
-public class WebConfig implements WebMvcConfigurer {
-
- @Override
- public void addArgumentResolvers(List argumentResolvers) {
- PageableHandlerMethodArgumentResolver resolver = new PageableHandlerMethodArgumentResolver();
- // Customize resolver properties here if needed
- argumentResolvers.add(resolver);
- }
-}
diff --git a/application/src/main/resources/application.yml b/application/src/main/resources/application.yml
index 5fc130f5..034da76b 100644
--- a/application/src/main/resources/application.yml
+++ b/application/src/main/resources/application.yml
@@ -85,8 +85,11 @@ security:
password: ${DH_AUTH_BASIC_PASSWORD:}
jwt:
issuer-uri: ${DH_AUTH_JWT_ISSUER_URI:}
- audience: ${DH_AUTH_JWT_AUDIENCE:dhcore}
+ audience: ${DH_AUTH_JWT_AUDIENCE:${security.oidc.client-id}}
claim: ${DH_AUTH_JWT_CLAIM:roles}
+ oidc:
+ client-id: ${DH_AUTH_OIDC_CLIENT_ID:}
+ scope:
diff --git a/frontend/.flattened-pom.xml b/frontend/.flattened-pom.xml
new file mode 100644
index 00000000..ecec6e8b
--- /dev/null
+++ b/frontend/.flattened-pom.xml
@@ -0,0 +1,53 @@
+
+
+ 4.0.0
+ it.smartcommunitylabdhub
+ dh-console
+ 0.0.29
+
+
+ org.projectlombok
+ lombok
+ 1.18.30
+ compile
+ true
+
+
+ org.springframework.boot
+ spring-boot
+ 3.2.0
+ compile
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+ 3.2.0
+ compile
+
+
+ org.springframework.boot
+ spring-boot-starter-security
+ 3.2.0
+ compile
+
+
+ org.springframework.boot
+ spring-boot-starter-thymeleaf
+ 3.2.0
+ compile
+
+
+ it.smartcommunitylabdhub
+ dh-commons
+ 0.0.29
+ compile
+
+
+ org.springdoc
+ springdoc-openapi-starter-common
+ 2.2.0
+ compile
+
+
+
diff --git a/frontend/console b/frontend/console
new file mode 160000
index 00000000..b96889f3
--- /dev/null
+++ b/frontend/console
@@ -0,0 +1 @@
+Subproject commit b96889f36295bf9c270f74decef1c6ec2f6bbf6b
diff --git a/frontend/pom.xml b/frontend/pom.xml
new file mode 100644
index 00000000..6bbc230d
--- /dev/null
+++ b/frontend/pom.xml
@@ -0,0 +1,136 @@
+
+
+ 4.0.0
+
+ it.smartcommunitylabdhub
+ digitalhub-core
+ ${revision}
+ ../
+
+ it.smartcommunitylabdhub
+ dh-console
+ console
+ DHCore console
+
+
+ v1.22.19
+ v18.17.1
+
+
+
+ org.projectlombok
+ lombok
+ ${lombok.version}
+ compile
+ true
+
+
+ org.springframework.boot
+ spring-boot
+ ${spring-boot.version}
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+ ${spring-boot.version}
+
+
+ org.springframework.boot
+ spring-boot-starter-security
+ ${spring-boot.version}
+
+
+ org.springframework.boot
+ spring-boot-starter-thymeleaf
+ ${spring-boot.version}
+
+
+
+ it.smartcommunitylabdhub
+ dh-commons
+ ${revision}
+
+
+
+ org.springdoc
+ springdoc-openapi-starter-common
+ ${springdoc.version}
+
+
+
+
+
+
+
+
+ com.github.eirslett
+ frontend-maven-plugin
+ 1.15.0
+
+
+ ${node.version}
+ ${yarn.version}
+ ${project.build.directory}
+
+
+
+
+ install-node-and-yarn
+
+ install-node-and-yarn
+
+
+
+
+ frontent-yarn-install
+
+ yarn
+
+ generate-resources
+
+ install
+ ${project.basedir}/console
+
+
+
+
+ frontend build
+
+ yarn
+
+ prepare-package
+
+ build:production
+ ${project.basedir}/console
+
+
+
+
+
+
+ maven-resources-plugin
+
+
+ frontend package
+
+ copy-resources
+
+ prepare-package
+
+ ${project.build.outputDirectory}/console
+
+
+ ${project.basedir}/console/dist
+ false
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/frontend/src/main/java/it/smartcommunitylabdhub/console/Keys.java b/frontend/src/main/java/it/smartcommunitylabdhub/console/Keys.java
new file mode 100644
index 00000000..7c10bb8e
--- /dev/null
+++ b/frontend/src/main/java/it/smartcommunitylabdhub/console/Keys.java
@@ -0,0 +1,8 @@
+package it.smartcommunitylabdhub.console;
+
+public class Keys {
+
+ public static final String CONSOLE_CONTEXT = "/console";
+
+ private Keys() {}
+}
diff --git a/frontend/src/main/java/it/smartcommunitylabdhub/console/config/ConsoleSecurityConfig.java b/frontend/src/main/java/it/smartcommunitylabdhub/console/config/ConsoleSecurityConfig.java
new file mode 100644
index 00000000..7b769bc1
--- /dev/null
+++ b/frontend/src/main/java/it/smartcommunitylabdhub/console/config/ConsoleSecurityConfig.java
@@ -0,0 +1,68 @@
+package it.smartcommunitylabdhub.console.config;
+
+import it.smartcommunitylabdhub.commons.config.SecurityProperties;
+import it.smartcommunitylabdhub.console.Keys;
+import java.util.ArrayList;
+import java.util.Arrays;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.http.SessionCreationPolicy;
+import org.springframework.security.web.SecurityFilterChain;
+import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
+import org.springframework.security.web.util.matcher.RequestMatcher;
+import org.springframework.util.StringUtils;
+import org.springframework.web.cors.CorsConfiguration;
+import org.springframework.web.cors.CorsConfigurationSource;
+import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
+
+@Configuration
+public class ConsoleSecurityConfig {
+
+ @Autowired
+ SecurityProperties properties;
+
+ @Value("${security.api.cors.origins}")
+ private String corsOrigins;
+
+ public RequestMatcher getRequestMatcher() {
+ return new AntPathRequestMatcher(Keys.CONSOLE_CONTEXT + "/**");
+ }
+
+ @Bean("consoleSecurityFilterChain")
+ public SecurityFilterChain apiSecurityFilterChain(HttpSecurity http) throws Exception {
+ http
+ .securityMatcher(getRequestMatcher())
+ .authorizeHttpRequests(auth -> {
+ auth.anyRequest().permitAll();
+ })
+ //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));
+
+ // allow cors
+ http.cors(cors -> {
+ if (StringUtils.hasText(corsOrigins)) {
+ cors.configurationSource(corsConfigurationSource(corsOrigins));
+ } else {
+ cors.disable();
+ }
+ });
+
+ return http.build();
+ }
+
+ private CorsConfigurationSource corsConfigurationSource(String origins) {
+ CorsConfiguration configuration = new CorsConfiguration();
+ configuration.setAllowedOriginPatterns(new ArrayList<>(StringUtils.commaDelimitedListToSet(origins)));
+ configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "PATCH"));
+ configuration.setAllowedHeaders(Arrays.asList("*"));
+ configuration.setExposedHeaders(Arrays.asList("authorization", "range"));
+ UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
+ source.registerCorsConfiguration("/**", configuration);
+ return source;
+ }
+}
diff --git a/frontend/src/main/java/it/smartcommunitylabdhub/console/config/WebConfig.java b/frontend/src/main/java/it/smartcommunitylabdhub/console/config/WebConfig.java
new file mode 100644
index 00000000..aaba8c1c
--- /dev/null
+++ b/frontend/src/main/java/it/smartcommunitylabdhub/console/config/WebConfig.java
@@ -0,0 +1,25 @@
+package it.smartcommunitylabdhub.console.config;
+
+import it.smartcommunitylabdhub.console.Keys;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.lang.NonNull;
+import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+import org.springframework.web.servlet.resource.EncodedResourceResolver;
+import org.springframework.web.servlet.resource.PathResourceResolver;
+
+@Configuration
+public class WebConfig implements WebMvcConfigurer {
+
+ @Override
+ public void addResourceHandlers(@NonNull ResourceHandlerRegistry registry) {
+ // user console dist
+ registry
+ .addResourceHandler(Keys.CONSOLE_CONTEXT + "/**")
+ .addResourceLocations("classpath:/console/")
+ .setCachePeriod(60 * 60 * 24 * 365)/* one year */
+ .resourceChain(true)
+ .addResolver(new EncodedResourceResolver())
+ .addResolver(new PathResourceResolver());
+ }
+}
diff --git a/frontend/src/main/java/it/smartcommunitylabdhub/console/controllers/ConsoleController.java b/frontend/src/main/java/it/smartcommunitylabdhub/console/controllers/ConsoleController.java
new file mode 100644
index 00000000..7948fe37
--- /dev/null
+++ b/frontend/src/main/java/it/smartcommunitylabdhub/console/controllers/ConsoleController.java
@@ -0,0 +1,74 @@
+package it.smartcommunitylabdhub.console.controllers;
+
+import it.smartcommunitylabdhub.commons.config.ApplicationProperties;
+import it.smartcommunitylabdhub.commons.config.SecurityProperties;
+import it.smartcommunitylabdhub.console.Keys;
+import jakarta.servlet.http.HttpServletRequest;
+import java.util.HashMap;
+import java.util.Map;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.Model;
+import org.springframework.util.StringUtils;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.servlet.ModelAndView;
+import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
+
+@Controller
+public class ConsoleController {
+
+ @Autowired
+ private ApplicationProperties applicationProperties;
+
+ @Autowired
+ private SecurityProperties securityProperties;
+
+ public static final String CONSOLE_CONTEXT = Keys.CONSOLE_CONTEXT;
+
+ @GetMapping("/")
+ public ModelAndView root() {
+ return new ModelAndView("redirect:" + CONSOLE_CONTEXT + "/");
+ }
+
+ // @GetMapping(value = { CONSOLE_CONTEXT, CONSOLE_CONTEXT + "/**" })
+ @GetMapping(
+ value = {
+ CONSOLE_CONTEXT + "/",
+ CONSOLE_CONTEXT + "/{path:^(?!\\S+(?:\\.[a-z0-9]{2,}))\\S+$}",
+ CONSOLE_CONTEXT + "/-/**",
+ }
+ )
+ public String console(Model model, HttpServletRequest request) {
+ String requestUrl = ServletUriComponentsBuilder
+ .fromRequestUri(request)
+ .replacePath(request.getContextPath())
+ .build()
+ .toUriString();
+
+ String applicationUrl = StringUtils.hasText(applicationProperties.getEndpoint())
+ ? applicationProperties.getEndpoint()
+ : requestUrl;
+
+ //build config
+ Map config = new HashMap<>();
+ config.put("REACT_APP_APPLICATION_URL", applicationUrl);
+ config.put("REACT_APP_API_URL", "/api/v1");
+
+ config.put("REACT_APP_CONTEXT_PATH", CONSOLE_CONTEXT);
+ config.put("REACT_APP_AUTH", "none");
+ if (securityProperties.isBasicAuthEnabled()) {
+ config.put("REACT_APP_LOGIN_URL", "/auth");
+ }
+
+ if (securityProperties.isOidcAuthEnabled()) {
+ config.put("REACT_APP_ISSUER_URI", securityProperties.getJwt().getIssuerUri());
+ config.put("REACT_APP_CLIENT_ID", securityProperties.getOidc().getClientId());
+ if (securityProperties.getOidc().getScope() != null) {
+ config.put("REACT_APP_SCOPE", String.join(" ", securityProperties.getOidc().getScope()));
+ }
+ }
+
+ model.addAttribute("config", config);
+ return "console.html";
+ }
+}
diff --git a/frontend/src/main/resources/templates/console.html b/frontend/src/main/resources/templates/console.html
new file mode 100644
index 00000000..bf01851c
--- /dev/null
+++ b/frontend/src/main/resources/templates/console.html
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+ dh-console
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/modules/commons/src/main/java/it/smartcommunitylabdhub/commons/config/ApplicationProperties.java b/modules/commons/src/main/java/it/smartcommunitylabdhub/commons/config/ApplicationProperties.java
new file mode 100644
index 00000000..a46911f3
--- /dev/null
+++ b/modules/commons/src/main/java/it/smartcommunitylabdhub/commons/config/ApplicationProperties.java
@@ -0,0 +1,20 @@
+package it.smartcommunitylabdhub.commons.config;
+
+import java.util.List;
+import lombok.Getter;
+import lombok.Setter;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+@ConfigurationProperties(prefix = "application", ignoreUnknownFields = true)
+@Getter
+@Setter
+public class ApplicationProperties {
+
+ private String endpoint;
+ private String name;
+ private String description;
+ private String version;
+ private List profiles;
+}
diff --git a/application/src/main/java/it/smartcommunitylabdhub/core/config/SecurityProperties.java b/modules/commons/src/main/java/it/smartcommunitylabdhub/commons/config/SecurityProperties.java
similarity index 74%
rename from application/src/main/java/it/smartcommunitylabdhub/core/config/SecurityProperties.java
rename to modules/commons/src/main/java/it/smartcommunitylabdhub/commons/config/SecurityProperties.java
index fd6a9127..b9ec79d9 100644
--- a/application/src/main/java/it/smartcommunitylabdhub/core/config/SecurityProperties.java
+++ b/modules/commons/src/main/java/it/smartcommunitylabdhub/commons/config/SecurityProperties.java
@@ -1,5 +1,6 @@
-package it.smartcommunitylabdhub.core.config;
+package it.smartcommunitylabdhub.commons.config;
+import java.util.List;
import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
@@ -19,6 +20,9 @@ public class SecurityProperties {
@NestedConfigurationProperty
private JwtAuthenticationProperties jwt;
+ @NestedConfigurationProperty
+ private OidcAuthenticationProperties oidc;
+
public boolean isBasicAuthEnabled() {
return basic != null && basic.isEnabled();
}
@@ -27,6 +31,10 @@ public boolean isJwtAuthEnabled() {
return jwt != null && jwt.isEnabled();
}
+ public boolean isOidcAuthEnabled() {
+ return oidc != null && oidc.isEnabled();
+ }
+
public boolean isRequired() {
return isBasicAuthEnabled() || isJwtAuthEnabled();
}
@@ -55,4 +63,16 @@ public boolean isEnabled() {
return StringUtils.hasText(issuerUri) && StringUtils.hasText(audience);
}
}
+
+ @Getter
+ @Setter
+ public static class OidcAuthenticationProperties {
+
+ private String clientId;
+ private List scope;
+
+ public boolean isEnabled() {
+ return StringUtils.hasText(clientId);
+ }
+ }
}
diff --git a/pom.xml b/pom.xml
index c7b755f3..8c99cbd6 100644
--- a/pom.xml
+++ b/pom.xml
@@ -34,6 +34,7 @@
modules/runtime-dbt
modules/runtime-nefertem
modules/runtime-mlrun
+ frontend
application