001/*
002 * The contents of this file are subject to the license and copyright
003 * detailed in the LICENSE and NOTICE files at the root of the source
004 * tree.
005 */
006
007package org.fcrepo.webapp;
008
009import java.util.HashSet;
010import java.util.List;
011
012import javax.servlet.Filter;
013
014import org.fcrepo.auth.common.ContainerRolesPrincipalProvider;
015import org.fcrepo.auth.common.DelegateHeaderPrincipalProvider;
016import org.fcrepo.auth.common.HttpHeaderPrincipalProvider;
017import org.fcrepo.auth.common.PrincipalProvider;
018import org.fcrepo.auth.common.ServletContainerAuthFilter;
019import org.fcrepo.auth.common.ServletContainerAuthenticatingRealm;
020import org.fcrepo.auth.webac.WebACAuthorizingRealm;
021import org.fcrepo.auth.webac.WebACFilter;
022import org.fcrepo.config.AuthPropsConfig;
023import org.fcrepo.config.ConditionOnPropertyTrue;
024
025import org.apache.shiro.realm.AuthenticatingRealm;
026import org.apache.shiro.realm.AuthorizingRealm;
027import org.apache.shiro.spring.LifecycleBeanPostProcessor;
028import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
029import org.apache.shiro.web.filter.InvalidRequestFilter;
030import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
031import org.apache.shiro.web.mgt.WebSecurityManager;
032import org.slf4j.Logger;
033import org.slf4j.LoggerFactory;
034import org.springframework.context.annotation.Bean;
035import org.springframework.context.annotation.Conditional;
036import org.springframework.context.annotation.Configuration;
037import org.springframework.core.annotation.Order;
038
039/**
040 * Spring config for auth
041 *
042 * @author pwinckles
043 */
044@Configuration
045@Conditional(AuthConfig.AuthorizationEnabled.class)
046public class AuthConfig {
047
048    private static final Logger LOGGER = LoggerFactory.getLogger(AuthConfig.class);
049
050    static class AuthorizationEnabled extends ConditionOnPropertyTrue {
051        AuthorizationEnabled() {
052            super(AuthPropsConfig.FCREPO_AUTH_ENABLED, true);
053        }
054    }
055    static class HeaderPrincipalEnabled extends ConditionOnPropertyTrue {
056        HeaderPrincipalEnabled() {
057            super(AuthPropsConfig.FCREPO_AUTH_PRINCIPAL_HEADER_ENABLED, false);
058        }
059    }
060    static class RolesPrincipalEnabled extends ConditionOnPropertyTrue {
061        RolesPrincipalEnabled() {
062            super(AuthPropsConfig.FCREPO_AUTH_PRINCIPAL_ROLES_ENABLED, false);
063        }
064    }
065    static class DelegatePrincipalEnabled extends ConditionOnPropertyTrue {
066        DelegatePrincipalEnabled() {
067            super(AuthPropsConfig.FCREPO_AUTH_PRINCIPAL_DELEGATE_ENABLED, true);
068        }
069    }
070
071    /**
072     * Optional PrincipalProvider filter that will inspect the request header, "some-header", for user role values
073     *
074     * @param propsConfig config properties
075     * @return header principal provider
076     */
077    @Bean
078    @Order(3)
079    @Conditional(AuthConfig.HeaderPrincipalEnabled.class)
080    public PrincipalProvider headerProvider(final AuthPropsConfig propsConfig) {
081        LOGGER.info("Auth header principal provider enabled");
082        final var provider = new HttpHeaderPrincipalProvider();
083        provider.setHeaderName(propsConfig.getAuthPrincipalHeaderName());
084        provider.setSeparator(propsConfig.getAuthPrincipalHeaderSeparator());
085        return provider;
086    }
087
088    /**
089     * Optional PrincipalProvider filter that will use container configured roles as principals
090     *
091     * @param propsConfig config properties
092     * @return roles principal provider
093     */
094    @Bean
095    @Order(4)
096    @Conditional(AuthConfig.RolesPrincipalEnabled.class)
097    public PrincipalProvider containerRolesProvider(final AuthPropsConfig propsConfig) {
098        LOGGER.info("Auth roles principal provider enabled");
099        final var provider = new ContainerRolesPrincipalProvider();
100        provider.setRoleNames(new HashSet<>(propsConfig.getAuthPrincipalRolesList()));
101        return provider;
102    }
103
104    /**
105     * delegatedPrincipleProvider filter allows a single user to be passed in the header "On-Behalf-Of",
106     *            this is to be used as the actor making the request when authenticating.
107     *            NOTE: Only users with the role fedoraAdmin can delegate to another user.
108     *            NOTE: Only supported in WebAC authentication
109     *
110     * @return delegate principal provider
111     */
112    @Bean
113    @Order(5)
114    @Conditional(AuthConfig.DelegatePrincipalEnabled.class)
115    public PrincipalProvider delegatedPrincipalProvider() {
116        LOGGER.info("Auth delegate principal provider enabled");
117        return new DelegateHeaderPrincipalProvider();
118    }
119
120    /**
121     * WebAC Authorization Realm
122     *
123     * @return authorization  realm
124     */
125    @Bean
126    public AuthorizingRealm webACAuthorizingRealm() {
127        return new WebACAuthorizingRealm();
128    }
129
130    /**
131     * Servlet Container Authentication Realm
132     *
133     * @return authentication realm
134     */
135    @Bean
136    public AuthenticatingRealm servletContainerAuthenticatingRealm() {
137        return new ServletContainerAuthenticatingRealm();
138    }
139
140    /**
141     * @return Security Manager
142     */
143    @Bean
144    public WebSecurityManager securityManager() {
145        final var manager = new DefaultWebSecurityManager();
146        manager.setRealms(List.of(webACAuthorizingRealm(), servletContainerAuthenticatingRealm()));
147        return manager;
148    }
149
150    /**
151     * Post processor that automatically invokes init() and destroy() methods
152     *
153     * @return post processor
154     */
155    @Bean
156    public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
157        return new LifecycleBeanPostProcessor();
158    }
159
160    /**
161     * @return Authentication Filter
162     */
163    @Bean
164    @Order(1)
165    public Filter servletContainerAuthFilter() {
166        return new ServletContainerAuthFilter();
167    }
168
169    /**
170     * @return Authorization Filter
171     */
172    @Bean
173    @Order(2)
174    public Filter webACFilter() {
175        return new WebACFilter();
176    }
177
178    /**
179     * Shiro's filter for rejecting invalid requests
180     *
181     * @return invalid request filter
182     */
183    @Bean
184    @Order(6)
185    public Filter invalidRequest() {
186        final var filter = new InvalidRequestFilter();
187        filter.setBlockNonAscii(false);
188        filter.setBlockBackslash(false);
189        filter.setBlockSemicolon(false);
190        return filter;
191    }
192
193    /**
194     * Shiro filter. When defining the filter chain, the Auth filter should come first, followed by 0 or more of the
195     * principal provider filters, and finally the webACFilter
196     *
197     * @param propsConfig config properties
198     * @return shiro filter
199     */
200    @Bean
201    @Order(100)
202    public ShiroFilterFactoryBean shiroFilter(final AuthPropsConfig propsConfig) {
203        final var filter = new ShiroFilterFactoryBean();
204        filter.setSecurityManager(securityManager());
205        filter.setFilterChainDefinitions("/** = servletContainerAuthFilter,"
206                + principalProviderChain(propsConfig) + "webACFilter");
207        return filter;
208    }
209
210    private String principalProviderChain(final AuthPropsConfig propsConfig) {
211        final var builder = new StringBuilder();
212
213        if (propsConfig.isAuthPrincipalHeaderEnabled()) {
214            builder.append("headerProvider,");
215        }
216        if (propsConfig.isAuthPrincipalRolesEnabled()) {
217            builder.append("containerRolesProvider,");
218        }
219        if (propsConfig.isAuthPrincipalDelegateEnabled()) {
220            builder.append("delegatedPrincipalProvider,");
221        }
222
223        return builder.toString();
224    }
225
226}