/*
 * Copyright 2002-2016 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.security.integration;

import static org.assertj.core.api.Assertions.assertThat;
import static org.hamcrest.CoreMatchers.containsString;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;
import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.authenticated;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import java.util.Collections;
import java.util.List;

import org.junit.Test;
import org.springframework.mock.web.MockHttpSession;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.session.SessionDestroyedEvent;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.ResultHandler;
import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;

/**
 * @author Luke Taylor
 */
public class ConcurrentSessionManagementTests extends AbstractWebServerIntegrationTests {

	@Test
	public void maxConcurrentLoginsValueIsRespected() throws Exception {
		final MockHttpSession session1 = new MockHttpSession();
		final MockHttpSession session2 = new MockHttpSession();

		MockMvc mockMvc = createMockMvc("classpath:/spring/http-security-concurrency.xml", "classpath:/spring/in-memory-provider.xml", "classpath:/spring/testapp-servlet.xml");

		mockMvc.perform(get("secure/index").session(session1))
			.andExpect(status().is3xxRedirection());

		MockHttpServletRequestBuilder login1 = login()
				.session(session1);
		mockMvc.
			perform(login1)
			.andExpect(authenticated().withUsername("jimi"));


		MockHttpServletRequestBuilder login2 = login()
				.session(session2);
		mockMvc.perform(login2)
			.andExpect(redirectedUrl("/login.jsp?login_error=true"));
		Exception exception = (Exception) session2.getAttribute("SPRING_SECURITY_LAST_EXCEPTION");
		assertThat(exception).isNotNull();
		assertThat(exception.getMessage()).contains("Maximum sessions of 1 for this principal exceeded");

		// Now logout to kill first session
		mockMvc.perform(post("/logout").with(csrf()))
			.andExpect(status().is3xxRedirection())
			.andDo(new ResultHandler() {
				@SuppressWarnings("serial")
				@Override
				public void handle(MvcResult result) throws Exception {
					context.publishEvent(new SessionDestroyedEvent(session1) {
						@Override
						public List<SecurityContext> getSecurityContexts() {
							return Collections.emptyList();
						}

						@Override
						public String getId() {
							return session1.getId();
						}
					});
				}
			});

		// Try second session again
		login2 = login()
				.session(session2);
		mockMvc.perform(login2)
			.andExpect(authenticated().withUsername("jimi"));

		mockMvc.perform(get("/secure/index").session(session2))
			.andExpect(content().string(containsString("A Secure Page")));
	}

	private MockHttpServletRequestBuilder login() {
		return post("/login")
		.param("username", "jimi")
		.param("password", "jimispassword")
		.with(csrf());
	}
}
