Skip to content

Commit 577cfe3

Browse files
Merge pull request #30 from Stcwal/backend/test/contoller
Backend/test/contoller
2 parents ab0f0b5 + 64ac135 commit 577cfe3

35 files changed

+2388
-86
lines changed

README.md

Whitespace-only changes.

backend/src/main/java/backend/fullstack/auth/AuthService.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ public AuthService(
5555
*/
5656
public User registerBootstrapAdmin(RegisterRequest request) {
5757
if (userRepository.count() > 0) {
58-
throw new AccessDeniedException("Public registration is disabled after bootstrap");
58+
throw new AccessDeniedException("Bootstrap registration is only available before the first user is created");
5959
}
6060

6161
if (request.getRole() != Role.ADMIN) {

backend/src/main/java/backend/fullstack/config/SecurityConfig.java

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -28,15 +28,18 @@
2828
public class SecurityConfig {
2929

3030
private final JwtAuthFilter jwtAuthFilter;
31+
private final SecurityErrorHandler securityErrorHandler;
3132
private final UserRepository userRepository;
3233
private final PasswordEncoder passwordEncoder;
3334

3435
public SecurityConfig(
3536
JwtAuthFilter jwtAuthFilter,
37+
SecurityErrorHandler securityErrorHandler,
3638
UserRepository userRepository,
3739
PasswordEncoder passwordEncoder
3840
) {
3941
this.jwtAuthFilter = jwtAuthFilter;
42+
this.securityErrorHandler = securityErrorHandler;
4043
this.userRepository = userRepository;
4144
this.passwordEncoder = passwordEncoder;
4245
}
@@ -46,12 +49,17 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti
4649
http
4750
.csrf(AbstractHttpConfigurer::disable)
4851
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
52+
.exceptionHandling(exceptions -> exceptions
53+
.authenticationEntryPoint(securityErrorHandler)
54+
.accessDeniedHandler(securityErrorHandler)
55+
)
4956
.authenticationProvider(authenticationProvider())
5057
.authorizeHttpRequests(auth -> auth
5158
.requestMatchers("/error").permitAll()
5259
.requestMatchers("/v3/api-docs/**", "/swagger-ui/**", "/swagger-ui.html").permitAll()
5360
.requestMatchers(HttpMethod.POST, "/api/auth/login", "/api/auth/logout").permitAll()
54-
.requestMatchers(HttpMethod.POST, "/api/auth/register").access(this::canAccessRegister)
61+
.requestMatchers(HttpMethod.POST, "/api/auth/register", "/api/organization")
62+
.access(this::canAccessBootstrapSetup)
5563
.anyRequest().authenticated()
5664
)
5765
.addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class);
@@ -78,20 +86,22 @@ public AuthenticationManager authenticationManager(AuthenticationConfiguration c
7886
return configuration.getAuthenticationManager();
7987
}
8088

81-
private AuthorizationDecision canAccessRegister(
89+
private AuthorizationDecision canAccessBootstrapSetup(
8290
Supplier<Authentication> authentication,
8391
RequestAuthorizationContext context
8492
) {
85-
return new AuthorizationDecision(isRegisterAccessAllowed(authentication.get()));
93+
return new AuthorizationDecision(isBootstrapSetupAllowed(authentication.get()));
8694
}
8795

8896
boolean isRegisterAccessAllowed(Authentication auth) {
89-
if (userRepository.count() == 0) {
90-
return true;
91-
}
97+
return isBootstrapSetupAllowed(auth);
98+
}
99+
100+
boolean isOrganizationCreateAccessAllowed(Authentication auth) {
101+
return isBootstrapSetupAllowed(auth);
102+
}
92103

93-
return auth != null
94-
&& auth.isAuthenticated()
95-
&& auth.getAuthorities().stream().anyMatch(a -> "ROLE_ADMIN".equals(a.getAuthority()));
104+
private boolean isBootstrapSetupAllowed(Authentication auth) {
105+
return userRepository.count() == 0;
96106
}
97107
}
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
package backend.fullstack.config;
2+
3+
import java.io.IOException;
4+
import java.time.LocalDateTime;
5+
6+
import org.springframework.http.HttpStatus;
7+
import org.springframework.http.MediaType;
8+
import org.springframework.security.access.AccessDeniedException;
9+
import org.springframework.security.core.AuthenticationException;
10+
import org.springframework.security.web.AuthenticationEntryPoint;
11+
import org.springframework.security.web.access.AccessDeniedHandler;
12+
import org.springframework.stereotype.Component;
13+
14+
import com.fasterxml.jackson.databind.ObjectMapper;
15+
16+
import backend.fullstack.dto.ErrorResponse;
17+
import jakarta.servlet.http.HttpServletRequest;
18+
import jakarta.servlet.http.HttpServletResponse;
19+
20+
/**
21+
* Writes security-layer authentication and authorization failures using the
22+
* same JSON error envelope as controller exceptions.
23+
*
24+
* @version 1.0
25+
* @since 04.04.26
26+
*/
27+
@Component
28+
public class SecurityErrorHandler implements AuthenticationEntryPoint, AccessDeniedHandler {
29+
30+
private final ObjectMapper objectMapper;
31+
32+
public SecurityErrorHandler(ObjectMapper objectMapper) {
33+
this.objectMapper = objectMapper;
34+
}
35+
36+
@Override
37+
public void commence(
38+
HttpServletRequest request,
39+
HttpServletResponse response,
40+
AuthenticationException authException
41+
) throws IOException {
42+
writeErrorResponse(
43+
response,
44+
HttpStatus.UNAUTHORIZED,
45+
"UNAUTHORIZED",
46+
authException.getMessage() == null ? "Authentication is required" : authException.getMessage(),
47+
request.getRequestURI()
48+
);
49+
}
50+
51+
@Override
52+
public void handle(
53+
HttpServletRequest request,
54+
HttpServletResponse response,
55+
AccessDeniedException accessDeniedException
56+
) throws IOException {
57+
writeErrorResponse(
58+
response,
59+
HttpStatus.FORBIDDEN,
60+
"ACCESS_DENIED",
61+
accessDeniedException.getMessage() == null ? "Access denied" : accessDeniedException.getMessage(),
62+
request.getRequestURI()
63+
);
64+
}
65+
66+
private void writeErrorResponse(
67+
HttpServletResponse response,
68+
HttpStatus status,
69+
String errorCode,
70+
String message,
71+
String path
72+
) throws IOException {
73+
ErrorResponse errorResponse = new ErrorResponse(
74+
LocalDateTime.now(),
75+
status.value(),
76+
status.getReasonPhrase(),
77+
errorCode,
78+
message,
79+
null,
80+
path
81+
);
82+
83+
response.setStatus(status.value());
84+
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
85+
objectMapper.writeValue(response.getOutputStream(), errorResponse);
86+
}
87+
}

backend/src/main/java/backend/fullstack/location/LocationController.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,18 @@
1919

2020
import java.util.List;
2121

22+
/**
23+
* Controller for managing locations within an organization.
24+
* Endpoints:
25+
* - POST /api/locations: Create a new location.
26+
* - GET /api/locations: Get all accessible locations.
27+
* - GET /api/locations/{id}: Get location by ID.
28+
* - PUT /api/locations/{id}: Update location.
29+
* - DELETE /api/locations/{id}: Delete location.
30+
*
31+
* @version 1.0
32+
* @since 03.04.26
33+
*/
2234
@RestController
2335
@RequestMapping("/api/locations")
2436
@RequiredArgsConstructor

backend/src/main/java/backend/fullstack/location/LocationService.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
/**
1717
* Service class for managing locations within an organization.
1818
*
19-
* @version 1.0
19+
* @version 1.1
2020
* @since 28.03.26
2121
*/
2222
@Service

backend/src/main/java/backend/fullstack/organization/OrganizationService.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@
1818
/**
1919
* Service class for managing organizations.
2020
*
21-
* @version 1.0
22-
* @since 28.03.26
21+
* @version 1.1
22+
* @since 03.04.26
2323
*/
2424
@Service
2525
@RequiredArgsConstructor
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
package backend.fullstack.permission;
2+
3+
import java.util.List;
4+
import java.util.Map;
5+
import java.util.Set;
6+
7+
import org.slf4j.Logger;
8+
import org.slf4j.LoggerFactory;
9+
import org.springframework.boot.ApplicationArguments;
10+
import org.springframework.boot.ApplicationRunner;
11+
import org.springframework.stereotype.Component;
12+
import org.springframework.transaction.annotation.Transactional;
13+
14+
import backend.fullstack.user.role.Role;
15+
16+
/**
17+
* Component responsible for seeding the permission system with initial data on application startup.
18+
*
19+
* @version 1.0
20+
* @since 31.03.26
21+
*/
22+
@Component
23+
public class PermissionBootstrapSeeder implements ApplicationRunner {
24+
25+
private static final Logger logger = LoggerFactory.getLogger(PermissionBootstrapSeeder.class);
26+
27+
private final PermissionDefinitionRepository permissionDefinitionRepository;
28+
private final RolePermissionBindingRepository rolePermissionBindingRepository;
29+
30+
public PermissionBootstrapSeeder(
31+
PermissionDefinitionRepository permissionDefinitionRepository,
32+
RolePermissionBindingRepository rolePermissionBindingRepository
33+
) {
34+
this.permissionDefinitionRepository = permissionDefinitionRepository;
35+
this.rolePermissionBindingRepository = rolePermissionBindingRepository;
36+
}
37+
38+
/**
39+
* Seeds the permission system with initial data on application startup.
40+
*
41+
* @param args the application arguments
42+
*/
43+
@Override
44+
@Transactional
45+
public void run(ApplicationArguments args) {
46+
try {
47+
seedPermissionDefinitions();
48+
seedRolePermissions();
49+
} catch (RuntimeException ex) {
50+
// Startup should not fail solely because permission seed cannot run.
51+
logger.warn("Permission bootstrap seed skipped due to repository/database issue", ex);
52+
}
53+
}
54+
55+
/**
56+
* Seeds the permission definitions with initial data.
57+
*/
58+
private void seedPermissionDefinitions() {
59+
for (Permission permission : Permission.values()) {
60+
permissionDefinitionRepository.findByPermissionKey(permission.key())
61+
.orElseGet(() -> permissionDefinitionRepository.save(
62+
PermissionDefinition.builder()
63+
.permissionKey(permission.key())
64+
.description(permission.key())
65+
.build()
66+
));
67+
}
68+
}
69+
70+
/**
71+
* Seeds the role permissions with initial data.
72+
*/
73+
private void seedRolePermissions() {
74+
Map<Role, Set<Permission>> defaults = DefaultRolePermissionMatrix.create();
75+
76+
for (Map.Entry<Role, Set<Permission>> entry : defaults.entrySet()) {
77+
Role role = entry.getKey();
78+
if (rolePermissionBindingRepository.existsByRole(role)) {
79+
continue;
80+
}
81+
82+
for (Permission permission : entry.getValue()) {
83+
PermissionDefinition definition = permissionDefinitionRepository
84+
.findByPermissionKey(permission.key())
85+
.orElseThrow(() -> new IllegalStateException("Permission definition missing: " + permission.key()));
86+
87+
rolePermissionBindingRepository.save(
88+
RolePermissionBinding.builder()
89+
.role(role)
90+
.permission(definition)
91+
.build()
92+
);
93+
}
94+
}
95+
}
96+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package backend.fullstack.permission;
2+
3+
/**
4+
* Enum representing the scope of a permission.
5+
*/
6+
public enum PermissionScope {
7+
ORGANIZATION,
8+
LOCATION
9+
}

backend/src/main/java/backend/fullstack/permission/catalog/DefaultRolePermissionMatrix.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ public static Map<Role, Set<Permission>> create() {
3636
Permission.LOGS_TEMPERATURE_READ,
3737
Permission.CHECKLISTS_READ,
3838
Permission.DEVIATIONS_READ,
39+
Permission.DEVIATIONS_RESOLVE,
3940
Permission.REPORTS_READ,
4041
Permission.REPORTS_EXPORT
4142
));
@@ -49,9 +50,13 @@ public static Map<Role, Set<Permission>> create() {
4950
Permission.LOGS_TEMPERATURE_CREATE,
5051
Permission.CHECKLISTS_READ,
5152
Permission.CHECKLISTS_COMPLETE,
53+
Permission.CHECKLISTS_APPROVE,
54+
Permission.CHECKLISTS_TEMPLATE_MANAGE,
5255
Permission.DEVIATIONS_READ,
5356
Permission.DEVIATIONS_CREATE,
54-
Permission.REPORTS_READ
57+
Permission.DEVIATIONS_RESOLVE,
58+
Permission.REPORTS_READ,
59+
Permission.REPORTS_EXPORT
5560
));
5661

5762
mapping.put(Role.STAFF, EnumSet.of(

0 commit comments

Comments
 (0)