ExplicitWorkspacePinValidationInterceptor.java

package com.taxonomy.workspace.service;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

/**
 * Validates every explicit browser-tab workspace pin before controller code can
 * catch a resolver exception and accidentally continue in a shared context.
 *
 * <p>Requests without an explicit pin retain the existing endpoint-specific
 * provisioning behaviour. Requests carrying either the custom header or the
 * equivalent query parameter must resolve to that exact owned workspace. The
 * header has the same precedence as {@link WorkspaceContextResolver}.</p>
 */
@Component
public class ExplicitWorkspacePinValidationInterceptor implements HandlerInterceptor {

    private final WorkspaceResolver workspaceResolver;

    public ExplicitWorkspacePinValidationInterceptor(WorkspaceResolver workspaceResolver) {
        this.workspaceResolver = workspaceResolver;
    }

    @Override
    public boolean preHandle(HttpServletRequest request,
                             HttpServletResponse response,
                             Object handler) {
        String requestedWorkspaceId = explicitWorkspaceId(request);
        if (requestedWorkspaceId == null) {
            return true;
        }

        RepositoryContext resolved = workspaceResolver.resolveCurrentRepositoryContext();
        if (resolved.scope() != RepositoryScope.WORKSPACE
                || !requestedWorkspaceId.equals(resolved.workspaceId())) {
            throw new AccessDeniedException(
                    "Pinned workspace does not match the resolved request context");
        }
        return true;
    }

    private static String explicitWorkspaceId(HttpServletRequest request) {
        String header = request.getHeader(WorkspaceContextResolver.WORKSPACE_HEADER);
        if (hasText(header)) {
            return header.strip();
        }
        String parameter = request.getParameter(
                WorkspaceContextResolver.WORKSPACE_QUERY_PARAMETER);
        return hasText(parameter) ? parameter.strip() : null;
    }

    private static boolean hasText(String value) {
        return value != null && !value.isBlank();
    }
}