feat: disable local auth when OIDC is configured (resource server mode)
- UiAuthController.login returns 404 when OIDC issuer is configured - JwtAuthenticationFilter skips internal user tokens in OIDC mode (agents still work) - UserAdminController.createUser and resetPassword return 400 in OIDC mode Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -25,6 +25,7 @@ import org.springframework.web.bind.annotation.RequestBody;
|
|||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import com.cameleer3.server.app.security.SecurityProperties;
|
||||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||||
|
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
@@ -47,12 +48,15 @@ public class UserAdminController {
|
|||||||
private final RbacService rbacService;
|
private final RbacService rbacService;
|
||||||
private final UserRepository userRepository;
|
private final UserRepository userRepository;
|
||||||
private final AuditService auditService;
|
private final AuditService auditService;
|
||||||
|
private final boolean oidcEnabled;
|
||||||
|
|
||||||
public UserAdminController(RbacService rbacService, UserRepository userRepository,
|
public UserAdminController(RbacService rbacService, UserRepository userRepository,
|
||||||
AuditService auditService) {
|
AuditService auditService, SecurityProperties securityProperties) {
|
||||||
this.rbacService = rbacService;
|
this.rbacService = rbacService;
|
||||||
this.userRepository = userRepository;
|
this.userRepository = userRepository;
|
||||||
this.auditService = auditService;
|
this.auditService = auditService;
|
||||||
|
String issuer = securityProperties.getOidcIssuerUri();
|
||||||
|
this.oidcEnabled = issuer != null && !issuer.isBlank();
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping
|
@GetMapping
|
||||||
@@ -78,8 +82,12 @@ public class UserAdminController {
|
|||||||
@PostMapping
|
@PostMapping
|
||||||
@Operation(summary = "Create a local user")
|
@Operation(summary = "Create a local user")
|
||||||
@ApiResponse(responseCode = "200", description = "User created")
|
@ApiResponse(responseCode = "200", description = "User created")
|
||||||
|
@ApiResponse(responseCode = "400", description = "Disabled in OIDC mode")
|
||||||
public ResponseEntity<UserDetail> createUser(@RequestBody CreateUserRequest request,
|
public ResponseEntity<UserDetail> createUser(@RequestBody CreateUserRequest request,
|
||||||
HttpServletRequest httpRequest) {
|
HttpServletRequest httpRequest) {
|
||||||
|
if (oidcEnabled) {
|
||||||
|
return ResponseEntity.badRequest().build();
|
||||||
|
}
|
||||||
String userId = "user:" + request.username();
|
String userId = "user:" + request.username();
|
||||||
UserInfo user = new UserInfo(userId, "local",
|
UserInfo user = new UserInfo(userId, "local",
|
||||||
request.email() != null ? request.email() : "",
|
request.email() != null ? request.email() : "",
|
||||||
@@ -178,10 +186,14 @@ public class UserAdminController {
|
|||||||
@PostMapping("/{userId}/password")
|
@PostMapping("/{userId}/password")
|
||||||
@Operation(summary = "Reset user password")
|
@Operation(summary = "Reset user password")
|
||||||
@ApiResponse(responseCode = "204", description = "Password reset")
|
@ApiResponse(responseCode = "204", description = "Password reset")
|
||||||
|
@ApiResponse(responseCode = "400", description = "Disabled in OIDC mode")
|
||||||
public ResponseEntity<Void> resetPassword(
|
public ResponseEntity<Void> resetPassword(
|
||||||
@PathVariable String userId,
|
@PathVariable String userId,
|
||||||
@Valid @RequestBody SetPasswordRequest request,
|
@Valid @RequestBody SetPasswordRequest request,
|
||||||
HttpServletRequest httpRequest) {
|
HttpServletRequest httpRequest) {
|
||||||
|
if (oidcEnabled) {
|
||||||
|
return ResponseEntity.badRequest().build();
|
||||||
|
}
|
||||||
userRepository.setPassword(userId, passwordEncoder.encode(request.password()));
|
userRepository.setPassword(userId, passwordEncoder.encode(request.password()));
|
||||||
auditService.log("reset_password", AuditCategory.USER_MGMT, userId, null, AuditResult.SUCCESS, httpRequest);
|
auditService.log("reset_password", AuditCategory.USER_MGMT, userId, null, AuditResult.SUCCESS, httpRequest);
|
||||||
return ResponseEntity.noContent().build();
|
return ResponseEntity.noContent().build();
|
||||||
|
|||||||
@@ -74,6 +74,12 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
|
|||||||
JwtValidationResult result = jwtService.validateAccessToken(token);
|
JwtValidationResult result = jwtService.validateAccessToken(token);
|
||||||
String subject = result.subject();
|
String subject = result.subject();
|
||||||
|
|
||||||
|
// In OIDC mode, only accept agent tokens via internal validation.
|
||||||
|
// User tokens must go through the OIDC decoder path.
|
||||||
|
if (oidcDecoder != null && subject != null && subject.startsWith("user:")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
List<String> roles = result.roles();
|
List<String> roles = result.roles();
|
||||||
if (!subject.startsWith("user:") && roles.isEmpty()) {
|
if (!subject.startsWith("user:") && roles.isEmpty()) {
|
||||||
roles = List.of("AGENT");
|
roles = List.of("AGENT");
|
||||||
|
|||||||
@@ -71,6 +71,10 @@ public class UiAuthController {
|
|||||||
content = @Content(schema = @Schema(implementation = ErrorResponse.class)))
|
content = @Content(schema = @Schema(implementation = ErrorResponse.class)))
|
||||||
public ResponseEntity<AuthTokenResponse> login(@RequestBody LoginRequest request,
|
public ResponseEntity<AuthTokenResponse> login(@RequestBody LoginRequest request,
|
||||||
HttpServletRequest httpRequest) {
|
HttpServletRequest httpRequest) {
|
||||||
|
if (isOidcEnabled()) {
|
||||||
|
return ResponseEntity.status(HttpStatus.NOT_FOUND)
|
||||||
|
.body(new AuthTokenResponse(null, null, "Local login disabled when OIDC is configured", null));
|
||||||
|
}
|
||||||
String configuredUser = properties.getUiUser();
|
String configuredUser = properties.getUiUser();
|
||||||
String configuredPassword = properties.getUiPassword();
|
String configuredPassword = properties.getUiPassword();
|
||||||
String subject = "user:" + request.username();
|
String subject = "user:" + request.username();
|
||||||
@@ -149,6 +153,11 @@ public class UiAuthController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean isOidcEnabled() {
|
||||||
|
String issuer = properties.getOidcIssuerUri();
|
||||||
|
return issuer != null && !issuer.isBlank();
|
||||||
|
}
|
||||||
|
|
||||||
public record LoginRequest(String username, String password) {}
|
public record LoginRequest(String username, String password) {}
|
||||||
public record RefreshRequest(String refreshToken) {}
|
public record RefreshRequest(String refreshToken) {}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user