mirror of
https://github.com/Stirling-Tools/Stirling-PDF.git
synced 2026-09-03 05:10:16 +03:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
832172e245 | ||
|
|
41097eb027 | ||
|
|
c49f419447 | ||
|
|
d2c5f2877d | ||
|
|
45e9dfa35e | ||
|
|
afaea63396 |
@@ -221,6 +221,13 @@ class InternalApiClientTest {
|
||||
assertThrows(SecurityException.class, () -> client.post("/api/v1/admin/settings", body));
|
||||
}
|
||||
|
||||
@Test
|
||||
void postRejectsUserEndpointsIncludingApiKeyEndpoint() {
|
||||
MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
|
||||
|
||||
assertThrows(SecurityException.class, () -> client.post("/api/v1/user/get-api-key", body));
|
||||
}
|
||||
|
||||
@Test
|
||||
void postRejectsAiEndpointsOutsideToolsSubnamespace() {
|
||||
// /api/v1/ai/orchestrate and other non-tool AI endpoints are not internally
|
||||
|
||||
+2
-1
@@ -399,7 +399,8 @@ public class ProprietaryUIDataController {
|
||||
}
|
||||
|
||||
@GetMapping("/account")
|
||||
@PreAuthorize("!hasAuthority('ROLE_DEMO_USER')")
|
||||
@PreAuthorize(
|
||||
"!@principalPolicy.isInternalApiUser(authentication) && !hasAuthority('ROLE_DEMO_USER')")
|
||||
@Operation(summary = "Get account page data")
|
||||
public ResponseEntity<AccountData> getAccountData(Authentication authentication) {
|
||||
if (authentication == null || !authentication.isAuthenticated()) {
|
||||
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
package stirling.software.proprietary.security;
|
||||
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import stirling.software.common.model.enumeration.Role;
|
||||
|
||||
/** Authorization predicates for endpoints that must only be callable by a real user. */
|
||||
@Component("principalPolicy")
|
||||
public class PrincipalPolicy {
|
||||
|
||||
public boolean isInternalApiUser(Authentication authentication) {
|
||||
return authentication != null
|
||||
&& (Role.INTERNAL_API_USER.getRoleId().equals(authentication.getName())
|
||||
|| authentication.getAuthorities().stream()
|
||||
.anyMatch(
|
||||
authority ->
|
||||
Role.INTERNAL_API_USER
|
||||
.getRoleId()
|
||||
.equals(authority.getAuthority())));
|
||||
}
|
||||
|
||||
public boolean isHumanUser(Authentication authentication) {
|
||||
return authentication != null
|
||||
&& authentication.isAuthenticated()
|
||||
&& !"anonymousUser".equals(authentication.getName())
|
||||
&& !isInternalApiUser(authentication);
|
||||
}
|
||||
}
|
||||
+8
-6
@@ -288,7 +288,8 @@ public class AuthController {
|
||||
* @param response HTTP response
|
||||
* @return Success message
|
||||
*/
|
||||
@PreAuthorize("!hasAuthority('ROLE_DEMO_USER')")
|
||||
@PreAuthorize(
|
||||
"!@principalPolicy.isInternalApiUser(authentication) && !hasAuthority('ROLE_DEMO_USER')")
|
||||
@PostMapping("/logout")
|
||||
public ResponseEntity<?> logout(HttpServletRequest request, HttpServletResponse response) {
|
||||
try {
|
||||
@@ -314,7 +315,8 @@ public class AuthController {
|
||||
* @param response HTTP response to set new JWT cookie
|
||||
* @return New token information
|
||||
*/
|
||||
@PreAuthorize("!hasAuthority('ROLE_DEMO_USER')")
|
||||
@PreAuthorize(
|
||||
"!@principalPolicy.isInternalApiUser(authentication) && !hasAuthority('ROLE_DEMO_USER')")
|
||||
@PostMapping("/refresh")
|
||||
public ResponseEntity<?> refresh(HttpServletRequest request, HttpServletResponse response) {
|
||||
try {
|
||||
@@ -418,7 +420,7 @@ public class AuthController {
|
||||
}
|
||||
}
|
||||
|
||||
@PreAuthorize("isAuthenticated() && !hasAuthority('ROLE_DEMO_USER')")
|
||||
@PreAuthorize("@principalPolicy.isHumanUser(authentication) && !hasAuthority('ROLE_DEMO_USER')")
|
||||
@GetMapping("/mfa/setup")
|
||||
public ResponseEntity<?> setupMfa(Authentication authentication) {
|
||||
if (authentication == null || !authentication.isAuthenticated()) {
|
||||
@@ -454,7 +456,7 @@ public class AuthController {
|
||||
}
|
||||
}
|
||||
|
||||
@PreAuthorize("isAuthenticated() && !hasAuthority('ROLE_DEMO_USER')")
|
||||
@PreAuthorize("@principalPolicy.isHumanUser(authentication) && !hasAuthority('ROLE_DEMO_USER')")
|
||||
@PostMapping("/mfa/enable")
|
||||
public ResponseEntity<?> enableMfa(
|
||||
@RequestBody MfaCodeRequest request, Authentication authentication) {
|
||||
@@ -506,7 +508,7 @@ public class AuthController {
|
||||
}
|
||||
}
|
||||
|
||||
@PreAuthorize("isAuthenticated() && !hasAuthority('ROLE_DEMO_USER')")
|
||||
@PreAuthorize("@principalPolicy.isHumanUser(authentication) && !hasAuthority('ROLE_DEMO_USER')")
|
||||
@PostMapping("/mfa/disable")
|
||||
public ResponseEntity<?> disableMfa(
|
||||
@RequestBody MfaCodeRequest request, Authentication authentication) {
|
||||
@@ -561,7 +563,7 @@ public class AuthController {
|
||||
}
|
||||
}
|
||||
|
||||
@PreAuthorize("isAuthenticated() && !hasAuthority('ROLE_DEMO_USER')")
|
||||
@PreAuthorize("@principalPolicy.isHumanUser(authentication) && !hasAuthority('ROLE_DEMO_USER')")
|
||||
@PostMapping("/mfa/setup/cancel")
|
||||
public ResponseEntity<?> cancelMfaSetup(Authentication authentication) {
|
||||
if (authentication == null || !authentication.isAuthenticated()) {
|
||||
|
||||
+8
-6
@@ -162,7 +162,7 @@ public class UserController {
|
||||
return userMap;
|
||||
}
|
||||
|
||||
@PreAuthorize("!hasAuthority('ROLE_DEMO_USER')")
|
||||
@PreAuthorize("@principalPolicy.isHumanUser(authentication) && !hasAuthority('ROLE_DEMO_USER')")
|
||||
@PostMapping("/change-username")
|
||||
@Audited(type = AuditEventType.USER_PROFILE_UPDATE, level = AuditLevel.BASIC)
|
||||
public ResponseEntity<?> changeUsername(
|
||||
@@ -222,7 +222,7 @@ public class UserController {
|
||||
"Username changed successfully. Please log in again."));
|
||||
}
|
||||
|
||||
@PreAuthorize("!hasAuthority('ROLE_DEMO_USER')")
|
||||
@PreAuthorize("@principalPolicy.isHumanUser(authentication) && !hasAuthority('ROLE_DEMO_USER')")
|
||||
@PostMapping("/change-password-on-login")
|
||||
@Audited(type = AuditEventType.USER_PROFILE_UPDATE, level = AuditLevel.BASIC)
|
||||
public ResponseEntity<?> changePasswordOnLogin(
|
||||
@@ -298,7 +298,7 @@ public class UserController {
|
||||
"Password changed successfully. Please log in again."));
|
||||
}
|
||||
|
||||
@PreAuthorize("!hasAuthority('ROLE_DEMO_USER')")
|
||||
@PreAuthorize("@principalPolicy.isHumanUser(authentication) && !hasAuthority('ROLE_DEMO_USER')")
|
||||
@PostMapping("/change-password")
|
||||
@Audited(type = AuditEventType.USER_PROFILE_UPDATE, level = AuditLevel.BASIC)
|
||||
public ResponseEntity<?> changePassword(
|
||||
@@ -333,7 +333,7 @@ public class UserController {
|
||||
"Password changed successfully. Please log in again."));
|
||||
}
|
||||
|
||||
@PreAuthorize("!hasAuthority('ROLE_DEMO_USER')")
|
||||
@PreAuthorize("@principalPolicy.isHumanUser(authentication) && !hasAuthority('ROLE_DEMO_USER')")
|
||||
@PostMapping("/updateUserSettings")
|
||||
/**
|
||||
* Updates the user settings based on the provided JSON payload.
|
||||
@@ -815,7 +815,7 @@ public class UserController {
|
||||
return ResponseEntity.ok(Map.of("message", "User deleted successfully"));
|
||||
}
|
||||
|
||||
@PreAuthorize("!hasAuthority('ROLE_DEMO_USER')")
|
||||
@PreAuthorize("@principalPolicy.isHumanUser(authentication) && !hasAuthority('ROLE_DEMO_USER')")
|
||||
@PostMapping("/get-api-key")
|
||||
public ResponseEntity<Map<String, String>> getApiKey(Principal principal) {
|
||||
if (principal == null) {
|
||||
@@ -831,7 +831,7 @@ public class UserController {
|
||||
return ResponseEntity.ok(Map.of("apiKey", apiKey));
|
||||
}
|
||||
|
||||
@PreAuthorize("!hasAuthority('ROLE_DEMO_USER')")
|
||||
@PreAuthorize("@principalPolicy.isHumanUser(authentication) && !hasAuthority('ROLE_DEMO_USER')")
|
||||
@PostMapping("/update-api-key")
|
||||
public ResponseEntity<Map<String, String>> updateApiKey(Principal principal) {
|
||||
if (principal == null) {
|
||||
@@ -954,6 +954,7 @@ public class UserController {
|
||||
}
|
||||
}
|
||||
|
||||
@PreAuthorize("@principalPolicy.isHumanUser(authentication)")
|
||||
@PostMapping("/complete-initial-setup")
|
||||
public ResponseEntity<?> completeInitialSetup() {
|
||||
try {
|
||||
@@ -982,6 +983,7 @@ public class UserController {
|
||||
}
|
||||
|
||||
// Lists enabled users for the signing picker; 'org' scope = instance-wide, else caller's team.
|
||||
@PreAuthorize("@principalPolicy.isHumanUser(authentication)")
|
||||
@GetMapping("/users")
|
||||
public ResponseEntity<List<UserSummaryDTO>> listUsers(Principal principal) {
|
||||
if (principal == null) {
|
||||
|
||||
+2
@@ -7,6 +7,7 @@ import java.util.List;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
@@ -47,6 +48,7 @@ import stirling.software.proprietary.workflow.service.WorkflowSessionService;
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/security")
|
||||
@PreAuthorize("@principalPolicy.isHumanUser(authentication)")
|
||||
@Tag(
|
||||
name = "Signing Sessions",
|
||||
description = "Signing session lifecycle and participant management")
|
||||
|
||||
+95
@@ -0,0 +1,95 @@
|
||||
package stirling.software.proprietary.security;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.security.authentication.AnonymousAuthenticationToken;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
|
||||
import stirling.software.common.model.enumeration.Role;
|
||||
|
||||
class PrincipalPolicyTest {
|
||||
|
||||
private final PrincipalPolicy policy = new PrincipalPolicy();
|
||||
|
||||
@Test
|
||||
void rejectsNullAuthentication() {
|
||||
assertFalse(policy.isHumanUser(null));
|
||||
}
|
||||
|
||||
@Test
|
||||
void doesNotTreatMissingAuthenticationAsInternal() {
|
||||
assertFalse(policy.isInternalApiUser(null));
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectsUnauthenticatedAuthentication() {
|
||||
Authentication authentication =
|
||||
new UsernamePasswordAuthenticationToken("peter", "password");
|
||||
|
||||
assertFalse(policy.isHumanUser(authentication));
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectsAnonymousAuthentication() {
|
||||
Authentication authentication =
|
||||
new AnonymousAuthenticationToken(
|
||||
"test-key",
|
||||
"anonymousUser",
|
||||
List.of(new SimpleGrantedAuthority("ROLE_ANONYMOUS")));
|
||||
|
||||
assertFalse(policy.isHumanUser(authentication));
|
||||
}
|
||||
|
||||
@Test
|
||||
void acceptsAuthenticatedHumanUser() {
|
||||
assertTrue(policy.isHumanUser(authenticated("peter")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void acceptsAuthenticatedAdminUser() {
|
||||
Authentication authentication =
|
||||
authenticated("admin", new SimpleGrantedAuthority(Role.ADMIN.getRoleId()));
|
||||
|
||||
assertTrue(policy.isHumanUser(authentication));
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectsInternalApiAuthorityEvenWithHumanUsername() {
|
||||
Authentication authentication =
|
||||
authenticated(
|
||||
"peter", new SimpleGrantedAuthority(Role.INTERNAL_API_USER.getRoleId()));
|
||||
|
||||
assertTrue(policy.isInternalApiUser(authentication));
|
||||
assertFalse(policy.isHumanUser(authentication));
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectsInternalApiUsernameEvenWithoutAuthority() {
|
||||
Authentication authentication = authenticated(Role.INTERNAL_API_USER.getRoleId());
|
||||
|
||||
assertTrue(policy.isInternalApiUser(authentication));
|
||||
assertFalse(policy.isHumanUser(authentication));
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectsInternalAuthorityWhenCombinedWithOtherAuthorities() {
|
||||
Authentication authentication =
|
||||
authenticated(
|
||||
"peter",
|
||||
new SimpleGrantedAuthority(Role.USER.getRoleId()),
|
||||
new SimpleGrantedAuthority(Role.INTERNAL_API_USER.getRoleId()));
|
||||
|
||||
assertFalse(policy.isHumanUser(authentication));
|
||||
}
|
||||
|
||||
private static Authentication authenticated(
|
||||
String username, SimpleGrantedAuthority... authorities) {
|
||||
return new UsernamePasswordAuthenticationToken(username, "password", List.of(authorities));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user