/******************************************************************************* * Cloud Foundry * Copyright (c) [2009-2014] Pivotal Software, Inc. All Rights Reserved. * * This product is licensed to you under the Apache License, Version 2.0 (the "License"). * You may not use this product except in compliance with the License. * * This product includes a number of subcomponents with * separate copyright notices and license terms. Your use of these * subcomponents is subject to the terms and conditions of the * subcomponent's license, as noted in the LICENSE file. *******************************************************************************/ package org.cloudfoundry.identity.uaa.login.feature; import org.cloudfoundry.identity.uaa.login.test.DefaultIntegrationTestConfig; import org.cloudfoundry.identity.uaa.login.test.IntegrationTestRule; import org.cloudfoundry.identity.uaa.login.test.TestClient; import org.cloudfoundry.identity.uaa.test.UaaTestAccounts; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.openqa.selenium.By; import org.openqa.selenium.WebDriver; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.web.client.HttpClientErrorException; import org.springframework.web.client.RestOperations; import org.springframework.web.client.RestTemplate; import org.springframework.web.util.UriComponentsBuilder; import java.util.HashMap; import java.util.List; import java.util.Map; import static org.junit.Assert.assertEquals; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = DefaultIntegrationTestConfig.class) public class AutologinIT { @Autowired @Rule public IntegrationTestRule integrationTestRule; @Autowired WebDriver webDriver; @Value("${integration.test.base_url}") String baseUrl; @Value("${integration.test.app_url}") String appUrl; @Autowired RestOperations restOperations; @Autowired TestClient testClient; private UaaTestAccounts testAccounts = UaaTestAccounts.standard(null); @Test public void testAutologinFlow() throws Exception { webDriver.get(baseUrl + "/logout.do"); HttpHeaders headers = getAppBasicAuthHttpHeaders(); Map<String, String> requestBody = new HashMap<>(); requestBody.put("username", testAccounts.getUserName()); requestBody.put("password", testAccounts.getPassword()); ResponseEntity<Map> autologinResponseEntity = restOperations.exchange(baseUrl + "/autologin", HttpMethod.POST, new HttpEntity<>(requestBody, headers), Map.class); String autologinCode = (String) autologinResponseEntity.getBody().get("code"); String authorizeUrl = UriComponentsBuilder.fromHttpUrl(baseUrl) .path("/oauth/authorize") .queryParam("redirect_uri", appUrl) .queryParam("response_type", "code") .queryParam("scope", "openid") .queryParam("client_id", "app") .queryParam("code", autologinCode) .build().toUriString(); webDriver.get(authorizeUrl); webDriver.get(baseUrl); assertEquals(testAccounts.getUserName(), webDriver.findElement(By.cssSelector(".header .nav")).getText()); } @Test public void testSimpleAutologinFlow() throws Exception { HttpHeaders headers = getAppBasicAuthHttpHeaders(); LinkedMultiValueMap<String, String> requestBody = new LinkedMultiValueMap<>(); requestBody.add("username", testAccounts.getUserName()); requestBody.add("password", testAccounts.getPassword()); //generate an autologin code with our credentials ResponseEntity<Map> autologinResponseEntity = restOperations.exchange(baseUrl + "/autologin", HttpMethod.POST, new HttpEntity<>(requestBody, headers), Map.class); String autologinCode = (String) autologinResponseEntity.getBody().get("code"); //start the authorization flow - this will issue a login event //by using the autologin code String authorizeUrl = UriComponentsBuilder.fromHttpUrl(baseUrl) .path("/oauth/authorize") .queryParam("redirect_uri", appUrl) .queryParam("response_type", "code") .queryParam("client_id", "app") .queryParam("code", autologinCode) .build().toUriString(); //rest template that does NOT follow redirects RestTemplate template = new RestTemplate(new DefaultIntegrationTestConfig.HttpClientFactory()); headers.remove("Authorization"); ResponseEntity<Map> authorizeResponse = template.exchange(authorizeUrl, HttpMethod.GET, new HttpEntity<>(new HashMap<String,String>(),headers), Map.class); //we are now logged in. retrieve the JSESSIONID List<String> cookies = authorizeResponse.getHeaders().get("Set-Cookie"); assertEquals(1, cookies.size()); headers = getAppBasicAuthHttpHeaders(); headers.add("Cookie", cookies.get(0)); //if we receive a 200, then we must approve our scopes if (HttpStatus.OK == authorizeResponse.getStatusCode()) { authorizeUrl = UriComponentsBuilder.fromHttpUrl(baseUrl) .path("/oauth/authorize") .queryParam("user_oauth_approval", "true") .build().toUriString(); authorizeResponse = template.exchange(authorizeUrl, HttpMethod.POST, new HttpEntity<>(new HashMap<String,String>(),headers), Map.class); } //approval is complete, we receive a token code back assertEquals(HttpStatus.FOUND, authorizeResponse.getStatusCode()); List<String> location = authorizeResponse.getHeaders().get("Location"); assertEquals(1, location.size()); String newCode = location.get(0).substring(location.get(0).indexOf("code=") + 5); //request a token using our code String tokenUrl = UriComponentsBuilder.fromHttpUrl(baseUrl) .path("/oauth/token") .queryParam("response_type", "token") .queryParam("grant_type", "authorization_code") .queryParam("code", newCode) .queryParam("redirect_uri", appUrl) .build().toUriString(); ResponseEntity<Map> tokenResponse = template.exchange( tokenUrl, HttpMethod.POST, new HttpEntity<>(new HashMap<String, String>(), headers), Map.class); assertEquals(HttpStatus.OK, tokenResponse.getStatusCode()); //here we must reset our state. we do that by following the logout flow. headers.clear(); headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); ResponseEntity<Void> loginResponse = restOperations.exchange(baseUrl + "/login.do", HttpMethod.POST, new HttpEntity<>(requestBody, headers), Void.class); cookies = loginResponse.getHeaders().get("Set-Cookie"); assertEquals(1, cookies.size()); headers.clear(); headers.add("Cookie", cookies.get(0)); restOperations.exchange(baseUrl + "/profile", HttpMethod.GET, new HttpEntity<>(null, headers),Void.class); String revokeApprovalsUrl = UriComponentsBuilder.fromHttpUrl(baseUrl) .path("/profile") .build().toUriString(); requestBody.clear(); requestBody.add("clientId","app"); requestBody.add("delete",""); ResponseEntity<Void> revokeResponse = template.exchange(revokeApprovalsUrl, HttpMethod.POST, new HttpEntity<>(requestBody, headers), Void.class); assertEquals(HttpStatus.FOUND, revokeResponse.getStatusCode()); } @Test public void testFormEncodedAutologinRequest() throws Exception { HttpHeaders headers = getAppBasicAuthHttpHeaders(); headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); MultiValueMap<String, String> requestBody = new LinkedMultiValueMap<>(); requestBody.add("username", testAccounts.getUserName()); requestBody.add("password", testAccounts.getPassword()); ResponseEntity<Map> autologinResponseEntity = restOperations.exchange(baseUrl + "/autologin", HttpMethod.POST, new HttpEntity<>(requestBody, headers), Map.class); String autologinCode = (String) autologinResponseEntity.getBody().get("code"); assertEquals(6, autologinCode.length()); } @Test public void testPasswordRequired() throws Exception { HttpHeaders headers = getAppBasicAuthHttpHeaders(); Map<String, String> requestBody = new HashMap<>(); requestBody.put("username", testAccounts.getUserName()); try { restOperations.exchange(baseUrl + "/autologin", HttpMethod.POST, new HttpEntity<>(requestBody, headers), Map.class); } catch (HttpClientErrorException e) { assertEquals(HttpStatus.UNAUTHORIZED, e.getStatusCode()); } } @Test public void testClientAuthorization() throws Exception { Map<String, String> requestBody = new HashMap<>(); requestBody.put("username", testAccounts.getUserName()); requestBody.put("password", testAccounts.getPassword()); try { restOperations.exchange(baseUrl + "/autologin", HttpMethod.POST, new HttpEntity<>(requestBody), Map.class); } catch (HttpClientErrorException e) { assertEquals(HttpStatus.UNAUTHORIZED, e.getStatusCode()); } } @Test public void testClientIdMustBeConsistent() throws Exception { webDriver.get(baseUrl + "/logout.do"); HttpHeaders headers = getAppBasicAuthHttpHeaders(); Map<String, String> requestBody = new HashMap<>(); requestBody.put("username", testAccounts.getUserName()); requestBody.put("password", testAccounts.getPassword()); ResponseEntity<Map> autologinResponseEntity = restOperations.exchange(baseUrl + "/autologin", HttpMethod.POST, new HttpEntity<>(requestBody, headers), Map.class); String autologinCode = (String) autologinResponseEntity.getBody().get("code"); String authorizeUrl = UriComponentsBuilder.fromHttpUrl(baseUrl) .path("/oauth/authorize") .queryParam("redirect_uri", appUrl) .queryParam("response_type", "code") .queryParam("scope", "openid") .queryParam("client_id", "stealer_of_codes") .queryParam("code", autologinCode) .build().toUriString(); try { restOperations.exchange(authorizeUrl, HttpMethod.GET, null, Void.class); } catch (HttpClientErrorException e) { assertEquals(HttpStatus.UNAUTHORIZED, e.getStatusCode()); } } private HttpHeaders getAppBasicAuthHttpHeaders() { HttpHeaders headers = new HttpHeaders(); headers.add("Authorization", testClient.getBasicAuthHeaderValue("app", "appclientsecret")); return headers; } }