CtanAuthFilter.java

/*
 * Copyright © 2024-2025 The CTAN Team and individual authors
 *
 * This file is distributed under the 3-clause BSD license.
 * See file LICENSE for details.
 */
package org.ctan.site;

import java.io.IOException;
import java.security.Principal;
import java.util.Set;

import org.ctan.site.domain.account.Role;
import org.ctan.site.services.account.JwtManager;
import org.ctan.site.stores.UserStore;

import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import io.dropwizard.hibernate.UnitOfWork;
import jakarta.annotation.Priority;
import jakarta.ws.rs.Priorities;
import jakarta.ws.rs.container.ContainerRequestContext;
import jakarta.ws.rs.container.ContainerRequestFilter;
import jakarta.ws.rs.core.SecurityContext;
import lombok.Builder;
import lombok.NonNull;

/**
 * The class <code>CtanAuthFilter</code> contains the filter for checking the
 * authentication.
 */
@Priority(Priorities.AUTHENTICATION)
public class CtanAuthFilter implements ContainerRequestFilter {

    /**
     * The class <code>CtanSecurityContext</code> contains the joined security
     * context and principal.
     */
    @Builder
    @SuppressFBWarnings(value = "EI_EXPOSE_REP2")
    private static final class CtanSecurityContext
        implements
            SecurityContext,
            Principal {

        /**
         * The field <code>account</code> contains the account name of the user.
         */
        private String account;

        /**
         * The field <code>roles</code> contains the set of roles of the user.
         */
        private Set<Role> roles;

        /**
         * This is the constructor for <code>CtanSecurityContext</code>.
         *
         * @param account the account
         * @param roles the roles
         */
        @SuppressFBWarnings(value = "EI_EXPOSE_REP2")
        public CtanSecurityContext(String account, Set<Role> roles) {

            this.account = account;
            this.roles = roles;
        }

        /**
         * {@inheritDoc}
         *
         * @see jakarta.ws.rs.core.SecurityContext#getAuthenticationScheme()
         */
        @Override
        public String getAuthenticationScheme() {

            return SecurityContext.BASIC_AUTH;
        }

        /**
         * {@inheritDoc}
         *
         * @see java.security.Principal#getName()
         */
        @Override
        public String getName() {

            return account;
        }

        /**
         * {@inheritDoc}
         *
         * @see jakarta.ws.rs.core.SecurityContext#getUserPrincipal()
         */
        @Override
        public Principal getUserPrincipal() {

            return this;
        }

        /**
         * {@inheritDoc}
         *
         * @see jakarta.ws.rs.core.SecurityContext#isSecure()
         */
        @Override
        public boolean isSecure() {

            return true;
        }

        /**
         * {@inheritDoc}
         *
         * @see jakarta.ws.rs.core.SecurityContext#isUserInRole(java.lang.String)
         */
        @Override
        public boolean isUserInRole(String role) {

            if (role == null) {
                return false;
            }
            for (Role r : roles) {
                if (role.equals(r.toString())) {
                    return true;
                }
            }
            return false;
        }
    }

    /**
     * The field <code>userStore</code> contains the user store.
     */
    private UserStore userStore;

    /**
     * This is the constructor for <code>CtanAuthFilter</code>.
     *
     * @param userStore the user store
     */
    @SuppressFBWarnings(value = "EI_EXPOSE_REP2")
    public CtanAuthFilter(UserStore userStore) {

        this.userStore = userStore;
    }

    /**
     * {@inheritDoc}
     *
     * @see jakarta.ws.rs.container.ContainerRequestFilter#filter(
     *     jakarta.ws.rs.container.ContainerRequestContext)
     */
    @Override
    @UnitOfWork(value = "siteDb")
    public void filter(@NonNull ContainerRequestContext requestContext)
        throws IOException {

        var jwt = requestContext.getHeaderString("Authentication");
        if (jwt == null) {
            return;
        }
        var account = JwtManager.verifyAuth(jwt);
        if (account == null) {
            return;
        }
        var user = userStore.getByAccount(account);
        if (user == null) {
            return;
        }
        requestContext
            .setSecurityContext(
                new CtanSecurityContext(account, user.getRoles()));
    }
}