Userrollen Verwaltung, Admin-Ablauf möglich, Rolle GOD
This commit is contained in:
parent
fcd94bfba0
commit
fd3f82659f
|
|
@ -2,12 +2,14 @@ package it.boergmann.tkdApp;
|
|||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||
|
||||
@SpringBootApplication
|
||||
public class TkdApiApplication {
|
||||
@EnableScheduling
|
||||
public class TkdAppApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(TkdApiApplication.class, args);
|
||||
SpringApplication.run(TkdAppApplication.class, args);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -2,20 +2,31 @@ package it.boergmann.tkdApp.config;
|
|||
|
||||
import it.boergmann.tkdApp.security.CustomUserDetailsService;
|
||||
import it.boergmann.tkdApp.security.JwtAuthenticationFilter;
|
||||
import jakarta.servlet.Filter;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.security.access.AccessDeniedException;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.authentication.AuthenticationProvider;
|
||||
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
|
||||
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
|
||||
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
import org.springframework.security.web.access.AccessDeniedHandler;
|
||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
|
|
@ -25,18 +36,34 @@ public class SecurityConfig {
|
|||
private final CustomUserDetailsService userDetailsService;
|
||||
private final JwtAuthenticationFilter jwtAuthFilter;
|
||||
|
||||
|
||||
// 🧱 1. API Security (JWT, kein Redirect)
|
||||
@Bean
|
||||
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||||
@Order(1)
|
||||
public SecurityFilterChain apiSecurity(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.csrf(csrf -> csrf.disable())
|
||||
.sessionManagement(session -> session
|
||||
.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
|
||||
)
|
||||
.authorizeHttpRequests(auth -> auth
|
||||
.securityMatcher("/api/**")
|
||||
.csrf(AbstractHttpConfigurer::disable)
|
||||
.sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
|
||||
.authorizeHttpRequests(authz -> authz
|
||||
.requestMatchers("/api/login", "/api/register").permitAll()
|
||||
.requestMatchers("/admin/**", "/api/users/**").hasRole("ADMIN")
|
||||
.requestMatchers("/api/**").authenticated()
|
||||
.requestMatchers("/register", "/login", "/css/**", "/js/**").permitAll()
|
||||
.anyRequest().authenticated()
|
||||
)
|
||||
.exceptionHandling(ex -> ex
|
||||
.authenticationEntryPoint((req, res, ex1) -> res.sendError(HttpServletResponse.SC_UNAUTHORIZED))
|
||||
)
|
||||
.addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class);
|
||||
|
||||
return http.build();
|
||||
}
|
||||
|
||||
// 🌐 2. Web Security (Form Login)
|
||||
@Bean
|
||||
@Order(2)
|
||||
public SecurityFilterChain webSecurity(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.authorizeHttpRequests(authz -> authz
|
||||
.requestMatchers("/login", "/register", "/css/**", "/js/**").permitAll()
|
||||
.anyRequest().authenticated()
|
||||
)
|
||||
.formLogin(form -> form
|
||||
|
|
@ -44,12 +71,36 @@ public class SecurityConfig {
|
|||
.defaultSuccessUrl("/me", true)
|
||||
.permitAll()
|
||||
)
|
||||
.addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class);
|
||||
|
||||
.logout(logout -> logout
|
||||
.logoutSuccessUrl("/login?logout")
|
||||
.permitAll()
|
||||
)
|
||||
.exceptionHandling(exception -> exception
|
||||
.accessDeniedPage("/access-denied")
|
||||
);
|
||||
|
||||
return http.build();
|
||||
}
|
||||
|
||||
|
||||
@Bean
|
||||
public AccessDeniedHandler accessDeniedHandler() {
|
||||
return new CustomAccessDeniedHandler(); // Siehe unten
|
||||
}
|
||||
|
||||
@Component
|
||||
public class CustomAccessDeniedHandler implements AccessDeniedHandler {
|
||||
|
||||
@Override
|
||||
public void handle(HttpServletRequest request,
|
||||
HttpServletResponse response,
|
||||
AccessDeniedException accessDeniedException) throws IOException, ServletException {
|
||||
|
||||
// Leite weiter auf eine benutzerdefinierte Seite
|
||||
response.sendRedirect("/access-denied");
|
||||
}
|
||||
}
|
||||
|
||||
@Bean
|
||||
public AuthenticationProvider authProvider() {
|
||||
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
|
||||
|
|
|
|||
|
|
@ -49,25 +49,4 @@ public class AuthController {
|
|||
}
|
||||
}
|
||||
|
||||
@PostMapping("/register")
|
||||
public ResponseEntity<?> register(@RequestBody @Valid RegisterRequest request) {
|
||||
if (userRepository.findByUsername(request.getUsername()).isPresent()) {
|
||||
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("Benutzername bereits vergeben");
|
||||
}
|
||||
|
||||
if (userRepository.findAll().stream().anyMatch(u -> u.getEmail().equalsIgnoreCase(request.getEmail()))) {
|
||||
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("Email bereits registriert");
|
||||
}
|
||||
|
||||
AppUser user = AppUser.builder()
|
||||
.username(request.getUsername())
|
||||
.password(passwordEncoder.encode(request.getPassword()))
|
||||
.email(request.getEmail())
|
||||
.roles(List.of(Role.NONE)) // 👉 noch nicht freigeschaltet
|
||||
.build();
|
||||
|
||||
userRepository.save(user);
|
||||
|
||||
return ResponseEntity.status(HttpStatus.CREATED).body("Registrierung erfolgreich. Admin muss freischalten.");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,30 +3,44 @@ package it.boergmann.tkdApp.controller.api;
|
|||
import it.boergmann.tkdApp.domain.Role;
|
||||
import it.boergmann.tkdApp.service.AppUserService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/users")
|
||||
@RequiredArgsConstructor
|
||||
@PreAuthorize("hasRole('ADMIN')")
|
||||
@PreAuthorize("hasAnyRole('ADMIN', 'GOD')")
|
||||
public class UserAdminApiController {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(UserAdminApiController.class);
|
||||
private final AppUserService userService;
|
||||
|
||||
@PostMapping("/{id}/enable")
|
||||
public ResponseEntity<?> enableUser(@PathVariable UUID id, @RequestParam Role role) {
|
||||
userService.activateUser(id, role);
|
||||
return ResponseEntity.ok(Map.of("message", "Benutzer freigeschaltet"));
|
||||
@PostMapping("/{id}/role/add")
|
||||
public ResponseEntity<?> addRoleApi(@PathVariable UUID id,
|
||||
@RequestParam Role role,
|
||||
@RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime validUntil) {
|
||||
|
||||
log.info("Role Add called");
|
||||
userService.updateUserRole(id, role, validUntil);
|
||||
Map<String, Object> response = new HashMap<>(Map.of("message", "Rolle hinzugefügt"));
|
||||
Optional.ofNullable(validUntil)
|
||||
.ifPresent(v -> response.put("expires", v));
|
||||
return ResponseEntity.ok(response);
|
||||
}
|
||||
|
||||
@PutMapping("/{id}/role")
|
||||
public ResponseEntity<?> updateRole(@PathVariable UUID id, @RequestParam Role role) {
|
||||
userService.changeUserRole(id, role);
|
||||
return ResponseEntity.ok(Map.of("message", "Benutzerrolle aktualisiert"));
|
||||
@GetMapping("/test")
|
||||
public ResponseEntity<?> testApiAccess() {
|
||||
log.info("TEST CALLED");
|
||||
return ResponseEntity.ok("Du bist eingeloggt als: " + SecurityContextHolder.getContext().getAuthentication().getName());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,17 +2,23 @@ package it.boergmann.tkdApp.controller.api;
|
|||
|
||||
import it.boergmann.tkdApp.dto.AppUserResponse;
|
||||
import it.boergmann.tkdApp.domain.AppUser;
|
||||
import it.boergmann.tkdApp.dto.RegisterRequest;
|
||||
import it.boergmann.tkdApp.service.AppUserService;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api")
|
||||
@RequiredArgsConstructor
|
||||
public class UserController {
|
||||
public class UserApiController {
|
||||
|
||||
private final AppUserService appUserService;
|
||||
private static final Logger log = LoggerFactory.getLogger(UserAdminApiController.class);
|
||||
|
||||
@GetMapping("/me")
|
||||
public ResponseEntity<AppUserResponse> getCurrentUser() {
|
||||
|
|
@ -20,8 +26,14 @@ public class UserController {
|
|||
AppUserResponse response = new AppUserResponse(
|
||||
user.getId(),
|
||||
user.getUsername(),
|
||||
user.getRoles()
|
||||
user.getAppUserRoles()
|
||||
);
|
||||
return ResponseEntity.ok(response);
|
||||
}
|
||||
|
||||
@PostMapping("/register")
|
||||
public ResponseEntity<?> registerUser(@Valid @RequestBody RegisterRequest request) {
|
||||
appUserService.registerNewUser(request);
|
||||
return ResponseEntity.ok().build();
|
||||
}
|
||||
}
|
||||
|
|
@ -5,42 +5,48 @@ import it.boergmann.tkdApp.domain.Role;
|
|||
import it.boergmann.tkdApp.service.AppUserService;
|
||||
import it.boergmann.tkdApp.repository.AppUserRepository;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
@Controller
|
||||
@RequestMapping("/admin")
|
||||
@RequiredArgsConstructor
|
||||
@PreAuthorize("hasRole('ADMIN')")
|
||||
@PreAuthorize("hasAnyRole('ADMIN', 'GOD')")
|
||||
public class AdminController {
|
||||
|
||||
private final AppUserRepository userRepository;
|
||||
private final AppUserService userService;
|
||||
|
||||
@GetMapping("/users")
|
||||
public String showPendingUsers(Model model) {
|
||||
List<AppUser> pendingUsers = userRepository.findAll().stream()
|
||||
.filter(u -> u.getRoles().contains(Role.NONE))
|
||||
.toList();
|
||||
public String showAllUsers(Model model) {
|
||||
List<AppUser> allUsers = userRepository.findAll();
|
||||
|
||||
model.addAttribute("users", pendingUsers);
|
||||
model.addAttribute("users", allUsers);
|
||||
model.addAttribute("roles", Role.values()); // für Dropdown
|
||||
return "admin_users";
|
||||
}
|
||||
|
||||
@PostMapping("/users/{id}/enable")
|
||||
public String enableUser(@PathVariable UUID id, @RequestParam Role role) {
|
||||
userService.activateUser(id, role);
|
||||
@PostMapping("/users/update")
|
||||
public String updateUserRoles(@RequestParam UUID userId,
|
||||
@RequestParam Role role,
|
||||
@RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime validUntil) {
|
||||
|
||||
userService.updateUserRole(userId, role, validUntil);
|
||||
return "redirect:/admin/users";
|
||||
}
|
||||
|
||||
@PostMapping("/users/{id}/role")
|
||||
public String changeUserRole(@PathVariable UUID id, @RequestParam Role role) {
|
||||
userService.changeUserRole(id, role);
|
||||
@PostMapping("/users/{id}/role/add")
|
||||
public String addRole(@PathVariable UUID id,
|
||||
@RequestParam Role role,
|
||||
@RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime validUntil) {
|
||||
userService.updateUserRole(id, role, validUntil);
|
||||
return "redirect:/admin/users";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import it.boergmann.tkdApp.dto.RegisterRequest;
|
|||
import it.boergmann.tkdApp.domain.AppUser;
|
||||
import it.boergmann.tkdApp.domain.Role;
|
||||
import it.boergmann.tkdApp.repository.AppUserRepository;
|
||||
import it.boergmann.tkdApp.service.AppUserService;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
|
|
@ -16,9 +17,9 @@ import java.util.List;
|
|||
|
||||
@Controller
|
||||
@RequiredArgsConstructor
|
||||
public class RegisterController {
|
||||
|
||||
public class UserController {
|
||||
private final AppUserRepository userRepository;
|
||||
private final AppUserService appUserService;
|
||||
private final PasswordEncoder passwordEncoder;
|
||||
|
||||
@GetMapping("/register")
|
||||
|
|
@ -29,26 +30,7 @@ public class RegisterController {
|
|||
|
||||
@PostMapping("/register")
|
||||
public String handleRegister(@ModelAttribute("user") @Valid RegisterRequest request, BindingResult result, Model model) {
|
||||
if (userRepository.findByUsername(request.getUsername()).isPresent()) {
|
||||
result.rejectValue("username", "error.user", "Benutzername existiert bereits");
|
||||
}
|
||||
|
||||
if (userRepository.findAll().stream().anyMatch(u -> u.getEmail().equalsIgnoreCase(request.getEmail()))) {
|
||||
result.rejectValue("email", "error.email", "Email-Adresse ist bereits registriert");
|
||||
}
|
||||
|
||||
if (result.hasErrors()) {
|
||||
return "register";
|
||||
}
|
||||
|
||||
AppUser newUser = AppUser.builder()
|
||||
.username(request.getUsername())
|
||||
.email(request.getEmail())
|
||||
.password(passwordEncoder.encode(request.getPassword()))
|
||||
.roles(List.of(Role.NONE))
|
||||
.build();
|
||||
|
||||
userRepository.save(newUser);
|
||||
appUserService.registerNewUser(request);
|
||||
|
||||
model.addAttribute("message", "Registrierung erfolgreich! Bitte auf Freischaltung durch Admin warten.");
|
||||
return "login";
|
||||
|
|
@ -6,6 +6,8 @@ import lombok.RequiredArgsConstructor;
|
|||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
|
||||
@Controller
|
||||
@RequiredArgsConstructor
|
||||
|
|
@ -24,4 +26,10 @@ public class WebController {
|
|||
model.addAttribute("user", user);
|
||||
return "profile"; // src/main/resources/templates/profile.html
|
||||
}
|
||||
|
||||
@GetMapping("/access-denied")
|
||||
public String accessDenied() {
|
||||
return "error/403";
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ import lombok.*;
|
|||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
|
@ -27,19 +29,20 @@ public class AppUser implements UserDetails{
|
|||
|
||||
private String password;
|
||||
|
||||
@ElementCollection(fetch = FetchType.EAGER)
|
||||
@Enumerated(EnumType.STRING)
|
||||
private List<Role> roles;
|
||||
@OneToMany(mappedBy = "user", cascade = CascadeType.ALL, fetch = FetchType.EAGER, orphanRemoval = true)
|
||||
@Builder.Default
|
||||
private List<AppUserRole> appUserRoles = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public Collection<? extends GrantedAuthority> getAuthorities() {
|
||||
return roles.stream()
|
||||
.map(role ->(GrantedAuthority)() -> "ROLE_" +role)
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
return appUserRoles.stream()
|
||||
.filter(r -> r.getValidUntil() == null || r.getValidUntil().isAfter(now)) // nur gültige Rollen
|
||||
.map(r -> (GrantedAuthority) () -> "ROLE_" + r.getRole().name())
|
||||
.toList();
|
||||
}
|
||||
|
||||
@Override public boolean isAccountNonExpired() { return true; }
|
||||
@Override public boolean isAccountNonLocked() { return true; }
|
||||
@Override public boolean isCredentialsNonExpired() { return true; }
|
||||
@Override public boolean isEnabled() { return true; }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,29 @@
|
|||
package it.boergmann.tkdApp.domain;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import lombok.*;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.UUID;
|
||||
|
||||
@Entity
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
public class AppUserRole {
|
||||
|
||||
@Id
|
||||
@GeneratedValue
|
||||
private UUID id;
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "user_id", nullable = false)
|
||||
private AppUser user;
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(nullable = false)
|
||||
private Role role;
|
||||
|
||||
private LocalDateTime validUntil; // 🕒 Ablaufzeitpunkt, optional
|
||||
}
|
||||
|
|
@ -3,5 +3,6 @@ package it.boergmann.tkdApp.domain;
|
|||
public enum Role {
|
||||
NONE,
|
||||
USER,
|
||||
ADMIN
|
||||
ADMIN,
|
||||
GOD
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package it.boergmann.tkdApp.dto;
|
||||
|
||||
import it.boergmann.tkdApp.domain.AppUserRole;
|
||||
import it.boergmann.tkdApp.domain.Role;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
|
|
@ -12,5 +13,5 @@ import java.util.UUID;
|
|||
public class AppUserResponse {
|
||||
private UUID id;
|
||||
private String username;
|
||||
private List<Role> roles;
|
||||
private List<AppUserRole> roles;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,14 +7,14 @@ import lombok.Data;
|
|||
|
||||
@Data
|
||||
public class RegisterRequest {
|
||||
@NotBlank
|
||||
@NotBlank(message = "Username darf nicht leer sein")
|
||||
private String username;
|
||||
|
||||
@NotBlank
|
||||
@NotBlank(message = "Passwort darf nicht leer sein")
|
||||
private String password;
|
||||
|
||||
@Email
|
||||
@NotBlank
|
||||
@NotBlank(message = "Email darf nicht leer sein")
|
||||
@Email(message = "Ungültige E-Mail-Adresse")
|
||||
private String email;
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,57 @@
|
|||
package it.boergmann.tkdApp.init;
|
||||
|
||||
import it.boergmann.tkdApp.domain.AppUser;
|
||||
import it.boergmann.tkdApp.domain.Role;
|
||||
import it.boergmann.tkdApp.domain.AppUserRole;
|
||||
import it.boergmann.tkdApp.repository.AppUserRepository;
|
||||
import it.boergmann.tkdApp.repository.AppUserRoleRepository;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.boot.ApplicationRunner;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
@Configuration
|
||||
@RequiredArgsConstructor
|
||||
public class StartupAdminInitializer {
|
||||
|
||||
private final AppUserRepository userRepository;
|
||||
private final AppUserRoleRepository roleRepository;
|
||||
private final PasswordEncoder passwordEncoder;
|
||||
|
||||
@Bean
|
||||
public ApplicationRunner initAdminUser() {
|
||||
return args -> {
|
||||
boolean adminExists = userRepository.findAll().stream()
|
||||
.flatMap(user -> user.getAppUserRoles().stream())
|
||||
.anyMatch(role -> role.getRole() == Role.ADMIN &&
|
||||
(role.getValidUntil() == null || role.getValidUntil().isAfter(LocalDateTime.now())));
|
||||
|
||||
if (!adminExists) {
|
||||
System.out.println("⚠️ Kein aktiver Admin gefunden. Erstelle Standard-Admin-User...");
|
||||
|
||||
AppUser admin = AppUser.builder()
|
||||
.email("admin@boergmann.it")
|
||||
.username("admin")
|
||||
.password(passwordEncoder.encode("Dimrb#7361"))
|
||||
.appUserRoles(List.of()) // Initial leer
|
||||
.build();
|
||||
|
||||
AppUser savedUser = userRepository.save(admin);
|
||||
|
||||
AppUserRole adminRole = AppUserRole.builder()
|
||||
.user(savedUser)
|
||||
.role(Role.GOD)
|
||||
.validUntil(null) // dauerhaft gültig
|
||||
.build();
|
||||
|
||||
roleRepository.save(adminRole);
|
||||
|
||||
System.out.println("✅ Admin-User admin/admin123 wurde erfolgreich angelegt.");
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
package it.boergmann.tkdApp.jobs;
|
||||
|
||||
import it.boergmann.tkdApp.domain.AppUser;
|
||||
import it.boergmann.tkdApp.domain.AppUserRole;
|
||||
import it.boergmann.tkdApp.domain.Role;
|
||||
import it.boergmann.tkdApp.repository.AppUserRepository;
|
||||
import it.boergmann.tkdApp.repository.AppUserRoleRepository;
|
||||
import jakarta.transaction.Transactional;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class RoleCleanupScheduler {
|
||||
|
||||
private final AppUserRoleRepository appUserRoleRepository;
|
||||
private final AppUserRepository appUserRepository;
|
||||
|
||||
@Scheduled(cron = "0 0 * * * *") // stündlich z. B.
|
||||
public void downgradeExpiredAdmins() {
|
||||
List<AppUserRole> expiredAdmins = appUserRoleRepository.findExpiredAppUserRole(Role.ADMIN, LocalDateTime.now());
|
||||
|
||||
for (AppUserRole expired : expiredAdmins) {
|
||||
appUserRoleRepository.delete(expired);
|
||||
AppUser user = expired.getUser();
|
||||
|
||||
// Nur wenn keine andere Rolle vorhanden, neue Rolle setzen
|
||||
if (user.getAppUserRoles().isEmpty()) {
|
||||
var fallback = AppUserRole.builder()
|
||||
.user(user)
|
||||
.role(Role.USER)
|
||||
.build();
|
||||
user.getAppUserRoles().add(fallback);
|
||||
appUserRepository.save(user);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,11 +1,20 @@
|
|||
package it.boergmann.tkdApp.repository;
|
||||
|
||||
import it.boergmann.tkdApp.domain.AppUser;
|
||||
import it.boergmann.tkdApp.domain.AppUserRole;
|
||||
import it.boergmann.tkdApp.domain.Role;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
public interface AppUserRepository extends JpaRepository<AppUser, UUID> {
|
||||
boolean existsByUsername(String username); // 🔥 hier hinzufügen!
|
||||
boolean existsByEmail(String email);
|
||||
Optional<AppUser> findByUsername(String username);
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
package it.boergmann.tkdApp.repository;
|
||||
|
||||
import it.boergmann.tkdApp.domain.AppUserRole;
|
||||
import it.boergmann.tkdApp.domain.Role;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
public interface AppUserRoleRepository extends JpaRepository<AppUserRole, UUID> {
|
||||
@Query("SELECT r FROM AppUserRole r WHERE r.role = :role AND r.validUntil IS NOT NULL AND r.validUntil < :now")
|
||||
List<AppUserRole> findExpiredAppUserRole(@Param("role") Role role, @Param("now") LocalDateTime now);
|
||||
}
|
||||
|
|
@ -20,7 +20,7 @@ public class CustomUserDetailsService implements UserDetailsService {
|
|||
AppUser user = userRepository.findByUsername(username)
|
||||
.orElseThrow(() -> new UsernameNotFoundException("User not found: " + username));
|
||||
|
||||
if (user.getRoles() == null || user.getRoles().contains(Role.NONE) || user.getRoles().isEmpty()) {
|
||||
if (user.getAppUserRoles() == null || user.getAppUserRoles().contains(Role.NONE) || user.getAppUserRoles().isEmpty()) {
|
||||
throw new UsernameNotFoundException("User has no active roles");
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,13 +2,17 @@ package it.boergmann.tkdApp.service;
|
|||
|
||||
import it.boergmann.tkdApp.domain.AppUser;
|
||||
import it.boergmann.tkdApp.domain.Role;
|
||||
import it.boergmann.tkdApp.domain.AppUserRole;
|
||||
import it.boergmann.tkdApp.dto.RegisterRequest;
|
||||
import it.boergmann.tkdApp.repository.AppUserRepository;
|
||||
import it.boergmann.tkdApp.repository.AppUserRoleRepository;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.UUID;
|
||||
|
||||
@Service
|
||||
|
|
@ -16,6 +20,8 @@ import java.util.UUID;
|
|||
public class AppUserService {
|
||||
|
||||
private final AppUserRepository userRepository;
|
||||
private final PasswordEncoder passwordEncoder;
|
||||
private final AppUserRoleRepository roleAssignmentRepository;
|
||||
|
||||
public AppUser getCurrentUser() {
|
||||
String username = SecurityContextHolder.getContext().getAuthentication().getName();
|
||||
|
|
@ -23,23 +29,52 @@ public class AppUserService {
|
|||
.orElseThrow(() -> new UsernameNotFoundException("User not found: " + username));
|
||||
}
|
||||
|
||||
public void activateUser(UUID userId, Role newRole) {
|
||||
public void updateUserRole(UUID userId, Role newRole, LocalDateTime validUntil) {
|
||||
System.out.println(userId.toString());
|
||||
System.out.println(newRole.toString());
|
||||
AppUser user = userRepository.findById(userId)
|
||||
.orElseThrow(() -> new IllegalArgumentException("User not found"));
|
||||
|
||||
if (!user.getRoles().contains(Role.NONE)) {
|
||||
throw new IllegalStateException("User is already activated");
|
||||
if (user.getAppUserRoles().stream().anyMatch(ra -> ra.getRole() == Role.GOD)) {
|
||||
throw new IllegalStateException("GOD-Nutzer dürfen nicht geändert werden.");
|
||||
}
|
||||
|
||||
user.setRoles(List.of(newRole)); // Nur eine Rolle zuweisen – oder erweitern
|
||||
// Alte Rollen entfernen
|
||||
user.getAppUserRoles().clear();
|
||||
|
||||
// Neue Rolle zuweisen
|
||||
var roleAssignment = AppUserRole.builder()
|
||||
.user(user)
|
||||
.role(newRole)
|
||||
.validUntil(validUntil)
|
||||
.build();
|
||||
|
||||
user.getAppUserRoles().add(roleAssignment);
|
||||
userRepository.save(user);
|
||||
}
|
||||
|
||||
public void changeUserRole(UUID userId, Role newRole) {
|
||||
AppUser user = userRepository.findById(userId)
|
||||
.orElseThrow(() -> new IllegalArgumentException("User not found"));
|
||||
public AppUser registerNewUser(RegisterRequest newUser) {
|
||||
if (userRepository.existsByUsername(newUser.getUsername()) || userRepository.existsByEmail(newUser.getEmail())) {
|
||||
throw new IllegalArgumentException("Benutzername oder E-Mail bereits vergeben");
|
||||
}
|
||||
|
||||
user.setRoles(List.of(newRole)); // 👈 Rolle hart überschrieben (alternativ: erweitern)
|
||||
userRepository.save(user);
|
||||
var user = AppUser.builder()
|
||||
.username(newUser.getUsername())
|
||||
.email(newUser.getEmail())
|
||||
.password(passwordEncoder.encode(newUser.getPassword()))
|
||||
.build();
|
||||
|
||||
var savedUser = userRepository.save(user);
|
||||
|
||||
// Setze initiale Rolle: NONE
|
||||
var roleAssignment = AppUserRole.builder()
|
||||
.user(savedUser)
|
||||
.role(Role.NONE)
|
||||
.validUntil(null)
|
||||
.build();
|
||||
|
||||
roleAssignmentRepository.save(roleAssignment);
|
||||
|
||||
return savedUser;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ spring:
|
|||
password: Sanctuary1-Crane-Erupt-Bogged
|
||||
jpa:
|
||||
hibernate:
|
||||
ddl-auto: update
|
||||
ddl-auto: create-drop
|
||||
properties:
|
||||
hibernate:
|
||||
dialect: org.hibernate.dialect.PostgreSQLDialect
|
||||
|
|
|
|||
|
|
@ -1,31 +1,36 @@
|
|||
<!DOCTYPE html>
|
||||
<html xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<title>Benutzer freischalten</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Offene Registrierungen</h1>
|
||||
|
||||
<table border="1">
|
||||
<h1>Benutzerverwaltung</h1>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Benutzername</th>
|
||||
<th>Username</th>
|
||||
<th>Email</th>
|
||||
<th>Aktuelle Rolle</th>
|
||||
<th>Neue Rolle</th>
|
||||
<th>Gültig bis (nur für ADMIN)</th>
|
||||
<th>Aktion</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr th:each="user : ${users}">
|
||||
<td th:text="${user.username}">Benutzer</td>
|
||||
<td th:text="${user.email}">Email</td>
|
||||
<td>
|
||||
<form th:action="@{'/admin/users/' + ${user.id} + '/role'}" method="post">
|
||||
<form th:action="@{/admin/users/update}" method="post">
|
||||
<input type="hidden" name="userId" th:value="${user.id}"/>
|
||||
|
||||
<td th:text="${user.username}"></td>
|
||||
<td th:text="${user.email}"></td>
|
||||
<td>
|
||||
<span th:each="ra : ${user.appUserRoles}"
|
||||
th:text="${ra.role} + ' (bis ' + ${ra.validUntil} + ')'"></span>
|
||||
</td>
|
||||
<td>
|
||||
<select name="role">
|
||||
<option th:selected="${user.roles.contains(T(it.boergmann.tkdApp.domain.Role).USER)}" value="USER">USER</option>
|
||||
<option th:selected="${user.roles.contains(T(it.boergmann.tkdApp.domain.Role).ADMIN)}" value="ADMIN">ADMIN</option>
|
||||
<option th:selected="${user.roles.contains(T(it.boergmann.tkdApp.domain.Role).NONE)}" value="NONE">NONE</option>
|
||||
<option th:each="r : ${roles}"
|
||||
th:value="${r}"
|
||||
th:text="${r}"></option>
|
||||
</select>
|
||||
<button type="submit">Rolle ändern</button>
|
||||
</form>
|
||||
</td>
|
||||
</td>
|
||||
<td><input type="datetime-local" name="validUntil"/></td>
|
||||
<td><button type="submit">Ändern</button></td>
|
||||
</form>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,12 @@
|
|||
<!-- src/main/resources/templates/error/403.html -->
|
||||
<!DOCTYPE html>
|
||||
<html xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<title>Kein Zugriff</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>🚫 Zugriff verweigert</h1>
|
||||
<p>Du hast leider keine Berechtigung für diese Seite.</p>
|
||||
<a href="/static" th:href="@{/static}">Zur Startseite</a>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -5,7 +5,9 @@
|
|||
</head>
|
||||
<body>
|
||||
<h1>Willkommen, <span th:text="${user.username}">Nutzer</span>!</h1>
|
||||
<p>Deine Rollen: <span th:text="${user.roles}">[]</span></p>
|
||||
<p>Deine Rollen: <span th:each="ra : ${user.appUserRoles}"
|
||||
th:text="${ra.role} + ' (bis ' + ${ra.validUntil} + ')'"></span>
|
||||
</td></p>
|
||||
<a href="/logout">Logout</a>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
Loading…
Reference in New Issue