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