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}