/*
 * Copyright 2012-2017 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.boot.autoconfigure.security.oauth2;

import java.net.URI;
import java.util.Arrays;
import java.util.Base64;
import java.util.List;

import com.fasterxml.jackson.databind.JsonNode;
import org.junit.Test;

import org.springframework.aop.support.AopUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration;
import org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration;
import org.springframework.boot.autoconfigure.security.SecurityProperties;
import org.springframework.boot.autoconfigure.security.oauth2.authserver.OAuth2AuthorizationServerConfiguration;
import org.springframework.boot.autoconfigure.security.oauth2.method.OAuth2MethodSecurityConfiguration;
import org.springframework.boot.autoconfigure.security.oauth2.resource.OAuth2ResourceServerConfiguration;
import org.springframework.boot.autoconfigure.security.oauth2.resource.ResourceServerProperties;
import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration;
import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration;
import org.springframework.boot.context.properties.source.ConfigurationPropertySources;
import org.springframework.boot.test.util.EnvironmentTestUtils;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.RequestEntity;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.PermissionEvaluator;
import org.springframework.security.access.annotation.Jsr250MethodSecurityMetadataSource;
import org.springframework.security.access.annotation.SecuredAnnotationSecurityMetadataSource;
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
import org.springframework.security.access.method.DelegatingMethodSecurityMetadataSource;
import org.springframework.security.access.method.MethodSecurityMetadataSource;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.access.prepost.PreInvocationAuthorizationAdvice;
import org.springframework.security.access.prepost.PrePostAnnotationSecurityMetadataSource;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.method.configuration.GlobalMethodSecurityConfiguration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.oauth2.client.OAuth2ClientContext;
import org.springframework.security.oauth2.client.OAuth2RestOperations;
import org.springframework.security.oauth2.client.token.grant.client.ClientCredentialsResourceDetails;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableOAuth2Client;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.ClientDetails;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.approval.ApprovalStore;
import org.springframework.security.oauth2.provider.approval.ApprovalStoreUserApprovalHandler;
import org.springframework.security.oauth2.provider.approval.TokenApprovalStore;
import org.springframework.security.oauth2.provider.approval.UserApprovalHandler;
import org.springframework.security.oauth2.provider.client.BaseClientDetails;
import org.springframework.security.oauth2.provider.client.InMemoryClientDetailsService;
import org.springframework.security.oauth2.provider.endpoint.AuthorizationEndpoint;
import org.springframework.security.oauth2.provider.expression.OAuth2MethodSecurityExpressionHandler;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.InMemoryTokenStore;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;

/**
 * Verify Spring Security OAuth2 auto-configuration secures end points properly, accepts
 * environmental overrides, and also backs off in the presence of other
 * resource/authorization components.
 *
 * @author Greg Turnquist
 * @author Dave Syer
 */
public class OAuth2AutoConfigurationTests {

	private static final Class<?> RESOURCE_SERVER_CONFIG = OAuth2ResourceServerConfiguration.class;

	private static final Class<?> AUTHORIZATION_SERVER_CONFIG = OAuth2AuthorizationServerConfiguration.class;

	private AnnotationConfigServletWebServerApplicationContext context;

	@Test
	public void testDefaultConfiguration() {
		this.context = new AnnotationConfigServletWebServerApplicationContext();
		this.context.register(AuthorizationAndResourceServerConfiguration.class,
				MinimalSecureWebApplication.class);
		this.context.refresh();
		this.context.getBean(AUTHORIZATION_SERVER_CONFIG);
		this.context.getBean(RESOURCE_SERVER_CONFIG);
		this.context.getBean(OAuth2MethodSecurityConfiguration.class);
		ClientDetails config = this.context.getBean(BaseClientDetails.class);
		AuthorizationEndpoint endpoint = this.context
				.getBean(AuthorizationEndpoint.class);
		UserApprovalHandler handler = (UserApprovalHandler) ReflectionTestUtils
				.getField(endpoint, "userApprovalHandler");
		ClientDetailsService clientDetailsService = this.context
				.getBean(ClientDetailsService.class);
		ClientDetails clientDetails = clientDetailsService
				.loadClientByClientId(config.getClientId());
		assertThat(AopUtils.isJdkDynamicProxy(clientDetailsService)).isTrue();
		assertThat(AopUtils.getTargetClass(clientDetailsService).getName())
				.isEqualTo(InMemoryClientDetailsService.class.getName());
		assertThat(handler).isInstanceOf(ApprovalStoreUserApprovalHandler.class);
		assertThat(clientDetails).isEqualTo(config);
		verifyAuthentication(config);
		assertThat(this.context.getBeanNamesForType(OAuth2RestOperations.class))
				.isEmpty();
	}

	@Test
	public void methodSecurityExpressionHandlerIsConfiguredWithRoleHierarchyFromTheContext() {
		this.context = new AnnotationConfigServletWebServerApplicationContext();
		this.context.register(RoleHierarchyConfiguration.class,
				AuthorizationAndResourceServerConfiguration.class,
				MinimalSecureWebApplication.class);
		this.context.refresh();
		PreInvocationAuthorizationAdvice advice = this.context
				.getBean(PreInvocationAuthorizationAdvice.class);
		MethodSecurityExpressionHandler expressionHandler = (MethodSecurityExpressionHandler) ReflectionTestUtils
				.getField(advice, "expressionHandler");
		RoleHierarchy roleHierarchy = (RoleHierarchy) ReflectionTestUtils
				.getField(expressionHandler, "roleHierarchy");
		assertThat(roleHierarchy).isSameAs(this.context.getBean(RoleHierarchy.class));
	}

	@Test
	public void methodSecurityExpressionHandlerIsConfiguredWithPermissionEvaluatorFromTheContext() {
		this.context = new AnnotationConfigServletWebServerApplicationContext();
		this.context.register(PermissionEvaluatorConfiguration.class,
				AuthorizationAndResourceServerConfiguration.class,
				MinimalSecureWebApplication.class);
		this.context.refresh();
		PreInvocationAuthorizationAdvice advice = this.context
				.getBean(PreInvocationAuthorizationAdvice.class);
		MethodSecurityExpressionHandler expressionHandler = (MethodSecurityExpressionHandler) ReflectionTestUtils
				.getField(advice, "expressionHandler");
		PermissionEvaluator permissionEvaluator = (PermissionEvaluator) ReflectionTestUtils
				.getField(expressionHandler, "permissionEvaluator");
		assertThat(permissionEvaluator)
				.isSameAs(this.context.getBean(PermissionEvaluator.class));
	}

	@Test
	public void testEnvironmentalOverrides() {
		this.context = new AnnotationConfigServletWebServerApplicationContext();
		EnvironmentTestUtils.addEnvironment(this.context,
				"security.oauth2.client.clientId:myclientid",
				"security.oauth2.client.clientSecret:mysecret",
				"security.oauth2.client.autoApproveScopes:read,write",
				"security.oauth2.client.accessTokenValiditySeconds:40",
				"security.oauth2.client.refreshTokenValiditySeconds:80");
		this.context.register(AuthorizationAndResourceServerConfiguration.class,
				MinimalSecureWebApplication.class);
		this.context.refresh();
		ClientDetails config = this.context.getBean(ClientDetails.class);
		assertThat(config.getClientId()).isEqualTo("myclientid");
		assertThat(config.getClientSecret()).isEqualTo("mysecret");
		assertThat(config.isAutoApprove("read")).isTrue();
		assertThat(config.isAutoApprove("write")).isTrue();
		assertThat(config.isAutoApprove("foo")).isFalse();
		assertThat(config.getAccessTokenValiditySeconds()).isEqualTo(40);
		assertThat(config.getRefreshTokenValiditySeconds()).isEqualTo(80);
		verifyAuthentication(config);
	}

	@Test
	public void testDisablingResourceServer() {
		this.context = new AnnotationConfigServletWebServerApplicationContext();
		this.context.register(AuthorizationServerConfiguration.class,
				MinimalSecureWebApplication.class);
		this.context.refresh();
		assertThat(countBeans(RESOURCE_SERVER_CONFIG)).isEqualTo(0);
		assertThat(countBeans(AUTHORIZATION_SERVER_CONFIG)).isEqualTo(1);
	}

	@Test
	public void testClientIsNotResourceServer() {
		this.context = new AnnotationConfigServletWebServerApplicationContext();
		this.context.register(ClientConfiguration.class,
				MinimalSecureWebApplication.class);
		this.context.refresh();
		assertThat(countBeans(RESOURCE_SERVER_CONFIG)).isEqualTo(0);
		assertThat(countBeans(AUTHORIZATION_SERVER_CONFIG)).isEqualTo(0);
		// Scoped target and proxy:
		assertThat(countBeans(OAuth2ClientContext.class)).isEqualTo(2);
	}

	@Test
	public void testCanUseClientCredentials() {
		this.context = new AnnotationConfigServletWebServerApplicationContext();
		this.context.register(TestSecurityConfiguration.class,
				MinimalSecureWebApplication.class);
		EnvironmentTestUtils.addEnvironment(this.context,
				"security.oauth2.client.clientId=client",
				"security.oauth2.client.grantType=client_credentials");
		ConfigurationPropertySources.attach(this.context.getEnvironment());
		this.context.refresh();
		OAuth2ClientContext bean = this.context.getBean(OAuth2ClientContext.class);
		assertThat(bean.getAccessTokenRequest()).isNotNull();
		assertThat(countBeans(ClientCredentialsResourceDetails.class)).isEqualTo(1);
		assertThat(countBeans(OAuth2ClientContext.class)).isEqualTo(1);
	}

	@Test
	public void testCanUseClientCredentialsWithEnableOAuth2Client() {
		this.context = new AnnotationConfigServletWebServerApplicationContext();
		this.context.register(ClientConfiguration.class,
				MinimalSecureWebApplication.class);
		EnvironmentTestUtils.addEnvironment(this.context,
				"security.oauth2.client.clientId=client",
				"security.oauth2.client.grantType=client_credentials");
		ConfigurationPropertySources.attach(this.context.getEnvironment());
		this.context.refresh();
		// The primary context is fine (not session scoped):
		OAuth2ClientContext bean = this.context.getBean(OAuth2ClientContext.class);
		assertThat(bean.getAccessTokenRequest()).isNotNull();
		assertThat(countBeans(ClientCredentialsResourceDetails.class)).isEqualTo(1);
		// Kind of a bug (should ideally be 1), but the cause is in Spring OAuth2 (there
		// is no need for the extra session-scoped bean). What this test proves is that
		// even if the user screws up and does @EnableOAuth2Client for client credentials,
		// it will still just about work (because of the @Primary annotation on the
		// Boot-created instance of OAuth2ClientContext).
		assertThat(countBeans(OAuth2ClientContext.class)).isEqualTo(2);
	}

	@Test
	public void testClientIsNotAuthCode() {
		AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
		context.register(MinimalSecureNonWebApplication.class);
		EnvironmentTestUtils.addEnvironment(context,
				"security.oauth2.client.clientId=client");
		context.refresh();
		assertThat(countBeans(context, ClientCredentialsResourceDetails.class))
				.isEqualTo(1);
		context.close();
	}

	@Test
	public void testDisablingAuthorizationServer() {
		this.context = new AnnotationConfigServletWebServerApplicationContext();
		this.context.register(ResourceServerConfiguration.class,
				MinimalSecureWebApplication.class);
		EnvironmentTestUtils.addEnvironment(this.context,
				"security.oauth2.resource.jwt.keyValue:DEADBEEF");
		ConfigurationPropertySources.attach(this.context.getEnvironment());
		this.context.refresh();
		assertThat(countBeans(RESOURCE_SERVER_CONFIG)).isEqualTo(1);
		assertThat(countBeans(AUTHORIZATION_SERVER_CONFIG)).isEqualTo(0);
		assertThat(countBeans(UserApprovalHandler.class)).isEqualTo(0);
		assertThat(countBeans(DefaultTokenServices.class)).isEqualTo(1);
	}

	@Test
	public void testResourceServerOverride() {
		this.context = new AnnotationConfigServletWebServerApplicationContext();
		this.context.register(AuthorizationAndResourceServerConfiguration.class,
				CustomResourceServer.class, MinimalSecureWebApplication.class);
		this.context.refresh();
		ClientDetails config = this.context.getBean(ClientDetails.class);
		assertThat(countBeans(AUTHORIZATION_SERVER_CONFIG)).isEqualTo(1);
		assertThat(countBeans(CustomResourceServer.class)).isEqualTo(1);
		assertThat(countBeans(RESOURCE_SERVER_CONFIG)).isEqualTo(1);
		verifyAuthentication(config);
	}

	@Test
	public void testAuthorizationServerOverride() {
		this.context = new AnnotationConfigServletWebServerApplicationContext();
		EnvironmentTestUtils.addEnvironment(this.context,
				"security.oauth2.resourceId:resource-id");
		this.context.register(AuthorizationAndResourceServerConfiguration.class,
				CustomAuthorizationServer.class, MinimalSecureWebApplication.class);
		this.context.refresh();
		BaseClientDetails config = new BaseClientDetails();
		config.setClientId("client");
		config.setClientSecret("secret");
		config.setResourceIds(Arrays.asList("resource-id"));
		config.setAuthorizedGrantTypes(Arrays.asList("password"));
		config.setAuthorities(AuthorityUtils.commaSeparatedStringToAuthorityList("USER"));
		config.setScope(Arrays.asList("read"));
		assertThat(countBeans(AUTHORIZATION_SERVER_CONFIG)).isEqualTo(0);
		assertThat(countBeans(RESOURCE_SERVER_CONFIG)).isEqualTo(1);
		verifyAuthentication(config);
	}

	@Test
	public void testDefaultPrePostSecurityAnnotations() {
		this.context = new AnnotationConfigServletWebServerApplicationContext();
		this.context.register(AuthorizationAndResourceServerConfiguration.class,
				MinimalSecureWebApplication.class);
		this.context.refresh();
		this.context.getBean(OAuth2MethodSecurityConfiguration.class);
		ClientDetails config = this.context.getBean(ClientDetails.class);
		DelegatingMethodSecurityMetadataSource source = this.context
				.getBean(DelegatingMethodSecurityMetadataSource.class);
		List<MethodSecurityMetadataSource> sources = source
				.getMethodSecurityMetadataSources();
		assertThat(sources.size()).isEqualTo(1);
		assertThat(sources.get(0).getClass().getName())
				.isEqualTo(PrePostAnnotationSecurityMetadataSource.class.getName());
		verifyAuthentication(config);
	}

	@Test
	public void testClassicSecurityAnnotationOverride() {
		this.context = new AnnotationConfigServletWebServerApplicationContext();
		this.context.register(SecuredEnabledConfiguration.class,
				MinimalSecureWebApplication.class);
		this.context.refresh();
		this.context.getBean(OAuth2MethodSecurityConfiguration.class);
		ClientDetails config = this.context.getBean(ClientDetails.class);
		DelegatingMethodSecurityMetadataSource source = this.context
				.getBean(DelegatingMethodSecurityMetadataSource.class);
		List<MethodSecurityMetadataSource> sources = source
				.getMethodSecurityMetadataSources();
		assertThat(sources.size()).isEqualTo(1);
		assertThat(sources.get(0).getClass().getName())
				.isEqualTo(SecuredAnnotationSecurityMetadataSource.class.getName());
		verifyAuthentication(config, HttpStatus.OK);
	}

	@Test
	public void testJsr250SecurityAnnotationOverride() {
		this.context = new AnnotationConfigServletWebServerApplicationContext();
		this.context.register(Jsr250EnabledConfiguration.class,
				MinimalSecureWebApplication.class);
		this.context.refresh();
		this.context.getBean(OAuth2MethodSecurityConfiguration.class);
		ClientDetails config = this.context.getBean(ClientDetails.class);
		DelegatingMethodSecurityMetadataSource source = this.context
				.getBean(DelegatingMethodSecurityMetadataSource.class);
		List<MethodSecurityMetadataSource> sources = source
				.getMethodSecurityMetadataSources();
		assertThat(sources.size()).isEqualTo(1);
		assertThat(sources.get(0).getClass().getName())
				.isEqualTo(Jsr250MethodSecurityMetadataSource.class.getName());
		verifyAuthentication(config, HttpStatus.OK);
	}

	@Test
	public void testMethodSecurityBackingOff() {
		this.context = new AnnotationConfigServletWebServerApplicationContext();
		this.context.register(CustomMethodSecurity.class, TestSecurityConfiguration.class,
				MinimalSecureWebApplication.class);
		this.context.refresh();
		DelegatingMethodSecurityMetadataSource source = this.context
				.getBean(DelegatingMethodSecurityMetadataSource.class);
		List<MethodSecurityMetadataSource> sources = source
				.getMethodSecurityMetadataSources();
		assertThat(sources.size()).isEqualTo(1);
		assertThat(sources.get(0).getClass().getName())
				.isEqualTo(PrePostAnnotationSecurityMetadataSource.class.getName());
	}

	@Test
	public void resourceServerConditionWhenJwkConfigurationPresentShouldMatch()
			throws Exception {
		this.context = new AnnotationConfigServletWebServerApplicationContext();
		EnvironmentTestUtils.addEnvironment(this.context,
				"security.oauth2.resource.jwk.key-set-uri:http://my-auth-server/token_keys");
		this.context.register(ResourceServerConfiguration.class,
				MinimalSecureWebApplication.class);
		this.context.refresh();
		assertThat(countBeans(RESOURCE_SERVER_CONFIG)).isEqualTo(1);
	}

	/**
	 * Connect to the oauth service, get a token, and then attempt some operations using
	 * it.
	 * @param config the client details.
	 */
	private void verifyAuthentication(ClientDetails config) {
		verifyAuthentication(config, HttpStatus.FORBIDDEN);
	}

	private void verifyAuthentication(ClientDetails config, HttpStatus finalStatus) {
		String baseUrl = "http://localhost:" + this.context.getWebServer().getPort();
		TestRestTemplate rest = new TestRestTemplate();
		// First, verify the web endpoint can't be reached
		assertEndpointUnauthorized(baseUrl, rest);
		// Since we can't reach it, need to collect an authorization token
		HttpHeaders headers = getHeaders(config);
		String url = baseUrl + "/oauth/token";
		JsonNode tokenResponse = rest.postForObject(url,
				new HttpEntity<>(getBody(), headers), JsonNode.class);
		String authorizationToken = tokenResponse.findValue("access_token").asText();
		String tokenType = tokenResponse.findValue("token_type").asText();
		String scope = tokenResponse.findValues("scope").get(0).toString();
		assertThat(tokenType).isEqualTo("bearer");
		assertThat(scope).isEqualTo("\"read\"");
		// Now we should be able to see that endpoint.
		headers.set("Authorization", "BEARER " + authorizationToken);
		ResponseEntity<String> securedResponse = rest
				.exchange(new RequestEntity<Void>(headers, HttpMethod.GET,
						URI.create(baseUrl + "/securedFind")), String.class);
		assertThat(securedResponse.getStatusCode()).isEqualTo(HttpStatus.OK);
		assertThat(securedResponse.getBody()).isEqualTo(
				"You reached an endpoint " + "secured by Spring Security OAuth2");
		ResponseEntity<String> entity = rest.exchange(new RequestEntity<Void>(headers,
				HttpMethod.POST, URI.create(baseUrl + "/securedSave")), String.class);
		assertThat(entity.getStatusCode()).isEqualTo(finalStatus);
	}

	private HttpHeaders getHeaders(ClientDetails config) {
		HttpHeaders headers = new HttpHeaders();
		String token = new String(Base64.getEncoder().encode(
				(config.getClientId() + ":" + config.getClientSecret()).getBytes()));
		headers.set("Authorization", "Basic " + token);
		return headers;
	}

	private MultiValueMap<String, Object> getBody() {
		MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
		body.set("grant_type", "password");
		body.set("username", "foo");
		body.set("password", "bar");
		body.set("scope", "read");
		return body;
	}

	private void assertEndpointUnauthorized(String baseUrl, TestRestTemplate rest) {
		URI uri = URI.create(baseUrl + "/secured");
		ResponseEntity<String> entity = rest
				.exchange(new RequestEntity<Void>(HttpMethod.GET, uri), String.class);
		assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.UNAUTHORIZED);
	}

	private int countBeans(Class<?> type) {
		return countBeans(this.context, type);
	}

	private int countBeans(ApplicationContext context, Class<?> type) {
		return context.getBeanNamesForType(type).length;
	}

	@Configuration
	@Import({ UseFreePortEmbeddedContainerConfiguration.class,
			SecurityAutoConfiguration.class, DispatcherServletAutoConfiguration.class,
			OAuth2AutoConfiguration.class, WebMvcAutoConfiguration.class,
			HttpMessageConvertersAutoConfiguration.class })
	protected static class MinimalSecureWebApplication {

	}

	@Configuration
	@Import({ SecurityAutoConfiguration.class, OAuth2AutoConfiguration.class })
	protected static class MinimalSecureNonWebApplication {

	}

	@Configuration
	@Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
	protected static class TestSecurityConfiguration
			extends WebSecurityConfigurerAdapter {

		@Override
		@Bean
		public AuthenticationManager authenticationManagerBean() throws Exception {
			return super.authenticationManagerBean();
		}

		@Autowired
		public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
			auth.inMemoryAuthentication().withUser("foo").password("bar").roles("USER");
		}

		@Bean
		TestWebApp testWebApp() {
			return new TestWebApp();
		}

	}

	@Configuration
	@EnableOAuth2Client
	protected static class ClientConfiguration extends TestSecurityConfiguration {

	}

	@Configuration
	@EnableAuthorizationServer
	@EnableResourceServer
	@EnableGlobalMethodSecurity(prePostEnabled = true)
	protected static class AuthorizationAndResourceServerConfiguration
			extends TestSecurityConfiguration {

	}

	@Configuration
	@EnableAuthorizationServer
	@EnableResourceServer
	@EnableGlobalMethodSecurity(securedEnabled = true)
	protected static class SecuredEnabledConfiguration extends TestSecurityConfiguration {

	}

	@Configuration
	@EnableAuthorizationServer
	@EnableResourceServer
	@EnableGlobalMethodSecurity(jsr250Enabled = true)
	protected static class Jsr250EnabledConfiguration extends TestSecurityConfiguration {

	}

	@Configuration
	@EnableAuthorizationServer
	protected static class AuthorizationServerConfiguration
			extends TestSecurityConfiguration {

	}

	@Configuration
	@EnableResourceServer
	protected static class ResourceServerConfiguration extends TestSecurityConfiguration {

	}

	@RestController
	protected static class TestWebApp {

		@GetMapping("/securedFind")
		@PreAuthorize("#oauth2.hasScope('read')")
		public String secureFind() {
			return "You reached an endpoint secured by Spring Security OAuth2";
		}

		@PostMapping("/securedSave")
		@PreAuthorize("#oauth2.hasScope('write')")
		public String secureSave() {
			return "You reached an endpoint secured by Spring Security OAuth2";
		}

	}

	@Configuration
	protected static class UseFreePortEmbeddedContainerConfiguration {

		@Bean
		TomcatServletWebServerFactory webServerFactory() {
			return new TomcatServletWebServerFactory(0);
		}

	}

	@Configuration
	@EnableResourceServer
	protected static class CustomResourceServer extends ResourceServerConfigurerAdapter {

		private final ResourceServerProperties config;

		protected CustomResourceServer(ResourceServerProperties config) {
			this.config = config;
		}

		@Override
		public void configure(ResourceServerSecurityConfigurer resources)
				throws Exception {
			if (this.config.getId() != null) {
				resources.resourceId(this.config.getId());
			}
		}

		@Override
		public void configure(HttpSecurity http) throws Exception {
			http.authorizeRequests().anyRequest().authenticated().and().httpBasic().and()
					.csrf().disable();
		}

	}

	@Configuration
	@EnableAuthorizationServer
	protected static class CustomAuthorizationServer
			extends AuthorizationServerConfigurerAdapter {

		private final AuthenticationManager authenticationManager;

		protected CustomAuthorizationServer(AuthenticationManager authenticationManager) {
			this.authenticationManager = authenticationManager;
		}

		@Bean
		public TokenStore tokenStore() {
			return new InMemoryTokenStore();
		}

		@Bean
		public ApprovalStore approvalStore(final TokenStore tokenStore) {
			TokenApprovalStore approvalStore = new TokenApprovalStore();
			approvalStore.setTokenStore(tokenStore);
			return approvalStore;
		}

		@Override
		public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
			clients.inMemory().withClient("client").secret("secret")
					.resourceIds("resource-id").authorizedGrantTypes("password")
					.authorities("USER").scopes("read")
					.redirectUris("http://localhost:8080");
		}

		@Override
		public void configure(AuthorizationServerEndpointsConfigurer endpoints)
				throws Exception {
			endpoints.tokenStore(tokenStore())
					.authenticationManager(this.authenticationManager);
		}

	}

	@Configuration
	@EnableGlobalMethodSecurity(prePostEnabled = true)
	protected static class CustomMethodSecurity
			extends GlobalMethodSecurityConfiguration {

		@Override
		protected MethodSecurityExpressionHandler createExpressionHandler() {
			return new OAuth2MethodSecurityExpressionHandler();
		}

	}

	@Configuration
	protected static class RoleHierarchyConfiguration {

		@Bean
		public RoleHierarchy roleHierarchy() {
			return mock(RoleHierarchy.class);
		}

	}

	@Configuration
	protected static class PermissionEvaluatorConfiguration {

		@Bean
		public PermissionEvaluator permissionEvaluator() {
			return mock(PermissionEvaluator.class);
		}

	}

}
