diff --git a/build/application/pom.xml b/build/application/pom.xml
index 54fa1bdd5cf..2464ce055fd 100644
--- a/build/application/pom.xml
+++ b/build/application/pom.xml
@@ -139,6 +139,10 @@
org.eclipse.dirigible
dirigible-components-security-keycloak
+
+ org.eclipse.dirigible
+ dirigible-components-security-snowflake
+
org.eclipse.dirigible
dirigible-components-security-oauth2
diff --git a/build/application/src/main/resources/logback.xml b/build/application/src/main/resources/logback.xml
index 6c6157e57ef..2ba7a4f5cab 100644
--- a/build/application/src/main/resources/logback.xml
+++ b/build/application/src/main/resources/logback.xml
@@ -75,4 +75,6 @@
+
+
diff --git a/components/core/core-base/src/main/java/org/eclipse/dirigible/components/base/http/access/CorsConfigurationSourceProvider.java b/components/core/core-base/src/main/java/org/eclipse/dirigible/components/base/http/access/CorsConfigurationSourceProvider.java
new file mode 100644
index 00000000000..7ab0f8a3e55
--- /dev/null
+++ b/components/core/core-base/src/main/java/org/eclipse/dirigible/components/base/http/access/CorsConfigurationSourceProvider.java
@@ -0,0 +1,29 @@
+package org.eclipse.dirigible.components.base.http.access;
+
+import org.springframework.web.cors.CorsConfiguration;
+import org.springframework.web.cors.CorsConfigurationSource;
+import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
+
+import java.util.Arrays;
+import java.util.List;
+
+public class CorsConfigurationSourceProvider {
+
+ public static CorsConfigurationSource get() {
+ CorsConfiguration configuration = new CorsConfiguration();
+ configuration.setAllowedOriginPatterns(List.of("*"));
+ configuration.setAllowCredentials(true);
+ configuration.setAllowedHeaders(
+ Arrays.asList("Access-Control-Allow-Headers", "Access-Control-Allow-Origin", "Access-Control-Request-Method",
+ "Access-Control-Request-Headers", "Origin", "Cache-Control", "Content-Type", "Authorization"));
+ configuration.setExposedHeaders(
+ Arrays.asList("Access-Control-Allow-Headers", "Access-Control-Allow-Origin", "Access-Control-Request-Method",
+ "Access-Control-Request-Headers", "Origin", "Cache-Control", "Content-Type", "Authorization"));
+ configuration.setAllowedMethods(Arrays.asList("HEAD", "DELETE", "GET", "POST", "PATCH", "PUT"));
+
+ UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
+ source.registerCorsConfiguration("/**", configuration);
+
+ return source;
+ }
+}
diff --git a/components/core/core-tenants/src/main/java/org/eclipse/dirigible/components/tenants/init/AdminUserInitializer.java b/components/core/core-tenants/src/main/java/org/eclipse/dirigible/components/tenants/init/AdminUserInitializer.java
index 7b59cde866f..4a1b14b83f5 100644
--- a/components/core/core-tenants/src/main/java/org/eclipse/dirigible/components/tenants/init/AdminUserInitializer.java
+++ b/components/core/core-tenants/src/main/java/org/eclipse/dirigible/components/tenants/init/AdminUserInitializer.java
@@ -27,9 +27,6 @@
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
-import java.nio.charset.StandardCharsets;
-import java.util.Base64;
-import java.util.Base64.Decoder;
import java.util.Optional;
/**
@@ -43,9 +40,6 @@ class AdminUserInitializer implements ApplicationListener
/** The Constant LOGGER. */
private static final Logger LOGGER = LoggerFactory.getLogger(AdminUserInitializer.class);
- /** The base 64 decoder. */
- private final Decoder base64Decoder;
-
/** The user service. */
private final UserService userService;
@@ -66,7 +60,6 @@ class AdminUserInitializer implements ApplicationListener
this.userService = userService;
this.defaultTenant = defaultTenant;
this.roleService = roleService;
- this.base64Decoder = Base64.getDecoder();
}
/**
@@ -120,15 +113,4 @@ private void assignRole(User user, Roles predefinedRole) {
.getId());
}
- /**
- * Decode.
- *
- * @param base64String the base 64 string
- * @return the string
- */
- private String decode(String base64String) {
- byte[] decodedValue = base64Decoder.decode(base64String);
- return new String(decodedValue, StandardCharsets.UTF_8);
- }
-
}
diff --git a/components/core/core-tenants/src/main/java/org/eclipse/dirigible/components/tenants/security/BasicSecurityConfig.java b/components/core/core-tenants/src/main/java/org/eclipse/dirigible/components/tenants/security/BasicSecurityConfig.java
index fe87d62f2c3..408c8c06b36 100644
--- a/components/core/core-tenants/src/main/java/org/eclipse/dirigible/components/tenants/security/BasicSecurityConfig.java
+++ b/components/core/core-tenants/src/main/java/org/eclipse/dirigible/components/tenants/security/BasicSecurityConfig.java
@@ -9,7 +9,7 @@
*/
package org.eclipse.dirigible.components.tenants.security;
-import java.util.Arrays;
+import org.eclipse.dirigible.components.base.http.access.CorsConfigurationSourceProvider;
import org.eclipse.dirigible.components.base.http.access.HttpSecurityURIConfigurator;
import org.eclipse.dirigible.components.tenants.tenant.TenantContextInitFilter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
@@ -20,9 +20,7 @@
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
-import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
-import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
/**
* The Class WebSecurityConfig.
@@ -44,7 +42,7 @@ public class BasicSecurityConfig {
SecurityFilterChain filterChain(HttpSecurity http, TenantContextInitFilter tenantContextInitFilter) throws Exception {
http.cors(Customizer.withDefaults())
.httpBasic(Customizer.withDefaults())
- .csrf(csrf -> csrf.disable())
+ .csrf(csrf -> csrf.disable())// if enabled, some functionalities will not work - like creating a project
.addFilterBefore(tenantContextInitFilter, UsernamePasswordAuthenticationFilter.class)
.formLogin(Customizer.withDefaults())
.logout(logout -> logout.deleteCookies("JSESSIONID"))
@@ -62,18 +60,6 @@ SecurityFilterChain filterChain(HttpSecurity http, TenantContextInitFilter tenan
*/
@Bean
CorsConfigurationSource corsConfigurationSource() {
- CorsConfiguration configuration = new CorsConfiguration();
- configuration.setAllowedOriginPatterns(Arrays.asList("*"));
- configuration.setAllowCredentials(true);
- configuration.setAllowedHeaders(
- Arrays.asList("Access-Control-Allow-Headers", "Access-Control-Allow-Origin", "Access-Control-Request-Method",
- "Access-Control-Request-Headers", "Origin", "Cache-Control", "Content-Type", "Authorization"));
- configuration.setExposedHeaders(
- Arrays.asList("Access-Control-Allow-Headers", "Access-Control-Allow-Origin", "Access-Control-Request-Method",
- "Access-Control-Request-Headers", "Origin", "Cache-Control", "Content-Type", "Authorization"));
- configuration.setAllowedMethods(Arrays.asList("HEAD", "DELETE", "GET", "POST", "PATCH", "PUT"));
- UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
- source.registerCorsConfiguration("/**", configuration);
- return source;
+ return CorsConfigurationSourceProvider.get();
}
}
diff --git a/components/pom.xml b/components/pom.xml
index 71ef5c83adc..4286b4c067f 100644
--- a/components/pom.xml
+++ b/components/pom.xml
@@ -33,6 +33,7 @@
security/security-basic
security/security-keycloak
security/security-oauth2
+ security/security-snowflake
data/data-structures
@@ -382,6 +383,11 @@
dirigible-components-security-keycloak
${project.version}
+
+ org.eclipse.dirigible
+ dirigible-components-security-snowflake
+ ${project.version}
+
org.eclipse.dirigible
dirigible-components-security-oauth2
diff --git a/components/security/security-snowflake/pom.xml b/components/security/security-snowflake/pom.xml
new file mode 100644
index 00000000000..721cb206087
--- /dev/null
+++ b/components/security/security-snowflake/pom.xml
@@ -0,0 +1,43 @@
+
+
+
+ 4.0.0
+
+
+ dirigible-components-parent
+ org.eclipse.dirigible
+ 11.0.0-SNAPSHOT
+ ../../pom.xml
+
+
+ Components - Security - Snowflake
+ dirigible-components-security-snowflake
+ jar
+
+
+
+ org.eclipse.dirigible
+ dirigible-components-core-base
+
+
+ org.eclipse.dirigible
+ dirigible-components-core-tenants
+
+
+ org.springframework.boot
+ spring-boot-starter-oauth2-client
+
+
+ org.springframework.boot
+ spring-boot-starter-security
+
+
+
+
+ ../../../licensing-header.txt
+ ../../../
+
+
+
\ No newline at end of file
diff --git a/components/security/security-snowflake/src/main/java/org/eclipse/dirigible/components/security/snowflake/InvalidSecurityContextException.java b/components/security/security-snowflake/src/main/java/org/eclipse/dirigible/components/security/snowflake/InvalidSecurityContextException.java
new file mode 100644
index 00000000000..35b56716cc8
--- /dev/null
+++ b/components/security/security-snowflake/src/main/java/org/eclipse/dirigible/components/security/snowflake/InvalidSecurityContextException.java
@@ -0,0 +1,8 @@
+package org.eclipse.dirigible.components.security.snowflake;
+
+public class InvalidSecurityContextException extends RuntimeException {
+
+ public InvalidSecurityContextException(String message) {
+ super(message);
+ }
+}
diff --git a/components/security/security-snowflake/src/main/java/org/eclipse/dirigible/components/security/snowflake/SnowflakeAdminUserInitializer.java b/components/security/security-snowflake/src/main/java/org/eclipse/dirigible/components/security/snowflake/SnowflakeAdminUserInitializer.java
new file mode 100644
index 00000000000..7371b53fe6a
--- /dev/null
+++ b/components/security/security-snowflake/src/main/java/org/eclipse/dirigible/components/security/snowflake/SnowflakeAdminUserInitializer.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (c) 2024 Eclipse Dirigible contributors
+ *
+ * All rights reserved. This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v2.0 which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v20.html
+ *
+ * SPDX-FileCopyrightText: Eclipse Dirigible contributors SPDX-License-Identifier: EPL-2.0
+ */
+package org.eclipse.dirigible.components.security.snowflake;
+
+import org.apache.commons.lang3.StringUtils;
+import org.eclipse.dirigible.commons.config.DirigibleConfig;
+import org.eclipse.dirigible.components.base.ApplicationListenersOrder.ApplicationReadyEventListeners;
+import org.eclipse.dirigible.components.base.http.roles.Roles;
+import org.eclipse.dirigible.components.base.tenant.DefaultTenant;
+import org.eclipse.dirigible.components.base.tenant.Tenant;
+import org.eclipse.dirigible.components.tenants.domain.User;
+import org.eclipse.dirigible.components.tenants.domain.UserRoleAssignment;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.boot.context.event.ApplicationReadyEvent;
+import org.springframework.context.ApplicationListener;
+import org.springframework.context.annotation.Profile;
+import org.springframework.core.annotation.Order;
+import org.springframework.stereotype.Component;
+
+import java.util.Optional;
+
+@Profile("snowflake")
+@Order(ApplicationReadyEventListeners.ADMIN_USER_INITIALIZER)
+@Component
+class SnowflakeAdminUserInitializer implements ApplicationListener {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(SnowflakeAdminUserInitializer.class);
+
+ private final Tenant defaultTenant;
+
+ private final SnowflakeUserManager snowflakeUserManager;
+
+ SnowflakeAdminUserInitializer(@DefaultTenant Tenant defaultTenant, SnowflakeUserManager snowflakeUserManager) {
+ this.defaultTenant = defaultTenant;
+ this.snowflakeUserManager = snowflakeUserManager;
+ }
+
+ @Override
+ public void onApplicationEvent(ApplicationReadyEvent event) {
+ LOGGER.info("Executing...");
+ initAdminUser();
+ LOGGER.info("Completed.");
+
+ }
+
+ private void initAdminUser() {
+ Optional optionalUsername = getSnowflakeAdminUsername();
+ if (optionalUsername.isEmpty()) {
+ LOGGER.warn("Admin user will not be initialized");
+ return;
+ }
+ String username = optionalUsername.get();
+
+ Optional existingUser = snowflakeUserManager.findUserByUsernameAndTenantId(username, defaultTenant.getId());
+ if (existingUser.isPresent()) {
+ LOGGER.info("A user with username [{}] for tenant [{}] already exists. Skipping its initialization.", username,
+ defaultTenant.getId());
+ return;
+ }
+ User adminUser = snowflakeUserManager.createNewUser(username, defaultTenant.getId());
+ LOGGER.info("Created admin user with username [{}] for tenant with id [{}]", adminUser.getUsername(), adminUser.getTenant()
+ .getId());
+ for (Roles predefinedRole : Roles.values()) {
+ assignRole(adminUser, predefinedRole);
+ }
+ }
+
+ /**
+ * Assign role.
+ *
+ * @param user the user
+ * @param predefinedRole the predefined role
+ */
+ private void assignRole(User user, Roles predefinedRole) {
+ UserRoleAssignment assignment = new UserRoleAssignment();
+ assignment.setUser(user);
+
+ String roleName = predefinedRole.getRoleName();
+ snowflakeUserManager.assignUserRoles(user, roleName);
+
+ LOGGER.info("Assigned role [{}] to user [{}] in tenant [{}]", roleName, user.getUsername(), user.getTenant()
+ .getId());
+ }
+
+ private Optional getSnowflakeAdminUsername() {
+ String username = DirigibleConfig.SNOWFLAKE_ADMIN_USERNAME.getStringValue();
+ if (StringUtils.isBlank(username)) {
+ LOGGER.warn("Missing snowflake admin username in configuration [{}].", DirigibleConfig.SNOWFLAKE_ADMIN_USERNAME.getKey());
+ return Optional.empty();
+ }
+ return Optional.of(username);
+ }
+
+}
diff --git a/components/security/security-snowflake/src/main/java/org/eclipse/dirigible/components/security/snowflake/SnowflakeAuthFilter.java b/components/security/security-snowflake/src/main/java/org/eclipse/dirigible/components/security/snowflake/SnowflakeAuthFilter.java
new file mode 100644
index 00000000000..640ce5bcd3a
--- /dev/null
+++ b/components/security/security-snowflake/src/main/java/org/eclipse/dirigible/components/security/snowflake/SnowflakeAuthFilter.java
@@ -0,0 +1,88 @@
+package org.eclipse.dirigible.components.security.snowflake;
+
+import jakarta.servlet.FilterChain;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.servlet.http.HttpSession;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.context.annotation.Profile;
+import org.springframework.lang.NonNull;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
+import org.springframework.stereotype.Component;
+import org.springframework.web.filter.OncePerRequestFilter;
+
+import java.io.IOException;
+import java.util.Objects;
+
+@Profile("snowflake")
+@Component
+class SnowflakeAuthFilter extends OncePerRequestFilter {
+ private static final Logger LOGGER = LoggerFactory.getLogger(SnowflakeAuthFilter.class);
+
+ private static final String SNOWFLAKE_USER_HEADER = "Sf-Context-Current-User";
+
+ private final SnowflakeUserDetailsService userDetailsService;
+
+ public SnowflakeAuthFilter(SnowflakeUserDetailsService userDetailsService) {
+ this.userDetailsService = userDetailsService;
+ }
+
+ @Override
+ protected void doFilterInternal(@NonNull HttpServletRequest request, @NonNull HttpServletResponse response,
+ @NonNull FilterChain filterChain) throws ServletException, IOException {
+ String currentUser = request.getHeader(SNOWFLAKE_USER_HEADER);
+
+ if (currentUser == null) {
+ LOGGER.debug("Missing user header with name [{}]. Forwarding the request further", SNOWFLAKE_USER_HEADER);
+
+ HttpSession session = request.getSession(false);
+ if (null != session) {
+ session.invalidate();
+ }
+ SecurityContextHolder.clearContext();
+ filterChain.doFilter(request, response);
+ return;
+ }
+
+ Authentication authentication = SecurityContextHolder.getContext()
+ .getAuthentication();
+
+ if (authentication == null) {
+ loginCurrentUser(request, currentUser);
+ } else {
+ validateSecurityContext(authentication, currentUser);
+ }
+
+ filterChain.doFilter(request, response);
+ }
+
+ private void validateSecurityContext(Authentication authentication, String currentUser) {
+ Object principal = authentication.getPrincipal();
+ if (principal instanceof UserDetails userDetails) {
+ String loggedInUsername = userDetails.getUsername();
+ if (!Objects.equals(currentUser, loggedInUsername)) {
+ String errMessage = "Current user [" + currentUser + "] doesn't match the one in the security context: " + loggedInUsername;
+ throw new InvalidSecurityContextException(errMessage);
+ }
+ } else {
+ throw new InvalidSecurityContextException("Unexpected type [" + principal.getClass() + "] for principal");
+ }
+ }
+
+ private void loginCurrentUser(HttpServletRequest request, String currentUser) {
+ UserDetails userDetails = this.userDetailsService.loadUserByUsername(currentUser);
+
+ UsernamePasswordAuthenticationToken authToken =
+ new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
+ authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
+
+ SecurityContextHolder.getContext()
+ .setAuthentication(authToken);
+ }
+}
diff --git a/components/security/security-snowflake/src/main/java/org/eclipse/dirigible/components/security/snowflake/SnowflakeLogoutHandler.java b/components/security/security-snowflake/src/main/java/org/eclipse/dirigible/components/security/snowflake/SnowflakeLogoutHandler.java
new file mode 100644
index 00000000000..d5092c24bde
--- /dev/null
+++ b/components/security/security-snowflake/src/main/java/org/eclipse/dirigible/components/security/snowflake/SnowflakeLogoutHandler.java
@@ -0,0 +1,34 @@
+package org.eclipse.dirigible.components.security.snowflake;
+
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import org.springframework.http.HttpHeaders;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.web.authentication.logout.LogoutHandler;
+import org.springframework.stereotype.Component;
+
+@Component
+class SnowflakeLogoutHandler implements LogoutHandler {
+
+ private static final String SNOWFLAKE_AUTH_COOKIE_PREFIX = "sfc-ss-ingress-auth-v1-";
+ private static final String SNOWFLAKE_AUTH_COOKIE_INVALIDATED_VALUE_PATTERN =
+ SNOWFLAKE_AUTH_COOKIE_PREFIX + "%s=; Expires=Thu, 01 Jan 1970 00:00:00 GMT; Path=/; domain=.%s; Secure; HttpOnly";
+
+ @Override
+ public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
+ String hostHeader = request.getHeader(HttpHeaders.HOST);
+ String subdomain = extractSubdomain(hostHeader);
+
+ String invalidatedAuthCookie = String.format(SNOWFLAKE_AUTH_COOKIE_INVALIDATED_VALUE_PATTERN, subdomain, hostHeader);
+
+ // set the cookie as header since addCookie cannot be used
+ // due to an RFC restriction
+ response.addHeader(HttpHeaders.SET_COOKIE, invalidatedAuthCookie);
+ }
+
+ private static String extractSubdomain(String host) {
+ int dotIdx = host.indexOf(".");
+ return dotIdx == -1 ? host : host.substring(0, dotIdx);
+ }
+
+}
diff --git a/components/security/security-snowflake/src/main/java/org/eclipse/dirigible/components/security/snowflake/SnowflakeSecurityConfig.java b/components/security/security-snowflake/src/main/java/org/eclipse/dirigible/components/security/snowflake/SnowflakeSecurityConfig.java
new file mode 100644
index 00000000000..e0feae9dd5d
--- /dev/null
+++ b/components/security/security-snowflake/src/main/java/org/eclipse/dirigible/components/security/snowflake/SnowflakeSecurityConfig.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 2024 Eclipse Dirigible contributors
+ *
+ * All rights reserved. This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v2.0 which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v20.html
+ *
+ * SPDX-FileCopyrightText: Eclipse Dirigible contributors SPDX-License-Identifier: EPL-2.0
+ */
+package org.eclipse.dirigible.components.security.snowflake;
+
+import org.eclipse.dirigible.components.base.http.access.CorsConfigurationSourceProvider;
+import org.eclipse.dirigible.components.base.http.access.HttpSecurityURIConfigurator;
+import org.eclipse.dirigible.components.tenants.tenant.TenantContextInitFilter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Profile;
+import org.springframework.security.config.Customizer;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.config.http.SessionCreationPolicy;
+import org.springframework.security.web.SecurityFilterChain;
+import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
+import org.springframework.web.cors.CorsConfigurationSource;
+
+@Profile("snowflake")
+@Configuration
+@EnableWebSecurity
+class SnowflakeSecurityConfig {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(SnowflakeSecurityConfig.class);
+
+ private final SnowflakeAuthFilter snowflakeAuthFilter;
+ private final SnowflakeLogoutHandler snowflakeLogoutHandler;
+
+ SnowflakeSecurityConfig(SnowflakeAuthFilter snowflakeAuthFilter, SnowflakeLogoutHandler snowflakeLogoutHandler) {
+ this.snowflakeAuthFilter = snowflakeAuthFilter;
+ this.snowflakeLogoutHandler = snowflakeLogoutHandler;
+ }
+
+ @Bean
+ SecurityFilterChain filterChain(HttpSecurity http, TenantContextInitFilter tenantContextInitFilter) throws Exception {
+ LOGGER.info("Configure snowflake security configurations");
+ http.cors(Customizer.withDefaults())
+ .csrf(csrf -> csrf.disable()) // if enabled, some functionalities will not work - like creating a project
+ .logout(logout -> logout.deleteCookies("JSESSIONID")
+ // consider redirect to reserved path "/sfc-endpoint/logout" and snowflakeLogoutHandler removal
+ .addLogoutHandler(snowflakeLogoutHandler)
+ .logoutSuccessUrl("/")
+ .invalidateHttpSession(true))
+ .headers(headers -> headers.frameOptions(frameOpts -> frameOpts.disable()))
+ .sessionManagement(c -> c.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED))
+ .addFilterBefore(tenantContextInitFilter, UsernamePasswordAuthenticationFilter.class)
+ .addFilterAt(snowflakeAuthFilter, UsernamePasswordAuthenticationFilter.class);
+
+ HttpSecurityURIConfigurator.configure(http);
+
+ return http.build();
+ }
+
+ @Bean
+ CorsConfigurationSource corsConfigurationSource() {
+ return CorsConfigurationSourceProvider.get();
+ }
+
+}
diff --git a/components/security/security-snowflake/src/main/java/org/eclipse/dirigible/components/security/snowflake/SnowflakeUserDetailsService.java b/components/security/security-snowflake/src/main/java/org/eclipse/dirigible/components/security/snowflake/SnowflakeUserDetailsService.java
new file mode 100644
index 00000000000..cc7e32e5c2d
--- /dev/null
+++ b/components/security/security-snowflake/src/main/java/org/eclipse/dirigible/components/security/snowflake/SnowflakeUserDetailsService.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2024 Eclipse Dirigible contributors
+ *
+ * All rights reserved. This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v2.0 which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v20.html
+ *
+ * SPDX-FileCopyrightText: Eclipse Dirigible contributors SPDX-License-Identifier: EPL-2.0
+ */
+package org.eclipse.dirigible.components.security.snowflake;
+
+import org.eclipse.dirigible.components.base.util.AuthoritiesUtil;
+import org.eclipse.dirigible.components.tenants.domain.User;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.context.annotation.Profile;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.stereotype.Service;
+
+import java.util.Set;
+
+@Profile("snowflake")
+@Service
+class SnowflakeUserDetailsService implements UserDetailsService {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(SnowflakeUserDetailsService.class);
+
+ private final SnowflakeUserManager userManager;
+
+ SnowflakeUserDetailsService(SnowflakeUserManager userManager) {
+ this.userManager = userManager;
+ }
+
+ @Override
+ public UserDetails loadUserByUsername(String username) {
+ LOGGER.debug("Loading user with username [{}]...", username);
+ User user = userManager.findUserByUsername(username)
+ .orElseGet(() -> userManager.createNewUser(username));
+
+ LOGGER.debug("Logged in user with username [{}] in tenant [{}]", user.getUsername(), user.getTenant());
+ Set userRoles = userManager.getUserRoleNames(user);
+ LOGGER.debug("User [{}] has assigned roles [{}]", user, userRoles);
+
+ Set auths = AuthoritiesUtil.toAuthorities(userRoles);
+
+ return new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(), auths);
+ }
+
+}
diff --git a/components/security/security-snowflake/src/main/java/org/eclipse/dirigible/components/security/snowflake/SnowflakeUserManager.java b/components/security/security-snowflake/src/main/java/org/eclipse/dirigible/components/security/snowflake/SnowflakeUserManager.java
new file mode 100644
index 00000000000..7cec82363d8
--- /dev/null
+++ b/components/security/security-snowflake/src/main/java/org/eclipse/dirigible/components/security/snowflake/SnowflakeUserManager.java
@@ -0,0 +1,62 @@
+package org.eclipse.dirigible.components.security.snowflake;
+
+import org.eclipse.dirigible.components.base.tenant.TenantContext;
+import org.eclipse.dirigible.components.security.domain.Role;
+import org.eclipse.dirigible.components.security.service.RoleService;
+import org.eclipse.dirigible.components.tenants.domain.User;
+import org.eclipse.dirigible.components.tenants.service.UserService;
+import org.springframework.stereotype.Component;
+
+import java.util.Optional;
+import java.util.Set;
+import java.util.UUID;
+
+@Component
+class SnowflakeUserManager {
+
+ private final UserService userService;
+ private final RoleService roleService;
+ private final TenantContext tenantContext;
+
+ SnowflakeUserManager(UserService userService, RoleService roleService, TenantContext tenantContext) {
+ this.userService = userService;
+ this.roleService = roleService;
+ this.tenantContext = tenantContext;
+ }
+
+ public Optional findUserByUsername(String username) {
+ String currentTenantId = tenantContext.getCurrentTenant()
+ .getId();
+ return findUserByUsernameAndTenantId(username, currentTenantId);
+ }
+
+ public Optional findUserByUsernameAndTenantId(String username, String tenantId) {
+ return userService.findUserByUsernameAndTenantId(toSnowflakeUsername(username), tenantId);
+ }
+
+ private String toSnowflakeUsername(String username) {
+ // usernames are passed in uppercase to the applications via header Sf-Context-Current-User
+ return username.toUpperCase();
+ }
+
+ public User createNewUser(String username) {
+ String currentTenantId = tenantContext.getCurrentTenant()
+ .getId();
+ return createNewUser(username, currentTenantId);
+ }
+
+ public User createNewUser(String username, String tenantId) {
+ String password = UUID.randomUUID()
+ .toString();// password not used in the Snowflake scenario
+ return userService.createNewUser(toSnowflakeUsername(username), password, tenantId);
+ }
+
+ public void assignUserRoles(User user, String roleName) {
+ Role role = roleService.findByName(roleName);
+ userService.assignUserRoles(user, role);
+ }
+
+ public Set getUserRoleNames(User user) {
+ return userService.getUserRoleNames(user);
+ }
+}
diff --git a/components/security/security-snowflake/src/main/resources/application-snowflake.properties b/components/security/security-snowflake/src/main/resources/application-snowflake.properties
new file mode 100644
index 00000000000..a89760437eb
--- /dev/null
+++ b/components/security/security-snowflake/src/main/resources/application-snowflake.properties
@@ -0,0 +1,13 @@
+#
+# Copyright (c) 2010-2024 Eclipse Dirigible contributors
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Eclipse Public License v2.0
+# which accompanies this distribution, and is available at
+# http://www.eclipse.org/legal/epl-v20.html
+#
+# SPDX-FileCopyrightText: Eclipse Dirigible contributors
+# SPDX-License-Identifier: EPL-2.0
+#
+
+basic.enabled=false
diff --git a/modules/commons/commons-config/src/main/java/org/eclipse/dirigible/commons/config/DirigibleConfig.java b/modules/commons/commons-config/src/main/java/org/eclipse/dirigible/commons/config/DirigibleConfig.java
index 14ad922d883..aa83324a2e9 100644
--- a/modules/commons/commons-config/src/main/java/org/eclipse/dirigible/commons/config/DirigibleConfig.java
+++ b/modules/commons/commons-config/src/main/java/org/eclipse/dirigible/commons/config/DirigibleConfig.java
@@ -52,6 +52,8 @@ public enum DirigibleConfig {
/** The tenant subdomain regex. */
TENANT_SUBDOMAIN_REGEX("DIRIGIBLE_TENANT_SUBDOMAIN_REGEX", "^([^\\.]+)\\..+$"),
+ SNOWFLAKE_ADMIN_USERNAME("DIRIGIBLE_SNOWFLAKE_ADMIN_USERNAME", null),
+
/** The basic admin username. */
BASIC_ADMIN_USERNAME("DIRIGIBLE_BASIC_USERNAME", toBase64("admin")),