package org.springframework.security.extensions.saml2.config;

import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Timer;
import java.util.regex.Pattern;

import org.apache.commons.httpclient.HttpClient;
import org.opensaml.Configuration;
import org.opensaml.PaosBootstrap;
import org.opensaml.saml2.metadata.provider.FilesystemMetadataProvider;
import org.opensaml.saml2.metadata.provider.HTTPMetadataProvider;
import org.opensaml.saml2.metadata.provider.MetadataProvider;
import org.opensaml.saml2.metadata.provider.MetadataProviderException;
import org.opensaml.xml.ConfigurationException;
import org.opensaml.xml.parse.ParserPool;
import org.opensaml.xml.parse.StaticBasicParserPool;
import org.opensaml.xml.parse.XMLParserException;
import org.opensaml.xml.security.keyinfo.NamedKeyInfoGeneratorManager;
import org.opensaml.xml.security.x509.CertPathPKIXTrustEvaluator;
import org.opensaml.xml.security.x509.PKIXTrustEvaluator;
import org.opensaml.xml.security.x509.X509KeyInfoGeneratorFactory;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.SecurityConfigurerAdapter;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.CsrfConfigurer;
import org.springframework.security.saml.SAMLAuthenticationProvider;
import org.springframework.security.saml.SAMLConstants;
import org.springframework.security.saml.SAMLDiscovery;
import org.springframework.security.saml.SAMLEntryPoint;
import org.springframework.security.saml.SAMLLogoutFilter;
import org.springframework.security.saml.SAMLLogoutProcessingFilter;
import org.springframework.security.saml.SAMLProcessingFilter;
import org.springframework.security.saml.context.SAMLContextProvider;
import org.springframework.security.saml.context.SAMLContextProviderLB;
import org.springframework.security.saml.key.JKSKeyManager;
import org.springframework.security.saml.key.KeyManager;
import org.springframework.security.saml.log.SAMLDefaultLogger;
import org.springframework.security.saml.metadata.CachingMetadataManager;
import org.springframework.security.saml.metadata.ExtendedMetadata;
import org.springframework.security.saml.metadata.ExtendedMetadataDelegate;
import org.springframework.security.saml.metadata.MetadataDisplayFilter;
import org.springframework.security.saml.metadata.MetadataGenerator;
import org.springframework.security.saml.metadata.MetadataGeneratorFilter;
import org.springframework.security.saml.processor.HTTPPostBinding;
import org.springframework.security.saml.processor.HTTPRedirectDeflateBinding;
import org.springframework.security.saml.processor.SAMLBinding;
import org.springframework.security.saml.processor.SAMLProcessor;
import org.springframework.security.saml.processor.SAMLProcessorImpl;
import org.springframework.security.saml.storage.SAMLMessageStorageFactory;
import org.springframework.security.saml.trust.MetadataCredentialResolver;
import org.springframework.security.saml.trust.PKIXInformationResolver;
import org.springframework.security.saml.userdetails.SAMLUserDetailsService;
import org.springframework.security.saml.util.VelocityFactory;
import org.springframework.security.saml.websso.SingleLogoutProfile;
import org.springframework.security.saml.websso.SingleLogoutProfileImpl;
import org.springframework.security.saml.websso.WebSSOProfile;
import org.springframework.security.saml.websso.WebSSOProfileConsumerImpl;
import org.springframework.security.saml.websso.WebSSOProfileImpl;
import org.springframework.security.saml.websso.WebSSOProfileOptions;
import org.springframework.security.web.DefaultSecurityFilterChain;
import org.springframework.security.web.FilterChainProxy;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.access.channel.ChannelProcessingFilter;
import org.springframework.security.web.authentication.logout.LogoutHandler;
import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import org.springframework.security.web.util.matcher.AndRequestMatcher;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.NegatedRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;

/*
 Spring security configurer for okta.
 @Author Mark Douglass
 @Author Jean de Klerk
*/
public class SAMLConfigurer extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {
	private IdentityProvider identityProvider = new IdentityProvider();
	private ServiceProvider serviceProvider = new ServiceProvider();

	private WebSSOProfileOptions webSSOProfileOptions = webSSOProfileOptions();
	private StaticBasicParserPool parserPool = staticBasicParserPool();
	private SAMLProcessor samlProcessor = samlProcessor();
	private SAMLDefaultLogger samlLogger = new SAMLDefaultLogger();
	private WebSSOProfileConsumerImpl webSSOProfileConsumer;
	private SAMLAuthenticationProvider samlAuthenticationProvider;
	private MetadataProvider metadataProvider;
	private ExtendedMetadataDelegate extendedMetadataDelegate;
	private CachingMetadataManager cachingMetadataManager;
	private WebSSOProfile webSSOProfile;
	private SingleLogoutProfile singleLogoutProfile;
	private SAMLUserDetailsService samlUserDetailsService;
	private boolean forcePrincipalAsString = false;

	private ObjectPostProcessor<Object> objectPostProcessor = new ObjectPostProcessor<Object>() {
		public <T> T postProcess(T object) {
			return object;
		}
	};


	private SAMLConfigurer() {
	}

	@Override
	public void init(HttpSecurity http) {

		metadataProvider = identityProvider.metadataProvider();
		ExtendedMetadata extendedMetadata = extendedMetadata(identityProvider.discoveryEnabled);
		extendedMetadataDelegate = extendedMetadataDelegate(extendedMetadata);
		serviceProvider.keyManager = serviceProvider.keyManager();
		cachingMetadataManager = cachingMetadataManager();
		webSSOProfile = webSSOProfile();
		singleLogoutProfile = singleLogoutProfile();

		if (webSSOProfileConsumer == null) {
			webSSOProfileConsumer = new WebSSOProfileConsumerImpl(samlProcessor, cachingMetadataManager);
			webSSOProfileConsumer.setMaxAuthenticationAge(serviceProvider.maxAuthenticationAge);
		}

		samlAuthenticationProvider = samlAuthenticationProvider(webSSOProfileConsumer);

		bootstrap();

		SAMLContextProvider contextProvider = contextProvider();
		SAMLEntryPoint samlEntryPoint = samlEntryPoint(contextProvider);
		SAMLLogoutFilter samlLogoutFilter = samlLogoutFilter(contextProvider);
		SAMLLogoutProcessingFilter samlLogoutProcessingFilter = samlLogoutProcessingFilter(contextProvider);

		try {
			http
				.httpBasic()
				.authenticationEntryPoint(samlEntryPoint);

			CsrfConfigurer<HttpSecurity> csrfConfigurer = http.getConfigurer(CsrfConfigurer.class);
			if(csrfConfigurer != null) {
				// Workaround to get working with Spring Security 3.2.
				RequestMatcher ignored = new AntPathRequestMatcher("/saml/SSO");
				RequestMatcher notIgnored = new NegatedRequestMatcher(ignored);
				RequestMatcher matcher = new AndRequestMatcher(new DefaultRequiresCsrfMatcher(), notIgnored);

				csrfConfigurer.requireCsrfProtectionMatcher(matcher);
			}
		} catch (Exception e) {
			e.printStackTrace();
		}

		http
			.addFilterBefore(metadataGeneratorFilter(samlEntryPoint, extendedMetadata), ChannelProcessingFilter.class)
			.addFilterAfter(samlFilter(samlEntryPoint, samlLogoutFilter, samlLogoutProcessingFilter, contextProvider),
					BasicAuthenticationFilter.class)
			.authenticationProvider(samlAuthenticationProvider);
	}

	public static SAMLConfigurer saml() {
		return new SAMLConfigurer();
	}

	public SAMLConfigurer userDetailsService(SAMLUserDetailsService samlUserDetailsService) {
		this.samlUserDetailsService = samlUserDetailsService;
		return this;
	}

	public SAMLConfigurer forcePrincipalAsString() {
		this.forcePrincipalAsString = true;
		return this;
	}

	public SAMLConfigurer webSSOProfileConsumer(WebSSOProfileConsumerImpl webSSOProfileConsumer) {
		this.webSSOProfileConsumer = webSSOProfileConsumer;
		return this;
	}

	public IdentityProvider identityProvider() {
		return identityProvider;
	}

	public ServiceProvider serviceProvider() {
		return serviceProvider;
	}

	private String entityBaseURL() {
		String entityBaseURL = serviceProvider.hostName + "/" + serviceProvider.basePath;
		entityBaseURL = entityBaseURL.replaceAll("//", "/").replaceAll("/$", "");
		entityBaseURL = serviceProvider.protocol + "://" + entityBaseURL;
		return entityBaseURL;
	}

	private SAMLEntryPoint samlEntryPoint(SAMLContextProvider contextProvider) {
		SAMLEntryPoint samlEntryPoint = new SAMLDslEntryPoint();
		samlEntryPoint.setDefaultProfileOptions(webSSOProfileOptions);
		samlEntryPoint.setWebSSOprofile(webSSOProfile);
		samlEntryPoint.setContextProvider(contextProvider);
		samlEntryPoint.setMetadata(cachingMetadataManager);
		samlEntryPoint.setSamlLogger(samlLogger);
		return samlEntryPoint;
	}

	private SimpleUrlLogoutSuccessHandler successLogoutHandler() {
		SimpleUrlLogoutSuccessHandler logoutSuccessHandler = new SimpleUrlLogoutSuccessHandler();
		logoutSuccessHandler.setDefaultTargetUrl("/");
		return logoutSuccessHandler;
	}

	private SecurityContextLogoutHandler logoutHandler() {
		SecurityContextLogoutHandler logoutHandler = new SecurityContextLogoutHandler();
		logoutHandler.setInvalidateHttpSession(true);
		logoutHandler.setClearAuthentication(true);
		return logoutHandler;
	}

	private SAMLLogoutFilter samlLogoutFilter(SAMLContextProvider contextProvider) {
		SAMLLogoutFilter samlLogoutFilter = new SAMLLogoutFilter(successLogoutHandler(),
				new LogoutHandler[]{logoutHandler()},
				new LogoutHandler[]{logoutHandler()});
		samlLogoutFilter.setProfile(singleLogoutProfile);
		samlLogoutFilter.setContextProvider(contextProvider);
		samlLogoutFilter.setSamlLogger(samlLogger);
		return samlLogoutFilter;
	}

	private SAMLLogoutProcessingFilter samlLogoutProcessingFilter(SAMLContextProvider contextProvider) {
		SAMLLogoutProcessingFilter samlLogoutProcessingFilter =
				new SAMLLogoutProcessingFilter(successLogoutHandler(), logoutHandler());
		samlLogoutProcessingFilter.setLogoutProfile(singleLogoutProfile);
		samlLogoutProcessingFilter.setContextProvider(contextProvider);
		samlLogoutProcessingFilter.setSamlLogger(samlLogger);
		samlLogoutProcessingFilter.setSAMLProcessor(samlProcessor);
		return samlLogoutProcessingFilter;
	}

	private SAMLProcessor samlProcessor() {
		Collection<SAMLBinding> bindings = new ArrayList<>();
		bindings.add(httpRedirectDeflateBinding(parserPool));
		bindings.add(httpPostBinding(parserPool));
		return new SAMLProcessorImpl(bindings);
	}

	private CachingMetadataManager cachingMetadataManager() {
		List<MetadataProvider> providers = new ArrayList<>();
		providers.add(extendedMetadataDelegate);

		CachingMetadataManager cachingMetadataManager = null;
		try {
			cachingMetadataManager = new CachingMetadataManager(providers);
		} catch (MetadataProviderException e) {
			e.printStackTrace();
		}

		cachingMetadataManager.setKeyManager(serviceProvider.keyManager);
		return cachingMetadataManager;
	}

	private StaticBasicParserPool staticBasicParserPool() {
		StaticBasicParserPool parserPool = new StaticBasicParserPool();
		try {
			parserPool.initialize();
		} catch (XMLParserException e) {
			e.printStackTrace();
		}
		return parserPool;
	}

	private ExtendedMetadataDelegate extendedMetadataDelegate(ExtendedMetadata extendedMetadata) {
		ExtendedMetadataDelegate extendedMetadataDelegate = new ExtendedMetadataDelegate(metadataProvider, extendedMetadata);
		extendedMetadataDelegate.setMetadataTrustCheck(false);
		extendedMetadataDelegate.setMetadataRequireSignature(false);
		return extendedMetadataDelegate;
	}

	private ExtendedMetadata extendedMetadata(boolean discoveryEnabled) {
		ExtendedMetadata extendedMetadata = new ExtendedMetadata();
		extendedMetadata.setIdpDiscoveryEnabled(discoveryEnabled);
		extendedMetadata.setSignMetadata(true);
		return extendedMetadata;
	}

	private WebSSOProfileOptions webSSOProfileOptions() {
		WebSSOProfileOptions webSSOProfileOptions = new WebSSOProfileOptions();
		webSSOProfileOptions.setIncludeScoping(false);
		return webSSOProfileOptions;
	}

	private void bootstrap() {
		try {
			PaosBootstrap.bootstrap();
		} catch (ConfigurationException e) {
			e.printStackTrace();
		}

		NamedKeyInfoGeneratorManager manager = Configuration.getGlobalSecurityConfiguration().getKeyInfoGeneratorManager();
		X509KeyInfoGeneratorFactory generator = new X509KeyInfoGeneratorFactory();
		generator.setEmitEntityCertificate(true);
		generator.setEmitEntityCertificateChain(true);
		manager.registerFactory(SAMLConstants.SAML_METADATA_KEY_INFO_GENERATOR, generator);
	}

	private HTTPPostBinding httpPostBinding(ParserPool parserPool) {
		return new HTTPPostBinding(parserPool, VelocityFactory.getEngine());
	}

	private HTTPRedirectDeflateBinding httpRedirectDeflateBinding(ParserPool parserPool) {
		return new HTTPRedirectDeflateBinding(parserPool);
	}

	private SAMLProcessingFilter samlWebSSOProcessingFilter(SAMLAuthenticationProvider samlAuthenticationProvider, SAMLContextProvider contextProvider, SAMLProcessor samlProcessor) throws Exception {
		SAMLProcessingFilter samlWebSSOProcessingFilter = new SAMLProcessingFilter();

		AuthenticationManagerBuilder authenticationManagerBuilder = new AuthenticationManagerBuilder(objectPostProcessor);
		authenticationManagerBuilder.authenticationProvider(samlAuthenticationProvider);
		samlWebSSOProcessingFilter.setAuthenticationManager(authenticationManagerBuilder.build());
		samlWebSSOProcessingFilter.setContextProvider(contextProvider);
		samlWebSSOProcessingFilter.setSAMLProcessor(samlProcessor);
		return samlWebSSOProcessingFilter;
	}

	private MetadataGeneratorFilter metadataGeneratorFilter(SAMLEntryPoint samlEntryPoint, ExtendedMetadata extendedMetadata) {
		MetadataGeneratorFilter metadataGeneratorFilter = new MetadataGeneratorFilter(getMetadataGenerator(samlEntryPoint, extendedMetadata));
		metadataGeneratorFilter.setManager(cachingMetadataManager);
		return metadataGeneratorFilter;
	}

	private FilterChainProxy samlFilter(SAMLEntryPoint samlEntryPoint, SAMLLogoutFilter samlLogoutFilter,
										SAMLLogoutProcessingFilter samlLogoutProcessingFilter, SAMLContextProvider contextProvider) {
		List<SecurityFilterChain> chains = new ArrayList<>();
		chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/login/**"),
			samlEntryPoint));
		chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/logout/**"),
			samlLogoutFilter));
		chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/metadata/**"),
			metadataDisplayFilter(contextProvider)));
		try {
			chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/SSO/**"),
				samlWebSSOProcessingFilter(samlAuthenticationProvider, contextProvider, samlProcessor)));
		} catch (Exception e) {
			e.printStackTrace();
		}
		chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/SingleLogout/**"),
				samlLogoutProcessingFilter));
		SAMLDiscovery samlDiscovery = new SAMLDiscovery();
		samlDiscovery.setMetadata(cachingMetadataManager);
		samlDiscovery.setContextProvider(contextProvider);
		chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/discovery/**"),
			samlDiscovery));
		return new FilterChainProxy(chains);
	}

	private WebSSOProfile webSSOProfile() {
		WebSSOProfileImpl webSSOProfile = new WebSSOProfileImpl(samlProcessor, cachingMetadataManager);
		webSSOProfile.setResponseSkew(serviceProvider.responseSkew);
		return webSSOProfile;
	}

	private SingleLogoutProfile singleLogoutProfile() {
		SingleLogoutProfileImpl singleLogoutProfile = new SingleLogoutProfileImpl();
		singleLogoutProfile.setMetadata(cachingMetadataManager);
		singleLogoutProfile.setProcessor(samlProcessor);
		singleLogoutProfile.setResponseSkew(serviceProvider.responseSkew);
		return singleLogoutProfile;
	}

	private SAMLAuthenticationProvider samlAuthenticationProvider(WebSSOProfileConsumerImpl webSSOProfileConsumer) {
		SAMLAuthenticationProvider samlAuthenticationProvider = new SAMLAuthenticationProvider();
		samlAuthenticationProvider.setForcePrincipalAsString(forcePrincipalAsString);
		samlAuthenticationProvider.setSamlLogger(samlLogger);
		samlAuthenticationProvider.setConsumer(webSSOProfileConsumer);
		samlAuthenticationProvider.setUserDetails(this.samlUserDetailsService);
		return samlAuthenticationProvider;
	}

	private SAMLContextProvider contextProvider() {
		SAMLContextProviderLB contextProvider = new SAMLContextProviderLB();
		contextProvider.setMetadata(cachingMetadataManager);
		contextProvider.setScheme(serviceProvider.protocol);
		contextProvider.setServerName(serviceProvider.hostName);
		contextProvider.setContextPath(serviceProvider.basePath);
		contextProvider.setKeyManager(serviceProvider.keyManager);

		MetadataCredentialResolver resolver = new MetadataCredentialResolver(cachingMetadataManager, serviceProvider.keyManager);
		PKIXTrustEvaluator pkixTrustEvaluator = new CertPathPKIXTrustEvaluator();
		PKIXInformationResolver pkixInformationResolver = new PKIXInformationResolver(resolver, cachingMetadataManager, serviceProvider.keyManager);

		contextProvider.setPkixResolver(pkixInformationResolver);
		contextProvider.setPkixTrustEvaluator(pkixTrustEvaluator);
		contextProvider.setMetadataResolver(resolver);

		if (serviceProvider.storageFactory != null) {
			contextProvider.setStorageFactory(serviceProvider.storageFactory);
		}

		return contextProvider;
	}

	private MetadataGenerator getMetadataGenerator(SAMLEntryPoint samlEntryPoint, ExtendedMetadata extendedMetadata) {
		MetadataGenerator metadataGenerator = new MetadataGenerator();

		metadataGenerator.setSamlEntryPoint(samlEntryPoint);
		metadataGenerator.setEntityBaseURL(entityBaseURL());
		metadataGenerator.setKeyManager(serviceProvider.keyManager);
		metadataGenerator.setEntityId(serviceProvider.entityId);
		metadataGenerator.setIncludeDiscoveryExtension(false);
		metadataGenerator.setExtendedMetadata(extendedMetadata);

		return metadataGenerator;
	}

	public class IdentityProvider {

		private String metadataFilePath;
		private boolean discoveryEnabled = true;

		public IdentityProvider metadataFilePath(String metadataFilePath) {
			this.metadataFilePath = metadataFilePath;
			return this;
		}

		public IdentityProvider discoveryEnabled(boolean discoveryEnabled) {
			this.discoveryEnabled = discoveryEnabled;
			return this;
		}

		private MetadataProvider metadataProvider() {
			if (metadataFilePath.startsWith("http")) {
				return httpMetadataProvider();
			} else {
				return fileSystemMetadataProvider();
			}
		}

		private HTTPMetadataProvider httpMetadataProvider() {
			try {
				HTTPMetadataProvider httpMetadataProvider = new HTTPMetadataProvider(new Timer(), new HttpClient(), metadataFilePath);
				httpMetadataProvider.setParserPool(parserPool);
				return httpMetadataProvider;
			} catch (MetadataProviderException e) {
				e.printStackTrace();
				return null;
			}
		}

		private FilesystemMetadataProvider fileSystemMetadataProvider() {
			DefaultResourceLoader loader = new DefaultResourceLoader();
			Resource metadataResource = loader.getResource(metadataFilePath);

			File samlMetadata = null;
			try {
				samlMetadata = metadataResource.getFile();
			} catch (IOException e) {
				e.printStackTrace();
			}

			FilesystemMetadataProvider filesystemMetadataProvider = null;
			try {
				filesystemMetadataProvider = new FilesystemMetadataProvider(samlMetadata);
			} catch (MetadataProviderException e) {
				e.printStackTrace();
			}
			filesystemMetadataProvider.setParserPool(parserPool);

			return filesystemMetadataProvider;
		}

		public SAMLConfigurer and () { return SAMLConfigurer.this; }
	}

	private MetadataDisplayFilter metadataDisplayFilter(SAMLContextProvider contextProvider) {
		MetadataDisplayFilter metadataDisplayFilter = new MetadataDisplayFilter();
		metadataDisplayFilter.setContextProvider(contextProvider);
		metadataDisplayFilter.setKeyManager(serviceProvider.keyManager);
		metadataDisplayFilter.setManager(cachingMetadataManager);
		return metadataDisplayFilter;
	}

	public class ServiceProvider {

		private KeyStore keyStore = new KeyStore();
		private KeyManager keyManager;
		private String protocol;
		private String hostName;
		private String basePath;
		private String entityId;
		private SAMLMessageStorageFactory storageFactory;
		private int responseSkew = 60;
		private long maxAuthenticationAge = 7200;

		public ServiceProvider protocol(String protocol) {
			this.protocol = protocol;
			return this;
		}

		public ServiceProvider hostname(String hostname) {
			this.hostName = hostname;
			return this;
		}

		public ServiceProvider basePath(String basePath) {
			this.basePath = basePath;
			return this;
		}

		public ServiceProvider entityId(String entityId) {
			this.entityId = entityId;
			return this;
		}

		public ServiceProvider storageFactory(SAMLMessageStorageFactory storageFactory) {
			this.storageFactory = storageFactory;
			return this;
		}

		public ServiceProvider responseSkew(int responseSkew) {
			this.responseSkew = responseSkew;
			return this;
		}

		public ServiceProvider maxAuthenticationAge(long maxAuthenticationAge) {
			this.maxAuthenticationAge = maxAuthenticationAge;
			return this;
		}

		public KeyStore keyStore() {
			return keyStore;
		}

		public SAMLConfigurer and () { return SAMLConfigurer.this; }

		private KeyManager keyManager() {
			DefaultResourceLoader loader = new DefaultResourceLoader();
			Resource storeFile = loader.getResource(keyStore.getStoreFilePath());
			if (keyStore.getStoreFilePath().startsWith("file://")) {
				try {
					storeFile = new FileSystemResource(storeFile.getFile());
				} catch (IOException e) {
					e.printStackTrace();
					throw new RuntimeException("Cannot load file system resource: " + keyStore.getStoreFilePath(), e);
				}
			}
			Map<String, String> passwords = new HashMap<>();
			passwords.put(keyStore.getKeyname(), keyStore.getKeyPassword());
			return new JKSKeyManager(storeFile, keyStore.getPassword(), passwords, keyStore.getKeyname());
		}

		public class KeyStore {
			private String storeFilePath;
			private String password;
			private String keyname;
			private String keyPassword;

			public KeyStore storeFilePath(String storeFilePath) {
				this.storeFilePath = storeFilePath;
				return this;
			}

			public KeyStore password(String password) {
				this.password = password;
				return this;
			}

			public KeyStore keyname(String keyname) {
				this.keyname = keyname;
				return this;
			}

			public KeyStore keyPassword(String keyPasswordword) {
				this.keyPassword = keyPasswordword;
				return this;
			}

			public ServiceProvider and() {
				return ServiceProvider.this;
			}

			public String getStoreFilePath() {
				return storeFilePath;
			}

			public String getPassword() {
				return password;
			}

			public String getKeyname() {
				return keyname;
			}

			public String getKeyPassword() {
				return keyPassword;
			}

			@Override
			public String toString() {
				return "KeyStore{" +
						"storeFilePath='" + storeFilePath + '\'' +
						", password='" + password + '\'' +
						", keyname='" + keyname + '\'' +
						", keyPassword='" + keyPassword + '\'' +
						'}';
			}

			@Override
			public boolean equals(Object o) {
				if (this == o) return true;
				if (o == null || getClass() != o.getClass()) return false;

				KeyStore keyStore = (KeyStore) o;

				if (storeFilePath != null ? !storeFilePath.equals(keyStore.storeFilePath) : keyStore.storeFilePath != null)
					return false;
				if (password != null ? !password.equals(keyStore.password) : keyStore.password != null) return false;
				if (keyname != null ? !keyname.equals(keyStore.keyname) : keyStore.keyname != null) return false;
				return keyPassword != null ? keyPassword.equals(keyStore.keyPassword) : keyStore.keyPassword == null;

			}

			@Override
			public int hashCode() {
				int result = storeFilePath != null ? storeFilePath.hashCode() : 0;
				result = 31 * result + (password != null ? password.hashCode() : 0);
				result = 31 * result + (keyname != null ? keyname.hashCode() : 0);
				result = 31 * result + (keyPassword != null ? keyPassword.hashCode() : 0);
				return result;
			}
		}
	}

	final class DefaultRequiresCsrfMatcher implements RequestMatcher {
		private Pattern allowedMethods = Pattern.compile("^(GET|HEAD|TRACE|OPTIONS)$");

		/* (non-Javadoc)
		 * @see org.springframework.security.web.util.matcher.RequestMatcher#matches(javax.servlet.http.HttpServletRequest)
		 */
		public boolean matches(HttpServletRequest request) {
			return !allowedMethods.matcher(request.getMethod()).matches();
		}
	}
}