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}