AuthorizationRulesConfigurer.java

package com.taxonomy.security.config;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.web.configurers.AuthorizeHttpRequestsConfigurer;
import org.springframework.stereotype.Component;

/**
 * Shared authorization rules used by both the form-login and Keycloak
 * security configurations. Ensures that role-based access control is
 * consistent regardless of the authentication method.
 */
@Component
public class AuthorizationRulesConfigurer {

    @Value("${taxonomy.security.swagger-public:true}")
    private boolean swaggerPublic;

    /**
     * Configures the shared authorization rules for all security filter chains.
     *
     * @param auth the authorization registry to configure
     */
    public void configure(
            AuthorizeHttpRequestsConfigurer<org.springframework.security.config.annotation.web.builders.HttpSecurity>
                    .AuthorizationManagerRequestMatcherRegistry auth) {
        // Public resources
        auth.requestMatchers("/login", "/error", "/css/**", "/js/**", "/images/**", "/webjars/**").permitAll();

        // OIDC callback endpoints (must be public for Keycloak redirects)
        auth.requestMatchers("/login/oauth2/**", "/oauth2/**").permitAll();

        // Change-password page — must be accessible to authenticated users
        auth.requestMatchers("/change-password").authenticated();

        // Health / status endpoints
        auth.requestMatchers("/actuator/health", "/actuator/health/**", "/actuator/info").permitAll();

        // OpenAPI / Swagger UI — configurable
        if (swaggerPublic) {
            auth.requestMatchers("/v3/api-docs/**", "/swagger-ui/**", "/swagger-ui.html").permitAll();
        } else {
            auth.requestMatchers("/v3/api-docs/**", "/swagger-ui/**", "/swagger-ui.html").authenticated();
        }

        // Admin-only
        auth.requestMatchers("/admin/**", "/api/admin/**").hasRole("ADMIN");
        auth.requestMatchers("/api/preferences/**").hasRole("ADMIN");

        // Write operations on architecture endpoints — ARCHITECT or ADMIN
        auth.requestMatchers(HttpMethod.POST,   "/api/relations/**").hasAnyRole("ARCHITECT", "ADMIN");
        auth.requestMatchers(HttpMethod.PUT,    "/api/relations/**").hasAnyRole("ARCHITECT", "ADMIN");
        auth.requestMatchers(HttpMethod.DELETE,  "/api/relations/**").hasAnyRole("ARCHITECT", "ADMIN");

        auth.requestMatchers(HttpMethod.POST,   "/api/dsl/**").hasAnyRole("ARCHITECT", "ADMIN");
        auth.requestMatchers(HttpMethod.PUT,    "/api/dsl/**").hasAnyRole("ARCHITECT", "ADMIN");
        auth.requestMatchers(HttpMethod.DELETE,  "/api/dsl/**").hasAnyRole("ARCHITECT", "ADMIN");

        auth.requestMatchers(HttpMethod.POST,   "/api/git/**").hasAnyRole("ARCHITECT", "ADMIN");
        auth.requestMatchers(HttpMethod.PUT,    "/api/git/**").hasAnyRole("ARCHITECT", "ADMIN");
        auth.requestMatchers(HttpMethod.DELETE,  "/api/git/**").hasAnyRole("ARCHITECT", "ADMIN");

        // Context navigation — reads for any user, writes for ARCHITECT/ADMIN
        auth.requestMatchers(HttpMethod.GET,    "/api/context/**").authenticated();
        auth.requestMatchers(HttpMethod.POST,   "/api/context/**").hasAnyRole("ARCHITECT", "ADMIN");

        // Workspace — reads for any user, writes for ADMIN
        auth.requestMatchers(HttpMethod.GET,    "/api/workspace/**").authenticated();
        auth.requestMatchers(HttpMethod.POST,   "/api/workspace/**").hasRole("ADMIN");

        auth.requestMatchers(HttpMethod.POST,   "/api/export/**").hasAnyRole("USER", "ARCHITECT", "ADMIN");

        // Reading API — any authenticated user
        auth.requestMatchers(HttpMethod.GET, "/api/**").authenticated();

        // POST to analyze/search etc. — any authenticated user
        auth.requestMatchers(HttpMethod.POST, "/api/analyze").authenticated();
        auth.requestMatchers(HttpMethod.POST, "/api/justify-leaf").authenticated();

        // GUI — any authenticated user
        auth.requestMatchers("/**").authenticated();
    }
}