- Integrated entra id into "prod"-profile
This commit is contained in:
parent
f6160a4153
commit
10687ffe5d
8 changed files with 207 additions and 28 deletions
4
pom.xml
4
pom.xml
|
|
@ -60,6 +60,10 @@
|
||||||
<groupId>com.azure.spring</groupId>
|
<groupId>com.azure.spring</groupId>
|
||||||
<artifactId>spring-cloud-azure-starter-jdbc-mysql</artifactId>
|
<artifactId>spring-cloud-azure-starter-jdbc-mysql</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-oauth2-client</artifactId>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-starter-validation</artifactId>
|
<artifactId>spring-boot-starter-validation</artifactId>
|
||||||
|
|
|
||||||
57
src/main/java/de/avatic/lcc/config/CorsConfig.java
Normal file
57
src/main/java/de/avatic/lcc/config/CorsConfig.java
Normal file
|
|
@ -0,0 +1,57 @@
|
||||||
|
package de.avatic.lcc.config;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.context.annotation.Profile;
|
||||||
|
import org.springframework.core.Ordered;
|
||||||
|
import org.springframework.core.annotation.Order;
|
||||||
|
import org.springframework.core.env.Environment;
|
||||||
|
import org.springframework.web.servlet.config.annotation.CorsRegistry;
|
||||||
|
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
|
||||||
|
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@EnableWebMvc
|
||||||
|
@Order(Ordered.HIGHEST_PRECEDENCE)
|
||||||
|
@Profile("dev | test")
|
||||||
|
public class CorsConfig implements WebMvcConfigurer {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private Environment environment;
|
||||||
|
|
||||||
|
@Value("${lcc.allowed_cors}")
|
||||||
|
private String allowedCors;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addCorsMappings(@NotNull CorsRegistry registry) {
|
||||||
|
String[] activeProfiles = environment.getActiveProfiles();
|
||||||
|
|
||||||
|
System.out.println("Active profiles: " + Arrays.toString(activeProfiles));
|
||||||
|
System.out.println("Allowed CORS: " + allowedCors);
|
||||||
|
|
||||||
|
if (Arrays.asList(activeProfiles).contains("dev")) {
|
||||||
|
|
||||||
|
System.out.println("Applying DEV CORS configuration");
|
||||||
|
|
||||||
|
// Development CORS configuration
|
||||||
|
registry.addMapping("/api/**")
|
||||||
|
.allowedOriginPatterns("http://localhost:*")
|
||||||
|
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
|
||||||
|
.allowedHeaders("*")
|
||||||
|
.exposedHeaders("X-Total-Count", "X-Page-Count", "X-Current-Page")
|
||||||
|
.allowCredentials(false);
|
||||||
|
} else {
|
||||||
|
// Production CORS configuration
|
||||||
|
registry.addMapping("/api/**")
|
||||||
|
.allowedOrigins(allowedCors)
|
||||||
|
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
|
||||||
|
.allowedHeaders("*")
|
||||||
|
.exposedHeaders("X-Total-Count", "X-Page-Count", "X-Current-Page")
|
||||||
|
.allowCredentials(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,6 +2,7 @@ package de.avatic.lcc.config;
|
||||||
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.context.annotation.Profile;
|
||||||
import org.springframework.core.io.ClassPathResource;
|
import org.springframework.core.io.ClassPathResource;
|
||||||
import org.springframework.core.io.Resource;
|
import org.springframework.core.io.Resource;
|
||||||
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
|
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
|
||||||
|
|
@ -10,6 +11,7 @@ import org.springframework.web.servlet.resource.PathResourceResolver;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@Configuration
|
@Configuration
|
||||||
|
@Profile("!dev & !test")
|
||||||
public class FrontendConfig implements WebMvcConfigurer {
|
public class FrontendConfig implements WebMvcConfigurer {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
||||||
27
src/main/java/de/avatic/lcc/config/LccOidcUser.java
Normal file
27
src/main/java/de/avatic/lcc/config/LccOidcUser.java
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
package de.avatic.lcc.config;
|
||||||
|
|
||||||
|
import org.springframework.security.core.GrantedAuthority;
|
||||||
|
import org.springframework.security.oauth2.core.oidc.OidcIdToken;
|
||||||
|
import org.springframework.security.oauth2.core.oidc.OidcUserInfo;
|
||||||
|
import org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
|
||||||
|
public class LccOidcUser extends DefaultOidcUser {
|
||||||
|
|
||||||
|
private final Integer userId;
|
||||||
|
|
||||||
|
public LccOidcUser(Collection<? extends GrantedAuthority> authorities,
|
||||||
|
OidcIdToken idToken,
|
||||||
|
OidcUserInfo userInfo,
|
||||||
|
String nameAttributeKey,
|
||||||
|
Integer userId) {
|
||||||
|
super(authorities, idToken, userInfo, nameAttributeKey);
|
||||||
|
this.userId = userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getSqlUserId() {
|
||||||
|
return userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -1,26 +1,39 @@
|
||||||
package de.avatic.lcc.config;
|
package de.avatic.lcc.config;
|
||||||
|
|
||||||
|
import de.avatic.lcc.model.users.User;
|
||||||
|
import de.avatic.lcc.repositories.users.GroupRepository;
|
||||||
|
import de.avatic.lcc.repositories.users.UserRepository;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.context.annotation.Profile;
|
import org.springframework.context.annotation.Profile;
|
||||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||||
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
|
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
|
||||||
|
import org.springframework.security.core.GrantedAuthority;
|
||||||
|
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||||
|
import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserRequest;
|
||||||
|
import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserService;
|
||||||
|
import org.springframework.security.oauth2.client.userinfo.OAuth2UserService;
|
||||||
|
import org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser;
|
||||||
|
import org.springframework.security.oauth2.core.oidc.user.OidcUser;
|
||||||
import org.springframework.security.web.SecurityFilterChain;
|
import org.springframework.security.web.SecurityFilterChain;
|
||||||
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.HashSet;
|
||||||
import java.util.List;
|
import java.util.Set;
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
public class SecurityConfig {
|
public class SecurityConfig {
|
||||||
|
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
@Profile("!dev & !test") // Only active when NOT in dev profile
|
@Profile("!dev & !test") // Only active when NOT in dev profile
|
||||||
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||||||
// Your production security configuration
|
http
|
||||||
|
.authorizeHttpRequests(auth -> auth
|
||||||
|
.anyRequest().authenticated()
|
||||||
|
)
|
||||||
|
.oauth2Login(oauth2 -> oauth2
|
||||||
|
.defaultSuccessUrl("/", true)
|
||||||
|
);
|
||||||
|
|
||||||
return http.build();
|
return http.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -32,4 +45,54 @@ public class SecurityConfig {
|
||||||
.csrf(AbstractHttpConfigurer::disable)
|
.csrf(AbstractHttpConfigurer::disable)
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
@Profile("!dev & !test")
|
||||||
|
public OAuth2UserService<OidcUserRequest, OidcUser> oidcUserService(UserRepository userRepository, GroupRepository groupRepository) {
|
||||||
|
final OidcUserService delegate = new OidcUserService();
|
||||||
|
|
||||||
|
return (userRequest) -> {
|
||||||
|
OidcUser oidcUser = delegate.loadUser(userRequest);
|
||||||
|
Integer userId = null;
|
||||||
|
|
||||||
|
// // Debug: Print all claims
|
||||||
|
// System.out.println("=== ID Token Claims ===");
|
||||||
|
// oidcUser.getIdToken().getClaims().forEach((key, value) ->
|
||||||
|
// System.out.println(key + ": " + value)
|
||||||
|
// );
|
||||||
|
// System.out.println("======================");
|
||||||
|
|
||||||
|
Set<GrantedAuthority> mappedAuthorities = new HashSet<>(oidcUser.getAuthorities());
|
||||||
|
|
||||||
|
// Try different ways to get email
|
||||||
|
String email = oidcUser.getEmail();
|
||||||
|
if (email == null) {
|
||||||
|
email = oidcUser.getAttribute("email");
|
||||||
|
}
|
||||||
|
if (email == null) {
|
||||||
|
email = oidcUser.getAttribute("preferred_username");
|
||||||
|
}
|
||||||
|
if (email == null) {
|
||||||
|
email = oidcUser.getAttribute("upn");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (email != null) {
|
||||||
|
User user = userRepository.getByEmail(email);
|
||||||
|
if (user != null) {
|
||||||
|
user.getGroups().forEach(group -> mappedAuthorities.add(new SimpleGrantedAuthority("ROLE_" + group.getName())));
|
||||||
|
userId = user.getId();
|
||||||
|
} else {
|
||||||
|
mappedAuthorities.add(new SimpleGrantedAuthority("ROLE_default"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new LccOidcUser(
|
||||||
|
mappedAuthorities,
|
||||||
|
oidcUser.getIdToken(),
|
||||||
|
oidcUser.getUserInfo(),
|
||||||
|
"preferred_username",
|
||||||
|
userId
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@ import de.avatic.lcc.model.users.User;
|
||||||
import de.avatic.lcc.repositories.pagination.SearchQueryPagination;
|
import de.avatic.lcc.repositories.pagination.SearchQueryPagination;
|
||||||
import de.avatic.lcc.repositories.pagination.SearchQueryResult;
|
import de.avatic.lcc.repositories.pagination.SearchQueryResult;
|
||||||
import org.springframework.jdbc.core.JdbcTemplate;
|
import org.springframework.jdbc.core.JdbcTemplate;
|
||||||
import org.springframework.jdbc.core.RowCallbackHandler;
|
|
||||||
import org.springframework.jdbc.core.RowMapper;
|
import org.springframework.jdbc.core.RowMapper;
|
||||||
import org.springframework.jdbc.support.GeneratedKeyHolder;
|
import org.springframework.jdbc.support.GeneratedKeyHolder;
|
||||||
import org.springframework.jdbc.support.KeyHolder;
|
import org.springframework.jdbc.support.KeyHolder;
|
||||||
|
|
@ -19,7 +18,6 @@ import java.sql.Statement;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
@Repository
|
@Repository
|
||||||
public class UserRepository {
|
public class UserRepository {
|
||||||
|
|
@ -69,7 +67,7 @@ public class UserRepository {
|
||||||
|
|
||||||
List<Integer> groupIds = findGroupIds(user.getGroups().stream().map(Group::getName).toList());
|
List<Integer> groupIds = findGroupIds(user.getGroups().stream().map(Group::getName).toList());
|
||||||
|
|
||||||
if(userId == null) {
|
if (userId == null) {
|
||||||
KeyHolder keyHolder = new GeneratedKeyHolder();
|
KeyHolder keyHolder = new GeneratedKeyHolder();
|
||||||
jdbcTemplate.update(connection -> {
|
jdbcTemplate.update(connection -> {
|
||||||
PreparedStatement ps = connection.prepareStatement(
|
PreparedStatement ps = connection.prepareStatement(
|
||||||
|
|
@ -86,15 +84,14 @@ public class UserRepository {
|
||||||
}, keyHolder);
|
}, keyHolder);
|
||||||
|
|
||||||
userId = Objects.requireNonNull(keyHolder.getKey()).intValue();
|
userId = Objects.requireNonNull(keyHolder.getKey()).intValue();
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
String query = """
|
String query = """
|
||||||
UPDATE sys_user SET email = ?, firstname = ?, lastname = ?, workday_id = ?, is_active = ? WHERE id = ?""";
|
UPDATE sys_user SET email = ?, firstname = ?, lastname = ?, workday_id = ?, is_active = ? WHERE id = ?""";
|
||||||
|
|
||||||
jdbcTemplate.update(query, user.getEmail(), user.getFirstName(), user.getLastName(), user.getWorkdayId(), user.getActive(), userId);
|
jdbcTemplate.update(query, user.getEmail(), user.getFirstName(), user.getLastName(), user.getWorkdayId(), user.getActive(), userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
updateUserGroupMappings(userId, groupIds);
|
updateUserGroupMappings(userId, groupIds);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -159,27 +156,42 @@ public class UserRepository {
|
||||||
FROM sys_user
|
FROM sys_user
|
||||||
WHERE id = ?""";
|
WHERE id = ?""";
|
||||||
|
|
||||||
return jdbcTemplate.queryForObject(query, new UserMapper(), id);
|
|
||||||
|
var user = jdbcTemplate.query(query, new UserMapper(), id);
|
||||||
|
|
||||||
|
return user.isEmpty() ? null : user.getFirst();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public User getByEmail(String email) {
|
||||||
|
String query = """
|
||||||
|
SELECT *
|
||||||
|
FROM sys_user
|
||||||
|
WHERE email = ?""";
|
||||||
|
|
||||||
|
var user = jdbcTemplate.query(query, new UserMapper(), email);
|
||||||
|
|
||||||
|
return user.isEmpty() ? null : user.getFirst();
|
||||||
}
|
}
|
||||||
|
|
||||||
private class UserMapper implements RowMapper<User> {
|
private class UserMapper implements RowMapper<User> {
|
||||||
@Override
|
@Override
|
||||||
public User mapRow(ResultSet rs, int rowNum) throws SQLException {
|
public User mapRow(ResultSet rs, int rowNum) throws SQLException {
|
||||||
|
|
||||||
var user = new User();
|
var user = new User();
|
||||||
|
|
||||||
int id = rs.getInt("id");
|
int id = rs.getInt("id");
|
||||||
|
|
||||||
user.setId(id);
|
user.setId(id);
|
||||||
user.setActive(rs.getBoolean("is_active"));
|
user.setActive(rs.getBoolean("is_active"));
|
||||||
user.setEmail(rs.getString("email"));
|
user.setEmail(rs.getString("email"));
|
||||||
user.setFirstName(rs.getString("firstname"));
|
user.setFirstName(rs.getString("firstname"));
|
||||||
user.setLastName(rs.getString("lastname"));
|
user.setLastName(rs.getString("lastname"));
|
||||||
user.setWorkdayId(rs.getString("workday_id"));
|
user.setWorkdayId(rs.getString("workday_id"));
|
||||||
|
|
||||||
user.setGroups(getGroupMemberships(id));
|
user.setGroups(getGroupMemberships(id));
|
||||||
|
|
||||||
return user;
|
return user;
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
package de.avatic.lcc.service.access;
|
package de.avatic.lcc.service.access;
|
||||||
|
|
||||||
|
import de.avatic.lcc.config.LccOidcUser;
|
||||||
import de.avatic.lcc.dto.calculation.CalculationStatus;
|
import de.avatic.lcc.dto.calculation.CalculationStatus;
|
||||||
import de.avatic.lcc.dto.calculation.PremiseDTO;
|
import de.avatic.lcc.dto.calculation.PremiseDTO;
|
||||||
import de.avatic.lcc.dto.calculation.edit.PremiseDetailDTO;
|
import de.avatic.lcc.dto.calculation.edit.PremiseDetailDTO;
|
||||||
|
|
@ -27,6 +28,9 @@ import de.avatic.lcc.service.transformer.premise.PremiseTransformer;
|
||||||
import de.avatic.lcc.util.exception.base.InternalErrorException;
|
import de.avatic.lcc.util.exception.base.InternalErrorException;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
|
import org.springframework.security.oauth2.core.oidc.user.OidcUser;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
|
@ -84,6 +88,14 @@ public class PremisesService {
|
||||||
//TODO use actual user.
|
//TODO use actual user.
|
||||||
userId = 1;
|
userId = 1;
|
||||||
|
|
||||||
|
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||||
|
|
||||||
|
//todo make a service. and simulate user rights in dev profile.
|
||||||
|
if (authentication != null && authentication.getPrincipal() instanceof LccOidcUser) {
|
||||||
|
LccOidcUser oidcUser = (LccOidcUser) authentication.getPrincipal();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
return SearchQueryResult.map(premiseRepository.listPremises(filter, new SearchQueryPagination(page, limit), userId, deleted, archived, done), admin ? premiseTransformer::toPremiseDTOWithUserInfo : premiseTransformer::toPremiseDTO);
|
return SearchQueryResult.map(premiseRepository.listPremises(filter, new SearchQueryPagination(page, limit), userId, deleted, archived, done), admin ? premiseTransformer::toPremiseDTOWithUserInfo : premiseTransformer::toPremiseDTO);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@ spring.datasource.username=${DB_USER}
|
||||||
spring.datasource.password=${DB_PASSWORD}
|
spring.datasource.password=${DB_PASSWORD}
|
||||||
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
|
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
|
||||||
spring.sql.init.mode=never
|
spring.sql.init.mode=never
|
||||||
#spring.profiles.active=setup
|
|
||||||
lcc.bulk.sheet_password=secretSheet?!
|
lcc.bulk.sheet_password=secretSheet?!
|
||||||
lcc.allowed_cors=${ALLOWED_CORS_DOMAIN}
|
lcc.allowed_cors=${ALLOWED_CORS_DOMAIN}
|
||||||
azure.maps.subscription.key=${AZURE_MAPS_SUBSCRIPTION_KEY}
|
azure.maps.subscription.key=${AZURE_MAPS_SUBSCRIPTION_KEY}
|
||||||
|
|
@ -13,5 +12,8 @@ azure.maps.client.id=your-app-registration-client-id
|
||||||
azure.maps.resource.id=/subscriptions/sub-id/resourceGroups/rg-name/providers/Microsoft.Maps/accounts/account-name
|
azure.maps.resource.id=/subscriptions/sub-id/resourceGroups/rg-name/providers/Microsoft.Maps/accounts/account-name
|
||||||
spring.servlet.multipart.max-file-size=30MB
|
spring.servlet.multipart.max-file-size=30MB
|
||||||
spring.servlet.multipart.max-request-size=50MB
|
spring.servlet.multipart.max-request-size=50MB
|
||||||
spring.web.resources.add-mappings=true
|
spring.cloud.azure.active-directory.enabled=true
|
||||||
|
spring.cloud.azure.active-directory.profile.tenant-id=${AZURE_TENANT_ID}
|
||||||
|
spring.cloud.azure.active-directory.credential.client-id=${AZURE_CLIENT_ID}
|
||||||
|
spring.cloud.azure.active-directory.credential.client-secret=${AZURE_CLIENT_SECRET}
|
||||||
|
spring.cloud.azure.active-directory.authorization-clients.graph.scopes=openid,profile,email,https://graph.microsoft.com/User.Read
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue