/*
* This file is part of ARSnova Backend.
* Copyright (C) 2012-2017 The ARSnova Team
*
* ARSnova Backend is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* ARSnova Backend is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package de.thm.arsnova.config;
import com.github.leleuj.ss.oauth.client.authentication.OAuthAuthenticationProvider;
import com.github.leleuj.ss.oauth.client.web.OAuthAuthenticationEntryPoint;
import com.github.leleuj.ss.oauth.client.web.OAuthAuthenticationFilter;
import de.thm.arsnova.CASLogoutSuccessHandler;
import de.thm.arsnova.CasUserDetailsService;
import de.thm.arsnova.LoginAuthenticationFailureHandler;
import de.thm.arsnova.LoginAuthenticationSucessHandler;
import de.thm.arsnova.security.ApplicationPermissionEvaluator;
import de.thm.arsnova.security.CustomLdapUserDetailsMapper;
import de.thm.arsnova.security.DbUserDetailsService;
import org.jasig.cas.client.validation.Cas20ProxyTicketValidator;
import org.scribe.up.provider.impl.FacebookProvider;
import org.scribe.up.provider.impl.Google2Provider;
import org.scribe.up.provider.impl.Google2Provider.Google2Scope;
import org.scribe.up.provider.impl.TwitterProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.FileSystemResource;
import org.springframework.ldap.core.support.LdapContextSource;
import org.springframework.security.access.PermissionEvaluator;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.cas.ServiceProperties;
import org.springframework.security.cas.authentication.CasAuthenticationProvider;
import org.springframework.security.cas.web.CasAuthenticationEntryPoint;
import org.springframework.security.cas.web.CasAuthenticationFilter;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.session.SessionRegistry;
import org.springframework.security.core.session.SessionRegistryImpl;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.ldap.DefaultSpringSecurityContextSource;
import org.springframework.security.ldap.authentication.BindAuthenticator;
import org.springframework.security.ldap.authentication.LdapAuthenticationProvider;
import org.springframework.security.ldap.authentication.LdapAuthenticator;
import org.springframework.security.ldap.authentication.NullLdapAuthoritiesPopulator;
import org.springframework.security.ldap.search.FilterBasedLdapUserSearch;
import org.springframework.security.ldap.userdetails.LdapAuthoritiesPopulator;
import org.springframework.security.ldap.userdetails.LdapUserDetailsMapper;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.authentication.Http403ForbiddenEntryPoint;
import org.springframework.security.web.authentication.logout.LogoutFilter;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
import org.springframework.security.web.header.writers.HstsHeaderWriter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.web.context.ServletContextAware;
import javax.servlet.ServletContext;
import java.util.ArrayList;
import java.util.List;
/**
* Loads property file and configures components used for authentication.
*/
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
@EnableWebSecurity
@Profile("!test")
public class SecurityConfig extends WebSecurityConfigurerAdapter implements ServletContextAware {
private static final Logger logger = LoggerFactory.getLogger(SecurityConfig.class);
private ServletContext servletContext;
@Value("${root-url}") private String rootUrl;
@Value("${security.user-db.enabled}") private boolean dbAuthEnabled;
@Value("${security.ldap.enabled}") private boolean ldapEnabled;
@Value("${security.ldap.url}") private String ldapUrl;
@Value("${security.ldap.user-id-attr:uid}") private String ldapUserIdAttr;
@Value("${security.ldap.user-dn-pattern:}") private String ldapUserDn;
@Value("${security.ldap.user-search-base:}") private String ldapSearchBase;
@Value("${security.ldap.user-search-filter:}") private String ldapSearchFilter;
@Value("${security.ldap.manager-user-dn:}") private String ldapManagerUserDn;
@Value("${security.ldap.manager-password:}") private String ldapManagerPassword;
@Value("${security.cas.enabled}") private boolean casEnabled;
@Value("${security.cas-server-url}") private String casUrl;
@Value("${security.facebook.enabled}") private boolean facebookEnabled;
@Value("${security.facebook.key}") private String facebookKey;
@Value("${security.facebook.secret}") private String facebookSecret;
@Value("${security.twitter.enabled}") private boolean twitterEnabled;
@Value("${security.twitter.key}") private String twitterKey;
@Value("${security.twitter.secret}") private String twitterSecret;
@Value("${security.google.enabled}") private boolean googleEnabled;
@Value("${security.google.key}") private String googleKey;
@Value("${security.google.secret}") private String googleSecret;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.exceptionHandling().authenticationEntryPoint(restAuthenticationEntryPoint());
http.csrf().disable();
http.headers()
.addHeaderWriter(new HstsHeaderWriter(false));
if (casEnabled) {
http.addFilter(casAuthenticationFilter());
http.addFilter(casLogoutFilter());
}
if (googleEnabled) {
http.addFilterAfter(googleFilter(), CasAuthenticationFilter.class);
}
if (facebookEnabled) {
http.addFilterAfter(facebookFilter(), CasAuthenticationFilter.class);
}
if (twitterEnabled) {
http.addFilterAfter(twitterFilter(), CasAuthenticationFilter.class);
}
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
List<String> providers = new ArrayList<>();
if (dbAuthEnabled) {
providers.add("user-db");
auth.authenticationProvider(daoAuthenticationProvider());
}
if (ldapEnabled) {
providers.add("ldap");
auth.authenticationProvider(ldapAuthenticationProvider());
}
if (casEnabled) {
providers.add("cas");
auth.authenticationProvider(casAuthenticationProvider());
}
if (googleEnabled) {
providers.add("google");
auth.authenticationProvider(googleAuthProvider());
}
if (facebookEnabled) {
providers.add("facebook");
auth.authenticationProvider(facebookAuthProvider());
}
if (twitterEnabled) {
providers.add("twitter");
auth.authenticationProvider(twitterAuthProvider());
}
logger.info("Enabled authentication providers: {}", providers);
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManager();
}
@Bean
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
final PropertySourcesPlaceholderConfigurer configurer = new PropertySourcesPlaceholderConfigurer();
configurer.setLocations(
new ClassPathResource("arsnova.properties.example"),
new FileSystemResource("file:///etc/arsnova/arsnova.properties")
);
configurer.setIgnoreResourceNotFound(true);
configurer.setIgnoreUnresolvablePlaceholders(false);
return configurer;
}
@Bean
public SessionRegistry sessionRegistry() {
return new SessionRegistryImpl();
}
@Bean
public PermissionEvaluator permissionEvaluator() {
return new ApplicationPermissionEvaluator();
}
@Bean
public static AuthenticationEntryPoint restAuthenticationEntryPoint() {
return new Http403ForbiddenEntryPoint();
}
@Bean
LoginAuthenticationSucessHandler successHandler() {
final LoginAuthenticationSucessHandler successHandler = new LoginAuthenticationSucessHandler();
successHandler.setTargetUrl(rootUrl);
return successHandler;
}
@Bean
LoginAuthenticationFailureHandler failureHandler() {
final LoginAuthenticationFailureHandler failureHandler = new LoginAuthenticationFailureHandler();
failureHandler.setDefaultFailureUrl(rootUrl);
return failureHandler;
}
// Database Authentication Configuration
@Bean
public DaoAuthenticationProvider daoAuthenticationProvider() {
final DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
authProvider.setUserDetailsService(dbUserDetailsService());
authProvider.setPasswordEncoder(passwordEncoder());
return authProvider;
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public DbUserDetailsService dbUserDetailsService() {
return new DbUserDetailsService();
}
@Bean
public SecurityContextLogoutHandler logoutHandler() {
return new SecurityContextLogoutHandler();
}
// LDAP Authentication Configuration
@Bean
public LdapAuthenticationProvider ldapAuthenticationProvider() {
LdapAuthenticationProvider ldapAuthenticationProvider = new LdapAuthenticationProvider(ldapAuthenticator(), ldapAuthoritiesPopulator());
ldapAuthenticationProvider.setUserDetailsContextMapper(customLdapUserDetailsMapper());
return ldapAuthenticationProvider;
}
@Bean
public LdapContextSource ldapContextSource() {
DefaultSpringSecurityContextSource contextSource = new DefaultSpringSecurityContextSource(ldapUrl);
/* TODO: implement support for LDAP bind using manager credentials */
if (!"".equals(ldapManagerUserDn) && !"".equals(ldapManagerPassword)) {
logger.debug("ldapManagerUserDn: {}", ldapManagerUserDn);
contextSource.setUserDn(ldapManagerUserDn);
contextSource.setPassword(ldapManagerPassword);
}
return contextSource;
}
@Bean
public LdapAuthenticator ldapAuthenticator() {
BindAuthenticator authenticator = new BindAuthenticator(ldapContextSource());
authenticator.setUserAttributes(new String[] {ldapUserIdAttr});
if (!"".equals(ldapSearchFilter)) {
logger.debug("ldapSearch: {} {}", ldapSearchBase, ldapSearchFilter);
authenticator.setUserSearch(new FilterBasedLdapUserSearch(ldapSearchBase, ldapSearchFilter, ldapContextSource()));
} else {
logger.debug("ldapUserDn: {}", ldapUserDn);
authenticator.setUserDnPatterns(new String[] {ldapUserDn});
}
return authenticator;
}
@Bean
public LdapAuthoritiesPopulator ldapAuthoritiesPopulator() {
return new NullLdapAuthoritiesPopulator();
}
@Bean
public LdapUserDetailsMapper customLdapUserDetailsMapper() {
logger.debug("ldapUserIdAttr: {}", ldapUserIdAttr);
return new CustomLdapUserDetailsMapper(ldapUserIdAttr);
}
// CAS Authentication Configuration
@Bean
public CasAuthenticationProvider casAuthenticationProvider() {
CasAuthenticationProvider authProvider = new CasAuthenticationProvider();
authProvider.setAuthenticationUserDetailsService(casUserDetailsService());
authProvider.setServiceProperties(casServiceProperties());
authProvider.setTicketValidator(casTicketValidator());
authProvider.setKey("casAuthProviderKey");
return authProvider;
}
@Bean
public CasUserDetailsService casUserDetailsService() {
return new CasUserDetailsService();
}
@Bean
public ServiceProperties casServiceProperties() {
ServiceProperties properties = new ServiceProperties();
properties.setService(rootUrl + servletContext.getContextPath() + "/login/cas");
properties.setSendRenew(false);
return properties;
}
@Bean
public Cas20ProxyTicketValidator casTicketValidator() {
return new Cas20ProxyTicketValidator(casUrl);
}
@Bean
public CasAuthenticationEntryPoint casAuthenticationEntryPoint() {
CasAuthenticationEntryPoint entryPoint = new CasAuthenticationEntryPoint();
entryPoint.setLoginUrl(casUrl + "/login");
entryPoint.setServiceProperties(casServiceProperties());
return entryPoint;
}
@Bean
public CasAuthenticationFilter casAuthenticationFilter() throws Exception {
CasAuthenticationFilter filter = new CasAuthenticationFilter();
filter.setAuthenticationManager(authenticationManager());
filter.setAuthenticationSuccessHandler(successHandler());
filter.setAuthenticationFailureHandler(failureHandler());
return filter;
}
@Bean
public LogoutFilter casLogoutFilter() {
LogoutFilter filter = new LogoutFilter(casLogoutSuccessHandler(), logoutHandler());
filter.setLogoutRequestMatcher(new AntPathRequestMatcher("/j_spring_cas_security_logout"));
return filter;
}
@Bean
public LogoutSuccessHandler casLogoutSuccessHandler() {
CASLogoutSuccessHandler handler = new CASLogoutSuccessHandler();
handler.setCasUrl(casUrl);
handler.setDefaultTarget(rootUrl);
return handler;
}
// Facebook Authentication Configuration
@Bean
public OAuthAuthenticationEntryPoint facebookEntryPoint() {
final OAuthAuthenticationEntryPoint entryPoint = new OAuthAuthenticationEntryPoint();
entryPoint.setProvider(facebookProvider());
return entryPoint;
}
@Bean
public FacebookProvider facebookProvider() {
final FacebookProvider provider = new FacebookProvider();
provider.setKey(facebookKey);
provider.setSecret(facebookSecret);
provider.setCallbackUrl(rootUrl + servletContext.getContextPath() + "/j_spring_facebook_security_check");
return provider;
}
@Bean
public OAuthAuthenticationFilter facebookFilter() throws Exception {
final OAuthAuthenticationFilter filter = new OAuthAuthenticationFilter("/j_spring_facebook_security_check");
filter.setProvider(facebookProvider());
filter.setAuthenticationManager(authenticationManager());
filter.setAuthenticationFailureHandler(failureHandler());
filter.setAuthenticationSuccessHandler(successHandler());
return filter;
}
@Bean
public OAuthAuthenticationProvider facebookAuthProvider() {
final OAuthAuthenticationProvider authProvider = new OAuthAuthenticationProvider();
authProvider.setProvider(facebookProvider());
return authProvider;
}
// Twitter Authentication Configuration
@Bean
public TwitterProvider twitterProvider() {
final TwitterProvider provider = new TwitterProvider();
provider.setKey(twitterKey);
provider.setSecret(twitterSecret);
provider.setCallbackUrl(rootUrl + servletContext.getContextPath() + "/j_spring_twitter_security_check");
return provider;
}
@Bean
public OAuthAuthenticationFilter twitterFilter() throws Exception {
final OAuthAuthenticationFilter filter = new OAuthAuthenticationFilter("/j_spring_twitter_security_check");
filter.setProvider(twitterProvider());
filter.setAuthenticationManager(authenticationManager());
filter.setAuthenticationFailureHandler(failureHandler());
filter.setAuthenticationSuccessHandler(successHandler());
return filter;
}
@Bean
public OAuthAuthenticationProvider twitterAuthProvider() {
final OAuthAuthenticationProvider authProvider = new OAuthAuthenticationProvider();
authProvider.setProvider(twitterProvider());
return authProvider;
}
// Google Authentication Configuration
@Bean
public Google2Provider googleProvider() {
final Google2Provider provider = new Google2Provider();
provider.setKey(googleKey);
provider.setSecret(googleSecret);
provider.setCallbackUrl(rootUrl + servletContext.getContextPath() + "/j_spring_google_security_check");
provider.setScope(Google2Scope.EMAIL);
return provider;
}
@Bean
public OAuthAuthenticationFilter googleFilter() throws Exception {
final OAuthAuthenticationFilter filter = new OAuthAuthenticationFilter("/j_spring_google_security_check");
filter.setProvider(googleProvider());
filter.setAuthenticationManager(authenticationManager());
filter.setAuthenticationFailureHandler(failureHandler());
filter.setAuthenticationSuccessHandler(successHandler());
return filter;
}
@Bean
public OAuthAuthenticationProvider googleAuthProvider() {
final OAuthAuthenticationProvider authProvider = new OAuthAuthenticationProvider();
authProvider.setProvider(googleProvider());
return authProvider;
}
@Override
public void setServletContext(ServletContext servletContext) {
this.servletContext = servletContext;
}
}